Skip to content

Commit

Permalink
fix: misalignment between manifest and notes
Browse files Browse the repository at this point in the history
  • Loading branch information
reuzel authored and antongolub committed Aug 13, 2021
1 parent ee2dee6 commit bb2ea25
Show file tree
Hide file tree
Showing 4 changed files with 274 additions and 157 deletions.
4 changes: 2 additions & 2 deletions lib/createInlinePluginCreator.js
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,8 @@ function createInlinePluginCreator(packages, multiContext, synchronizer, flags)
if (subs) notes.push(subs.replace(/^(#+) (\[?\d+\.\d+\.\d+\]?)/, `$1 ${name} $2`));

// If it has upgrades add an upgrades section.
const upgrades = pkg.localDeps.filter((d) => d._nextRelease);
if (upgrades.length) {
const upgrades = pkg._depsChanged.filter((d) => d._nextRelease && d._nextRelease.version);
if (upgrades && upgrades.length > 0) {
notes.push(`### Dependencies`);
const bullets = upgrades.map((d) => `* **${d.name}:** upgraded to ${d._nextRelease.version}`);
notes.push(bullets.join("\n"));
Expand Down
198 changes: 126 additions & 72 deletions lib/updateDeps.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,93 +130,95 @@ const _nextPreHighestVersion = (latestTag, lastVersion, pkgPreRelease) => {
* @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 {Package[]} ignore=[] Packages to ignore (to prevent infinite loops).
* @param {Package[]} ignore=[] Packages to ignore (to prevent infinite loops when traversing graphs of dependencies).
* @returns {string|undefined} Resolved release type.
* @internal
*/
const resolveReleaseType = (pkg, bumpStrategy = "override", releaseStrategy = "patch", ignore = []) => {
// NOTE This fn also updates pkg deps, so it must be invoked anyway.
const dependentReleaseType = getDependentRelease(pkg, bumpStrategy, releaseStrategy, ignore);

// Release type found by commitAnalyzer.
if (pkg._nextType) {
return pkg._nextType;
}

if (!dependentReleaseType) {
return undefined;
//make sure any dependency changes are resolved before returning the release type
if (!pkg._depsResolved) {
//create a list of dependencies that require change
pkg._depsChanged = pkg.localDeps
.filter((d) => !ignore.includes(d))
.filter((d) => needsDependencyUpdate(pkg, d, bumpStrategy, releaseStrategy, [pkg, ...ignore]));

//get the (preliminary) release type of the package based on release strategy (and analyzed changed dependencies)
pkg._nextType = getDependentRelease(pkg, releaseStrategy);

//indicate that all deps are resolved (fixates the next type and depsChanged)
pkg._depsResolved = ignore.length === 0;
}

// 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.

pkg._nextType = releaseStrategy === "inherit" ? dependentReleaseType : releaseStrategy;

return pkg._nextType;
};

/**
* Get dependent release type by recursive scanning and updating its deps.
*
* @param {Package} pkg The package with local deps to check.
* @param {string} bumpStrategy Dependency resolution strategy: override, satisfy, inherit.
* @param {string} releaseStrategy Release type triggered by deps updating: patch, minor, major, inherit.
* Indicates if the manifest file requires a change for the given dependency
* @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 {Package[]} ignore Packages to ignore (to prevent infinite loops).
* @returns {string|undefined} Returns the highest release type if found, undefined otherwise
* @internal
*/
const getDependentRelease = (pkg, bumpStrategy, releaseStrategy, ignore) => {
const severityOrder = ["patch", "minor", "major"];
const { localDeps, manifest = {} } = pkg;
const { dependencies = {}, devDependencies = {}, peerDependencies = {}, optionalDependencies = {} } = manifest;

const needsDependencyUpdate = (pkg, dependency, bumpStrategy, releaseStrategy, ignore) => {
//get last release of dependency
const depLastVersion = dependency._lastRelease && dependency._lastRelease.version;

// 3. check if dependency was released before (if not, this is assumed to be a new package + dependency)
const wasReleased = depLastVersion !== undefined;
if (!wasReleased) return true; //new packages always require a package re-release

//get nextType of the dependency (recursion occurs here!)
// Has changed if...
// 1. Any local dep package itself triggered changed
// 2. Any local dep package has local deps that triggered change.
const depNextType = resolveReleaseType(dependency, bumpStrategy, releaseStrategy, ignore);

//get estimated next version of dependency (which is lastVersion if no change expected)
const depNextVersion = depNextType
? dependency._preRelease
? getNextPreVersion(dependency)
: getNextVersion(dependency)
: depLastVersion;

//get list of manifest dependencies
const { dependencies = {}, devDependencies = {}, peerDependencies = {}, optionalDependencies = {} } = pkg.manifest;
const scopes = [dependencies, devDependencies, peerDependencies, optionalDependencies];
const bumpDependency = (scope, name, nextVersion) => {
const currentVersion = scope[name];
if (!nextVersion || !currentVersion) {
return false;
}

const resolvedVersion = resolveNextVersion(currentVersion, nextVersion, bumpStrategy);
if (currentVersion !== resolvedVersion) {
scope[name] = resolvedVersion;
return true;
}
// 4. 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)
);

//return if update is required
return requireUpdate;
};

/**
* Checks if an update of a package is necessary in the given list of dependencies, based on semantic versioning rules
* and the bumpStrategy.
* @param {Object} scope object containing dependencies. Dependency names are the keys, dependency rule the values.
* @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.
* @returns {boolean} true if a the dependency exists in the scope and requires a version update
*/
const manifestUpdateNecessary = (scope, name, nextVersion, bumpStrategy) => {
const currentVersion = scope[name];
if (!nextVersion || !currentVersion) {
return false;
};

// prettier-ignore
return localDeps
.filter((p) => !ignore.includes(p))
.reduce((releaseType, p) => {
const name = p.name;

// Has changed if...
// 1. Any local dep package itself has changed
// 2. Any local dep package has local deps that have changed.
const nextType = resolveReleaseType(p, bumpStrategy, releaseStrategy,[...ignore, ...localDeps]);

// Set the nextVersion fallback to the last local dependency package last version
let nextVersion = p._lastRelease && p._lastRelease.version;

// Update the nextVersion only if there is a next type to be bumped
if (nextType) nextVersion = p._preRelease ? getNextPreVersion(p) : getNextVersion(p);
const lastVersion = pkg._lastRelease && pkg._lastRelease.version;

// 3. And this change should correspond to manifest updating rule.
const requireRelease = scopes
.reduce((res, scope) => bumpDependency(scope, name, nextVersion) || res, !lastVersion)

return requireRelease && (severityOrder.indexOf(nextType) > severityOrder.indexOf(releaseType))
? nextType
: releaseType;
}, undefined);
}

//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);

return currentVersion !== resolvedVersion;
};

/**
* Resolve next version of dependency.
* Resolve next version of dependency based on bumpStrategy
*
* @param {string} currentVersion Current dep version
* @param {string} nextVersion Next release type: patch, minor, major
Expand Down Expand Up @@ -254,6 +256,46 @@ const resolveNextVersion = (currentVersion, nextVersion, bumpStrategy = "overrid
return nextVersion;
};

/**
* Get dependent release type by analyzing the current nextType and changed dependencies
* @param {Package} pkg The package to determine next type of release of
* @param {string} releaseStrategy Release type triggered by deps updating: patch, minor, major, inherit.
* @returns {string|undefined} Returns the highest release type if found, undefined otherwise
* @internal
*/
const getDependentRelease = (pkg, releaseStrategy) => {
const severityOrder = ["patch", "minor", "major"];

// 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.

//return type set by commit analyzer if no deps changed
if (
!pkg._lastRelease || //new package
!pkg._depsChanged || //no deps analyzed
pkg._depsChanged.length === 0 || //no deps available
pkg._depsChanged.every((dep) => !dep._nextType && dep._lastRelease) //no new deps or deps upgraded
)
return pkg._nextType;

if (releaseStrategy === "inherit") {
//find highest release type if strategy is inherit, starting of type set by commit analyzer
return pkg._depsChanged.reduce((maxReleaseType, dependency) => {
return severityOrder.indexOf(dependency._nextType) > severityOrder.indexOf(maxReleaseType)
? dependency._nextType
: maxReleaseType;
}, pkg._nextType);
}

//return highest of commit analyzer found change and releaseStrategy
//releaseStrategy of major could override local update of minor
return severityOrder.indexOf(pkg._nextType) > severityOrder.indexOf(releaseStrategy)
? pkg._nextType
: releaseStrategy;
};

/**
* Update pkg deps.
*
Expand All @@ -265,14 +307,26 @@ const updateManifestDeps = (pkg) => {
const { manifest, path } = pkg;
const { indent, trailingWhitespace } = recognizeFormat(manifest.__contents__);

// Loop through localDeps to verify release consistency.
pkg.localDeps.forEach((d) => {
// Loop through changed deps to verify release consistency.
pkg._depsChanged.forEach((dependency) => {
// Get version of dependency.
const release = d._nextRelease || d._lastRelease;
const release = dependency._nextRelease || dependency._lastRelease;

// Cannot establish version.
if (!release || !release.version)
throw Error(`Cannot release because dependency ${d.name} has not been released`);
throw Error(`Cannot release because dependency ${dependency.name} has not been released`);

//update changed dependencies
const {
dependencies = {},
devDependencies = {},
peerDependencies = {},
optionalDependencies = {},
} = pkg.manifest;
const scopes = [dependencies, devDependencies, peerDependencies, optionalDependencies];
scopes.forEach((scope) => {
if (scope[dependency.name]) scope[dependency.name] = release.version;
});
});

if (!auditManifestChanges(manifest, path)) {
Expand Down
Loading

0 comments on commit bb2ea25

Please sign in to comment.