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