Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/dotnet #109

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
node_modules/
coverage
2 changes: 2 additions & 0 deletions jest.setup-git-windows.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// To fix 'git-upload-pack: command not found' error on Windows, add 'C:\\Program Files\\Git\\mingw64\\bin' to PATH environment variable
process.env.Path += ';C:\\Program Files\\Git\\mingw64\\bin';
32 changes: 22 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,34 @@
"scripts": {
"format": "prettier --write --single-quote --trailing-comma es5",
"format:all": "yarn format \"./**/*.js\"",
"test": "jest"
"test": "jest --coverage"
},
"license": "MIT",
"peerDependencies": {
"semantic-release": ">=15.11.x"
},
"dependencies": {
"debug": "^3.1.0",
"@semantic-release/error": "^2.2.0",
"debug": "^3.2.7",
"execa": "^0.8.0",
"p-limit": "^1.2.0",
"fs-extra": "^10.0.0",
"glob-promise": "^4.2.0",
"p-limit": "^1.3.0",
"pkg-up": "^2.0.0",
"ramda": "^0.25.0",
"read-pkg": "^5.0.0",
"semantic-release-plugin-decorators": "^3.0.0"
"read-pkg": "^5.2.0",
"semantic-release-plugin-decorators": "^3.0.1"
},
"devDependencies": {
"husky": "^4.2.1",
"jest": "^25.1.0",
"lint-staged": "^10.0.7",
"@types/jest": "^26.0.24",
"husky": "^4.3.8",
"jest": "^25.5.4",
"jest-extended": "^0.11.5",
"lint-staged": "^10.5.4",
"prettier": "^1.19.1",
"semantic-release": "^17.0.2",
"semantic-release-github-pr": "^6.0.0"
"semantic-release": "^17.4.4",
"semantic-release-github-pr": "^6.0.1",
"tempy": "^1.0.1"
},
"husky": {
"hooks": {
Expand All @@ -41,5 +47,11 @@
"*.js": [
"yarn format"
]
},
"jest": {
"setupFilesAfterEnv": [
"jest-extended",
"./jest.setup-git-windows.js"
]
}
}
43 changes: 43 additions & 0 deletions src/core/git-utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
const execa = require('execa');
const { relative } = require('path');

const git = async (args, options = {}) => {
const { stdout } = await execa('git', args, options);
return stdout;
};

/**
* // https://stackoverflow.com/questions/424071/how-to-list-all-the-files-in-a-commit
* @async
* @param {string} hash Git commit hash.
* @param {string} cwd Working directory.
* @return {Promise<Array>} List of modified files in a commit.
*/
const getCommitFiles = async (hash, cwd) =>
(
await git(['diff-tree', '--no-commit-id', '--name-only', '-r', hash], {
cwd,
})
).split('\n');

/**
* https://stackoverflow.com/a/957978/89594
* @async
* @param {string} cwd Working directory.
* @return {Promise<String>} System path of the git repository.
*/
const getRoot = cwd => git(['rev-parse', '--show-toplevel'], { cwd });

/**
* @async
* @param {string} path Absolute path.
* @param {string} cwd Working directory.
* @return {Promise<String>} Path relative to the git root.
*/
const getRelativePath = async (path, cwd) => relative(await getRoot(cwd), path);

module.exports = {
getCommitFiles,
getRoot,
getRelativePath,
};
48 changes: 48 additions & 0 deletions src/core/git-utils.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
const { outputFile } = require('fs-extra');
const { resolve, relative, normalize } = require('path');

const { getRoot, getRelativePath, getCommitFiles } = require('./git-utils');

const { commitAll } = require('../test-env/common');
const { setupNpmTestEnv } = require('../test-env/npm');

describe('git-utils', () => {
it('gets commit files', async () => {
const gitRoot = await setupNpmTestEnv();

const filePath = resolve(gitRoot, 'projects', 'project1', 'file1.txt');
await outputFile(filePath, 'content1');

const hash = await commitAll(gitRoot, 'fix: file1');

const commitFiles = (await getCommitFiles(hash, gitRoot)).map(x =>
normalize(x)
);
expect(commitFiles).toIncludeAllMembers([relative(gitRoot, filePath)]);
});

it('gets git root from root', async () => {
const gitRoot = await setupNpmTestEnv();

const root = normalize(await getRoot(gitRoot));
expect(root).toBe(gitRoot);
});

it('gets git root from subdirectory', async () => {
const projectName = 'my-project';
const gitRoot = await setupNpmTestEnv([projectName]);
const project1Root = resolve(gitRoot, 'projects', projectName);

const root = normalize(await getRoot(project1Root));
expect(root).toBe(gitRoot);
});

it('gets relative path', async () => {
const projectName = 'my-project';
const gitRoot = await setupNpmTestEnv([projectName]);
const project1Root = resolve(gitRoot, 'projects', projectName);

const relativePath = await getRelativePath(project1Root, project1Root);
expect(relativePath).toBe(relative(gitRoot, project1Root));
});
});
File renamed without changes.
9 changes: 5 additions & 4 deletions src/log-plugin-version.js → src/core/log-plugin-version.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
const { resolve } = require('path');
const readPkg = require('read-pkg');
const debug = require('debug')('semantic-release:monorepo');

const logPluginVersion = type => plugin => async (pluginConfig, config) => {
const logPluginVersion = (type, getProjectVersion) => plugin => async (
pluginConfig,
config
) => {
if (config.options.debug) {
const { version } = await readPkg(resolve(__dirname, '../'));
const version = await getProjectVersion();
debug('Running %o version %o', type, version);
}

Expand Down
40 changes: 17 additions & 23 deletions src/only-package-commits.js → src/core/only-package-commits.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,13 @@
const { identity, memoizeWith, pipeP } = require('ramda');
const pkgUp = require('pkg-up');
const readPkg = require('read-pkg');
const path = require('path');
const pLimit = require('p-limit');
const debug = require('debug')('semantic-release:monorepo');
const { getCommitFiles, getRoot } = require('./git-utils');
const pLimit = require('p-limit');
const path = require('path');
const { identity, memoizeWith, pipeP } = require('ramda');

const { getCommitFiles, getRelativePath } = require('./git-utils');
const { mapCommits } = require('./options-transforms');

const memoizedGetCommitFiles = memoizeWith(identity, getCommitFiles);

/**
* Get the normalized PACKAGE root path, relative to the git PROJECT root.
*/
const getPackagePath = async () => {
const packagePath = await pkgUp();
const gitRoot = await getRoot();

return path.relative(gitRoot, path.resolve(packagePath, '..'));
};

const withFiles = async commits => {
const limit = pLimit(Number(process.env.SRM_MAX_THREADS) || 500);
return Promise.all(
Expand All @@ -31,8 +20,8 @@ const withFiles = async commits => {
);
};

const onlyPackageCommits = async commits => {
const packagePath = await getPackagePath();
const onlyPackageCommits = getProjectRoot => async commits => {
const packagePath = await getRelativePath(await getProjectRoot());
debug('Filter commits by package path: "%s"', packagePath);
const commitsWithFiles = await withFiles(commits);
// Convert package root path into segments - one for each folder
Expand Down Expand Up @@ -67,8 +56,10 @@ const tapA = fn => async x => {
return x;
};

const logFilteredCommitCount = logger => async ({ commits }) => {
const { name } = await readPkg();
const logFilteredCommitCount = getProjectName => logger => async ({
commits,
}) => {
const name = await getProjectName();

logger.log(
'Found %s commits for package %s since last release',
Expand All @@ -77,14 +68,17 @@ const logFilteredCommitCount = logger => async ({ commits }) => {
);
};

const withOnlyPackageCommits = plugin => async (pluginConfig, config) => {
const withOnlyPackageCommits = (
getProjectRoot,
getProjectName
) => plugin => async (pluginConfig, config) => {
const { logger } = config;

return plugin(
pluginConfig,
await pipeP(
mapCommits(onlyPackageCommits),
tapA(logFilteredCommitCount(logger))
mapCommits(onlyPackageCommits(getProjectRoot)),
tapA(logFilteredCommitCount(getProjectName)(logger))
)(config)
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const { compose, composeP, lensProp } = require('ramda');

const { overA } = require('./lens-utils');

const commits = lensProp('commits');
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
const {
mapNextReleaseVersion,
mapLastReleaseVersionToLastReleaseGitTag,
mapNextReleaseVersionToNextReleaseGitTag,
mapCommits,
} = require('./options-transforms');
const { mapNextReleaseVersion, mapCommits } = require('./options-transforms');

const OPTIONS = {
commits: [1, 2, 3, 4],
Expand All @@ -23,7 +18,7 @@ describe('semantic-release plugin options transforms', () => {
it('allows mapping the "commits" option', async () => {
const fn = commits => commits.filter(even);

expect(await mapCommits(fn)(OPTIONS)).toEqual({
await expect(mapCommits(fn)(OPTIONS)).resolves.toEqual({
...OPTIONS,
commits: [2, 4],
});
Expand All @@ -32,7 +27,7 @@ describe('semantic-release plugin options transforms', () => {

describe('#mapNextReleaseVersion', () => {
it('maps the nextRelease.version option', async () => {
expect(await mapNextReleaseVersion(toTag)(OPTIONS)).toEqual({
await expect(mapNextReleaseVersion(toTag)(OPTIONS)).resolves.toEqual({
...OPTIONS,
nextRelease: {
version: 'tag-4.5.6',
Expand Down
89 changes: 89 additions & 0 deletions src/core/plugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
const { compose } = require('ramda');
const { wrapStep } = require('semantic-release-plugin-decorators');

const logPluginVersion = require('./log-plugin-version');
const withOnlyPackageCommits = require('./only-package-commits');
const versionToGitTag = require('./version-to-git-tag');

const {
mapNextReleaseVersion,
withOptionsTransforms,
} = require('./options-transforms');

const analyzeCommits = (getProjectRoot, getProjectName, getProjectVersion) =>
wrapStep(
'analyzeCommits',
compose(
logPluginVersion('analyzeCommits', getProjectVersion),
withOnlyPackageCommits(getProjectRoot, getProjectName)
),
{
wrapperName: 'semantic-release-monorepo',
}
);

const generateNotes = (getProjectRoot, getProjectName, getProjectVersion) =>
wrapStep(
'generateNotes',
compose(
logPluginVersion('generateNotes', getProjectVersion),
withOnlyPackageCommits(getProjectRoot, getProjectName),
withOptionsTransforms([
mapNextReleaseVersion(versionToGitTag(getProjectName)),
])
),
{
wrapperName: 'semantic-release-monorepo',
}
);

const success = (getProjectRoot, getProjectName, getProjectVersion) =>
wrapStep(
'success',
compose(
logPluginVersion('success', getProjectVersion),
withOnlyPackageCommits(getProjectRoot, getProjectName),
withOptionsTransforms([
mapNextReleaseVersion(versionToGitTag(getProjectName)),
])
),
{
wrapperName: 'semantic-release-monorepo',
}
);

const fail = (getProjectRoot, getProjectName, getProjectVersion) =>
wrapStep(
'fail',
compose(
logPluginVersion('fail', getProjectVersion),
withOnlyPackageCommits(getProjectRoot, getProjectName),
withOptionsTransforms([
mapNextReleaseVersion(versionToGitTag(getProjectName)),
])
),
{
wrapperName: 'semantic-release-monorepo',
}
);

module.exports = (
getProjectRoot,
getProjectName,
getProjectNameSync,
getProjectVersion
) => ({
analyzeCommits: analyzeCommits(
getProjectRoot,
getProjectName,
getProjectVersion
),
generateNotes: generateNotes(
getProjectRoot,
getProjectName,
getProjectVersion
),
success: success(getProjectRoot, getProjectName, getProjectVersion),
fail: fail(getProjectRoot, getProjectName, getProjectVersion),
tagFormat: getProjectNameSync() + '-v${version}',
});
8 changes: 8 additions & 0 deletions src/core/version-to-git-tag.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module.exports = getProjectName => async version => {
if (!version) {
return null;
}

const name = await getProjectName();
return `${name}-v${version}`;
};
18 changes: 18 additions & 0 deletions src/core/version-to-git-tag.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const versionToGitTag = require('./version-to-git-tag');

describe('#versionToGitTag', () => {
describe('if passed a falsy version', () => {
it('returns null rather than creating a bad git-tag', async done => {
expect(
await versionToGitTag(async () => (await readPkg()).name)('')
).toBe(null);
expect(
await versionToGitTag(async () => (await readPkg()).name)(undefined)
).toBe(null);
expect(
await versionToGitTag(async () => (await readPkg()).name)(null)
).toBe(null);
done();
});
});
});
Loading