diff --git a/__snapshots__/manifest.js b/__snapshots__/manifest.js index bc4243a7c..f53bd18c8 100644 --- a/__snapshots__/manifest.js +++ b/__snapshots__/manifest.js @@ -1,3 +1,110 @@ +exports['Manifest plugins runs the node-workspace plugin: changes'] = ` + +filename: node/pkg1/CHANGELOG.md +# Changelog + +### [0.123.5](https://www.github.com/fake/repo/compare/pkg1-v0.123.4...pkg1-v0.123.5) (1983-10-10) + + +### Bug Fixes + +* **@node/pkg1:** bugfix pkg1 ([43e6f0d](https://www.github.com/fake/repo/commit/43e6f0d178468c33349fe3faac287d4c)) + +filename: node/pkg1/package.json +{ + "name": "@node/pkg1", + "version": "0.123.5" +} + +filename: python/CHANGELOG.md +# Changelog + +### [1.2.4](https://www.github.com/fake/repo/compare/foolib-v1.2.3...foolib-v1.2.4) (1983-10-10) + + +### Bug Fixes + +* **foolib:** bufix python foolib ([8df9117](https://www.github.com/fake/repo/commit/8df9117959264dc5b7b6c72ff36b8846)) + +filename: python/setup.cfg +version=1.2.4 + +filename: python/setup.py +version = "1.2.4" + +filename: python/src/foolib/version.py +__version__ = "1.2.4" + +filename: node/pkg2/package.json +{ + "name": "@node/pkg2", + "version": "0.1.3", + "dependencies": { + "@node/pkg1": "^0.123.5" + } +} + +filename: node/pkg2/CHANGELOG.md +# Changelog + +### [0.1.3](https://www.github.com/fake/repo/compare/pkg2-v0.1.2...pkg2-v0.1.3) (1983-10-10) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @node/pkg1 bumped from ^0.123.4 to ^0.123.5 + +filename: .release-please-manifest.json +{ + "node/pkg1": "0.123.5", + "node/pkg2": "0.1.3", + "python": "1.2.4" +} + +` + +exports['Manifest plugins runs the node-workspace plugin: options'] = ` + +upstreamOwner: fake +upstreamRepo: repo +title: chore: release main +branch: release-please/branches/main +description: :robot: I have created a release \\*beep\\* \\*boop\\* +--- +
@node/pkg1: 0.123.5 + + +### Bug Fixes + +* **@node/pkg1:** bugfix pkg1 ([43e6f0d](https://www.github.com/fake/repo/commit/43e6f0d178468c33349fe3faac287d4c)) +
+
foolib: 1.2.4 + + +### Bug Fixes + +* **foolib:** bufix python foolib ([8df9117](https://www.github.com/fake/repo/commit/8df9117959264dc5b7b6c72ff36b8846)) +
+
@node/pkg2: 0.1.3 + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @node/pkg1 bumped from ^0.123.4 to ^0.123.5 +
+ + +This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). +primary: main +force: true +fork: false +message: chore: release main +` + exports['Manifest pullRequest allows root module to be published, via special "." path: changes'] = ` filename: CHANGELOG.md @@ -64,7 +171,10 @@ filename: node/pkg2/CHANGELOG.md filename: node/pkg2/package.json { "name": "@node/pkg2", - "version": "2.0.0" + "version": "2.0.0", + "dependencies": { + "@node/pkg1": "^0.123.4" + } } filename: .release-please-manifest.json @@ -83,10 +193,8 @@ upstreamRepo: repo title: chore: release main branch: release-please/branches/main description: :robot: I have created a release \\*beep\\* \\*boop\\* - --- -googleapis: 3.0.0 -## [3.0.0](https://www.github.com/fake/repo/compare/googleapis-v2.0.0...googleapis-v3.0.0) (1983-10-10) +
googleapis: 3.0.0 ### ⚠ BREAKING CHANGES @@ -103,12 +211,8 @@ googleapis: 3.0.0 ### Bug Fixes * **root:** root only change ([8b55db3](https://www.github.com/fake/repo/commit/8b55db3f6115306cc9c132bec0bb1447)) ---- - - ---- -@node/pkg1: 4.0.0 -## [4.0.0](https://www.github.com/fake/repo/compare/pkg1-v3.2.1...pkg1-v4.0.0) (1983-10-10) +
+
@node/pkg1: 4.0.0 ### ⚠ BREAKING CHANGES @@ -118,12 +222,8 @@ googleapis: 3.0.0 ### Features * **@node/pkg1:** major new feature ([e3ab0ab](https://www.github.com/fake/repo/commit/e3ab0abfd66e66324f685ceeececf35c)) ---- - - ---- -@node/pkg2: 2.0.0 -## [2.0.0](https://www.github.com/fake/repo/compare/pkg2-v1.2.3...pkg2-v2.0.0) (1983-10-10) +
+
@node/pkg2: 2.0.0 ### ⚠ BREAKING CHANGES @@ -133,7 +233,7 @@ googleapis: 3.0.0 ### Features * **@node/pkg2:** major new feature ([72f962d](https://www.github.com/fake/repo/commit/72f962d44ba0bcee15594ea6bdc67d8b)) ---- +
This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). @@ -178,7 +278,10 @@ filename: node/pkg2/CHANGELOG.md filename: node/pkg2/package.json { "name": "@node/pkg2", - "version": "0.2.0" + "version": "0.2.0", + "dependencies": { + "@node/pkg1": "^0.123.4" + } } filename: python/CHANGELOG.md @@ -216,10 +319,8 @@ upstreamRepo: repo title: chore: release main branch: release-please/branches/main description: :robot: I have created a release \\*beep\\* \\*boop\\* - --- -@node/pkg1: 4.0.0 -## [4.0.0](https://www.github.com/fake/repo/compare/pkg1-v3.2.1...pkg1-v4.0.0) (1983-10-10) +
@node/pkg1: 4.0.0 ### ⚠ BREAKING CHANGES @@ -229,29 +330,21 @@ description: :robot: I have created a release \\*beep\\* \\*boop\\* ### Features * **@node/pkg1:** major new feature ([e3ab0ab](https://www.github.com/fake/repo/commit/e3ab0abfd66e66324f685ceeececf35c)) ---- - - ---- -@node/pkg2: 0.2.0 -## [0.2.0](https://www.github.com/fake/repo/compare/pkg2-v0.1.2...pkg2-v0.2.0) (1983-10-10) +
+
@node/pkg2: 0.2.0 ### Features * **@node/pkg2:** new feature ([6cefc4f](https://www.github.com/fake/repo/commit/6cefc4f5b1f432a24f7c066c5dd95e68)) ---- - - ---- -foolib: 1.2.4 -### [1.2.4](https://www.github.com/fake/repo/compare/foolib-v1.2.3...foolib-v1.2.4) (1983-10-10) +
+
foolib: 1.2.4 ### Bug Fixes * **foolib:** bufix python foolib ([8df9117](https://www.github.com/fake/repo/commit/8df9117959264dc5b7b6c72ff36b8846)) ---- +
This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). @@ -296,7 +389,10 @@ filename: node/pkg2/CHANGELOG.md filename: node/pkg2/package.json { "name": "@node/pkg2", - "version": "0.2.0" + "version": "0.2.0", + "dependencies": { + "@node/pkg1": "^0.123.4" + } } filename: python/CHANGELOG.md @@ -334,10 +430,8 @@ upstreamRepo: repo title: chore: release main branch: release-please/branches/main description: :robot: I have created a release \\*beep\\* \\*boop\\* - --- -@node/pkg1: 4.0.0 -## [4.0.0](https://www.github.com/fake/repo/compare/pkg1-v3.2.1...pkg1-v4.0.0) (1983-10-10) +
@node/pkg1: 4.0.0 ### ⚠ BREAKING CHANGES @@ -347,29 +441,21 @@ description: :robot: I have created a release \\*beep\\* \\*boop\\* ### Features * **@node/pkg1:** major new feature ([e3ab0ab](https://www.github.com/fake/repo/commit/e3ab0abfd66e66324f685ceeececf35c)) ---- - - ---- -@node/pkg2: 0.2.0 -## [0.2.0](https://www.github.com/fake/repo/compare/pkg2-v0.1.2...pkg2-v0.2.0) (1983-10-10) +
+
@node/pkg2: 0.2.0 ### Features * **@node/pkg2:** new feature ([6cefc4f](https://www.github.com/fake/repo/commit/6cefc4f5b1f432a24f7c066c5dd95e68)) ---- - - ---- -foolib: 1.2.4 -### [1.2.4](https://www.github.com/fake/repo/compare/foolib-v1.2.3...foolib-v1.2.4) (1983-10-10) +
+
foolib: 1.2.4 ### Bug Fixes * **foolib:** bufix python foolib ([8df9117](https://www.github.com/fake/repo/commit/8df9117959264dc5b7b6c72ff36b8846)) ---- +
This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). @@ -414,7 +500,10 @@ filename: node/pkg2/CHANGELOG.md filename: node/pkg2/package.json { "name": "@node/pkg2", - "version": "0.2.0" + "version": "0.2.0", + "dependencies": { + "@node/pkg1": "^0.123.4" + } } filename: python/CHANGELOG.md @@ -452,10 +541,8 @@ upstreamRepo: repo title: chore: release main branch: release-please/branches/main description: :robot: I have created a release \\*beep\\* \\*boop\\* - --- -@node/pkg1: 4.0.0 -## [4.0.0](https://www.github.com/fake/repo/compare/pkg1-v3.2.1...pkg1-v4.0.0) (1983-10-10) +
@node/pkg1: 4.0.0 ### ⚠ BREAKING CHANGES @@ -465,29 +552,21 @@ description: :robot: I have created a release \\*beep\\* \\*boop\\* ### Features * **@node/pkg1:** major new feature ([e3ab0ab](https://www.github.com/fake/repo/commit/e3ab0abfd66e66324f685ceeececf35c)) ---- - - ---- -@node/pkg2: 0.2.0 -## [0.2.0](https://www.github.com/fake/repo/compare/pkg2-v0.1.2...pkg2-v0.2.0) (1983-10-10) +
+
@node/pkg2: 0.2.0 ### Features * **@node/pkg2:** new feature ([6cefc4f](https://www.github.com/fake/repo/commit/6cefc4f5b1f432a24f7c066c5dd95e68)) ---- - - ---- -foolib: 1.2.4 -### [1.2.4](https://www.github.com/fake/repo/compare/foolib-v1.2.3...foolib-v1.2.4) (1983-10-10) +
+
foolib: 1.2.4 ### Bug Fixes * **foolib:** bufix python foolib ([8df9117](https://www.github.com/fake/repo/commit/8df9117959264dc5b7b6c72ff36b8846)) ---- +
This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). @@ -532,7 +611,10 @@ filename: node/pkg2/CHANGELOG.md filename: node/pkg2/package.json { "name": "@node/pkg2", - "version": "0.2.0" + "version": "0.2.0", + "dependencies": { + "@node/pkg1": "^0.123.4" + } } filename: python/CHANGELOG.md @@ -570,10 +652,8 @@ upstreamRepo: repo title: chore: release main branch: release-please/branches/main description: :robot: I have created a release \\*beep\\* \\*boop\\* - --- -@node/pkg1: 4.0.0 -## [4.0.0](https://www.github.com/fake/repo/compare/pkg1-v3.2.1...pkg1-v4.0.0) (1983-10-10) +
@node/pkg1: 4.0.0 ### ⚠ BREAKING CHANGES @@ -583,29 +663,21 @@ description: :robot: I have created a release \\*beep\\* \\*boop\\* ### Features * **@node/pkg1:** major new feature ([e3ab0ab](https://www.github.com/fake/repo/commit/e3ab0abfd66e66324f685ceeececf35c)) ---- - - ---- -@node/pkg2: 0.2.0 -## [0.2.0](https://www.github.com/fake/repo/compare/pkg2-v0.1.2...pkg2-v0.2.0) (1983-10-10) +
+
@node/pkg2: 0.2.0 ### Features * **@node/pkg2:** new feature ([6cefc4f](https://www.github.com/fake/repo/commit/6cefc4f5b1f432a24f7c066c5dd95e68)) ---- - - ---- -foolib: 1.2.4 -### [1.2.4](https://www.github.com/fake/repo/compare/foolib-v1.2.3...foolib-v1.2.4) (1983-10-10) +
+
foolib: 1.2.4 ### Bug Fixes * **foolib:** bufix python foolib ([8df9117](https://www.github.com/fake/repo/commit/8df9117959264dc5b7b6c72ff36b8846)) ---- +
This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). @@ -650,7 +722,10 @@ filename: node/pkg2/CHANGELOG.md filename: node/pkg2/package.json { "name": "@node/pkg2", - "version": "1.0.0" + "version": "1.0.0", + "dependencies": { + "@node/pkg1": "^0.123.4" + } } filename: python/CHANGELOG.md @@ -688,10 +763,8 @@ upstreamRepo: repo title: chore: release main branch: release-please/branches/main description: :robot: I have created a release \\*beep\\* \\*boop\\* - --- -@node/pkg1: 4.0.0 -## [4.0.0](https://www.github.com/fake/repo/compare/pkg1-v3.2.1...pkg1-v4.0.0) (1983-10-10) +
@node/pkg1: 4.0.0 ### ⚠ BREAKING CHANGES @@ -701,29 +774,21 @@ description: :robot: I have created a release \\*beep\\* \\*boop\\* ### Features * **@node/pkg1:** major new feature ([e3ab0ab](https://www.github.com/fake/repo/commit/e3ab0abfd66e66324f685ceeececf35c)) ---- - - ---- -@node/pkg2: 1.0.0 -## 1.0.0 (1983-10-10) +
+
@node/pkg2: 1.0.0 ### Features * **@node/pkg2:** new feature ([6cefc4f](https://www.github.com/fake/repo/commit/6cefc4f5b1f432a24f7c066c5dd95e68)) ---- - - ---- -foolib: 1.2.4 -### [1.2.4](https://www.github.com/fake/repo/compare/foolib-v1.2.3...foolib-v1.2.4) (1983-10-10) +
+
foolib: 1.2.4 ### Bug Fixes * **foolib:** bufix python foolib ([8df9117](https://www.github.com/fake/repo/commit/8df9117959264dc5b7b6c72ff36b8846)) ---- +
This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). @@ -789,10 +854,8 @@ upstreamRepo: repo title: chore: release main branch: release-please/branches/main description: :robot: I have created a release \\*beep\\* \\*boop\\* - --- -@node/pkg1: 4.0.0 -## [4.0.0](https://www.github.com/fake/repo/compare/pkg1-v3.2.1...pkg1-v4.0.0) (1983-10-10) +
@node/pkg1: 4.0.0 ### ⚠ BREAKING CHANGES @@ -802,18 +865,14 @@ description: :robot: I have created a release \\*beep\\* \\*boop\\* ### Features * **@node/pkg1:** major new feature ([e3ab0ab](https://www.github.com/fake/repo/commit/e3ab0abfd66e66324f685ceeececf35c)) ---- - - ---- -foolib: 1.2.4 -### [1.2.4](https://www.github.com/fake/repo/compare/foolib-v1.2.3...foolib-v1.2.4) (1983-10-10) +
+
foolib: 1.2.4 ### Bug Fixes * **foolib:** bufix python foolib ([8df9117](https://www.github.com/fake/repo/commit/8df9117959264dc5b7b6c72ff36b8846)) ---- +
This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). @@ -838,7 +897,10 @@ filename: node/pkg2/CHANGELOG.md filename: node/pkg2/package.json { "name": "@node/pkg2", - "version": "0.2.0" + "version": "0.2.0", + "dependencies": { + "@node/pkg1": "^0.123.4" + } } filename: .release-please-manifest.json @@ -857,16 +919,14 @@ upstreamRepo: repo title: chore: release main branch: release-please/branches/main description: :robot: I have created a release \\*beep\\* \\*boop\\* - --- -@node/pkg2: 0.2.0 -## [0.2.0](https://www.github.com/fake/repo/compare/pkg2-v0.1.2...pkg2-v0.2.0) (1983-10-10) +
@node/pkg2: 0.2.0 ### Features * **@node/pkg2:** new feature ([6cefc4f](https://www.github.com/fake/repo/commit/6cefc4f5b1f432a24f7c066c5dd95e68)) ---- +
This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). @@ -915,7 +975,10 @@ filename: node/pkg2/CHANGELOG.md filename: node/pkg2/package.json { "name": "@node/pkg2", - "version": "0.3.0" + "version": "0.3.0", + "dependencies": { + "@node/pkg1": "^0.123.4" + } } filename: python/CHANGELOG.md @@ -962,10 +1025,8 @@ upstreamRepo: repo title: chore: release main branch: release-please/branches/main description: :robot: I have created a release \\*beep\\* \\*boop\\* - --- -@node/pkg1: 5.5.5 -### [5.5.5](https://www.github.com/fake/repo/compare/pkg1-v0.1.1...pkg1-v5.5.5) (1983-10-10) +
@node/pkg1: 5.5.5 ### ⚠ BREAKING CHANGES @@ -975,12 +1036,8 @@ description: :robot: I have created a release \\*beep\\* \\*boop\\* ### Default Features Section * **@node/pkg1:** node feature ([8ef6b52](https://www.github.com/fake/repo/commit/8ef6b521e268395ded9b66bb1ff89696)) ---- - - ---- -@node/pkg2: 0.3.0 -## [0.3.0](https://www.github.com/fake/repo/compare/pkg2-v0.2.2...pkg2-v0.3.0) (1983-10-10) +
+
@node/pkg2: 0.3.0 ### ⚠ BREAKING CHANGES @@ -990,12 +1047,8 @@ description: :robot: I have created a release \\*beep\\* \\*boop\\* ### Default Features Section * **@node/pkg2:** node2 feature ([f3373c7](https://www.github.com/fake/repo/commit/f3373c71ffaf1a67b19ce6a116e861ea)) ---- - - ---- -foolib: 1.0.0 -## [1.0.0](https://www.github.com/fake/repo/compare/foolib-v0.1.1...foolib-v1.0.0) (1983-10-10) +
+
foolib: 1.0.0 ### ⚠ BREAKING CHANGES @@ -1010,7 +1063,7 @@ foolib: 1.0.0 ### Python Features Section * **foolib:** python feature ([33fcc00](https://www.github.com/fake/repo/commit/33fcc0047b2eb3a66854f25c480b5b7e)) ---- +
This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). diff --git a/__snapshots__/node-workspace.js b/__snapshots__/node-workspace.js new file mode 100644 index 000000000..c0644ba5a --- /dev/null +++ b/__snapshots__/node-workspace.js @@ -0,0 +1,1244 @@ +exports['NodeWorkspaceDependencyUpdates run does not update dependencies on preMajor versions with minor bump changes'] = ` +==================== +{ + "config": { + "releaseType": "node", + "packageName": "@here/pkgA", + "path": "packages/pkgA" + }, + "prData": { + "version": "1.1.2", + "changes": {} + } +} + +filename: packages/pkgA/package.json +{ + "name": "@here/pkgA", + "version": "1.1.2", + "dependencies": { + "@there/foo": "^4.1.7" + } +} + +filename: packages/pkgA/CHANGELOG.md +# Changelog + +All notable changes to this project will be documented in this file.### [1.1.2](https://www.github.com/fake/repo/compare/pkgA-v1.1.1...pkgA-v1.1.2) (1983-10-10) + + +### Bug Fixes + +* We fixed a bug! +==================== +{ + "config": { + "releaseType": "node", + "packageName": "@here/pkgB", + "path": "packages/pkgB" + }, + "prData": { + "version": "0.3.0", + "changes": {} + } +} + +filename: packages/pkgB/package.json +{ + "name": "@here/pkgB", + "version": "0.3.0", + "dependencies": { + "@here/pkgA": "^1.1.2", + "someExternal": "^9.2.3" + } +} + +filename: packages/pkgB/CHANGELOG.md +# Changelog + +All notable changes to this project will be documented in this file. + +### [0.3.0](https://www.github.com/fake/repo/compare/pkgB-v0.2.1...pkgB-v0.3.0) (1983-10-10) + + +### Features + +* We added a feature + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @here/pkgA bumped from ^1.1.1 to ^1.1.2 + +### [0.2.1](https://www.github.com/fake/repo/compare/pkgB-v0.2.0...pkgB-v0.2.1) (1983-10-10) + + +### Bug Fixes + +* We fixed a bug +==================== +{ + "config": { + "path": "packages/pkgC", + "releaseType": "node", + "packageName": "@here/pkgC" + }, + "prData": { + "version": "3.3.4", + "changes": {} + } +} + +filename: packages/pkgC/package.json +{ + "name": "@here/pkgC", + "version": "3.3.4", + "dependencies": { + "@here/pkgA": "^1.1.2", + "@here/pkgB": "^0.2.1", + "anotherExternal": "^4.3.1" + } +} + +filename: packages/pkgC/CHANGELOG.md +# Changelog + +All notable changes to this project will be documented in this file. + +### [3.3.4](https://www.github.com/fake/repo/compare/pkgC-v3.3.3...pkgC-v3.3.4) (1983-10-10) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @here/pkgA bumped from ^1.1.1 to ^1.1.2 + +### [3.3.3](https://www.github.com/fake/repo/compare/pkgC-v3.3.2...pkgC-v3.3.3) (1983-10-10) + + +### Bug Fixes + +* We fixed a bug + + +` + +exports['NodeWorkspaceDependencyUpdates run does not update dependencies on preMajor versions with minor bump logs'] = [ + [ + "node-workspace: found packages/pkgA/package.json in changes", + "success" + ], + [ + "node-workspace: found packages/pkgB/package.json in changes", + "success" + ], + [ + "node-workspace: loaded packages/pkgA/package.json from existing changes", + "success" + ], + [ + "node-workspace: loaded packages/pkgB/package.json from existing changes", + "success" + ], + [ + "node-workspace: loaded packages/pkgC/package.json from github", + "success" + ], + [ + "node-workspace: setting packages/pkgA/package.json to 1.1.2 from release-please", + "success" + ], + [ + "node-workspace: setting packages/pkgB/package.json to 0.3.0 from release-please", + "success" + ], + [ + "node-workspace: setting packages/pkgC/package.json to 3.3.4 from dependency bump", + "success" + ], + [ + "node-workspace: @here/pkgB.@here/pkgA updated to ^1.1.2", + "success" + ], + [ + "node-workspace: @here/pkgC.@here/pkgA updated to ^1.1.2", + "success" + ] +] + +exports['NodeWorkspaceDependencyUpdates run does not update dependency to pre-release version changes'] = ` +==================== +{ + "config": { + "releaseType": "node", + "packageName": "@here/pkgA", + "path": "packages/pkgA" + }, + "prData": { + "version": "1.1.2-alpha.0", + "changes": {} + } +} + +filename: packages/pkgA/package.json +{ + "name": "@here/pkgA", + "version": "1.1.2-alpha.0", + "dependencies": { + "@there/foo": "^4.1.7" + } +} + +filename: packages/pkgA/CHANGELOG.md +# Changelog + +All notable changes to this project will be documented in this file. + +### [1.1.2-alpha.0](https://www.github.com/fake/repo/compare/pkgA-v1.1.1...pkgA-v1.1.2-alpha.0) (1983-10-10) + + +### Bug Fixes + +* We fixed a bug! + +` + +exports['NodeWorkspaceDependencyUpdates run does not update dependency to pre-release version logs'] = [ + [ + "node-workspace: found packages/pkgA/package.json in changes", + "success" + ], + [ + "node-workspace: loaded packages/pkgA/package.json from existing changes", + "success" + ], + [ + "node-workspace: loaded packages/pkgB/package.json from github", + "success" + ], + [ + "node-workspace: setting packages/pkgA/package.json to 1.1.2-alpha.0 from release-please", + "success" + ] +] + +exports['NodeWorkspaceDependencyUpdates run does not update dependent with invalid version changes'] = ` +==================== +{ + "config": { + "releaseType": "node", + "packageName": "@here/pkgA", + "path": "packages/pkgA" + }, + "prData": { + "version": "1.1.2", + "changes": {} + } +} + +filename: packages/pkgA/package.json +{ + "name": "@here/pkgA", + "version": "1.1.2", + "dependencies": { + "@there/foo": "^4.1.7" + } +} + +filename: packages/pkgA/CHANGELOG.md +# Changelog + +All notable changes to this project will be documented in this file.### [1.1.2](https://www.github.com/fake/repo/compare/pkgA-v1.1.1...pkgA-v1.1.2) (1983-10-10) + + +### Bug Fixes + +* We fixed a bug! +==================== +{ + "config": { + "path": "packages/pkgB", + "releaseType": "node", + "packageName": "@here/pkgB" + }, + "prData": { + "version": "some-invalid-version", + "changes": {} + } +} + +filename: packages/pkgB/package.json +{ + "name": "@here/pkgB", + "version": "some-invalid-version", + "dependencies": { + "@here/pkgA": "^1.1.2", + "someExternal": "^9.2.3" + } +} + +filename: packages/pkgB/CHANGELOG.md +# Changelog + +All notable changes to this project will be documented in this file. + +## [some-invalid-version](https://www.github.com/fake/repo/compare/pkgB-vsome-invalid-version...pkgB-vsome-invalid-version) (1983-10-10) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @here/pkgA bumped from ^1.1.1 to ^1.1.2 + +### [some-invalid-version](https://www.github.com/fake/repo/compare/pkgB-v2.2.1...pkgB-vsome-invalid-version) (1983-10-10) + + +### Bug Fixes + +* We fixed a bug and set the version wonky on purpose? + + +` + +exports['NodeWorkspaceDependencyUpdates run does not update dependent with invalid version logs'] = [ + [ + "node-workspace: found packages/pkgA/package.json in changes", + "success" + ], + [ + "node-workspace: loaded packages/pkgA/package.json from existing changes", + "success" + ], + [ + "node-workspace: loaded packages/pkgB/package.json from github", + "success" + ], + [ + "node-workspace: setting packages/pkgA/package.json to 1.1.2 from release-please", + "success" + ], + [ + "node-workspace: Don't know how to patch @here/pkgB's version(some-invalid-version)", + "failure" + ], + [ + "node-workspace: setting packages/pkgB/package.json to some-invalid-version from failed to patch bump", + "success" + ], + [ + "node-workspace: @here/pkgB.@here/pkgA updated to ^1.1.2", + "success" + ] +] + +exports['NodeWorkspaceDependencyUpdates run handles a simple chain where root pkg update cascades to dependents changes'] = ` +==================== +{ + "config": { + "releaseType": "node", + "packageName": "@here/pkgA", + "path": "packages/pkgA" + }, + "prData": { + "version": "1.1.2", + "changes": {} + } +} + +filename: packages/pkgA/package.json +{ + "name": "@here/pkgA", + "version": "1.1.2", + "dependencies": { + "@there/foo": "^4.1.7" + } +} + +filename: packages/pkgA/CHANGELOG.md +# Changelog + +All notable changes to this project will be documented in this file.### [1.1.2](https://www.github.com/fake/repo/compare/pkgA-v1.1.1...pkgA-v1.1.2) (1983-10-10) + + +### Bug Fixes + +* We fixed a bug! +==================== +{ + "config": { + "releaseType": "python", + "path": "py/pkg" + }, + "prData": { + "version": "1.1.2", + "changes": {} + } +} + +filename: py/pkg/setup.py +some python version content +==================== +{ + "config": { + "path": "packages/pkgB", + "releaseType": "node", + "packageName": "@here/pkgB" + }, + "prData": { + "version": "2.2.3", + "changes": {} + } +} + +filename: packages/pkgB/package.json +{ + "name": "@here/pkgB", + "version": "2.2.3", + "dependencies": { + "@here/pkgA": "^1.1.2", + "someExternal": "^9.2.3" + } +} + +filename: packages/pkgB/CHANGELOG.md +# Changelog + +All notable changes to this project will be documented in this file. + +### [2.2.3](https://www.github.com/fake/repo/compare/pkgB-v2.2.2...pkgB-v2.2.3) (1983-10-10) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @here/pkgA bumped from ^1.1.1 to ^1.1.2 + +### [2.2.2](https://www.github.com/fake/repo/compare/pkgB-v2.2.1...pkgB-v2.2.2) (1983-10-10) + + +### Bug Fixes + +* We fixed a bug + +==================== +{ + "config": { + "path": "packages/pkgC", + "releaseType": "node", + "packageName": "@here/pkgC" + }, + "prData": { + "version": "3.3.4", + "changes": {} + } +} + +filename: packages/pkgC/package.json +{ + "name": "@here/pkgC", + "version": "3.3.4", + "dependencies": { + "@here/pkgB": "^2.2.3", + "anotherExternal": "^4.3.1" + } +} + +filename: packages/pkgC/CHANGELOG.md +# Changelog + +### [3.3.4](https://www.github.com/fake/repo/compare/pkgC-v3.3.3...pkgC-v3.3.4) (1983-10-10) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @here/pkgB bumped from ^2.2.2 to ^2.2.3 + + +` + +exports['NodeWorkspaceDependencyUpdates run handles a simple chain where root pkg update cascades to dependents logs'] = [ + [ + "node-workspace: found packages/pkgA/package.json in changes", + "success" + ], + [ + "node-workspace: loaded packages/pkgA/package.json from existing changes", + "success" + ], + [ + "node-workspace: loaded packages/pkgB/package.json from github", + "success" + ], + [ + "node-workspace: loaded packages/pkgC/package.json from github", + "success" + ], + [ + "node-workspace: setting packages/pkgA/package.json to 1.1.2 from release-please", + "success" + ], + [ + "node-workspace: setting packages/pkgB/package.json to 2.2.3 from dependency bump", + "success" + ], + [ + "node-workspace: setting packages/pkgC/package.json to 3.3.4 from dependency bump", + "success" + ], + [ + "node-workspace: @here/pkgB.@here/pkgA updated to ^1.1.2", + "success" + ], + [ + "node-workspace: @here/pkgC.@here/pkgB updated to ^2.2.3", + "success" + ] +] + +exports['NodeWorkspaceDependencyUpdates run handles a triangle: root and one leg updates bumps other leg changes'] = ` +==================== +{ + "config": { + "releaseType": "node", + "packageName": "@here/pkgA", + "path": "packages/pkgA" + }, + "prData": { + "version": "1.1.2", + "changes": {} + } +} + +filename: packages/pkgA/package.json +{ + "name": "@here/pkgA", + "version": "1.1.2", + "dependencies": { + "@there/foo": "^4.1.7" + } +} + +filename: packages/pkgA/CHANGELOG.md +# Changelog + +All notable changes to this project will be documented in this file.### [1.1.2](https://www.github.com/fake/repo/compare/pkgA-v1.1.1...pkgA-v1.1.2) (1983-10-10) + + +### Bug Fixes + +* We fixed a bug! +==================== +{ + "config": { + "releaseType": "node", + "packageName": "@here/pkgB", + "path": "packages/pkgB" + }, + "prData": { + "version": "2.3.0", + "changes": {} + } +} + +filename: packages/pkgB/package.json +{ + "name": "@here/pkgB", + "version": "2.3.0", + "dependencies": { + "@here/pkgA": "^1.1.2", + "someExternal": "^9.2.3" + } +} + +filename: packages/pkgB/CHANGELOG.md +# Changelog + +All notable changes to this project will be documented in this file. + +### [2.3.0](https://www.github.com/fake/repo/compare/pkgB-v2.2.2...pkgB-v2.3.0) (1983-10-10) + + +### Features + +* We added a feature + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @here/pkgA bumped from ^1.1.1 to ^1.1.2 + +### [2.2.2](https://www.github.com/fake/repo/compare/pkgB-v2.2.1...pkgB-v2.2.2) (1983-10-10) + + +### Bug Fixes + +* We fixed a bug +==================== +{ + "config": { + "path": "packages/pkgC", + "releaseType": "node", + "packageName": "@here/pkgC" + }, + "prData": { + "version": "3.3.4", + "changes": {} + } +} + +filename: packages/pkgC/package.json +{ + "name": "@here/pkgC", + "version": "3.3.4", + "dependencies": { + "@here/pkgA": "^1.1.2", + "@here/pkgB": "^2.3.0", + "anotherExternal": "^4.3.1" + } +} + +filename: packages/pkgC/CHANGELOG.md +# Changelog + +All notable changes to this project will be documented in this file. + +### [3.3.4](https://www.github.com/fake/repo/compare/pkgC-v3.3.3...pkgC-v3.3.4) (1983-10-10) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @here/pkgA bumped from ^1.1.1 to ^1.1.2 + * @here/pkgB bumped from ^2.2.2 to ^2.3.0 + +### [3.3.3](https://www.github.com/fake/repo/compare/pkgC-v3.3.2...pkgC-v3.3.3) (1983-10-10) + + +### Bug Fixes + +* We fixed a bug + + +` + +exports['NodeWorkspaceDependencyUpdates run handles a triangle: root and one leg updates bumps other leg logs'] = [ + [ + "node-workspace: found packages/pkgA/package.json in changes", + "success" + ], + [ + "node-workspace: found packages/pkgB/package.json in changes", + "success" + ], + [ + "node-workspace: loaded packages/pkgA/package.json from existing changes", + "success" + ], + [ + "node-workspace: loaded packages/pkgB/package.json from existing changes", + "success" + ], + [ + "node-workspace: loaded packages/pkgC/package.json from github", + "success" + ], + [ + "node-workspace: setting packages/pkgA/package.json to 1.1.2 from release-please", + "success" + ], + [ + "node-workspace: setting packages/pkgB/package.json to 2.3.0 from release-please", + "success" + ], + [ + "node-workspace: setting packages/pkgC/package.json to 3.3.4 from dependency bump", + "success" + ], + [ + "node-workspace: @here/pkgB.@here/pkgA updated to ^1.1.2", + "success" + ], + [ + "node-workspace: @here/pkgC.@here/pkgA updated to ^1.1.2", + "success" + ], + [ + "node-workspace: @here/pkgC.@here/pkgB updated to ^2.3.0", + "success" + ] +] + +exports['NodeWorkspaceDependencyUpdates run handles discontiguous graph changes'] = ` +==================== +{ + "config": { + "releaseType": "node", + "packageName": "@here/pkgA", + "path": "packages/pkgA" + }, + "prData": { + "version": "1.1.2", + "changes": {} + } +} + +filename: packages/pkgA/package.json +{ + "name": "@here/pkgA", + "version": "1.1.2", + "dependencies": { + "@there/foo": "^4.1.7" + } +} + +filename: packages/pkgA/CHANGELOG.md +# Changelog + +All notable changes to this project will be documented in this file.### [1.1.2](https://www.github.com/fake/repo/compare/pkgA-v1.1.1...pkgA-v1.1.2) (1983-10-10) + + +### Bug Fixes + +* We fixed a bug! +==================== +{ + "config": { + "releaseType": "node", + "packageName": "@here/pkgAA", + "path": "packages/pkgAA" + }, + "prData": { + "version": "11.2.0", + "changes": {} + } +} + +filename: packages/pkgAA/package.json +{ + "name": "@here/pkgAA", + "version": "11.2.0", + "dependencies": { + "@there/foo": "^4.1.7" + } +} + +filename: packages/pkgAA/CHANGELOG.md +### [11.2.0](https://www.github.com/fake/repo/compare/pkgAA-v11.1.1...pkgAA-v11.2.0) (1983-10-10) + + +### Features + +* We added a feature +==================== +{ + "config": { + "path": "packages/pkgB", + "releaseType": "node", + "packageName": "@here/pkgB" + }, + "prData": { + "version": "2.2.3", + "changes": {} + } +} + +filename: packages/pkgB/package.json +{ + "name": "@here/pkgB", + "version": "2.2.3", + "dependencies": { + "@here/pkgA": "^1.1.2", + "someExternal": "^9.2.3" + } +} + +filename: packages/pkgB/CHANGELOG.md +# Changelog + +All notable changes to this project will be documented in this file. + +### [2.2.3](https://www.github.com/fake/repo/compare/pkgB-v2.2.2...pkgB-v2.2.3) (1983-10-10) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @here/pkgA bumped from ^1.1.1 to ^1.1.2 + +### [2.2.2](https://www.github.com/fake/repo/compare/pkgB-v2.2.1...pkgB-v2.2.2) (1983-10-10) + + +### Bug Fixes + +* We fixed a bug + +==================== +{ + "config": { + "path": "packages/pkgBB", + "releaseType": "node", + "packageName": "@here/pkgBB" + }, + "prData": { + "version": "22.2.3", + "changes": {} + } +} + +filename: packages/pkgBB/package.json +{ + "name": "@here/pkgBB", + "version": "22.2.3", + "dependencies": { + "@here/pkgAA": "^11.2.0", + "someExternal": "^9.2.3" + } +} + +filename: packages/pkgBB/CHANGELOG.md +# Changelog + +All notable changes to this project will be documented in this file. + +### [22.2.3](https://www.github.com/fake/repo/compare/pkgBB-v22.2.2...pkgBB-v22.2.3) (1983-10-10) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @here/pkgAA bumped from ^11.1.1 to ^11.2.0 + +### [22.2.2](https://www.github.com/fake/repo/compare/pkgBB-v22.2.1...pkgBB-v22.2.2) (1983-10-10) + + +### Bug Fixes + +* We fixed a bug + + +` + +exports['NodeWorkspaceDependencyUpdates run handles discontiguous graph logs'] = [ + [ + "node-workspace: found packages/pkgA/package.json in changes", + "success" + ], + [ + "node-workspace: found packages/pkgAA/package.json in changes", + "success" + ], + [ + "node-workspace: loaded packages/pkgA/package.json from existing changes", + "success" + ], + [ + "node-workspace: loaded packages/pkgB/package.json from github", + "success" + ], + [ + "node-workspace: loaded packages/pkgAA/package.json from existing changes", + "success" + ], + [ + "node-workspace: loaded packages/pkgBB/package.json from github", + "success" + ], + [ + "node-workspace: setting packages/pkgA/package.json to 1.1.2 from release-please", + "success" + ], + [ + "node-workspace: setting packages/pkgB/package.json to 2.2.3 from dependency bump", + "success" + ], + [ + "node-workspace: setting packages/pkgAA/package.json to 11.2.0 from release-please", + "success" + ], + [ + "node-workspace: setting packages/pkgBB/package.json to 22.2.3 from dependency bump", + "success" + ], + [ + "node-workspace: @here/pkgB.@here/pkgA updated to ^1.1.2", + "success" + ], + [ + "node-workspace: @here/pkgBB.@here/pkgAA updated to ^11.2.0", + "success" + ] +] + +exports['NodeWorkspaceDependencyUpdates run handles errors retrieving changelogs changes'] = ` +==================== +{ + "config": { + "releaseType": "node", + "packageName": "@here/pkgA", + "path": "packages/pkgA" + }, + "prData": { + "version": "1.1.2", + "changes": {} + } +} + +filename: packages/pkgA/package.json +{ + "name": "@here/pkgA", + "version": "1.1.2", + "dependencies": { + "@there/foo": "^4.1.7" + } +} + +filename: packages/pkgA/CHANGELOG.md +# Changelog + +All notable changes to this project will be documented in this file.### [1.1.2](https://www.github.com/fake/repo/compare/pkgA-v1.1.1...pkgA-v1.1.2) (1983-10-10) + + +### Bug Fixes + +* We fixed a bug! +==================== +{ + "config": { + "path": "packages/pkgB", + "releaseType": "node", + "changelogPath": "CHANGES.md", + "packageName": "@here/pkgB" + }, + "prData": { + "version": "2.2.3", + "changes": {} + } +} + +filename: packages/pkgB/package.json +{ + "name": "@here/pkgB", + "version": "2.2.3", + "dependencies": { + "@here/pkgA": "^1.1.2", + "someExternal": "^9.2.3" + } +} + +==================== +{ + "config": { + "path": "packages/pkgC", + "releaseType": "node", + "packageName": "@here/pkgC" + }, + "prData": { + "version": "3.3.4", + "changes": {} + } +} + +filename: packages/pkgC/package.json +{ + "name": "@here/pkgC", + "version": "3.3.4", + "dependencies": { + "@here/pkgB": "^2.2.3", + "anotherExternal": "^4.3.1" + } +} + +filename: packages/pkgC/CHANGELOG.md +# Changelog + +### [3.3.4](https://www.github.com/fake/repo/compare/pkgC-v3.3.3...pkgC-v3.3.4) (1983-10-10) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @here/pkgB bumped from ^2.2.2 to ^2.2.3 + + +` + +exports['NodeWorkspaceDependencyUpdates run handles errors retrieving changelogs logs'] = [ + [ + "node-workspace: found packages/pkgA/package.json in changes", + "success" + ], + [ + "node-workspace: loaded packages/pkgA/package.json from existing changes", + "success" + ], + [ + "node-workspace: loaded packages/pkgB/package.json from github", + "success" + ], + [ + "node-workspace: loaded packages/pkgC/package.json from github", + "success" + ], + [ + "node-workspace: setting packages/pkgA/package.json to 1.1.2 from release-please", + "success" + ], + [ + "node-workspace: setting packages/pkgB/package.json to 2.2.3 from dependency bump", + "success" + ], + [ + "node-workspace: setting packages/pkgC/package.json to 3.3.4 from dependency bump", + "success" + ], + [ + "node-workspace: @here/pkgB.@here/pkgA updated to ^1.1.2", + "success" + ], + [ + "node-workspace: @here/pkgC.@here/pkgB updated to ^2.2.3", + "success" + ], + [ + "node-workspace: Failed to retrieve packages/pkgB/CHANGES.md: Error: error: 501", + "failure" + ], + [ + "node-workspace: Creating a new changelog at packages/pkgC/CHANGELOG.md", + "success" + ] +] + +exports['NodeWorkspaceDependencyUpdates run handles unusual changelog formats changes'] = ` +==================== +{ + "config": { + "releaseType": "node", + "packageName": "@here/pkgA", + "path": "packages/pkgA" + }, + "prData": { + "version": "1.1.2", + "changes": {} + } +} + +filename: packages/pkgA/package.json +{ + "name": "@here/pkgA", + "version": "1.1.2", + "dependencies": { + "@there/foo": "^4.1.7" + } +} + +filename: packages/pkgA/CHANGELOG.md +# Changelog + +All notable changes to this project will be documented in this file.### [1.1.2](https://www.github.com/fake/repo/compare/pkgA-v1.1.1...pkgA-v1.1.2) (1983-10-10) + + +### Bug Fixes + +* We fixed a bug! +==================== +{ + "config": { + "releaseType": "node", + "packageName": "@here/pkgB", + "path": "packages/pkgB" + }, + "prData": { + "version": "2.3.0", + "changes": {} + } +} + +filename: packages/pkgB/package.json +{ + "name": "@here/pkgB", + "version": "2.3.0", + "dependencies": { + "@here/pkgA": "^1.1.2", + "someExternal": "^9.2.3" + } +} + +filename: packages/pkgB/CHANGELOG.md +# Changelog + +All notable changes to this project will be documented in this file. + +### [2.3.0](https://www.github.com/fake/repo/compare/pkgB-v2.2.2...pkgB-v2.3.0) (1983-10-10) + + +### Features + +* We added a feature + +### some stuff we did not expect + +* and more unexpected stuff + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @here/pkgA bumped from ^1.1.1 to ^1.1.2 + +` + +exports['NodeWorkspaceDependencyUpdates run handles unusual changelog formats logs'] = [ + [ + "node-workspace: found packages/pkgA/package.json in changes", + "success" + ], + [ + "node-workspace: found packages/pkgB/package.json in changes", + "success" + ], + [ + "node-workspace: loaded packages/pkgA/package.json from existing changes", + "success" + ], + [ + "node-workspace: loaded packages/pkgB/package.json from existing changes", + "success" + ], + [ + "node-workspace: setting packages/pkgA/package.json to 1.1.2 from release-please", + "success" + ], + [ + "node-workspace: setting packages/pkgB/package.json to 2.3.0 from release-please", + "success" + ], + [ + "node-workspace: @here/pkgB.@here/pkgA updated to ^1.1.2", + "success" + ], + [ + "node-workspace: Appending update notes to end of changelog for @here/pkgB", + "failure" + ] +] + +exports['NodeWorkspaceDependencyUpdates run updates dependent from pre-release version changes'] = ` +==================== +{ + "config": { + "releaseType": "node", + "packageName": "@here/pkgA", + "path": "packages/pkgA" + }, + "prData": { + "version": "1.1.2", + "changes": {} + } +} + +filename: packages/pkgA/package.json +{ + "name": "@here/pkgA", + "version": "1.1.2", + "dependencies": { + "@there/foo": "^4.1.7" + } +} + +filename: packages/pkgA/CHANGELOG.md +# Changelog + +All notable changes to this project will be documented in this file.### [1.1.2](https://www.github.com/fake/repo/compare/pkgA-v1.1.1...pkgA-v1.1.2) (1983-10-10) + + +### Bug Fixes + +* We fixed a bug! +==================== +{ + "config": { + "path": "packages/pkgB", + "releaseType": "node", + "packageName": "@here/pkgB" + }, + "prData": { + "version": "2.2.3", + "changes": {} + } +} + +filename: packages/pkgB/package.json +{ + "name": "@here/pkgB", + "version": "2.2.3", + "dependencies": { + "@here/pkgA": "^1.1.2", + "someExternal": "^9.2.3" + } +} + +filename: packages/pkgB/CHANGELOG.md +# Changelog + +All notable changes to this project will be documented in this file. + +### [2.2.3](https://www.github.com/fake/repo/compare/pkgB-v2.2.2...pkgB-v2.2.3) (1983-10-10) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @here/pkgA bumped from ^1.1.2-alpha.0 to ^1.1.2 + +### [2.2.2](https://www.github.com/fake/repo/compare/pkgB-v2.2.1...pkgB-v2.2.2) (1983-10-10) + + +### Bug Fixes + +* We fixed a bug + + +` + +exports['NodeWorkspaceDependencyUpdates run updates dependent from pre-release version logs'] = [ + [ + "node-workspace: found packages/pkgA/package.json in changes", + "success" + ], + [ + "node-workspace: loaded packages/pkgA/package.json from existing changes", + "success" + ], + [ + "node-workspace: loaded packages/pkgB/package.json from github", + "success" + ], + [ + "node-workspace: setting packages/pkgA/package.json to 1.1.2 from release-please", + "success" + ], + [ + "node-workspace: setting packages/pkgB/package.json to 2.2.3 from dependency bump", + "success" + ], + [ + "node-workspace: @here/pkgB.@here/pkgA updated to ^1.1.2", + "success" + ] +] diff --git a/docs/manifest-releaser.md b/docs/manifest-releaser.md index 45c6c5df1..b59cd7c34 100644 --- a/docs/manifest-releaser.md +++ b/docs/manifest-releaser.md @@ -100,6 +100,9 @@ documented in comments) // - only applicable at top-level config. "bootstrap-sha": "6fc119838885b0cb831e78ddd23ac01cb819e585", + // see Plugins section below + // absence defaults to [] (i.e. no plugins) + "plugins": ["node-workspace"], // optional top-level defaults that can be overriden per package: @@ -249,3 +252,72 @@ with a combined version of the library, i.e., This functionality can be achieved by using the special `"."` path. `"."` indicates a release should be created when any changes are made to the codebase. + +## Plugins + +Plugins can be added to perform extra release processing that cannot be achieved +by an individual releaser because that releaser only has the context of a single +package on which to operate. A plugin operates in the context of all the +packages in the monorepo after release-please has run individual releasers on +each package but before the final PR is created or updated. + +### Plugin usage + +To use a plugin in your manifest based release-please setup, simply add it to +the array of the `"plugins"` key in your release-please-config.json. (Note: the +plugin must already be implemented, see below) + +### Plugin implementation + +A `ManifestPlugin` instance has these resources avilable: +- `this.gh`: a `GitHub` instance for any API operations it might want to perform +- `this.config`: a `Config` object representing all the packages configured for the monorepo + +It must implement a `run` method which receives two arguments: +- the latest versions of all packages (ultimately be written to the manifest) +- an array of the mapping of package-to-currently-proposed-changes +and makes any modifications, additions, or deletions to either argument (in +addition to any other out-of-band side effect) and returns both (potentially +modified) arguments in a tuple. + +For example, a very basic plugin that simply logs the number of packages +currently appearing in the release written as `src/plugins/num-packages.ts`: + +```typescript +import {CheckpointType} from '../util/checkpoint'; + +export default class LogNumberPkgsReleased extends ManifestPlugin { + + async run( + newManifestVersions: VersionsMap, + pkgsWithPRData: ManifestPackageWithPRData[] + ): Promise<[VersionsMap, ManifestPackageWithPRData[]]> { + this.log( + `Number of packages to release: ${pkgsWithPRData.length}`, + CheckpointType.Success + ); + return [newManifestVersions, pkgsWithPRData]; + } +} +``` + +The `num-packages` plugin is not very interesting. Also, if it is not last in +the `"plugins"` configuration array, it might not be accurate (a subsequent +plugin could add or remove entries to/from `pkgsWithPRData`) + +However, one place a plugin has particular value is in a monorepo where local +packages depend on the latest version of each other (e.g. yarn/npm workspaces +for Node, or cargo workspaces for Rust). + + +### node-workspace + +The `node-workspace` plugin builds a graph of local node packages configured +in release-please-config.json and the dependency relationships between them. +It looks at what packages were updated by release-please and updates their +reference in other packages' dependencies lists. Even when a particular package +was not updated by release-please, if a dependency did have an update, it will +be patch bump the package, create a changelog entry, and add it to the list of +PR changes. Under the hood this plugin adapts specific dependency graph building +and updating functionality from the popular +[lerna](https://github.com/lerna/lerna) tool. diff --git a/package.json b/package.json index 287cc11f7..18fc895d7 100644 --- a/package.json +++ b/package.json @@ -61,9 +61,14 @@ "dependencies": { "@conventional-commits/parser": "^0.4.1", "@iarna/toml": "^2.2.5", + "@lerna/collect-updates": "^4.0.0", + "@lerna/package": "^3.16.0", + "@lerna/package-graph": "^3.18.5", + "@lerna/run-topologically": "^3.18.5", "@octokit/graphql": "^4.3.1", "@octokit/request": "^5.3.4", "@octokit/rest": "^18.0.4", + "@types/npm-package-arg": "^6.1.0", "chalk": "^4.0.0", "code-suggester": "^1.4.0", "conventional-changelog-conventionalcommits": "^4.4.0", diff --git a/src/github.ts b/src/github.ts index ab252bb20..79d9e59fe 100644 --- a/src/github.ts +++ b/src/github.ts @@ -112,6 +112,7 @@ export interface GitHubPR { body: string; updates: Update[]; labels: string[]; + changes?: Changes; } export interface MergedGitHubPR { @@ -1184,8 +1185,10 @@ export class GitHub { return undefined; } - // Actually update the files for the release: - const changes = await this.getChangeSet(options.updates, defaultBranch); + // Update the files for the release if not already supplied + const changes = + options.changes ?? + (await this.getChangeSet(options.updates, defaultBranch)); const prNumber = await createPullRequest( this.octokit, changes, @@ -1225,7 +1228,7 @@ export class GitHub { } } - private async getChangeSet( + async getChangeSet( updates: Update[], defaultBranch: string ): Promise { diff --git a/src/index.ts b/src/index.ts index 58456c958..a859a83ee 100644 --- a/src/index.ts +++ b/src/index.ts @@ -17,6 +17,7 @@ import {ReleaseType} from './releasers'; import {ReleasePR} from './release-pr'; import {ChangelogSection} from './conventional-commits'; import {Checkpoint} from './util/checkpoint'; +import {Changes} from 'code-suggester'; export {ReleaseCandidate, ReleasePR} from './release-pr'; @@ -80,6 +81,35 @@ export interface ManifestFactoryOptions extends GitHubFactoryOptions, ManifestOptions {} +// allow list of releaser options used to build ReleasePR subclass instances +// and GitHubRelease instances for each package. Rejected items: +// 1. `monorepoTags` and `pullRequestTitlePattern` will never apply +// 2. `lastPackageVersion` and `versionFile` could be supported if/when ruby +// support in the manifest is made available. However, ideally they'd be +// factored out of the ruby ReleasePR prior to adding support here. +// 3. currently unsupported but possible future support: +// - `snapshot` +// - `label` +export type ManifestPackage = Pick< + ReleasePROptions & GitHubReleaseOptions, + | 'draft' + | 'packageName' + | 'bumpMinorPreMajor' + | 'bumpPatchForMinorPreMajor' + | 'releaseAs' + | 'changelogSections' + | 'changelogPath' +> & { + // these items are not optional in the manifest context. + path: string; + releaseType: ReleaseType; +}; + +export interface ManifestPackageWithPRData { + config: ManifestPackage; + prData: {version: string; changes: Changes}; +} + // ReleasePR Constructor options export interface ReleasePRConstructorOptions extends ReleasePROptions, diff --git a/src/manifest.ts b/src/manifest.ts index ae14a0af7..73b358209 100644 --- a/src/manifest.ts +++ b/src/manifest.ts @@ -14,7 +14,7 @@ import {CommitSplit} from './commit-split'; import {GitHub, GitHubFileContents, ReleaseCreateResponse} from './github'; -import {Update, VersionsMap} from './updaters/update'; +import {VersionsMap} from './updaters/update'; import {ReleaseType} from './releasers'; import {Commit} from './graphql-to-commits'; import { @@ -27,8 +27,8 @@ import {BranchName} from './util/branch-name'; import { factory, ManifestConstructorOptions, - ReleasePROptions, - GitHubReleaseOptions, + ManifestPackage, + ManifestPackageWithPRData, } from '.'; import {ChangelogSection} from './conventional-commits'; import {ReleasePleaseManifest} from './updaters/release-please-manifest'; @@ -38,8 +38,10 @@ import { GitHubReleaseResponse, GITHUB_RELEASE_LABEL, } from './github-release'; -import {OpenPROptions} from './release-pr'; import {ReleasePR} from './release-pr'; +import {Changes} from 'code-suggester'; +import {getPlugin} from './plugins'; +import {ManifestPlugin} from './plugins/plugin'; interface ReleaserConfigJson { 'release-type'?: ReleaseType; @@ -57,45 +59,17 @@ interface ReleaserPackageConfig extends ReleaserConfigJson { export interface Config extends ReleaserConfigJson { packages: Record; - parsedPackages: Package[]; + parsedPackages: ManifestPackage[]; 'bootstrap-sha'?: string; + plugins?: string[]; } -// allow list of releaser options used to build ReleasePR subclass instances -// and GitHubRelease instances for each package. Rejected items: -// 1. `monorepoTags` and `pullRequestTitlePattern` will never apply -// 2. `lastPackageVersion` and `versionFile` could be supported if/when ruby -// support in the manifest is made available. However, ideally they'd be -// factored out of the ruby ReleasePR prior to adding support here. -// 3. currently unsupported but possible future support: -// - `snapshot` -// - `label` -type Package = Pick< - ReleasePROptions & GitHubReleaseOptions, - | 'draft' - | 'packageName' - | 'bumpMinorPreMajor' - | 'bumpPatchForMinorPreMajor' - | 'releaseAs' - | 'changelogSections' - | 'changelogPath' -> & { - // these items are not optional in the manifest context. - path: string; - releaseType: ReleaseType; -}; - interface PackageForReleaser { - config: Package; + config: ManifestPackage; commits: Commit[]; lastVersion?: string; } -interface PackageWithPRData { - config: Package; - openPROptions: OpenPROptions; -} - type ManifestJson = Record; export type ManifestGitHubReleaseResult = @@ -428,7 +402,7 @@ export class Manifest { } private async getReleasePR( - pkg: Package + pkg: ManifestPackage ): Promise<[ReleasePR, boolean | undefined]> { const {releaseType, draft, ...options} = pkg; const releaserOptions = { @@ -446,9 +420,9 @@ export class Manifest { private async runReleasers( packagesForReleasers: PackageForReleaser[], sha?: string - ): Promise<[VersionsMap, PackageWithPRData[]]> { - const manifestUpdates = new Map(); - const openPRPackages = []; + ): Promise<[VersionsMap, ManifestPackageWithPRData[]]> { + const newManifestVersions = new Map(); + const pkgsWithChanges = []; for (const pkg of packagesForReleasers) { const [releasePR] = await this.getReleasePR(pkg.config); const pkgName = await releasePR.getPackageName(); @@ -480,30 +454,25 @@ export class Manifest { ); if (openPROptions) { pkg.config.packageName = (await releasePR.getPackageName()).name; - openPRPackages.push({config: pkg.config, openPROptions}); - manifestUpdates.set(pkg.config.path, openPROptions.version); + const changes = await this.gh.getChangeSet( + openPROptions.updates, + await this.gh.getDefaultBranch() + ); + pkgsWithChanges.push({ + config: pkg.config, + prData: {version: openPROptions.version, changes}, + }); + newManifestVersions.set(pkg.config.path, openPROptions.version); } } - return [manifestUpdates, openPRPackages]; + return [newManifestVersions, pkgsWithChanges]; } - private async buildManifestPR( - manifestUpdates: VersionsMap, - openPRPackages: PackageWithPRData[] - ): Promise<[string, Update[]]> { - let body = ':robot: I have created a release \\*beep\\* \\*boop\\*'; - const updates: Update[] = []; - for (const openPRPackage of openPRPackages) { - body += - '\n\n---\n' + - `${openPRPackage.config.packageName}: ${openPRPackage.openPROptions.version}\n` + - `${openPRPackage.openPROptions.changelogEntry}`; - updates.push(...openPRPackage.openPROptions.updates); - } - - // TODO: `Update` interface to supply cached contents for use in - // GitHub.getChangeSet processing could be simplified to just use a - // string - no need for a full blown GitHubFileContents + private async getManifestChanges( + newManifestVersions: VersionsMap + ): Promise { + // TODO: simplify `Update.contents?` to just be a string - no need to + // roundtrip through a GitHubFileContents const manifestContents: GitHubFileContents = { sha: '', parsedContent: '', @@ -511,21 +480,82 @@ export class Manifest { JSON.stringify(await this.getManifestJson()) ).toString('base64'), }; - updates.push( - new ReleasePleaseManifest({ - changelogEntry: '', - packageName: '', - path: this.manifestFileName, - version: '', - versions: manifestUpdates, - contents: manifestContents, - }) + const manifestUpdate = new ReleasePleaseManifest({ + changelogEntry: '', + packageName: '', + path: this.manifestFileName, + version: '', + versions: newManifestVersions, + contents: manifestContents, + }); + return await this.gh.getChangeSet( + [manifestUpdate], + await this.gh.getDefaultBranch() ); + } + + private buildPRBody(pkg: ManifestPackageWithPRData): string { + const version = pkg.prData.version; + let body = + '
' + + `${pkg.config.packageName}: ${version}` + + ''; + let changelogPath = pkg.config.changelogPath ?? 'CHANGELOG.md'; + if (pkg.config.path !== '.') { + changelogPath = `${pkg.config.path}/${changelogPath}`; + } + const changelog = pkg.prData.changes.get(changelogPath)?.content; + if (!changelog) { + this.checkpoint( + `Failed to find ${changelogPath}`, + CheckpointType.Failure + ); + } else { + const match = changelog.match( + // changelog entries start like + // ## 1.0.0 (1983... + // ## [4.0.0](https... + // ### [1.2.4](https... + RegExp( + `.*###? \\[?${version}\\]?.*?\n(?.*?)` + + // either the next changelog or new lines / spaces to the end if + // this is the first entry in the changelog + '(\n###? [0-9[].*|[\n ]*$)', + 's' + ) + ); + if (!match) { + this.checkpoint( + `Failed to find entry in changelog for ${version}`, + CheckpointType.Failure + ); + } else { + const {currentEntry} = match.groups!; + body += '\n\n\n' + currentEntry.trim() + '\n'; + } + } + body += '
\n'; + return body; + } + + private async buildManifestPR( + newManifestVersions: VersionsMap, + // using version, changes + packages: ManifestPackageWithPRData[] + ): Promise<[string, Changes]> { + let body = ':robot: I have created a release \\*beep\\* \\*boop\\*\n---\n'; + let changes = new Map(); + for (const pkg of packages) { + body += this.buildPRBody(pkg); + changes = new Map([...changes, ...pkg.prData.changes]); + } + const manifestChanges = await this.getManifestChanges(newManifestVersions); + changes = new Map([...changes, ...manifestChanges]); body += '\n\nThis PR was generated with [Release Please]' + `(https://github.com/googleapis/${RELEASE_PLEASE}). See [documentation]` + `(https://github.com/googleapis/${RELEASE_PLEASE}#${RELEASE_PLEASE}).`; - return [body, updates]; + return [body, changes]; } private async commitsSinceSha(sha?: string): Promise { @@ -536,6 +566,15 @@ export class Manifest { return this.gh.commitsSinceShaRest(fromSha); } + private async getPlugins(): Promise { + const plugins = []; + const config = await this.getConfigJson(); + for (const p of config.plugins ?? []) { + plugins.push(await getPlugin(p, this.gh, config)); + } + return plugins; + } + async pullRequest(): Promise { const valid = await this.validate(); if (!valid) { @@ -549,11 +588,11 @@ export class Manifest { commits, lastMergedPR?.sha ); - const [manifestUpdates, openPRPackages] = await this.runReleasers( + let [newManifestVersions, pkgsWithChanges] = await this.runReleasers( packagesForReleasers, lastMergedPR?.sha ); - if (openPRPackages.length === 0) { + if (pkgsWithChanges.length === 0) { this.checkpoint( 'No user facing changes to release', CheckpointType.Success @@ -561,16 +600,24 @@ export class Manifest { return; } - const [body, updates] = await this.buildManifestPR( - manifestUpdates, - openPRPackages + for (const plugin of await this.getPlugins()) { + [newManifestVersions, pkgsWithChanges] = await plugin.run( + newManifestVersions, + pkgsWithChanges + ); + } + + const [body, changes] = await this.buildManifestPR( + newManifestVersions, + pkgsWithChanges ); const pr = await this.gh.openPR({ branch: branchName, title: `chore: release ${await this.gh.getDefaultBranch()}`, body: body, - updates, + updates: [], labels: DEFAULT_LABELS, + changes, }); if (pr) { await this.gh.addLabels(DEFAULT_LABELS, pr); diff --git a/src/plugins/index.ts b/src/plugins/index.ts new file mode 100644 index 000000000..86af77a04 --- /dev/null +++ b/src/plugins/index.ts @@ -0,0 +1,29 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import {ManifestPlugin} from './plugin'; +import {GitHub} from '../github'; +import {Config} from '../manifest'; + +export async function getPlugin( + name: string, + github: GitHub, + config: Config +): Promise { + // the prefixed './' should be sufficient to tell webpack to include all the + // plugin files under src/plugins/ + // https://webpack.js.org/api/module-methods/#dynamic-expressions-in-import + const instance = (await import(`./${name}`)).default; + return new instance(github, config, name); +} diff --git a/src/plugins/node-workspace.ts b/src/plugins/node-workspace.ts new file mode 100644 index 000000000..0f3f49055 --- /dev/null +++ b/src/plugins/node-workspace.ts @@ -0,0 +1,412 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import * as semver from 'semver'; +import cu = require('@lerna/collect-updates'); +import Package = require('@lerna/package'); +import {PackageJson} from '@lerna/package'; +import PackageGraph = require('@lerna/package-graph'); +import runTopologically = require('@lerna/run-topologically'); +import {ManifestPlugin} from './plugin'; +import {ManifestPackageWithPRData, ManifestPackage} from '..'; +import {VersionsMap} from '../updaters/update'; +import {packageJsonStringify} from '../util/package-json-stringify'; +import {CheckpointType} from '../util/checkpoint'; +import {RELEASE_PLEASE} from '../constants'; +import {Changes} from 'code-suggester'; +import {ConventionalCommits} from '../conventional-commits'; +import {Changelog} from '../updaters/changelog'; + +type PathPkgJson = Map; + +export default class NodeWorkspaceDependencyUpdates extends ManifestPlugin { + // package.json contents already updated by the node releasers. + private filterPackages( + pkgsWithPRData: ManifestPackageWithPRData[] + ): PathPkgJson { + const pathPkgs = new Map(); + for (const pkg of pkgsWithPRData) { + if (pkg.config.releaseType === 'node' && pkg.config.path !== '.') { + for (const [path, fileData] of pkg.prData.changes) { + if (path === `${pkg.config.path}/package.json`) { + this.log(`found ${path} in changes`, CheckpointType.Success); + pathPkgs.set(path, JSON.parse(fileData.content!) as PackageJson); + } + } + } + } + return pathPkgs; + } + + // all packages' package.json content - both updated by this run as well as + // those that did not update (no user facing commits). + private async getAllWorkspacePackages( + rpUpdatedPkgs: PathPkgJson + ): Promise> { + const nodePkgs = new Map(); + for (const pkg of this.config.parsedPackages) { + if (pkg.releaseType !== 'node' || pkg.path === '.') { + continue; + } + const path = `${pkg.path}/package.json`; + let contents: PackageJson; + const alreadyUpdated = rpUpdatedPkgs.get(path); + if (alreadyUpdated) { + contents = alreadyUpdated; + } else { + const fileContents = await this.gh.getFileContents(path); + contents = JSON.parse(fileContents.parsedContent); + } + this.log( + `loaded ${path} from ${alreadyUpdated ? 'existing changes' : 'github'}`, + CheckpointType.Success + ); + nodePkgs.set(path, new Package(contents, path)); + } + return nodePkgs; + } + + private async runLernaVersion( + rpUpdatedPkgs: PathPkgJson, + allPkgs: Map + ): Promise> { + // Build the graph of all the packages: similar to https://git.io/Jqf1v + const packageGraph = new PackageGraph( + // use pkg.toJSON() which does a shallow copy of the internal data storage + // so we can preserve the original allPkgs for version diffing later. + [...allPkgs].map(([path, pkg]) => new Package(pkg.toJSON(), path)), + 'allDependencies' + ); + + // release-please already did the work of @lerna/collectUpdates (identifying + // which packages need version bumps based on conventional commits). We use + // that as our `isCandidate` callback in @lerna/collectUpdates.collectPackages. + // similar to https://git.io/JqUOB + // `collectPackages` includes "localDependents" of our release-please updated + // packages as they need to be patch bumped. + const updatesWithDependents = cu.collectPackages(packageGraph, { + isCandidate: node => rpUpdatedPkgs.has(node.location), + }); + + // our implementation of producing a Map similar to + // `this.updatesVersions` which is used to set updated package + // (https://git.io/JqfD7) and dependency (https://git.io/JqU3q) versions + // + // `lerna version` accomplishes this with: + // `getVersionsForUpdates` (https://git.io/JqfyI) + // -> `getVersion` + `reduceVersions` (https://git.io/JqfDI) + const updatesVersions = new Map(); + const invalidVersions = new Set(); + for (const node of updatesWithDependents) { + let version: string; + let source: string; + if (rpUpdatedPkgs.has(node.location)) { + version = node.version; + source = RELEASE_PLEASE; + } else { + // must be a dependent, assume a "patch" bump. + const patch = semver.inc(node.version, 'patch'); + if (patch === null) { + this.log( + `Don't know how to patch ${node.name}'s version(${node.version})`, + CheckpointType.Failure + ); + invalidVersions.add(node.name); + version = node.version; + source = 'failed to patch bump'; + } else { + version = patch; + source = 'dependency bump'; + } + } + this.log( + `setting ${node.location} to ${version} from ${source}`, + CheckpointType.Success + ); + updatesVersions.set(node.name, version); + } + + // our implementation of a subset of `updatePackageVersions` to produce a + // callback for updating versions and dependencies (https://git.io/Jqfyu) + const runner = async (pkg: Package): Promise => { + pkg.set('version', updatesVersions.get(pkg.name)); + const graphPkg = packageGraph.get(pkg.name); + for (const [depName, resolved] of graphPkg.localDependencies) { + const depVersion = updatesVersions.get(depName); + if (depVersion && resolved.type !== 'directory') { + pkg.updateLocalDependency(resolved, depVersion, '^'); + this.log( + `${pkg.name}.${depName} updated to ^${depVersion}`, + CheckpointType.Success + ); + } + } + return pkg; + }; + + // https://git.io/Jqfyp + const allUpdated = await runTopologically( + updatesWithDependents.map(node => node.pkg), + runner, + { + graphType: 'allDependencies', + concurrency: 1, + rejectCycles: false, + } + ); + return new Map(allUpdated.map(p => [p.location, p])); + } + + private async updatePkgsWithPRData( + pkgsWithPRData: ManifestPackageWithPRData[], + newManifestVersions: VersionsMap, + allUpdated: Map, + allOrigPkgs: Map + ) { + // already had version bumped by release-please, may have also had + // dependency version bumps as well + for (const data of pkgsWithPRData) { + if (data.config.releaseType !== 'node' || data.config.path === '.') { + continue; + } + const filePath = `${data.config.path}/package.json`; + const updated = allUpdated.get(filePath)!; // bug if not defined + data.prData.changes.set(filePath, { + content: packageJsonStringify(updated.toJSON()), + mode: '100644', + }); + await this.setChangelogEntry( + data.config, + data.prData.changes, + updated, + allOrigPkgs.get(filePath)! // bug if undefined. + ); + allUpdated.delete(filePath); + } + + // non-release-please updated packages that have updates solely because + // dependency versions incremented. + for (const [filePath, updated] of allUpdated) { + const pkg = this.config.parsedPackages.find( + p => `${p.path}/package.json` === filePath + )!; // bug if undefined. + pkg.packageName = updated.name; + const content = packageJsonStringify(updated.toJSON()); + const changes: Changes = new Map([[filePath, {content, mode: '100644'}]]); + await this.setChangelogEntry( + pkg, + changes, + updated, + allOrigPkgs.get(filePath)! // bug if undefined. + ); + pkgsWithPRData.push({ + config: pkg, + prData: { + version: updated.version, + changes, + }, + }); + newManifestVersions.set( + filePath.replace(/\/package.json$/, ''), + updated.version + ); + } + } + + private getChangelogDepsNotes( + pkg: Package, + origPkgJson: PackageJson + ): string { + let depUpdateNotes = ''; + type DT = + | 'dependencies' + | 'devDependencies' + | 'peerDependencies' + | 'optionalDependencies'; + const depTypes: DT[] = [ + 'dependencies', + 'devDependencies', + 'peerDependencies', + 'optionalDependencies', + ]; + const updates: Map = new Map(); + for (const depType of depTypes) { + const depUpdates = []; + const pkgDepTypes = pkg[depType]; + if (pkgDepTypes === undefined) { + continue; + } + for (const [depName, currentDepVer] of Object.entries(pkgDepTypes)) { + const origDepVer = origPkgJson[depType]?.[depName]; + if (currentDepVer !== origDepVer) { + depUpdates.push( + `\n * ${depName} bumped from ${origDepVer} to ${currentDepVer}` + ); + } + } + if (depUpdates.length > 0) { + updates.set(depType, depUpdates); + } + } + for (const [dt, notes] of updates) { + depUpdateNotes += `\n * ${dt}`; + for (const note of notes) { + depUpdateNotes += note; + } + } + return depUpdateNotes; + } + + private updateChangelogEntry( + exChangelog: string, + changelogEntry: string, + pkg: Package + ): string { + changelogEntry = changelogEntry.replace( + new RegExp(`^###? \\[${pkg.version}\\].*### Dependencies`, 's'), + '### Dependencies' + ); + const match = exChangelog.match( + new RegExp( + `(?^.*?###? \\[${pkg.version}\\].*?\n)(?###? [0-9[].*)`, + 's' + ) + ); + if (!match) { + this.log( + `Appending update notes to end of changelog for ${pkg.name}`, + CheckpointType.Failure + ); + changelogEntry = `${exChangelog}\n\n\n${changelogEntry}`; + } else { + const {before, after} = match.groups!; + changelogEntry = `${before.trim()}\n\n\n${changelogEntry}\n\n${after.trim()}`; + } + return changelogEntry; + } + + private async newChangelogEntry( + changelogEntry: string, + changelogPath: string, + pkg: Package + ): Promise { + let changelog; + try { + changelog = (await this.gh.getFileContents(changelogPath)).parsedContent; + } catch (e) { + if (e.status !== 404) { + this.log( + `Failed to retrieve ${changelogPath}: ${e}`, + CheckpointType.Failure + ); + return ''; + } else { + this.log( + `Creating a new changelog at ${changelogPath}`, + CheckpointType.Success + ); + } + } + const changelogUpdater = new Changelog({ + path: changelogPath, + changelogEntry, + version: pkg.version, + packageName: pkg.name, + }); + return changelogUpdater.updateContent(changelog); + } + + private async setChangelogEntry( + config: ManifestPackage, + changes: Changes, + pkg: Package, + origPkgJson: PackageJson + ) { + const depUpdateNotes = this.getChangelogDepsNotes(pkg, origPkgJson); + if (!depUpdateNotes) { + return; + } + + const cc = new ConventionalCommits({ + changelogSections: [{type: 'deps', section: 'Dependencies'}], + commits: [ + { + sha: '', + message: 'deps: The following workspace dependencies were updated', + files: [], + }, + ], + owner: this.gh.owner, + repository: this.gh.repo, + bumpMinorPreMajor: config.bumpMinorPreMajor, + }); + let tagPrefix = config.packageName?.match(/^@[\w-]+\//) + ? config.packageName.split('/')[1] + : config.packageName; + tagPrefix += '-v'; + let changelogEntry = await cc.generateChangelogEntry({ + version: pkg.version, + currentTag: tagPrefix + pkg.version, + previousTag: tagPrefix + origPkgJson.version, + }); + changelogEntry += depUpdateNotes; + + let updatedChangelog; + let changelogPath = config.changelogPath ?? 'CHANGELOG.md'; + if (config.path !== '.') { + changelogPath = `${config.path}/${changelogPath}`; + } + const exChangelog = changes.get(changelogPath)?.content; + if (exChangelog) { + updatedChangelog = this.updateChangelogEntry( + exChangelog, + changelogEntry, + pkg + ); + } else { + updatedChangelog = await this.newChangelogEntry( + changelogEntry, + changelogPath, + pkg + ); + } + if (updatedChangelog) { + changes.set(changelogPath, { + content: updatedChangelog, + mode: '100644', + }); + } + } + + /** + * Update node monorepo workspace package dependencies. + * Inspired by and using a subset of the logic from `lerna version` + */ + async run( + newManifestVersions: VersionsMap, + pkgsWithPRData: ManifestPackageWithPRData[] + ): Promise<[VersionsMap, ManifestPackageWithPRData[]]> { + const rpUpdatedPkgs = this.filterPackages(pkgsWithPRData); + const allPkgs = await this.getAllWorkspacePackages(rpUpdatedPkgs); + const allUpdated = await this.runLernaVersion(rpUpdatedPkgs, allPkgs); + await this.updatePkgsWithPRData( + pkgsWithPRData, + newManifestVersions, + allUpdated, + allPkgs + ); + + return [newManifestVersions, pkgsWithPRData]; + } +} diff --git a/src/plugins/plugin.ts b/src/plugins/plugin.ts new file mode 100644 index 000000000..b939d9b53 --- /dev/null +++ b/src/plugins/plugin.ts @@ -0,0 +1,52 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import {ManifestPackageWithPRData} from '..'; +import {VersionsMap} from '../updaters/update'; +import {GitHub} from '../github'; +import {Config} from '../manifest'; +import {checkpoint, Checkpoint, CheckpointType} from '../util/checkpoint'; + +export abstract class ManifestPlugin { + gh: GitHub; + config: Config; + tag: string; + checkpoint: Checkpoint; + constructor( + github: GitHub, + config: Config, + tag: string, + logger?: Checkpoint + ) { + this.gh = github; + this.config = config; + this.checkpoint = logger || checkpoint; + this.tag = tag; + } + /** + * @param newManifestVersions - new package versions set by releasers and any + * previous plugins + * @param pkgsWithPRData - PR data per package (e.g. changelog, package.json) + * @returns - tuple of the input arguments including any changes, additions + * and/or subtractions. + */ + abstract run( + newManifestVersions: VersionsMap, + pkgsWithPRData: ManifestPackageWithPRData[] + ): Promise<[VersionsMap, ManifestPackageWithPRData[]]>; + + protected log(msg: string, cpType: CheckpointType) { + this.checkpoint(`${this.tag}: ${msg}`, cpType); + } +} diff --git a/src/updaters/package-json.ts b/src/updaters/package-json.ts index e1a8ca697..a6b1ce894 100644 --- a/src/updaters/package-json.ts +++ b/src/updaters/package-json.ts @@ -15,6 +15,7 @@ import {checkpoint, CheckpointType} from '../util/checkpoint'; import {Update, UpdateOptions, VersionsMap} from './update'; import {GitHubFileContents} from '../github'; +import {packageJsonStringify} from '../util/package-json-stringify'; export class PackageJson implements Update { path: string; @@ -41,6 +42,6 @@ export class PackageJson implements Update { CheckpointType.Success ); parsed.version = this.version; - return JSON.stringify(parsed, null, 2) + '\n'; + return packageJsonStringify(parsed); } } diff --git a/src/util/package-json-stringify.ts b/src/util/package-json-stringify.ts new file mode 100644 index 000000000..9ae66f4f7 --- /dev/null +++ b/src/util/package-json-stringify.ts @@ -0,0 +1,17 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +export function packageJsonStringify(parsed: object): string { + return JSON.stringify(parsed, null, 2) + '\n'; +} diff --git a/test/fixtures/manifest/repo/node/pkg2/package.json b/test/fixtures/manifest/repo/node/pkg2/package.json index 2d87510d3..8312f850e 100644 --- a/test/fixtures/manifest/repo/node/pkg2/package.json +++ b/test/fixtures/manifest/repo/node/pkg2/package.json @@ -1,4 +1,7 @@ { "name": "@node/pkg2", - "version": "0.1.2" + "version": "0.1.2", + "dependencies": { + "@node/pkg1": "^0.123.4" + } } diff --git a/test/manifest.ts b/test/manifest.ts index aad171aec..db8527b44 100644 --- a/test/manifest.ts +++ b/test/manifest.ts @@ -2049,4 +2049,80 @@ describe('Manifest', () => { }); } }); + + describe('plugins', () => { + it('runs the node-workspace plugin', async function () { + const manifest = JSON.stringify({ + 'node/pkg1': '0.123.4', + 'node/pkg2': '0.1.2', + python: '1.2.3', + }); + const config = JSON.stringify({ + plugins: ['node-workspace'], + packages: { + 'node/pkg1': {}, + 'node/pkg2': {}, + python: { + 'release-type': 'python', + 'package-name': 'foolib', + }, + }, + }); + const commits = [ + buildMockCommit('fix(foolib): bufix python foolib', [ + 'python/src/foolib/foo.py', + ]), + buildMockCommit('fix(@node/pkg1): bugfix pkg1', [ + 'node/pkg1/src/foo.ts', + ]), + ]; + + const github = new GitHub({ + owner: 'fake', + repo: 'repo', + defaultBranch, + }); + const mock = mockGithub(github); + expectManifest(mock, {manifest, lastReleaseSha}); + expectPR(mock, {lastReleaseSha}); + expectCommitsSinceSha(mock, {commits, lastReleaseSha}); + expectGetFiles(mock, { + fixtureFiles: [ + 'node/pkg1/package.json', + 'node/pkg2/package.json', + 'python/setup.py', + 'python/setup.cfg', + 'python/src/foolib/version.py', + ], + inlineFiles: [ + ['release-please-config.json', config], + ['.release-please-manifest.json', manifest], + ], + }); + expectLabelAndComment(mock, {addLabel}); + stubSuggesterWithSnapshot(sandbox, this.test!.fullTitle()); + const logs: [string, CheckpointType][] = []; + const checkpoint = (msg: string, type: CheckpointType) => + logs.push([msg, type]); + + const pr = await new Manifest({github, checkpoint}).pullRequest(); + + mock.verify(); + expect(pr).to.equal(22); + expect(logs).to.eql([ + [ + 'Found version 0.123.4 for node/pkg1 in ' + + '.release-please-manifest.json at abc123 of main', + CheckpointType.Success, + ], + [ + 'Found version 1.2.3 for python in ' + + '.release-please-manifest.json at abc123 of main', + CheckpointType.Success, + ], + ['Processing package: Node(@node/pkg1)', CheckpointType.Success], + ['Processing package: Python(foolib)', CheckpointType.Success], + ]); + }); + }); }); diff --git a/test/plugins/node-workspace.ts b/test/plugins/node-workspace.ts new file mode 100644 index 000000000..64a1e9e9e --- /dev/null +++ b/test/plugins/node-workspace.ts @@ -0,0 +1,1027 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import NodeWorkspaceDependencyUpdates from '../../src/plugins/node-workspace'; +import {describe, it, afterEach} from 'mocha'; +import {expect} from 'chai'; +import * as sinon from 'sinon'; +import {GitHub} from '../../src/github'; +import {Config} from '../../src/manifest'; +import {buildGitHubFileRaw} from '../releasers/utils'; +import {ManifestPackageWithPRData} from '../../src'; +import {packageJsonStringify} from '../../src/util/package-json-stringify'; +import {CheckpointType} from '../../src/util/checkpoint'; +import {stringifyExpectedChanges} from '../helpers'; +import snapshot = require('snap-shot-it'); + +const sandbox = sinon.createSandbox(); + +function stringifyActual(actual: ManifestPackageWithPRData[]) { + let stringified = ''; + for (const pkgsWithPRData of actual) { + const changes = pkgsWithPRData.prData.changes; + stringified += + '='.repeat(20) + + '\n' + + JSON.stringify( + pkgsWithPRData, + (k, v) => (k === ' changes' ? undefined : v), + 2 + ) + + '\n'; + stringified += stringifyExpectedChanges([...changes]) + '\n'; + } + return stringified; +} + +const pkgAData: ManifestPackageWithPRData = { + config: { + releaseType: 'node', + packageName: '@here/pkgA', + path: 'packages/pkgA', + }, + prData: { + version: '1.1.2', + changes: new Map([ + [ + 'packages/pkgA/package.json', + { + content: packageJsonStringify({ + name: '@here/pkgA', + version: '1.1.2', + dependencies: {'@there/foo': '^4.1.7'}, + }), + mode: '100644', + }, + ], + [ + 'packages/pkgA/CHANGELOG.md', + { + content: + '# Changelog' + + '\n\nAll notable changes to this project will be ' + + 'documented in this file.' + + '### [1.1.2](https://www.github.com/fake/repo/compare' + + '/pkgA-v1.1.1...pkgA-v1.1.2) (1983-10-10)' + + '\n\n\n### Bug Fixes' + + '\n\n* We fixed a bug!', + mode: '100644', + }, + ], + ]), + }, +}; + +describe('NodeWorkspaceDependencyUpdates', () => { + afterEach(() => { + sandbox.restore(); + }); + + function mockGithub(github: GitHub) { + return sandbox.mock(github); + } + + function expectGetFiles( + mock: sinon.SinonMock, + namesContents: [string, string | number | false][] + ) { + for (const [file, contents] of namesContents) { + if (typeof contents === 'string') { + mock + .expects('getFileContentsOnBranch') + .withExactArgs(file, 'main') + .once() + .resolves(buildGitHubFileRaw(contents)); + } else if (contents) { + mock + .expects('getFileContentsOnBranch') + .withExactArgs(file, 'main') + .once() + .rejects( + Object.assign(Error(`error: ${contents}`), {status: contents}) + ); + } else { + mock + .expects('getFileContentsOnBranch') + .withExactArgs(file, 'main') + .never(); + } + } + } + + describe('run', () => { + it('handles a simple chain where root pkg update cascades to dependents', async function () { + const config: Config = { + packages: {}, // unused, required by interface + parsedPackages: [ + {path: 'packages/pkgA', releaseType: 'node'}, + {path: 'packages/pkgB', releaseType: 'node'}, + {path: 'packages/pkgC', releaseType: 'node'}, + // should ignore non-node packages + {path: 'py/pkg', releaseType: 'python'}, + ], + }; + const github = new GitHub({ + owner: 'fake', + repo: 'repo', + defaultBranch: 'main', + }); + const mock = mockGithub(github); + + // packages B and C did not get release-please updates but B depends on A + // and C depends on B so both should be getting patch bumps + expectGetFiles(mock, [ + ['packages/pkgA/package.json', false], + ['packages/pkgA/CHANGELOG.md', false], + [ + 'packages/pkgB/package.json', + JSON.stringify({ + name: '@here/pkgB', + version: '2.2.2', + dependencies: { + '@here/pkgA': '^1.1.1', + someExternal: '^9.2.3', + }, + }), + ], + [ + 'packages/pkgB/CHANGELOG.md', + '# Changelog' + + '\n\nAll notable changes to this project will be ' + + 'documented in this file.' + + '\n\n### [2.2.2](https://www.github.com/fake/repo/compare' + + '/pkgB-v2.2.1...pkgB-v2.2.2) (1983-10-10)' + + '\n\n\n### Bug Fixes' + + '\n\n* We fixed a bug', + ], + [ + 'packages/pkgC/package.json', + JSON.stringify({ + name: '@here/pkgC', + version: '3.3.3', + dependencies: { + '@here/pkgB': '^2.2.2', + anotherExternal: '^4.3.1', + }, + }), + ], + ['packages/pkgC/CHANGELOG.md', ''], + ]); + + // pkgA had a patch bump from manifest.runReleasers() + const newManifestVersions = new Map([ + ['packages/pkgA', '1.1.2'], + ['py/pkg', '1.1.2'], + ]); + // all incoming non-node changes should be left alone and returned. + const pyPkgData: ManifestPackageWithPRData = { + config: { + releaseType: 'python', + path: 'py/pkg', + }, + prData: { + version: '1.1.2', + changes: new Map([ + [ + 'py/pkg/setup.py', + { + content: 'some python version content', + mode: '100644', + }, + ], + ]), + }, + }; + const pkgsWithPRData: ManifestPackageWithPRData[] = [ + pkgAData, + pyPkgData, // should ignore non-node packages + ]; + + const logs: [string, CheckpointType][] = []; + const checkpoint = (msg: string, type: CheckpointType) => + logs.push([msg, type]); + const nodeWS = new NodeWorkspaceDependencyUpdates( + github, + config, + 'node-workspace', + checkpoint + ); + const [actualManifest, actualChanges] = await nodeWS.run( + newManifestVersions, + pkgsWithPRData + ); + mock.verify(); + expect([...actualManifest]).to.eql([ + ['packages/pkgA', '1.1.2'], + ['py/pkg', '1.1.2'], + ['packages/pkgB', '2.2.3'], + ['packages/pkgC', '3.3.4'], + ]); + const snapPrefix = this.test!.fullTitle(); + snapshot(snapPrefix + ' logs', logs); + snapshot(snapPrefix + ' changes', stringifyActual(actualChanges)); + }); + + it('handles a triangle: root and one leg updates bumps other leg', async function () { + const config: Config = { + packages: {}, // unused, required by interface + parsedPackages: [ + {path: 'packages/pkgA', releaseType: 'node'}, + {path: 'packages/pkgB', releaseType: 'node'}, + {path: 'packages/pkgC', releaseType: 'node'}, + ], + }; + const github = new GitHub({ + owner: 'fake', + repo: 'repo', + defaultBranch: 'main', + }); + const mock = mockGithub(github); + // package C did not get a release-please updated but it depends on both + // A and B which did get release-please bumps so it should receive a + // patch bump + expectGetFiles(mock, [ + ['packages/pkgA/package.json', false], + ['packages/pkgA/CHANGELOG.md', false], + ['packages/pkgB/package.json', false], + ['packages/pkgB/CHANGELOG.md', false], + [ + 'packages/pkgC/package.json', + JSON.stringify({ + name: '@here/pkgC', + version: '3.3.3', + dependencies: { + '@here/pkgA': '^1.1.1', + '@here/pkgB': '^2.2.2', + anotherExternal: '^4.3.1', + }, + }), + ], + [ + 'packages/pkgC/CHANGELOG.md', + '# Changelog' + + '\n\nAll notable changes to this project will be ' + + 'documented in this file.' + + '\n\n### [3.3.3](https://www.github.com/fake/repo/compare' + + '/pkgC-v3.3.2...pkgC-v3.3.3) (1983-10-10)' + + '\n\n\n### Bug Fixes' + + '\n\n* We fixed a bug', + ], + ]); + + // pkgA had a patch bump and pkgB had a minor bump from + // manifest.runReleasers() + const newManifestVersions = new Map([ + ['packages/pkgA', '1.1.2'], + ['packages/pkgB', '2.3.0'], + ]); + const pkgsWithPRData: ManifestPackageWithPRData[] = [ + pkgAData, + { + config: { + releaseType: 'node', + packageName: '@here/pkgB', + path: 'packages/pkgB', + }, + prData: { + version: '2.3.0', + changes: new Map([ + [ + 'packages/pkgB/package.json', + { + content: packageJsonStringify({ + name: '@here/pkgB', + version: '2.3.0', + dependencies: { + // release-please does not update dependency versions + '@here/pkgA': '^1.1.1', + someExternal: '^9.2.3', + }, + }), + mode: '100644', + }, + ], + [ + 'packages/pkgB/CHANGELOG.md', + { + content: + '# Changelog' + + '\n\nAll notable changes to this project will be ' + + 'documented in this file.' + + '\n\n### [2.3.0](https://www.github.com/fake/repo/compare' + + '/pkgB-v2.2.2...pkgB-v2.3.0) (1983-10-10)' + + '\n\n\n### Features' + + '\n\n* We added a feature' + + '\n\n### [2.2.2](https://www.github.com/fake/repo/compare' + + '/pkgB-v2.2.1...pkgB-v2.2.2) (1983-10-10)' + + '\n\n\n### Bug Fixes' + + '\n\n* We fixed a bug', + mode: '100644', + }, + ], + ]), + }, + }, + ]; + + const logs: [string, CheckpointType][] = []; + const checkpoint = (msg: string, type: CheckpointType) => + logs.push([msg, type]); + const nodeWS = new NodeWorkspaceDependencyUpdates( + github, + config, + 'node-workspace', + checkpoint + ); + const [actualManifest, actualChanges] = await nodeWS.run( + newManifestVersions, + pkgsWithPRData + ); + mock.verify(); + expect([...actualManifest]).to.eql([ + ['packages/pkgA', '1.1.2'], + ['packages/pkgB', '2.3.0'], + ['packages/pkgC', '3.3.4'], + ]); + const snapPrefix = this.test!.fullTitle(); + snapshot(snapPrefix + ' logs', logs); + snapshot(snapPrefix + ' changes', stringifyActual(actualChanges)); + }); + + it('does not update dependencies on preMajor versions with minor bump', async function () { + const config: Config = { + packages: {}, // unused, required by interface + parsedPackages: [ + {path: 'packages/pkgA', releaseType: 'node'}, + {path: 'packages/pkgB', releaseType: 'node'}, + {path: 'packages/pkgC', releaseType: 'node'}, + ], + }; + const github = new GitHub({ + owner: 'fake', + repo: 'repo', + defaultBranch: 'main', + }); + const mock = mockGithub(github); + // package C did not get a release-please updated but it depends on both + // A and B which did get release-please bumps so it should receive a + // patch bump + expectGetFiles(mock, [ + ['packages/pkgA/package.json', false], + ['packages/pkgA/CHANGELOG.md', false], + ['packages/pkgB/package.json', false], + ['packages/pkgB/CHANGELOG.md', false], + [ + 'packages/pkgC/package.json', + JSON.stringify({ + name: '@here/pkgC', + version: '3.3.3', + dependencies: { + '@here/pkgA': '^1.1.1', + '@here/pkgB': '^0.2.1', + anotherExternal: '^4.3.1', + }, + }), + ], + [ + 'packages/pkgC/CHANGELOG.md', + '# Changelog' + + '\n\nAll notable changes to this project will be ' + + 'documented in this file.' + + '\n\n### [3.3.3](https://www.github.com/fake/repo/compare' + + '/pkgC-v3.3.2...pkgC-v3.3.3) (1983-10-10)' + + '\n\n\n### Bug Fixes' + + '\n\n* We fixed a bug', + ], + ]); + + // pkgA had a patch bump and pkgB had a minor bump from + // manifest.runReleasers() + const newManifestVersions = new Map([ + ['packages/pkgA', '1.1.2'], + ['packages/pkgB', '0.3.0'], + ]); + const pkgsWithPRData: ManifestPackageWithPRData[] = [ + pkgAData, + { + config: { + releaseType: 'node', + packageName: '@here/pkgB', + path: 'packages/pkgB', + }, + prData: { + version: '0.3.0', + changes: new Map([ + [ + 'packages/pkgB/package.json', + { + content: packageJsonStringify({ + name: '@here/pkgB', + version: '0.3.0', + dependencies: { + // release-please does not update dependency versions + '@here/pkgA': '^1.1.1', + someExternal: '^9.2.3', + }, + }), + mode: '100644', + }, + ], + [ + 'packages/pkgB/CHANGELOG.md', + { + content: + '# Changelog' + + '\n\nAll notable changes to this project will be ' + + 'documented in this file.' + + '\n\n### [0.3.0](https://www.github.com/fake/repo/compare' + + '/pkgB-v0.2.1...pkgB-v0.3.0) (1983-10-10)' + + '\n\n\n### Features' + + '\n\n* We added a feature' + + '\n\n### [0.2.1](https://www.github.com/fake/repo/compare' + + '/pkgB-v0.2.0...pkgB-v0.2.1) (1983-10-10)' + + '\n\n\n### Bug Fixes' + + '\n\n* We fixed a bug', + mode: '100644', + }, + ], + ]), + }, + }, + ]; + + const logs: [string, CheckpointType][] = []; + const checkpoint = (msg: string, type: CheckpointType) => + logs.push([msg, type]); + const nodeWS = new NodeWorkspaceDependencyUpdates( + github, + config, + 'node-workspace', + checkpoint + ); + const [actualManifest, actualChanges] = await nodeWS.run( + newManifestVersions, + pkgsWithPRData + ); + mock.verify(); + expect([...actualManifest]).to.eql([ + ['packages/pkgA', '1.1.2'], + ['packages/pkgB', '0.3.0'], + ['packages/pkgC', '3.3.4'], + ]); + const snapPrefix = this.test!.fullTitle(); + snapshot(snapPrefix + ' logs', logs); + snapshot(snapPrefix + ' changes', stringifyActual(actualChanges)); + }); + + it('handles unusual changelog formats', async function () { + const config: Config = { + packages: {}, // unused, required by interface + parsedPackages: [ + {path: 'packages/pkgA', releaseType: 'node'}, + {path: 'packages/pkgB', releaseType: 'node'}, + ], + }; + const github = new GitHub({ + owner: 'fake', + repo: 'repo', + defaultBranch: 'main', + }); + const mock = mockGithub(github); + // + expectGetFiles(mock, [ + ['packages/pkgA/package.json', false], + ['packages/pkgA/CHANGELOG.md', false], + ['packages/pkgB/package.json', false], + ['packages/pkgB/CHANGELOG.md', false], + ]); + + // pkgA had a patch bump and pkgB had a minor bump from + // manifest.runReleasers() + const newManifestVersions = new Map([ + ['packages/pkgA', '1.1.2'], + ['packages/pkgB', '2.3.0'], + ]); + const pkgsWithPRData: ManifestPackageWithPRData[] = [ + pkgAData, + { + config: { + releaseType: 'node', + packageName: '@here/pkgB', + path: 'packages/pkgB', + }, + prData: { + version: '2.3.0', + changes: new Map([ + [ + 'packages/pkgB/package.json', + { + content: packageJsonStringify({ + name: '@here/pkgB', + version: '2.3.0', + dependencies: { + // release-please does not update dependency versions + '@here/pkgA': '^1.1.1', + someExternal: '^9.2.3', + }, + }), + mode: '100644', + }, + ], + [ + 'packages/pkgB/CHANGELOG.md', + { + content: + '# Changelog' + + '\n\nAll notable changes to this project will be ' + + 'documented in this file.' + + '\n\n### [2.3.0](https://www.github.com/fake/repo/compare' + + '/pkgB-v2.2.2...pkgB-v2.3.0) (1983-10-10)' + + '\n\n\n### Features' + + '\n\n* We added a feature' + + '\n\n### some stuff we did not expect' + + '\n\n* and more unexpected stuff', + mode: '100644', + }, + ], + ]), + }, + }, + ]; + + const logs: [string, CheckpointType][] = []; + const checkpoint = (msg: string, type: CheckpointType) => + logs.push([msg, type]); + const nodeWS = new NodeWorkspaceDependencyUpdates( + github, + config, + 'node-workspace', + checkpoint + ); + const [actualManifest, actualChanges] = await nodeWS.run( + newManifestVersions, + pkgsWithPRData + ); + mock.verify(); + expect([...actualManifest]).to.eql([ + ['packages/pkgA', '1.1.2'], + ['packages/pkgB', '2.3.0'], + ]); + const snapPrefix = this.test!.fullTitle(); + snapshot(snapPrefix + ' logs', logs); + snapshot(snapPrefix + ' changes', stringifyActual(actualChanges)); + }); + + it('handles errors retrieving changelogs', async function () { + const config: Config = { + packages: {}, // unused, required by interface + parsedPackages: [ + {path: 'packages/pkgA', releaseType: 'node'}, + { + path: 'packages/pkgB', + releaseType: 'node', + changelogPath: 'CHANGES.md', + }, + {path: 'packages/pkgC', releaseType: 'node'}, + ], + }; + const github = new GitHub({ + owner: 'fake', + repo: 'repo', + defaultBranch: 'main', + }); + const mock = mockGithub(github); + expectGetFiles(mock, [ + ['packages/pkgA/package.json', false], + ['packages/pkgA/CHANGELOG.md', false], + [ + 'packages/pkgB/package.json', + JSON.stringify({ + name: '@here/pkgB', + version: '2.2.2', + dependencies: { + '@here/pkgA': '^1.1.1', + someExternal: '^9.2.3', + }, + }), + ], + ['packages/pkgB/CHANGES.md', 501], + [ + 'packages/pkgC/package.json', + JSON.stringify({ + name: '@here/pkgC', + version: '3.3.3', + dependencies: { + '@here/pkgB': '^2.2.2', + anotherExternal: '^4.3.1', + }, + }), + ], + ['packages/pkgC/CHANGELOG.md', 404], + ]); + + // pkgA had a patch bump from manifest.runReleasers() + const newManifestVersions = new Map([['packages/pkgA', '1.1.2']]); + const pkgsWithPRData: ManifestPackageWithPRData[] = [pkgAData]; + + const logs: [string, CheckpointType][] = []; + const checkpoint = (msg: string, type: CheckpointType) => + logs.push([msg, type]); + const nodeWS = new NodeWorkspaceDependencyUpdates( + github, + config, + 'node-workspace', + checkpoint + ); + const [actualManifest, actualChanges] = await nodeWS.run( + newManifestVersions, + pkgsWithPRData + ); + mock.verify(); + expect([...actualManifest]).to.eql([ + ['packages/pkgA', '1.1.2'], + ['packages/pkgB', '2.2.3'], + ['packages/pkgC', '3.3.4'], + ]); + const snapPrefix = this.test!.fullTitle(); + snapshot(snapPrefix + ' logs', logs); + snapshot(snapPrefix + ' changes', stringifyActual(actualChanges)); + }); + + it('handles discontiguous graph', async function () { + const config: Config = { + packages: {}, // unused, required by interface + parsedPackages: [ + {path: 'packages/pkgA', releaseType: 'node'}, + {path: 'packages/pkgB', releaseType: 'node'}, + {path: 'packages/pkgAA', releaseType: 'node'}, + {path: 'packages/pkgBB', releaseType: 'node'}, + ], + }; + const github = new GitHub({ + owner: 'fake', + repo: 'repo', + defaultBranch: 'main', + }); + const mock = mockGithub(github); + // package B did not get a release-please updated but it depends on A + // which did so it should patch bump + // package BB did not get a release-please updated but it depends on AA + // which did so it should patch bump + expectGetFiles(mock, [ + ['packages/pkgA/package.json', false], + ['packages/pkgA/CHANGELOG.md', false], + [ + 'packages/pkgB/package.json', + JSON.stringify({ + name: '@here/pkgB', + version: '2.2.2', + dependencies: { + '@here/pkgA': '^1.1.1', + someExternal: '^9.2.3', + }, + }), + ], + [ + 'packages/pkgB/CHANGELOG.md', + '# Changelog' + + '\n\nAll notable changes to this project will be ' + + 'documented in this file.' + + '\n\n### [2.2.2](https://www.github.com/fake/repo/compare' + + '/pkgB-v2.2.1...pkgB-v2.2.2) (1983-10-10)' + + '\n\n\n### Bug Fixes' + + '\n\n* We fixed a bug\n', + ], + [ + 'packages/pkgBB/package.json', + JSON.stringify({ + name: '@here/pkgBB', + version: '22.2.2', + dependencies: { + '@here/pkgAA': '^11.1.1', + someExternal: '^9.2.3', + }, + }), + ], + [ + 'packages/pkgBB/CHANGELOG.md', + '# Changelog' + + '\n\nAll notable changes to this project will be ' + + 'documented in this file.' + + '\n\n### [22.2.2](https://www.github.com/fake/repo/compare' + + '/pkgBB-v22.2.1...pkgBB-v22.2.2) (1983-10-10)' + + '\n\n\n### Bug Fixes' + + '\n\n* We fixed a bug\n', + ], + ]); + + // pkgA had a patch bump and pkgAA had a minor bump from + // manifest.runReleasers() + const newManifestVersions = new Map([ + ['packages/pkgA', '1.1.2'], + ['packages/pkgAA', '11.2.0'], + ]); + const pkgAAData: ManifestPackageWithPRData = { + config: { + releaseType: 'node', + packageName: '@here/pkgAA', + path: 'packages/pkgAA', + }, + prData: { + version: '11.2.0', + changes: new Map([ + [ + 'packages/pkgAA/package.json', + { + content: packageJsonStringify({ + name: '@here/pkgAA', + version: '11.2.0', + dependencies: {'@there/foo': '^4.1.7'}, + }), + mode: '100644', + }, + ], + [ + 'packages/pkgAA/CHANGELOG.md', + { + content: + '### [11.2.0](https://www.github.com/fake/repo/compare' + + '/pkgAA-v11.1.1...pkgAA-v11.2.0) (1983-10-10)' + + '\n\n\n### Features' + + '\n\n* We added a feature', + mode: '100644', + }, + ], + ]), + }, + }; + const pkgsWithPRData: ManifestPackageWithPRData[] = [pkgAData, pkgAAData]; + + const logs: [string, CheckpointType][] = []; + const checkpoint = (msg: string, type: CheckpointType) => + logs.push([msg, type]); + const nodeWS = new NodeWorkspaceDependencyUpdates( + github, + config, + 'node-workspace', + checkpoint + ); + const [actualManifest, actualChanges] = await nodeWS.run( + newManifestVersions, + pkgsWithPRData + ); + mock.verify(); + expect([...actualManifest]).to.eql([ + ['packages/pkgA', '1.1.2'], + ['packages/pkgAA', '11.2.0'], + ['packages/pkgB', '2.2.3'], + ['packages/pkgBB', '22.2.3'], + ]); + const snapPrefix = this.test!.fullTitle(); + snapshot(snapPrefix + ' logs', logs); + snapshot(snapPrefix + ' changes', stringifyActual(actualChanges)); + }); + + it('updates dependent from pre-release version', async function () { + const config: Config = { + packages: {}, // unused, required by interface + parsedPackages: [ + {path: 'packages/pkgA', releaseType: 'node'}, + {path: 'packages/pkgB', releaseType: 'node'}, + ], + }; + const github = new GitHub({ + owner: 'fake', + repo: 'repo', + defaultBranch: 'main', + }); + const mock = mockGithub(github); + // package B did not get a release-please updated but it depends on A + // which did so it should patch bump + expectGetFiles(mock, [ + ['packages/pkgA/package.json', false], + ['packages/pkgA/CHANGELOG.md', false], + [ + 'packages/pkgB/package.json', + JSON.stringify({ + name: '@here/pkgB', + version: '2.2.2', + dependencies: { + // manually set in some prior release + '@here/pkgA': '^1.1.2-alpha.0', + someExternal: '^9.2.3', + }, + }), + ], + [ + 'packages/pkgB/CHANGELOG.md', + '# Changelog' + + '\n\nAll notable changes to this project will be ' + + 'documented in this file.' + + '\n\n### [2.2.2](https://www.github.com/fake/repo/compare' + + '/pkgB-v2.2.1...pkgB-v2.2.2) (1983-10-10)' + + '\n\n\n### Bug Fixes' + + '\n\n* We fixed a bug\n', + ], + ]); + + // pkgA got promoted from previous 1.1.2-alpha.0 pre-release + // by manifest.runReleasers() + const newManifestVersions = new Map([['packages/pkgA', '1.1.2']]); + const pkgsWithPRData = [pkgAData]; + + const logs: [string, CheckpointType][] = []; + const checkpoint = (msg: string, type: CheckpointType) => + logs.push([msg, type]); + const nodeWS = new NodeWorkspaceDependencyUpdates( + github, + config, + 'node-workspace', + checkpoint + ); + const [actualManifest, actualChanges] = await nodeWS.run( + newManifestVersions, + pkgsWithPRData + ); + mock.verify(); + expect([...actualManifest]).to.eql([ + ['packages/pkgA', '1.1.2'], + ['packages/pkgB', '2.2.3'], + ]); + const snapPrefix = this.test!.fullTitle(); + snapshot(snapPrefix + ' logs', logs); + snapshot(snapPrefix + ' changes', stringifyActual(actualChanges)); + }); + + it('does not update dependency to pre-release version', async function () { + const config: Config = { + packages: {}, // unused, required by interface + parsedPackages: [ + {path: 'packages/pkgA', releaseType: 'node'}, + {path: 'packages/pkgB', releaseType: 'node'}, + ], + }; + const github = new GitHub({ + owner: 'fake', + repo: 'repo', + defaultBranch: 'main', + }); + const mock = mockGithub(github); + expectGetFiles(mock, [ + ['packages/pkgA/package.json', false], + ['packages/pkgA/CHANGELOG.md', false], + [ + 'packages/pkgB/package.json', + JSON.stringify({ + name: '@here/pkgB', + version: '2.2.2', + dependencies: { + '@here/pkgA': '^1.1.1', + someExternal: '^9.2.3', + }, + }), + ], + ]); + + // pkgA got set to 1.1.2-alpha.0 pre-release + // by manifest.runReleasers() + const newManifestVersions = new Map([['packages/pkgA', '1.1.2-alpha.0']]); + const pkgsWithPRData: ManifestPackageWithPRData[] = [ + { + config: { + releaseType: 'node', + packageName: '@here/pkgA', + path: 'packages/pkgA', + }, + prData: { + version: '1.1.2-alpha.0', + changes: new Map([ + [ + 'packages/pkgA/package.json', + { + content: packageJsonStringify({ + name: '@here/pkgA', + version: '1.1.2-alpha.0', + dependencies: {'@there/foo': '^4.1.7'}, + }), + mode: '100644', + }, + ], + [ + 'packages/pkgA/CHANGELOG.md', + { + content: + '# Changelog' + + '\n\nAll notable changes to this project will be ' + + 'documented in this file.' + + '\n\n### [1.1.2-alpha.0](https://www.github.com/fake/repo/compare' + + '/pkgA-v1.1.1...pkgA-v1.1.2-alpha.0) (1983-10-10)' + + '\n\n\n### Bug Fixes' + + '\n\n* We fixed a bug!', + mode: '100644', + }, + ], + ]), + }, + }, + ]; + + const logs: [string, CheckpointType][] = []; + const checkpoint = (msg: string, type: CheckpointType) => + logs.push([msg, type]); + const nodeWS = new NodeWorkspaceDependencyUpdates( + github, + config, + 'node-workspace', + checkpoint + ); + const [actualManifest, actualChanges] = await nodeWS.run( + newManifestVersions, + pkgsWithPRData + ); + mock.verify(); + expect([...actualManifest]).to.eql([['packages/pkgA', '1.1.2-alpha.0']]); + const snapPrefix = this.test!.fullTitle(); + snapshot(snapPrefix + ' logs', logs); + snapshot(snapPrefix + ' changes', stringifyActual(actualChanges)); + }); + + it('does not update dependent with invalid version', async function () { + const github = new GitHub({ + owner: 'fake', + repo: 'repo', + defaultBranch: 'main', + }); + const mock = mockGithub(github); + expectGetFiles(mock, [ + ['packages/pkgA/package.json', false], + ['packages/pkgA/CHANGELOG.md', false], + [ + 'packages/pkgB/package.json', + JSON.stringify({ + name: '@here/pkgB', + version: 'some-invalid-version', + dependencies: { + '@here/pkgA': '^1.1.1', + someExternal: '^9.2.3', + }, + }), + ], + [ + 'packages/pkgB/CHANGELOG.md', + '# Changelog' + + '\n\nAll notable changes to this project will be ' + + 'documented in this file.' + + '\n\n### [some-invalid-version](https://www.github.com/fake/repo/compare' + + '/pkgB-v2.2.1...pkgB-vsome-invalid-version) (1983-10-10)' + + '\n\n\n### Bug Fixes' + + '\n\n* We fixed a bug and set the version wonky on purpose?\n', + ], + ]); + const config: Config = { + packages: {}, // unused, required by interface + parsedPackages: [ + {path: 'packages/pkgA', releaseType: 'node'}, + {path: 'packages/pkgB', releaseType: 'node'}, + ], + }; + + // pkgA got set to 1.1.2 by manifest.runReleasers() + const newManifestVersions = new Map([['packages/pkgA', '1.1.2']]); + const pkgsWithPRData = [pkgAData]; + + const logs: [string, CheckpointType][] = []; + const checkpoint = (msg: string, type: CheckpointType) => + logs.push([msg, type]); + const nodeWS = new NodeWorkspaceDependencyUpdates( + github, + config, + 'node-workspace', + checkpoint + ); + const [actualManifest, actualChanges] = await nodeWS.run( + newManifestVersions, + pkgsWithPRData + ); + mock.verify(); + expect([...actualManifest]).to.eql([ + ['packages/pkgA', '1.1.2'], + ['packages/pkgB', 'some-invalid-version'], + ]); + const snapPrefix = this.test!.fullTitle(); + snapshot(snapPrefix + ' logs', logs); + snapshot(snapPrefix + ' changes', stringifyActual(actualChanges)); + }); + }); +}); diff --git a/typings/lerna__collect-updates.d.ts b/typings/lerna__collect-updates.d.ts new file mode 100644 index 000000000..a90c273fa --- /dev/null +++ b/typings/lerna__collect-updates.d.ts @@ -0,0 +1,39 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * skeleton only representing what we use. + */ +declare module '@lerna/collect-updates' { + import PackageGraph = require('@lerna/package-graph'); + import {PackageGraphNode} from '@lerna/package-graph'; + + namespace collectUpdates { + interface CollectPackagesParams { + // default: () => true + isCandidate?: (node: PackageGraphNode, name: string) => boolean; + // default: undefined + onInclude?: (name: string) => void; + // default: undefined + excludeDependents?: boolean; + } + + function collectPackages( + packages: Map | typeof PackageGraph, + params?: CollectPackagesParams + ): PackageGraphNode[]; + } + + export = collectUpdates; +} diff --git a/typings/lerna__package-graph.d.ts b/typings/lerna__package-graph.d.ts new file mode 100644 index 000000000..52e7c0944 --- /dev/null +++ b/typings/lerna__package-graph.d.ts @@ -0,0 +1,50 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * skeleton only representing what we use. + */ +declare module '@lerna/package-graph' { + import Package = require('@lerna/package'); + import npa = require('npm-package-arg'); + + namespace PackageGraph { + // unclear why we need this. Possibly related to + // https://github.com/typescript-eslint/typescript-eslint/issues/1856 + // eslint-disable-next-line @typescript-eslint/no-unused-vars + class PackageGraphNode { + name: string; + version: string; + location: string; + pkg: Package; + localDependencies: Map; + localDependents: Map; + externalDependencies: Map; + constructor(pkg: Package); + } + } + + class PackageGraph extends Map { + rawPackageList: Package[]; + constructor( + packages: Package[], + graphType: 'allDependencies' | 'dependencies' + ); + + get(name: string): PackageGraph.PackageGraphNode; + addDependents(filteredPackages: Package[]): Package[]; + } + + export = PackageGraph; +} diff --git a/typings/lerna__package.d.ts b/typings/lerna__package.d.ts new file mode 100644 index 000000000..c549fae0e --- /dev/null +++ b/typings/lerna__package.d.ts @@ -0,0 +1,54 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * skeleton only representing what we use. + */ +declare module '@lerna/package' { + import npa = require('npm-package-arg'); + + namespace Package { + interface PackageJson { + name: string; + version: string; + dependencies?: Record; + devDependencies?: Record; + optionalDependencies?: Record; + peerDependencies?: Record; + } + } + + class Package { + location: string; + name: string; + version: string; + dependencies?: Record; + devDependencies?: Record; + optionalDependencies?: Record; + peerDependencies?: Record; + constructor(pkg: Package.PackageJson, location: string, rootPath?: string); + + updateLocalDependency( + resolved: npa.Result, + depVersion: string, + savePrefix: string + ): void; + + // real interface is `val: any` but we only ever set a string + set(key: string, val: string): this; + toJSON(): Package.PackageJson; + } + + export = Package; +} diff --git a/typings/lerna__run-topologically.d.ts b/typings/lerna__run-topologically.d.ts new file mode 100644 index 000000000..5550f06e1 --- /dev/null +++ b/typings/lerna__run-topologically.d.ts @@ -0,0 +1,32 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * skeleton only representing what we use. + */ +declare module '@lerna/run-topologically' { + import Package = require('@lerna/package'); + interface Options { + concurrency: number; + graphType: 'allDependencies' | 'dependencies'; + rejectCycles: boolean; + } + function runTopologically( + packages: Package[], + runner: (pkg: Package) => Promise, + opts: Options + ): Promise; + + export = runTopologically; +}