@@ -130,93 +130,95 @@ const _nextPreHighestVersion = (latestTag, lastVersion, pkgPreRelease) => {
130130 * @param {Package } pkg Package object.
131131 * @param {string|undefined } bumpStrategy Dependency resolution strategy: override, satisfy, inherit.
132132 * @param {string|undefined } releaseStrategy Release type triggered by deps updating: patch, minor, major, inherit.
133- * @param {Package[] } ignore=[] Packages to ignore (to prevent infinite loops).
133+ * @param {Package[] } ignore=[] Packages to ignore (to prevent infinite loops when traversing graphs of dependencies ).
134134 * @returns {string|undefined } Resolved release type.
135135 * @internal
136136 */
137137const resolveReleaseType = ( pkg , bumpStrategy = "override" , releaseStrategy = "patch" , ignore = [ ] ) => {
138- // NOTE This fn also updates pkg deps, so it must be invoked anyway.
139- const dependentReleaseType = getDependentRelease ( pkg , bumpStrategy , releaseStrategy , ignore ) ;
140-
141- // Release type found by commitAnalyzer.
142- if ( pkg . _nextType ) {
143- return pkg . _nextType ;
144- }
145-
146- if ( ! dependentReleaseType ) {
147- return undefined ;
138+ //make sure any dependency changes are resolved before returning the release type
139+ if ( ! pkg . _depsResolved ) {
140+ //create a list of dependencies that require change
141+ pkg . _depsChanged = pkg . localDeps
142+ . filter ( ( d ) => ! ignore . includes ( d ) )
143+ . filter ( ( d ) => needsDependencyUpdate ( pkg , d , bumpStrategy , releaseStrategy , [ pkg , ...ignore ] ) ) ;
144+
145+ //get the (preliminary) release type of the package based on release strategy (and analyzed changed dependencies)
146+ pkg . _nextType = getDependentRelease ( pkg , releaseStrategy ) ;
147+
148+ //indicate that all deps are resolved (fixates the next type and depsChanged)
149+ pkg . _depsResolved = ignore . length === 0 ;
148150 }
149151
150- // Define release type for dependent package if any of its deps changes.
151- // `patch`, `minor`, `major` — strictly declare the release type that occurs when any dependency is updated.
152- // `inherit` — applies the "highest" release of updated deps to the package.
153- // For example, if any dep has a breaking change, `major` release will be applied to the all dependants up the chain.
154-
155- pkg . _nextType = releaseStrategy === "inherit" ? dependentReleaseType : releaseStrategy ;
156-
157152 return pkg . _nextType ;
158153} ;
159154
160155/**
161- * Get dependent release type by recursive scanning and updating its deps.
162- *
163- * @param {Package } pkg The package with local deps to check.
164- * @param {string } bumpStrategy Dependency resolution strategy: override, satisfy, inherit.
165- * @param {string } releaseStrategy Release type triggered by deps updating: patch, minor, major, inherit.
156+ * Indicates if the manifest file requires a change for the given dependency
157+ * @param {Package } pkg Package object.
158+ * @param {string|undefined } bumpStrategy Dependency resolution strategy: override, satisfy, inherit.
159+ * @param {string|undefined } releaseStrategy Release type triggered by deps updating: patch, minor, major, inherit.
166160 * @param {Package[] } ignore Packages to ignore (to prevent infinite loops).
167- * @returns {string|undefined } Returns the highest release type if found, undefined otherwise
168- * @internal
169161 */
170- const getDependentRelease = ( pkg , bumpStrategy , releaseStrategy , ignore ) => {
171- const severityOrder = [ "patch" , "minor" , "major" ] ;
172- const { localDeps, manifest = { } } = pkg ;
173- const { dependencies = { } , devDependencies = { } , peerDependencies = { } , optionalDependencies = { } } = manifest ;
162+
163+ const needsDependencyUpdate = ( pkg , dependency , bumpStrategy , releaseStrategy , ignore ) => {
164+ //get last release of dependency
165+ const depLastVersion = dependency . _lastRelease && dependency . _lastRelease . version ;
166+
167+ // 3. check if dependency was released before (if not, this is assumed to be a new package + dependency)
168+ const wasReleased = depLastVersion !== undefined ;
169+ if ( ! wasReleased ) return true ; //new packages always require a package re-release
170+
171+ //get nextType of the dependency (recursion occurs here!)
172+ // Has changed if...
173+ // 1. Any local dep package itself triggered changed
174+ // 2. Any local dep package has local deps that triggered change.
175+ const depNextType = resolveReleaseType ( dependency , bumpStrategy , releaseStrategy , ignore ) ;
176+
177+ //get estimated next version of dependency (which is lastVersion if no change expected)
178+ const depNextVersion = depNextType
179+ ? dependency . _preRelease
180+ ? getNextPreVersion ( dependency )
181+ : getNextVersion ( dependency )
182+ : depLastVersion ;
183+
184+ //get list of manifest dependencies
185+ const { dependencies = { } , devDependencies = { } , peerDependencies = { } , optionalDependencies = { } } = pkg . manifest ;
174186 const scopes = [ dependencies , devDependencies , peerDependencies , optionalDependencies ] ;
175- const bumpDependency = ( scope , name , nextVersion ) => {
176- const currentVersion = scope [ name ] ;
177- if ( ! nextVersion || ! currentVersion ) {
178- return false ;
179- }
180187
181- const resolvedVersion = resolveNextVersion ( currentVersion , nextVersion , bumpStrategy ) ;
182- if ( currentVersion !== resolvedVersion ) {
183- scope [ name ] = resolvedVersion ;
184- return true ;
185- }
188+ // 4. Check if the manifest dependency rules warrants an update (in any of the dependency scopes)
189+ const requireUpdate = scopes . some ( ( scope ) =>
190+ manifestUpdateNecessary ( scope , dependency . name , depNextVersion , bumpStrategy )
191+ ) ;
192+
193+ //return if update is required
194+ return requireUpdate ;
195+ } ;
186196
197+ /**
198+ * Checks if an update of a package is necessary in the given list of dependencies, based on semantic versioning rules
199+ * and the bumpStrategy.
200+ * @param {Object } scope object containing dependencies. Dependency names are the keys, dependency rule the values.
201+ * @param {string } name name of the dependency to update
202+ * @param {string } nextVersion the new version of the dependency
203+ * @param {string } bumpStrategy Dependency resolution strategy: override, satisfy, inherit.
204+ * @returns {boolean } true if a the dependency exists in the scope and requires a version update
205+ */
206+ const manifestUpdateNecessary = ( scope , name , nextVersion , bumpStrategy ) => {
207+ const currentVersion = scope [ name ] ;
208+ if ( ! nextVersion || ! currentVersion ) {
187209 return false ;
188- } ;
189-
190- // prettier-ignore
191- return localDeps
192- . filter ( ( p ) => ! ignore . includes ( p ) )
193- . reduce ( ( releaseType , p ) => {
194- const name = p . name ;
195-
196- // Has changed if...
197- // 1. Any local dep package itself has changed
198- // 2. Any local dep package has local deps that have changed.
199- const nextType = resolveReleaseType ( p , bumpStrategy , releaseStrategy , [ ...ignore , ...localDeps ] ) ;
200-
201- // Set the nextVersion fallback to the last local dependency package last version
202- let nextVersion = p . _lastRelease && p . _lastRelease . version ;
203-
204- // Update the nextVersion only if there is a next type to be bumped
205- if ( nextType ) nextVersion = p . _preRelease ? getNextPreVersion ( p ) : getNextVersion ( p ) ;
206- const lastVersion = pkg . _lastRelease && pkg . _lastRelease . version ;
207-
208- // 3. And this change should correspond to manifest updating rule.
209- const requireRelease = scopes
210- . reduce ( ( res , scope ) => bumpDependency ( scope , name , nextVersion ) || res , ! lastVersion )
211-
212- return requireRelease && ( severityOrder . indexOf ( nextType ) > severityOrder . indexOf ( releaseType ) )
213- ? nextType
214- : releaseType ;
215- } , undefined ) ;
210+ }
211+
212+ //calculate the next version of the manifest dependency, given the current version
213+ //this checks the semantic versioning rules. Resolved version will remain
214+ //current version if the currentVersion "encompasses" the next version
215+ const resolvedVersion = resolveNextVersion ( currentVersion , nextVersion , bumpStrategy ) ;
216+
217+ return currentVersion !== resolvedVersion ;
216218} ;
217219
218220/**
219- * Resolve next version of dependency.
221+ * Resolve next version of dependency based on bumpStrategy
220222 *
221223 * @param {string } currentVersion Current dep version
222224 * @param {string } nextVersion Next release type: patch, minor, major
@@ -254,6 +256,46 @@ const resolveNextVersion = (currentVersion, nextVersion, bumpStrategy = "overrid
254256 return nextVersion ;
255257} ;
256258
259+ /**
260+ * Get dependent release type by analyzing the current nextType and changed dependencies
261+ * @param {Package } pkg The package to determine next type of release of
262+ * @param {string } releaseStrategy Release type triggered by deps updating: patch, minor, major, inherit.
263+ * @returns {string|undefined } Returns the highest release type if found, undefined otherwise
264+ * @internal
265+ */
266+ const getDependentRelease = ( pkg , releaseStrategy ) => {
267+ const severityOrder = [ "patch" , "minor" , "major" ] ;
268+
269+ // Define release type for dependent package if any of its deps changes.
270+ // `patch`, `minor`, `major` — strictly declare the release type that occurs when any dependency is updated.
271+ // `inherit` — applies the "highest" release of updated deps to the package.
272+ // For example, if any dep has a breaking change, `major` release will be applied to the all dependants up the chain.
273+
274+ //return type set by commit analyzer if no deps changed
275+ if (
276+ ! pkg . _lastRelease || //new package
277+ ! pkg . _depsChanged || //no deps analyzed
278+ pkg . _depsChanged . length === 0 || //no deps available
279+ pkg . _depsChanged . every ( ( dep ) => ! dep . _nextType && dep . _lastRelease ) //no new deps or deps upgraded
280+ )
281+ return pkg . _nextType ;
282+
283+ if ( releaseStrategy === "inherit" ) {
284+ //find highest release type if strategy is inherit, starting of type set by commit analyzer
285+ return pkg . _depsChanged . reduce ( ( maxReleaseType , dependency ) => {
286+ return severityOrder . indexOf ( dependency . _nextType ) > severityOrder . indexOf ( maxReleaseType )
287+ ? dependency . _nextType
288+ : maxReleaseType ;
289+ } , pkg . _nextType ) ;
290+ }
291+
292+ //return highest of commit analyzer found change and releaseStrategy
293+ //releaseStrategy of major could override local update of minor
294+ return severityOrder . indexOf ( pkg . _nextType ) > severityOrder . indexOf ( releaseStrategy )
295+ ? pkg . _nextType
296+ : releaseStrategy ;
297+ } ;
298+
257299/**
258300 * Update pkg deps.
259301 *
@@ -265,14 +307,26 @@ const updateManifestDeps = (pkg) => {
265307 const { manifest, path } = pkg ;
266308 const { indent, trailingWhitespace } = recognizeFormat ( manifest . __contents__ ) ;
267309
268- // Loop through localDeps to verify release consistency.
269- pkg . localDeps . forEach ( ( d ) => {
310+ // Loop through changed deps to verify release consistency.
311+ pkg . _depsChanged . forEach ( ( dependency ) => {
270312 // Get version of dependency.
271- const release = d . _nextRelease || d . _lastRelease ;
313+ const release = dependency . _nextRelease || dependency . _lastRelease ;
272314
273315 // Cannot establish version.
274316 if ( ! release || ! release . version )
275- throw Error ( `Cannot release because dependency ${ d . name } has not been released` ) ;
317+ throw Error ( `Cannot release because dependency ${ dependency . name } has not been released` ) ;
318+
319+ //update changed dependencies
320+ const {
321+ dependencies = { } ,
322+ devDependencies = { } ,
323+ peerDependencies = { } ,
324+ optionalDependencies = { } ,
325+ } = pkg . manifest ;
326+ const scopes = [ dependencies , devDependencies , peerDependencies , optionalDependencies ] ;
327+ scopes . forEach ( ( scope ) => {
328+ if ( scope [ dependency . name ] ) scope [ dependency . name ] = release . version ;
329+ } ) ;
276330 } ) ;
277331
278332 if ( ! auditManifestChanges ( manifest , path ) ) {
0 commit comments