Skip to content

Commit

Permalink
feat: use upstream release-please (#334)
Browse files Browse the repository at this point in the history
This also adds 100% test coverage to `release-please` and
`release-manager` using `nock`.

Fixes #177
  • Loading branch information
lukekarrys committed Nov 28, 2023
1 parent c892260 commit 2daff23
Show file tree
Hide file tree
Showing 40 changed files with 1,916 additions and 654 deletions.
20 changes: 20 additions & 0 deletions README.md
Expand Up @@ -123,3 +123,23 @@ trivial to swap out this content directory for a different one as it is only
referenced in a single place in `lib/config.js`. However, it's not currently
possible to change this value at runtime, but that might become possible in
future versions of this package.

### Testing

The files `test/release/release-manager.js` and `test/release/release-please.js`
use recorded `nock` fixtures to generate snapshots. To update these fixtures run:

```sh
GITHUB_TOKEN=<YOUR_PAT> npm run test:record --- test/release/release-{please,manager}.js
```

If you only need to update fixtures for one, it's best to only run that single
test file.

#### `test/release/release-please.js`

This test file uses the repo `npm/npm-cli-release-please` to record its
fixtures. It expects `https://github.com/npm/npm-cli-release-please` to be
checked out in a sibling directory to this repo. It also needs the current
branch set to `template-oss-mock-testing-branch-do-not-delete` before the tests
are run.
2 changes: 0 additions & 2 deletions bin/release-manager.js
@@ -1,6 +1,5 @@
#!/usr/bin/env node

const { join } = require('path')
const core = require('@actions/core')
const { parseArgs } = require('util')
const ReleaseManager = require('../lib/release/release-manager')
Expand All @@ -10,7 +9,6 @@ ReleaseManager.run({
token: process.env.GITHUB_TOKEN,
repo: process.env.GITHUB_REPOSITORY,
cwd: process.cwd(),
pkg: require(join(process.cwd(), 'package.json')),
...parseArgs({
options: {
pr: { type: 'string' },
Expand Down
16 changes: 13 additions & 3 deletions lib/content/release-please-config-json.hbs
@@ -1,13 +1,23 @@
{
"separate-pull-requests": {{{ del }}},
"plugins": {{#if isMonoPublic }}["node-workspace"]{{ else }}{{{ del }}}{{/if}},
"exclude-packages-from-root": true,
{{#if isMonoPublic }}
"plugins": [
"node-workspace",
"node-workspace-format"
],
{{/if}}
"exclude-packages-from-root": {{{ del }}},
"group-pull-request-title-pattern": "chore: release ${version}",
"pull-request-title-pattern": "chore: release${component} ${version}",
"changelog-sections": {{{ json changelogTypes }}},
"prerelease-type": "pre",
"packages": {
"{{ pkgPath }}": {
{{#if isRoot}}"package-name": ""{{/if}}
{{#if isRoot}}
"package-name": ""{{#if workspacePaths}},
"exclude-paths": {{{ json workspacePaths }}}
{{/if}}
{{/if}}
}
}
}
6 changes: 3 additions & 3 deletions lib/release/changelog.js
@@ -1,4 +1,4 @@
const { link, code, specRe, list, dateFmt, makeGitHubUrl } = require('./util')
const { link, code, wrapSpecs, list, formatDate, makeGitHubUrl } = require('./util')

class Changelog {
static BREAKING = 'breaking'
Expand All @@ -11,7 +11,7 @@ class Changelog {
}

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

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

// A list og the authors github handles or names
Expand Down
107 changes: 107 additions & 0 deletions lib/release/node-workspace-format.js
@@ -0,0 +1,107 @@
const localeCompare = require('@isaacs/string-locale-compare')('en')
const { ManifestPlugin } = require('release-please/build/src/plugin.js')
const { addPath } = require('release-please/build/src/plugins/workspace.js')
const { TagName } = require('release-please/build/src/util/tag-name.js')
const { ROOT_PROJECT_PATH } = require('release-please/build/src/manifest.js')
const { DEPS, link, wrapSpecs } = require('./util.js')

const WORKSPACE_MESSAGE = (name, version) => `${DEPS}(workspace): ${name}@${version}`
const WORKSPACE_SCOPE = /(?<scope>workspace): `?(?<name>\S+?)[@\s](?<version>\S+?)`?$/gm

module.exports = class extends ManifestPlugin {
static WORKSPACE_MESSAGE = WORKSPACE_MESSAGE

#releasesByPackage = new Map()
#pathsByComponent = new Map()

async preconfigure (strategiesByPath) {
// First build a list of all releases that will happen based on
// the conventional commits
for (const path in strategiesByPath) {
const component = await strategiesByPath[path].getComponent()
const packageName = await await strategiesByPath[path].getDefaultPackageName()
this.#pathsByComponent.set(component, path)
this.#releasesByPackage.set(packageName, { path, component })
}

return strategiesByPath
}

run (candidates) {
this.#rewriteWorkspaceChangelogItems(candidates)
this.#sortReleases(candidates)
return candidates
}

// I don't like how release-please formats workspace changelog entries
// so this rewrites them to look like the rest of our changelog. This can't
// be part of the changelog plugin since they are written after that by the
// node-workspace plugin. A possible PR to release-please could add an option
// to customize these or run them through the changelog notes generator.
#rewriteWorkspaceChangelogItems (candidates) {
for (const candidate of candidates) {
for (const release of candidate.pullRequest.body.releaseData) {
// Update notes with a link to each workspaces release notes
// now that we have all of the releases in a single pull request
release.notes =
release.notes.replace(WORKSPACE_SCOPE, (...args) => {
const { scope, name, version } = args.pop()
const { path, component } = this.#releasesByPackage.get(name)
const { tagSeparator, includeVInTag } = this.repositoryConfig[path]
const { repository: { owner, repo } } = this.github
const tag = new TagName(version, component, tagSeparator, includeVInTag).toString()
const url = `https://github.com/${owner}/${repo}/releases/tag/${tag}`
return `${link(scope, url)}: ${wrapSpecs(`${name}@${version}`)}`
})

// remove the other release please dependencies list which always starts with
// the following line and then each line is indented. so we search for the line
// and remove and indented lines until the next non indented line.
let foundRemoveStart = false
let foundRemoveEnd = false
release.notes = release.notes
.split('\n')
.filter((line) => {
if (line === '* The following workspace dependencies were updated') {
foundRemoveStart = true
} else if (foundRemoveStart && !foundRemoveEnd) {
// TODO: test when inserted dependencies is not the last thing in the changelog
/* istanbul ignore next */
if (!line || !line.startsWith(' ')) {
foundRemoveEnd = true
}
}
// If we found the start, remove all lines until we've found the end
return foundRemoveStart ? foundRemoveEnd : true
})
.join('\n')

// Find the associated changelog and update that too
const path = this.#pathsByComponent.get(release.component)
for (const update of candidate.pullRequest.updates) {
if (update.path === addPath(path, 'CHANGELOG.md')) {
update.updater.changelogEntry = release.notes
}
}
}
}
}

// Sort root release to the top of the pull request
// release please pre sorts based on graph order so
#sortReleases (candidates) {
for (const candidate of candidates) {
candidate.pullRequest.body.releaseData.sort((a, b) => {
const aPath = this.#pathsByComponent.get(a.component)
const bPath = this.#pathsByComponent.get(b.component)
if (aPath === ROOT_PROJECT_PATH) {
return -1
}
if (bPath === ROOT_PROJECT_PATH) {
return 1
}
return localeCompare(aPath, bPath)
})
}
}
}

0 comments on commit 2daff23

Please sign in to comment.