Skip to content

Commit 2daff23

Browse files
authored
feat: use upstream release-please (#334)
This also adds 100% test coverage to `release-please` and `release-manager` using `nock`. Fixes #177
1 parent c892260 commit 2daff23

40 files changed

+1916
-654
lines changed

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,3 +123,23 @@ trivial to swap out this content directory for a different one as it is only
123123
referenced in a single place in `lib/config.js`. However, it's not currently
124124
possible to change this value at runtime, but that might become possible in
125125
future versions of this package.
126+
127+
### Testing
128+
129+
The files `test/release/release-manager.js` and `test/release/release-please.js`
130+
use recorded `nock` fixtures to generate snapshots. To update these fixtures run:
131+
132+
```sh
133+
GITHUB_TOKEN=<YOUR_PAT> npm run test:record --- test/release/release-{please,manager}.js
134+
```
135+
136+
If you only need to update fixtures for one, it's best to only run that single
137+
test file.
138+
139+
#### `test/release/release-please.js`
140+
141+
This test file uses the repo `npm/npm-cli-release-please` to record its
142+
fixtures. It expects `https://github.com/npm/npm-cli-release-please` to be
143+
checked out in a sibling directory to this repo. It also needs the current
144+
branch set to `template-oss-mock-testing-branch-do-not-delete` before the tests
145+
are run.

bin/release-manager.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
#!/usr/bin/env node
22

3-
const { join } = require('path')
43
const core = require('@actions/core')
54
const { parseArgs } = require('util')
65
const ReleaseManager = require('../lib/release/release-manager')
@@ -10,7 +9,6 @@ ReleaseManager.run({
109
token: process.env.GITHUB_TOKEN,
1110
repo: process.env.GITHUB_REPOSITORY,
1211
cwd: process.cwd(),
13-
pkg: require(join(process.cwd(), 'package.json')),
1412
...parseArgs({
1513
options: {
1614
pr: { type: 'string' },
Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,23 @@
11
{
22
"separate-pull-requests": {{{ del }}},
3-
"plugins": {{#if isMonoPublic }}["node-workspace"]{{ else }}{{{ del }}}{{/if}},
4-
"exclude-packages-from-root": true,
3+
{{#if isMonoPublic }}
4+
"plugins": [
5+
"node-workspace",
6+
"node-workspace-format"
7+
],
8+
{{/if}}
9+
"exclude-packages-from-root": {{{ del }}},
510
"group-pull-request-title-pattern": "chore: release ${version}",
611
"pull-request-title-pattern": "chore: release${component} ${version}",
712
"changelog-sections": {{{ json changelogTypes }}},
13+
"prerelease-type": "pre",
814
"packages": {
915
"{{ pkgPath }}": {
10-
{{#if isRoot}}"package-name": ""{{/if}}
16+
{{#if isRoot}}
17+
"package-name": ""{{#if workspacePaths}},
18+
"exclude-paths": {{{ json workspacePaths }}}
19+
{{/if}}
20+
{{/if}}
1121
}
1222
}
1323
}

lib/release/changelog.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const { link, code, specRe, list, dateFmt, makeGitHubUrl } = require('./util')
1+
const { link, code, wrapSpecs, list, formatDate, makeGitHubUrl } = require('./util')
22

33
class Changelog {
44
static BREAKING = 'breaking'
@@ -11,7 +11,7 @@ class Changelog {
1111
}
1212

1313
constructor ({ version, url, sections }) {
14-
this.#title = `## ${url ? link(version, url) : version} (${dateFmt()})`
14+
this.#title = `## ${url ? link(version, url) : version} (${formatDate()})`
1515
for (const section of sections) {
1616
this.#types.add(section.type)
1717
this.#titles[section.type] = section.section
@@ -136,7 +136,7 @@ class ChangelogNotes {
136136

137137
// The title of the commit, with the optional scope as a prefix
138138
const scope = commit.scope && `${commit.scope}:`
139-
const subject = commit.bareMessage.replace(specRe, code('$1'))
139+
const subject = wrapSpecs(commit.bareMessage)
140140
entry.push([scope, subject].filter(Boolean).join(' '))
141141

142142
// A list og the authors github handles or names
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
const localeCompare = require('@isaacs/string-locale-compare')('en')
2+
const { ManifestPlugin } = require('release-please/build/src/plugin.js')
3+
const { addPath } = require('release-please/build/src/plugins/workspace.js')
4+
const { TagName } = require('release-please/build/src/util/tag-name.js')
5+
const { ROOT_PROJECT_PATH } = require('release-please/build/src/manifest.js')
6+
const { DEPS, link, wrapSpecs } = require('./util.js')
7+
8+
const WORKSPACE_MESSAGE = (name, version) => `${DEPS}(workspace): ${name}@${version}`
9+
const WORKSPACE_SCOPE = /(?<scope>workspace): `?(?<name>\S+?)[@\s](?<version>\S+?)`?$/gm
10+
11+
module.exports = class extends ManifestPlugin {
12+
static WORKSPACE_MESSAGE = WORKSPACE_MESSAGE
13+
14+
#releasesByPackage = new Map()
15+
#pathsByComponent = new Map()
16+
17+
async preconfigure (strategiesByPath) {
18+
// First build a list of all releases that will happen based on
19+
// the conventional commits
20+
for (const path in strategiesByPath) {
21+
const component = await strategiesByPath[path].getComponent()
22+
const packageName = await await strategiesByPath[path].getDefaultPackageName()
23+
this.#pathsByComponent.set(component, path)
24+
this.#releasesByPackage.set(packageName, { path, component })
25+
}
26+
27+
return strategiesByPath
28+
}
29+
30+
run (candidates) {
31+
this.#rewriteWorkspaceChangelogItems(candidates)
32+
this.#sortReleases(candidates)
33+
return candidates
34+
}
35+
36+
// I don't like how release-please formats workspace changelog entries
37+
// so this rewrites them to look like the rest of our changelog. This can't
38+
// be part of the changelog plugin since they are written after that by the
39+
// node-workspace plugin. A possible PR to release-please could add an option
40+
// to customize these or run them through the changelog notes generator.
41+
#rewriteWorkspaceChangelogItems (candidates) {
42+
for (const candidate of candidates) {
43+
for (const release of candidate.pullRequest.body.releaseData) {
44+
// Update notes with a link to each workspaces release notes
45+
// now that we have all of the releases in a single pull request
46+
release.notes =
47+
release.notes.replace(WORKSPACE_SCOPE, (...args) => {
48+
const { scope, name, version } = args.pop()
49+
const { path, component } = this.#releasesByPackage.get(name)
50+
const { tagSeparator, includeVInTag } = this.repositoryConfig[path]
51+
const { repository: { owner, repo } } = this.github
52+
const tag = new TagName(version, component, tagSeparator, includeVInTag).toString()
53+
const url = `https://github.com/${owner}/${repo}/releases/tag/${tag}`
54+
return `${link(scope, url)}: ${wrapSpecs(`${name}@${version}`)}`
55+
})
56+
57+
// remove the other release please dependencies list which always starts with
58+
// the following line and then each line is indented. so we search for the line
59+
// and remove and indented lines until the next non indented line.
60+
let foundRemoveStart = false
61+
let foundRemoveEnd = false
62+
release.notes = release.notes
63+
.split('\n')
64+
.filter((line) => {
65+
if (line === '* The following workspace dependencies were updated') {
66+
foundRemoveStart = true
67+
} else if (foundRemoveStart && !foundRemoveEnd) {
68+
// TODO: test when inserted dependencies is not the last thing in the changelog
69+
/* istanbul ignore next */
70+
if (!line || !line.startsWith(' ')) {
71+
foundRemoveEnd = true
72+
}
73+
}
74+
// If we found the start, remove all lines until we've found the end
75+
return foundRemoveStart ? foundRemoveEnd : true
76+
})
77+
.join('\n')
78+
79+
// Find the associated changelog and update that too
80+
const path = this.#pathsByComponent.get(release.component)
81+
for (const update of candidate.pullRequest.updates) {
82+
if (update.path === addPath(path, 'CHANGELOG.md')) {
83+
update.updater.changelogEntry = release.notes
84+
}
85+
}
86+
}
87+
}
88+
}
89+
90+
// Sort root release to the top of the pull request
91+
// release please pre sorts based on graph order so
92+
#sortReleases (candidates) {
93+
for (const candidate of candidates) {
94+
candidate.pullRequest.body.releaseData.sort((a, b) => {
95+
const aPath = this.#pathsByComponent.get(a.component)
96+
const bPath = this.#pathsByComponent.get(b.component)
97+
if (aPath === ROOT_PROJECT_PATH) {
98+
return -1
99+
}
100+
if (bPath === ROOT_PROJECT_PATH) {
101+
return 1
102+
}
103+
return localeCompare(aPath, bPath)
104+
})
105+
}
106+
}
107+
}

0 commit comments

Comments
 (0)