Skip to content

Commit

Permalink
fix: handle pnpm monorepo workspace:^ version correctly (#111) (#128)
Browse files Browse the repository at this point in the history
* fix: update resolveNextVersion to handle pnpm workspace: versions
* fix: updateManifestDeps now use dep version in package.json (instead of release version) to determine next dep version
* refactor: use regex to substitude version with workspace
* test: add integration tests with yarn workspace ranges

closes #111
  • Loading branch information
lyh543 committed Sep 14, 2022
1 parent 2dedffc commit 2d083e0
Show file tree
Hide file tree
Showing 9 changed files with 231 additions and 10 deletions.
32 changes: 27 additions & 5 deletions lib/updateDeps.js
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,9 @@ const manifestUpdateNecessary = (scope, name, nextVersion, bumpStrategy, prefix)
* @internal
*/
const resolveNextVersion = (currentVersion, nextVersion, bumpStrategy = "override", prefix = "") => {
// handle cases of "workspace protocol" defined in yarn and pnpm workspace, whose version starts with "workspace:"
currentVersion = substituteWorkspaceVersion(currentVersion, nextVersion);

//no change...
if (currentVersion === nextVersion) return currentVersion;

Expand All @@ -260,10 +263,10 @@ const resolveNextVersion = (currentVersion, nextVersion, bumpStrategy = "overrid
const currentChunks = currentVersion.split(sep);
// prettier-ignore
const resolvedChunks = currentChunks.map((chunk, i) =>
nextChunks[i]
? chunk.replace(/\d+/, nextChunks[i])
: chunk
);
nextChunks[i]
? chunk.replace(/\d+/, nextChunks[i])
: chunk
);

return resolvedChunks.join(sep);
}
Expand All @@ -273,6 +276,25 @@ const resolveNextVersion = (currentVersion, nextVersion, bumpStrategy = "overrid
return prefix + nextVersion;
};

/**
* Substitute "workspace:" in currentVersion
* See:
* {@link https://yarnpkg.com/features/workspaces#publishing-workspaces}
* {@link https://pnpm.io/workspaces#publishing-workspace-packages}
*
* @param {string} currentVersion Current version, may start with "workspace:"
* @param {string} nextVersion Next version
* @returns {string} current version without "workspace:"
*/
const substituteWorkspaceVersion = (currentVersion, nextVersion) => {
if (currentVersion.startsWith("workspace:")) {
const [, range, caret] = /^workspace:(([\^~*])?.*)$/.exec(currentVersion);

return caret === range ? (caret === "*" ? nextVersion : caret + nextVersion) : range;
}
return currentVersion;
};

/**
* Update pkg deps.
*
Expand Down Expand Up @@ -306,7 +328,7 @@ const updateManifestDeps = (pkg, writeOut = true, bumpStrategy = "override", pre
scopes.forEach((scope) => {
if (scope[dependency.name]) {
scope[dependency.name] = resolveNextVersion(
get(dependency, "_lastRelease.version", "0.0.0"),
scope[dependency.name],
release.version,
bumpStrategy,
prefix
Expand Down
20 changes: 20 additions & 0 deletions test/fixtures/yarnWorkspacesRanges/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "msr-test-yarn",
"author": "Dave Houlbrooke <dave@shax.com>",
"version": "0.0.0-semantically-released",
"private": true,
"license": "0BSD",
"engines": {
"node": ">=8.3"
},
"workspaces": [
"packages/*"
],
"release": {
"plugins": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator"
],
"noCi": true
}
}
14 changes: 14 additions & 0 deletions test/fixtures/yarnWorkspacesRanges/packages/a/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "msr-test-a",
"version": "0.0.0",
"dependencies": {
"msr-test-b": "workspace:*"
},
"devDependencies": {
"msr-test-c": "workspace:^"
},
"peerDependencies": {
"msr-test-d": "workspace:~",
"left-pad": "latest"
}
}
7 changes: 7 additions & 0 deletions test/fixtures/yarnWorkspacesRanges/packages/b/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "msr-test-b",
"version": "0.0.0",
"optionalDependencies": {
"msr-test-d": "workspace:^0.0.0"
}
}
3 changes: 3 additions & 0 deletions test/fixtures/yarnWorkspacesRanges/packages/c/.releaserc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"tagFormat": "multi-semantic-release-test-c@v${version}"
}
4 changes: 4 additions & 0 deletions test/fixtures/yarnWorkspacesRanges/packages/c/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"name": "msr-test-c",
"version": "0.0.0"
}
4 changes: 4 additions & 0 deletions test/fixtures/yarnWorkspacesRanges/packages/d/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"name": "msr-test-d",
"version": "0.0.0"
}
134 changes: 129 additions & 5 deletions test/lib/multiSemanticRelease.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1729,21 +1729,145 @@ describe("multiSemanticRelease()", () => {
// Check manifests.
expect(require(`${cwd}/packages/a/package.json`)).toMatchObject({
peerDependencies: {
"msr-test-c": strategy === "inherit" ? "1.0.0" : prefix + "1.0.0",
"msr-test-c": strategy === "inherit" ? "^1.0.0" : prefix + "1.0.0",
},
});
expect(require(`${cwd}/packages/b/package.json`)).toMatchObject({
dependencies: {
"msr-test-a": strategy === "inherit" ? "1.0.0" : prefix + "1.0.0",
"msr-test-a": strategy === "inherit" ? "^1.0.0" : prefix + "1.0.0",
},
devDependencies: {
"msr-test-c": strategy === "inherit" ? "1.0.0" : prefix + "1.0.0",
"msr-test-c": strategy === "inherit" ? "^1.0.0" : prefix + "1.0.0",
},
});
expect(require(`${cwd}/packages/c/package.json`)).toMatchObject({
devDependencies: {
"msr-test-b": strategy === "inherit" ? "1.0.0" : prefix + "1.0.0",
"msr-test-d": strategy === "inherit" ? "1.0.0" : prefix + "1.0.0",
"msr-test-b": strategy === "inherit" ? "^1.0.0" : prefix + "1.0.0",
"msr-test-d": strategy === "inherit" ? "^1.0.0" : prefix + "1.0.0",
},
});
});
});
describe.each([
["override", "^"],
["satisfy", "^"],
["inherit", "^"],
])("With Yarn Workspace Ranges & deps.bump=%s & deps.prefix=%s", (strategy, prefix) => {
test('should replace "workspace:" with correct version', async () => {
// Create Git repo with copy of Yarn workspaces fixture.
const cwd = gitInit();
copyDirectory(`test/fixtures/yarnWorkspacesRanges/`, cwd);
const sha = gitCommitAll(cwd, "feat: Initial release");
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/a/package.json`,
`packages/b/package.json`,
`packages/c/package.json`,
`packages/d/package.json`,
],
{},
{ cwd, stdout, stderr },
{ deps: { bump: strategy, prefix } }
);

// Get stdout and stderr output.
const err = stderr.getContentsAsString("utf8");
expect(err).toBe(false);
const out = stdout.getContentsAsString("utf8");
expect(out).toMatch("Started multirelease! Loading 4 packages...");
expect(out).toMatch("Loaded package msr-test-a");
expect(out).toMatch("Loaded package msr-test-b");
expect(out).toMatch("Loaded package msr-test-c");
expect(out).toMatch("Loaded package msr-test-d");
expect(out).toMatch("Queued 4 packages! Starting release...");
expect(out).toMatch("Created tag msr-test-a@1.0.0");
expect(out).toMatch("Created tag msr-test-b@1.0.0");
expect(out).toMatch("Created tag msr-test-c@1.0.0");
expect(out).toMatch("Created tag msr-test-d@1.0.0");
expect(out).toMatch("Released 4 of 4 packages, semantically!");

// A.
expect(result[0].name).toBe("msr-test-a");
expect(result[0].result.lastRelease).toEqual({});
expect(result[0].result.nextRelease).toMatchObject({
gitHead: sha,
gitTag: "msr-test-a@1.0.0",
type: "minor",
version: "1.0.0",
});
expect(result[0].result.nextRelease.notes).toMatch("# msr-test-a 1.0.0");
expect(result[0].result.nextRelease.notes).toMatch("### Features\n\n* Initial release");
expect(result[0].result.nextRelease.notes).toMatch(
"### Dependencies\n\n* **msr-test-b:** upgraded to 1.0.0\n* **msr-test-c:** upgraded to 1.0.0"
);

// B.
expect(result[1].name).toBe("msr-test-b");
expect(result[1].result.lastRelease).toEqual({});
expect(result[1].result.nextRelease).toMatchObject({
gitHead: sha,
gitTag: "msr-test-b@1.0.0",
type: "minor",
version: "1.0.0",
});
expect(result[1].result.nextRelease.notes).toMatch("# msr-test-b 1.0.0");
expect(result[1].result.nextRelease.notes).toMatch("### Features\n\n* Initial release");
expect(result[1].result.nextRelease.notes).toMatch(
"### Dependencies\n\n* **msr-test-d:** upgraded to 1.0.0"
);

// C.
expect(result[2].name).toBe("msr-test-c");
expect(result[2].result.lastRelease).toEqual({});
expect(result[2].result.nextRelease).toMatchObject({
gitHead: sha,
gitTag: "msr-test-c@1.0.0",
type: "minor",
version: "1.0.0",
});
expect(result[2].result.nextRelease.notes).toMatch("# msr-test-c 1.0.0");
expect(result[2].result.nextRelease.notes).toMatch("### Features\n\n* Initial release");

// D.
expect(result[3].name).toBe("msr-test-d");
expect(result[3].result.lastRelease).toEqual({});
expect(result[3].result.nextRelease).toMatchObject({
gitHead: sha,
gitTag: "msr-test-d@1.0.0",
type: "minor",
version: "1.0.0",
});
expect(result[3].result.nextRelease.notes).toMatch("# msr-test-d 1.0.0");
expect(result[3].result.nextRelease.notes).toMatch("### Features\n\n* Initial release");

// ONLY four times.
expect(result).toHaveLength(4);

// Check manifests.
expect(require(`${cwd}/packages/a/package.json`)).toMatchObject({
dependencies: {
"msr-test-b": "1.0.0",
},
devDependencies: {
"msr-test-c": strategy === "override" ? prefix + "1.0.0" : "^1.0.0",
},
peerDependencies: {
"msr-test-d": strategy === "override" ? prefix + "1.0.0" : "~1.0.0",
},
});
expect(require(`${cwd}/packages/b/package.json`)).toMatchObject({
optionalDependencies: {
"msr-test-d": strategy === "override" ? prefix + "1.0.0" : "^1.0.0",
},
});
});
Expand Down
23 changes: 23 additions & 0 deletions test/lib/updateDeps.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,29 @@ describe("resolveNextVersion()", () => {
["*", "2.0.0", "inherit", "*"],
["~1.0", "2.0.0", "inherit", "~2.0"],
["~2.0", "2.1.0", "inherit", "~2.1"],

// cases of "workspace protocol" defined in yarn and pnpm
["workspace:*", "1.0.1", undefined, "1.0.1"],
["workspace:*", "1.0.1", "override", "1.0.1"],

["workspace:*", "1.3.0", "satisfy", "1.3.0"],
["workspace:~", "1.0.1", "satisfy", "~1.0.1"],
["workspace:^", "1.3.0", "satisfy", "^1.3.0"],
// the following cases should be treated as if "workspace:" was removed
["workspace:^1.0.0", "1.0.1", "satisfy", "^1.0.0"],
["workspace:^1.2.0", "1.3.0", "satisfy", "^1.2.0"],
["workspace:1.2.x", "1.2.2", "satisfy", "1.2.x"],

["workspace:*", "1.3.0", "inherit", "1.3.0"],
["workspace:~", "1.1.0", "inherit", "~1.1.0"],
["workspace:^", "2.0.0", "inherit", "^2.0.0"],
// the following cases should be treated as if "workspace:" was removed
["workspace:~1.0.0", "1.1.0", "inherit", "~1.1.0"],
["workspace:1.2.x", "1.2.1", "inherit", "1.2.x"],
["workspace:1.2.x", "1.3.0", "inherit", "1.3.x"],
["workspace:^1.0.0", "2.0.0", "inherit", "^2.0.0"],
["workspace:~1.0", "2.0.0", "inherit", "~2.0"],
["workspace:~2.0", "2.1.0", "inherit", "~2.1"],
]

cases.forEach(([currentVersion, nextVersion, strategy, resolvedVersion]) => {
Expand Down

0 comments on commit 2d083e0

Please sign in to comment.