Skip to content

Commit

Permalink
feat: add sequential-init flag to avoid hypothetical concurrent initi…
Browse files Browse the repository at this point in the history
…alization collisions

relates dhoulb/multi-semantic-release#24
  • Loading branch information
antongolub committed Jul 19, 2020
1 parent c7ec00a commit 348678e
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 8 deletions.
2 changes: 1 addition & 1 deletion bin/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const cli = meow(
$ multi-semantic-release
Options
'none'
--sequential-init Avoid hypothetical concurrent initialization collisions.
Examples
$ multi-semantic-release
Expand Down
2 changes: 1 addition & 1 deletion bin/runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ module.exports = (flags) => {
console.log("yarn paths", paths);

// Do multirelease (log out any errors).
multiSemanticRelease(paths, {}, { cwd }).then(
multiSemanticRelease(paths, {}, { cwd }, flags).then(
() => {
// Success.
process.exit(0);
Expand Down
15 changes: 11 additions & 4 deletions lib/createInlinePluginCreator.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ function createInlinePluginCreator(packages, multiContext, synchronizer) {

// Announcement of readiness for release.
announceForAll("_readyToGenerateNotes");
announceForAll("_readyForRelease");

/**
* Update pkg deps.
Expand Down Expand Up @@ -85,6 +86,12 @@ function createInlinePluginCreator(packages, multiContext, synchronizer) {
// And bind actual logger.
Object.assign(pkg.loggerRef, context.logger);

pkg._ready = true;
emit(
"_readyForRelease",
todo().find((p) => !p._ready)
);

return plugins.verifyConditions(context);
};

Expand Down Expand Up @@ -198,11 +205,11 @@ function createInlinePluginCreator(packages, multiContext, synchronizer) {

const publish = async (pluginOptions, context) => {
pkg._prepared = true;
const nextPkgToProcess = todo().find((p) => p._nextType && !p._prepared);

if (nextPkgToProcess) {
emit("_readyToGenerateNotes", nextPkgToProcess);
}
emit(
"_readyToGenerateNotes",
todo().find((p) => p._nextType && !p._prepared)
);

// Wait for all packages to be `prepare`d and tagged by `semantic-release`
await waitForAll("_prepared", (p) => p._nextType);
Expand Down
18 changes: 16 additions & 2 deletions lib/multiSemanticRelease.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,14 @@ const createInlinePluginCreator = require("./createInlinePluginCreator");
* @param {string[]} paths An array of paths to package.json files.
* @param {Object} inputOptions An object containing semantic-release options.
* @param {Object} settings An object containing: cwd, env, stdout, stderr (mainly for configuring tests).
* @param {Object} flags Argv flags.
* @returns {Promise<Package[]>} Promise that resolves to a list of package objects with `result` property describing whether it released or not.
*/
async function multiSemanticRelease(
paths,
inputOptions = {},
{ cwd = process.cwd(), env = process.env, stdout = process.stdout, stderr = process.stderr } = {}
{ cwd = process.cwd(), env = process.env, stdout = process.stdout, stderr = process.stderr } = {},
flags = {}
) {
// Check params.
check(paths, "paths: string[]");
Expand All @@ -72,10 +74,22 @@ async function multiSemanticRelease(

// Shared signal bus.
const synchronizer = getSynchronizer(packages);
const { getLucky } = synchronizer;

// Release all packages.
const createInlinePlugin = createInlinePluginCreator(packages, multiContext, synchronizer);
await Promise.all(packages.map((pkg) => releasePackage(pkg, createInlinePlugin, multiContext)));
await Promise.all(
packages.map(async (pkg) => {
// Avoid hypothetical concurrent initialization collisions.
// https://github.com/dhoulb/multi-semantic-release/issues/24
if (flags.sequentialInit) {
getLucky("_readyForRelease", pkg);
await pkg._readyForRelease;
}

return releasePackage(pkg, createInlinePlugin, multiContext);
})
);
const released = packages.filter((pkg) => pkg.result).length;

// Return packages list.
Expand Down
56 changes: 56 additions & 0 deletions test/lib/multiSemanticRelease.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,62 @@ describe("multiSemanticRelease()", () => {
},
});
});
test("Changes in some packages (sequential-init)", async () => {
// Create Git repo.
const cwd = gitInit();
// Initial commit.
copyDirectory(`test/fixtures/yarnWorkspaces/`, cwd);
const sha1 = gitCommitAll(cwd, "feat: Initial release");
gitTag(cwd, "[email protected]");
gitTag(cwd, "[email protected]");
gitTag(cwd, "[email protected]");
gitTag(cwd, "[email protected]");
// Second commit.
writeFileSync(`${cwd}/packages/a/aaa.txt`, "AAA");
const sha2 = gitCommitAll(cwd, "feat(aaa): Add missing text file");
const url = gitInitOrigin(cwd);
gitPush(cwd);

// Capture output.
const stdout = new WritableStreamBuffer();
const stderr = new WritableStreamBuffer();

// Call multiSemanticRelease()
// Doesn't include plugins that actually publish.
const multiSemanticRelease = require("../../");
const result = await multiSemanticRelease(
[
`packages/c/package.json`,
`packages/d/package.json`,
`packages/b/package.json`,
`packages/a/package.json`,
],
{},
{ cwd, stdout, stderr },
{ sequentialInit: true }
);

// Check manifests.
expect(require(`${cwd}/packages/a/package.json`)).toMatchObject({
peerDependencies: {
"msr-test-c": "1.0.1",
},
});
expect(require(`${cwd}/packages/b/package.json`)).toMatchObject({
dependencies: {
"msr-test-a": "1.1.0",
},
devDependencies: {
"msr-test-c": "1.0.1",
},
});
expect(require(`${cwd}/packages/c/package.json`)).toMatchObject({
devDependencies: {
"msr-test-b": "1.0.1",
"msr-test-d": "1.0.0",
},
});
});
test("Error if release's local deps have no version number", async () => {
// Create Git repo with copy of Yarn workspaces fixture.
const cwd = gitInit();
Expand Down

0 comments on commit 348678e

Please sign in to comment.