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: add new bump option "prefix" #101

Merged
merged 7 commits into from
May 13, 2022
Merged
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 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