Skip to content

Commit

Permalink
feat: adding dependent prerelease fix behind flag
Browse files Browse the repository at this point in the history
* If you have a legacy fix where you are doing a work around and need the old behavior, please add skipEvaluateTags to config
  • Loading branch information
hanseltime committed Oct 1, 2023
1 parent 9963fb0 commit 4968fb6
Show file tree
Hide file tree
Showing 7 changed files with 742 additions and 28 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ Alternatively some options may be set via CLI flags.
| bump | `override \| satisfy \| inherit` | `--deps.bump` | Define deps version updating rule. Allowed: override, satisfy, inherit. **`override` by default.** |
| release | `patch \| minor \| major \| inherit` | `--deps.release` | Define release type for dependent package if any of its deps changes. Supported values: patch, minor, major, inherit. **`patch` by default** |
| prefix | `'^' \| '~' \| ''` | `--deps.prefix` | Optional prefix to be attached to the next version if `bump` is set to `override`. **`''` by default**. |
| skipEvaluateTags | `boolean` | `--deps.skipEvaluateTags` | Optional flag to skip using release tags for evaluating prerelease version bumping. This is almost always the correct option since semantic-relesae will be creating tags for every dependency and it would lead to us bumping to a non-existent version. Set to false if you've already compensated for this in your workflow previously. **`'true'` by default**. |


### Examples

Expand Down
26 changes: 25 additions & 1 deletion lib/getConfigMultiSemrel.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,28 @@ import { cosmiconfig } from "cosmiconfig";
import { pickBy, isNil, castArray, uniq } from "lodash-es";
import { createRequire } from "node:module";

/**
* @typedef {Object} DepsConfig
* @property {'override' | 'satisfy' | 'inherit'} bump
* @property {'patch' | 'minor' | 'major' | 'inherit'} release
* @property {'^' | '~' | ''} prefix
* @property {boolean} skipEvaluateTags
*/

/**
* @typedef {Object} MultiReleaseConfig
* @property {boolean} sequentialInit
* @property {boolean} sequentialPrepare
* @property {boolean} firstParent
* @property {boolean} debug
* @property {boolean} ignorePrivate
* @property {Array<string>} ignorePackages
* @property {string} tagFormat
* @property {boolean} dryRun
* @property {DepsConfig} deps
* @property {boolean} silent
*/

const CONFIG_NAME = "multi-release";
const CONFIG_FILES = [
"package.json",
Expand Down Expand Up @@ -36,7 +58,7 @@ const mergeConfig = (a = {}, b = {}) => {
*
* @param {string} cwd The directory to search.
* @param {Object} cliOptions cli supplied options.
* @returns {Object} The found configuration option
* @returns {MultiReleaseConfig} The found configuration option
*
* @internal
*/
Expand Down Expand Up @@ -72,6 +94,8 @@ export default async function getConfig(cwd, cliOptions) {
bump: "override",
release: "patch",
prefix: "",
// Default enacts new legacy
skipEvaluateTags: true,
},
silent: false,
},
Expand Down
32 changes: 28 additions & 4 deletions lib/multiSemanticRelease.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ import { createRequire } from "module";
* @param {Package[]} localDeps Array of local dependencies this package relies on.
* @param {context|void} context The semantic-release context for this package's release (filled in once semantic-release runs).
* @param {undefined|Result|false} result The result of semantic-release (object with lastRelease, nextRelease, commits, releases), false if this package was skipped (no changes or similar), or undefined if the package's release hasn't completed yet.
* @param {Object} options Aggregate of semantic-release options for the package
* @param {boolean} skipTagsForDepBump if set to true, the package will use tags to determine if its dependencies need to change (legacy functionality)
* @param {Object} _lastRelease The last release object for the package before its current release (set during anaylze-commit)
* @param {Object} _nextRelease The next release object (the release the package is releasing for this cycle) (set during generateNotes)
*/

/**
Expand Down Expand Up @@ -83,7 +87,15 @@ async function multiSemanticRelease(

// Vars.
const globalOptions = await getConfig(cwd);
const multiContext = { globalOptions, inputOptions, cwd, env, stdout, stderr };
const multiContext = {
globalOptions,
inputOptions,
cwd,
env,
stdout,
stderr,
skipTagsForDepBump: flags.deps.skipEvaluateTags,
};
const { queue, packages: _packages } = await topo({
cwd,
workspacesExtra: Array.isArray(flags.ignorePackages) ? flags.ignorePackages.map((p) => `!${p}`) : [],
Expand Down Expand Up @@ -141,7 +153,7 @@ async function multiSemanticRelease(
*
* @internal
*/
async function getPackage(path, { globalOptions, inputOptions, env, cwd, stdout, stderr }) {
async function getPackage(path, { globalOptions, inputOptions, env, cwd, stdout, stderr, skipTagsForDepBump }) {
// Make path absolute.
path = cleanPath(path, cwd);
const dir = dirname(path);
Expand Down Expand Up @@ -173,7 +185,17 @@ async function getPackage(path, { globalOptions, inputOptions, env, cwd, stdout,
const { options, plugins } = await getConfigSemantic({ cwd: dir, env, stdout, stderr }, finalOptions);

// Return package object.
return { path, dir, name, manifest, deps, options, plugins, fakeLogger: fakeLogger };
return {
path,
dir,
name,
manifest,
deps,
options,
plugins,
fakeLogger: fakeLogger,
skipTagsForDepBump: !!skipTagsForDepBump,
};
}

/**
Expand Down Expand Up @@ -239,7 +261,9 @@ async function releasePackage(pkg, createInlinePlugin, multiContext, flags) {

function normalizeFlags(_flags) {
return {
deps: {},
deps: {
skipEvaluateTags: !!_flags.deps?.skipEvaluateTags,
},
..._flags,
};
}
Expand Down
38 changes: 25 additions & 13 deletions lib/updateDeps.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,40 +44,52 @@ const getVersionFromTag = (pkg, tag) => {
/**
* Resolve next package version on prereleases.
*
* Will resolve highest next version of either:
*
* 1. The last release for the package during this multi-release cycle
* 2. (if the package has skipTagsForDepBump true):
* a. the highest increment of the tags array provided
* b. the highest increment of the gitTags for the prerelease
*
* @param {Package} pkg Package object.
* @param {Array<string>} tags Override list of tags from specific pkg and branch.
* @returns {string|undefined} Next pkg version.
* @internal
*/
const getNextPreVersion = (pkg, tags) => {
const tagFilters = [pkg._preRelease];
const lastVersion = pkg._lastRelease && pkg._lastRelease.version;
// Note: this is only set is a current multi-semantic-release released
const lastVersionForCurrentRelease = pkg._lastRelease && pkg._lastRelease.version;

if (pkg.skipTagsForDepBump && tags) {
throw new Error("Supplied tags for NextPreVersion but the package does not use tags for next prerelease");
}

// Extract tags:
// 1. Set filter to extract only package tags
// 2. Get tags from a branch considering the filters established
// 3. Resolve the versions from the tags
// TODO: replace {cwd: '.'} with multiContext.cwd
if (pkg.name) tagFilters.push(pkg.name);
if (!tags) {
if (!tags && !pkg.skipTagsForDepBump) {
try {
tags = getTags(pkg._branch, { cwd: process.cwd() }, tagFilters);
tags = getTags(pkg._branch, { cwd: pkg.dir }, tagFilters);
} catch (e) {
tags = [];
console.warn(e);
console.warn(`Try 'git pull ${pkg._branch}'`);
}
}

const lastPreRelTag = getPreReleaseTag(lastVersion);
const lastPreRelTag = getPreReleaseTag(lastVersionForCurrentRelease);
const isNewPreRelTag = lastPreRelTag && lastPreRelTag !== pkg._preRelease;

const versionToSet =
isNewPreRelTag || !lastVersion
isNewPreRelTag || !lastVersionForCurrentRelease
? `1.0.0-${pkg._preRelease}.1`
: _nextPreVersionCases(
tags.map((tag) => getVersionFromTag(pkg, tag)).filter((tag) => tag),
lastVersion,
tags ? tags.map((tag) => getVersionFromTag(pkg, tag)).filter((tag) => tag) : [],
lastVersionForCurrentRelease,
pkg._nextType,
pkg._preRelease
);
Expand All @@ -101,23 +113,23 @@ const getPreReleaseTag = (version) => {
/**
* Resolve next prerelease special cases: highest version from tags or major/minor/patch.
*
* @param {Array} tags List of all released tags from package.
* @param {string} lastVersion Last package version released.
* @param {Array<string>} tags - if non-empty, we will use these tags as part fo the comparison
* @param {string} lastVersionForCurrentMultiRelease Last package version released from multi-semantic-release
* @param {string} pkgNextType Next type evaluated for the next package type.
* @param {string} pkgPreRelease Package prerelease suffix.
* @returns {string|undefined} Next pkg version.
* @internal
*/
const _nextPreVersionCases = (tags, lastVersion, pkgNextType, pkgPreRelease) => {
const _nextPreVersionCases = (tags, lastVersionForCurrentMultiRelease, pkgNextType, pkgPreRelease) => {
// Case 1: Normal release on last version and is now converted to a prerelease
if (!semver.prerelease(lastVersion)) {
const { major, minor, patch } = semver.parse(lastVersion);
if (!semver.prerelease(lastVersionForCurrentMultiRelease)) {
const { major, minor, patch } = semver.parse(lastVersionForCurrentMultiRelease);
return `${semver.inc(`${major}.${minor}.${patch}`, pkgNextType || "patch")}-${pkgPreRelease}.1`;
}

// Case 2: Validates version with tags
const latestTag = getLatestVersion(tags, { withPrerelease: true });
return _nextPreHighestVersion(latestTag, lastVersion, pkgPreRelease);
return _nextPreHighestVersion(latestTag, lastVersionForCurrentMultiRelease, pkgPreRelease);
};

/**
Expand Down
11 changes: 9 additions & 2 deletions test/helpers/file.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { basename, join } from "path";
import { basename, join, resolve } from "path";
import { copyFileSync, existsSync, mkdirSync, lstatSync, readdirSync, readFileSync, writeFileSync } from "fs";

// Deep copy a directory.
Expand Down Expand Up @@ -44,5 +44,12 @@ function createNewTestingFiles(folders, cwd) {
});
}

function addPrereleaseToPackageRootConfig(rootDir, releaseBranch) {
const packageUri = resolve(join(rootDir, "package.json"));
const packageJson = JSON.parse(readFileSync(packageUri).toString());

packageJson.release.branches = ["master", { name: releaseBranch, prerelease: true }];
}

// Exports.
export { copyDirectory, isDirectory, createNewTestingFiles };
export { copyDirectory, isDirectory, createNewTestingFiles, addPrereleaseToPackageRootConfig };
Loading

0 comments on commit 4968fb6

Please sign in to comment.