Skip to content

Commit

Permalink
feat: add new bump directive "prefix" (#101)
Browse files Browse the repository at this point in the history
  • Loading branch information
moroine authored May 13, 2022
1 parent 1c7e347 commit 001e344
Show file tree
Hide file tree
Showing 12 changed files with 1,372 additions and 1,204 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ There are several tweaks to adapt **msr** to some corner cases:
|`--first-parent` |bool |Apply commit filtering to current branch only|`false`|
|`--deps.bump` |string |Define deps version update rule. `override` — replace any prev version with the next one, `satisfy` — check the next pkg version against its current references. If it matches (`*` matches to any, `1.1.0` matches `1.1.x`, `1.5.0` matches to `^1.0.0` and so on) release will not be triggered, if not `override` strategy will be applied instead; `inherit` will try to follow the current declaration version/range. `~1.0.0` + `minor` turns into `~1.1.0`, `1.x` + `major` gives `2.x`, but `1.x` + `minor` gives `1.x` so there will be no release, etc. + **Experimental feat** | `override`
|`--deps.release` |string |Define release type for dependent package if any of its deps changes. `patch`, `minor`, `major` — strictly declare the release type that occurs when any dependency is updated; `inherit` — applies the "highest" release of updated deps to the package. For example, if any dep has a breaking change, `major` release will be applied to the all dependants up the chain. **Experimental feat** | `patch`
|`--deps.prefix` |string |Optional prefix to be attached to the next version if `--deps.bump` set to `override`. Supported values: `^` \| `~` \| `''` (empty string) | `''` (empty string)
|`--dry-run` |bool |Dry run mode| `false`
|`--ignore-packages`|string |Packages list to be ignored on bumping process (append to the ones that already exist at package.json workspaces)|`null`
|`--ignore-private-packages`|bool |Private packages will be ignored |`false`
Expand Down
5 changes: 5 additions & 0 deletions bin/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const cli = meow(
--sequential-init Avoid hypothetical concurrent initialization collisions.
--first-parent Apply commit filtering to current branch only.
--deps.bump Define deps version updating rule. Allowed: override, satisfy, inherit.
--deps.prefix Optional prefix to be attached to the next dep version if '--deps.bump' set to 'override'. Supported values: '^' | '~' | '' (empty string as default).
--deps.release Define release type for dependent package if any of its deps changes. Supported values: patch, minor, major, inherit.
--ignore-packages Packages' list to be ignored on bumping process
--ignore-private-packages Ignore private packages
Expand Down Expand Up @@ -43,6 +44,10 @@ const cli = meow(
type: "string",
default: "patch",
},
"deps.prefix": {
type: "string",
default: "",
},
ignorePackages: {
type: "string",
},
Expand Down
4 changes: 2 additions & 2 deletions lib/createInlinePluginCreator.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ function createInlinePluginCreator(packages, multiContext, synchronizer, flags)
const signal = "_depCheck" + round++;

//estimate the type of update for the package
const nextType = resolveReleaseType(pkg, flags.deps.bump, flags.deps.release);
const nextType = resolveReleaseType(pkg, flags.deps.bump, flags.deps.release, flags.deps.prefix);

//indicate if it changed
pkg[signal] = pkg._nextType === nextType ? "stable" : "changed";
Expand Down Expand Up @@ -232,7 +232,7 @@ function createInlinePluginCreator(packages, multiContext, synchronizer, flags)
const pkgs = uniqBy([pkg, ...getRootPkgs(context, { _depsChanged: pkg._depsChanged })], "path");

// Loop through each manifest and update its dependencies if needed
pkgs.forEach((item) => updateManifestDeps(item));
pkgs.forEach((item) => updateManifestDeps(item, true, flags.deps.bump, flags.deps.prefix));
pkg._depsUpdated = true;

const res = await plugins.prepare(context);
Expand Down
30 changes: 19 additions & 11 deletions lib/updateDeps.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,17 +138,18 @@ const getHighestReleaseType = (...releaseTypes) =>
* @param {Package} pkg Package object.
* @param {string|undefined} bumpStrategy Dependency resolution strategy: override, satisfy, inherit.
* @param {string|undefined} releaseStrategy Release type triggered by deps updating: patch, minor, major, inherit.
* @param {string} prefix Dependency version prefix to be attached if `bumpStrategy='override'`. ^ | ~ | '' (defaults to empty string)
* @returns {string|undefined} Resolved release type.
* @internal
*/
const resolveReleaseType = (pkg, bumpStrategy = "override", releaseStrategy = "patch") => {
const resolveReleaseType = (pkg, bumpStrategy = "override", releaseStrategy = "patch", prefix = "") => {
// Define release type for dependent package if any of its deps changes.
// `patch`, `minor`, `major` — strictly declare the release type that occurs when any dependency is updated.
// `inherit` — applies the "highest" release of updated deps to the package.
// For example, if any dep has a breaking change, `major` release will be applied to the all dependants up the chain.

//create a list of dependencies that require change to the manifest
pkg._depsChanged = pkg.localDeps.filter((d) => needsDependencyUpdate(pkg, d, bumpStrategy));
pkg._depsChanged = pkg.localDeps.filter((d) => needsDependencyUpdate(pkg, d, bumpStrategy, prefix));

//check if any dependencies have changed. If not return the current type of release
if (
Expand All @@ -173,10 +174,11 @@ const resolveReleaseType = (pkg, bumpStrategy = "override", releaseStrategy = "p
* Indicates if the manifest file requires a change for the given dependency
* @param {Package} pkg Package object.
* @param {Package} dependency dependency to check
* @param {string|undefined} bumpStrategy Dependency resolution strategy: override, satisfy, inherit.
* @param {string|undefined} bumpStrategy Dependency resolution strategy: override, satisfy, inherit.
* @param {string} prefix Dependency version prefix to be attached if `bumpStrategy='override'`. ^ | ~ | '' (defaults to empty string)
* @returns {boolean } true if dependency needs to change
*/
const needsDependencyUpdate = (pkg, dependency, bumpStrategy) => {
const needsDependencyUpdate = (pkg, dependency, bumpStrategy, prefix) => {
//get last release of dependency
const depLastVersion = dependency._lastRelease && dependency._lastRelease.version;

Expand All @@ -197,7 +199,7 @@ const needsDependencyUpdate = (pkg, dependency, bumpStrategy) => {

//Check if the manifest dependency rules warrants an update (in any of the dependency scopes)
const requireUpdate = scopes.some((scope) =>
manifestUpdateNecessary(scope, dependency.name, depNextVersion, bumpStrategy)
manifestUpdateNecessary(scope, dependency.name, depNextVersion, bumpStrategy, prefix)
);

//return if update is required
Expand All @@ -211,9 +213,10 @@ const needsDependencyUpdate = (pkg, dependency, bumpStrategy) => {
* @param {string} name name of the dependency to update
* @param {string} nextVersion the new version of the dependency
* @param {string} bumpStrategy Dependency resolution strategy: override, satisfy, inherit.
* @param {string} prefix Dependency version prefix to be attached if `bumpStrategy='override'`. ^ | ~ | '' (defaults to empty string)
* @returns {boolean} true if a the dependency exists in the scope and requires a version update
*/
const manifestUpdateNecessary = (scope, name, nextVersion, bumpStrategy) => {
const manifestUpdateNecessary = (scope, name, nextVersion, bumpStrategy, prefix) => {
const currentVersion = scope[name];
if (!nextVersion || !currentVersion) {
return false;
Expand All @@ -222,7 +225,7 @@ const manifestUpdateNecessary = (scope, name, nextVersion, bumpStrategy) => {
//calculate the next version of the manifest dependency, given the current version
//this checks the semantic versioning rules. Resolved version will remain
//current version if the currentVersion "encompasses" the next version
const resolvedVersion = resolveNextVersion(currentVersion, nextVersion, bumpStrategy);
const resolvedVersion = resolveNextVersion(currentVersion, nextVersion, bumpStrategy, prefix);

return currentVersion !== resolvedVersion;
};
Expand All @@ -233,10 +236,11 @@ const manifestUpdateNecessary = (scope, name, nextVersion, bumpStrategy) => {
* @param {string} currentVersion Current dep version
* @param {string} nextVersion Next release type: patch, minor, major
* @param {string|undefined} bumpStrategy Resolution strategy: inherit, override, satisfy
* @param {string} prefix Dependency version prefix to be attached if `bumpStrategy='override'`. ^ | ~ | '' (defaults to empty string)
* @returns {string} Next dependency version
* @internal
*/
const resolveNextVersion = (currentVersion, nextVersion, bumpStrategy = "override") => {
const resolveNextVersion = (currentVersion, nextVersion, bumpStrategy = "override", prefix = "") => {
//no change...
if (currentVersion === nextVersion) return currentVersion;

Expand Down Expand Up @@ -266,18 +270,20 @@ const resolveNextVersion = (currentVersion, nextVersion, bumpStrategy = "overrid

// "override"
// By default next package version would be set as is for the all dependants.
return nextVersion;
return prefix + nextVersion;
};

/**
* Update pkg deps.
*
* @param {Package} pkg The package this function is being called on.
* @param {boolean} writeOut Commit the package to the file store (set to false to suppres)
* @param {string|undefined} bumpStrategy Resolution strategy: inherit, override, satisfy
* @param {string} prefix Dependency version prefix to be attached if `bumpStrategy='override'`. ^ | ~ | '' (defaults to empty string)
* @returns {undefined}
* @internal
*/
const updateManifestDeps = (pkg, writeOut = true) => {
const updateManifestDeps = (pkg, writeOut = true, bumpStrategy = "override", prefix = "") => {
const { manifest, path } = pkg;

// Loop through changed deps to verify release consistency.
Expand All @@ -298,7 +304,9 @@ const updateManifestDeps = (pkg, writeOut = true) => {
} = pkg.manifest;
const scopes = [dependencies, devDependencies, peerDependencies, optionalDependencies];
scopes.forEach((scope) => {
if (scope[dependency.name]) scope[dependency.name] = release.version;
if (scope[dependency.name]) {
scope[dependency.name] = bumpStrategy === "override" ? prefix + release.version : release.version;
}
});
});

Expand Down
20 changes: 20 additions & 0 deletions test/fixtures/yarnWorkspacesPackagesCarret/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": ["packages/*"]
},
"release": {
"plugins": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator"
],
"noCi": true
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "msr-test-a",
"version": "0.0.0",
"peerDependencies": {
"msr-test-c": "^0.0.0",
"left-pad": "latest"
}
}
11 changes: 11 additions & 0 deletions test/fixtures/yarnWorkspacesPackagesCarret/packages/b/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "msr-test-b",
"version": "0.0.0",
"dependencies": {
"msr-test-a": "^0.0.0"
},
"devDependencies": {
"msr-test-c": "^0.0.0",
"left-pad": "latest"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"tagFormat": "multi-semantic-release-test-c@v${version}"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "msr-test-c",
"version": "0.0.0",
"devDependencies": {
"msr-test-b": "^0.0.0",
"msr-test-d": "^0.0.0"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"name": "msr-test-d",
"version": "0.0.0"
}
131 changes: 131 additions & 0 deletions test/lib/multiSemanticRelease.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1617,4 +1617,135 @@ describe("multiSemanticRelease()", () => {
});
});
});
describe.each([
["override", "yarnWorkspacesPackagesCarret", "^"],
["override", "yarnWorkspacesPackages", "~"],
["inherit", "yarnWorkspacesPackagesCarret", "^"],
])("With deps.bump=%s & deps.prefix=%s & fixture=%s", (strategy, fixtureName, prefix) => {
test("should bump with deps.prefix", async () => {
// Create Git repo with copy of Yarn workspaces fixture.
const cwd = gitInit();
copyDirectory(`test/fixtures/${fixtureName}/`, 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-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-a:** upgraded to 1.0.0\n* **msr-test-c:** 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");
expect(result[2].result.nextRelease.notes).toMatch(
"### Dependencies\n\n* **msr-test-b:** upgraded to 1.0.0"
);

// 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");
expect(result[3].result.nextRelease.notes).not.toMatch("### Dependencies");

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

// Check manifests.
expect(require(`${cwd}/packages/a/package.json`)).toMatchObject({
peerDependencies: {
"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",
},
devDependencies: {
"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",
},
});
});
});
});
Loading

0 comments on commit 001e344

Please sign in to comment.