From 57e8cbc6a89888e32e8a18b6909cab04b3e3fac3 Mon Sep 17 00:00:00 2001 From: Luke Karrys Date: Mon, 12 Sep 2022 10:10:15 -0700 Subject: [PATCH] wip --- bin/release-please.js | 19 +- lib/release-please/changelog.js | 25 +- lib/release-please/github.js | 86 +++---- lib/release-please/index.js | 2 + lib/release-please/node-workspace.js | 263 ++++++++++++--------- lib/release-please/save-fixture.js | 15 -- lib/release-please/util.js | 2 +- package.json | 1 - tap-snapshots/test/check/diffs.js.test.cjs | 1 - test/release-please/changelog.js | 44 ++-- test/release-please/node-workspace.js | 177 -------------- 11 files changed, 249 insertions(+), 386 deletions(-) delete mode 100644 lib/release-please/save-fixture.js delete mode 100644 test/release-please/node-workspace.js diff --git a/bin/release-please.js b/bin/release-please.js index 10dd1930..3ee33f75 100755 --- a/bin/release-please.js +++ b/bin/release-please.js @@ -9,7 +9,18 @@ const [branch] = process.argv.slice(2) const setOutput = (key, val) => { if (val && (!Array.isArray(val) || val.length)) { if (dryRun) { - console.log(key, JSON.stringify(val, null, 2)) + if (key === 'pr') { + console.log('PR:', val.title.toString()) + console.log('='.repeat(40)) + console.log(val.body.toString()) + console.log('='.repeat(40)) + for (const update of val.updates.filter(u => u.updater.changelogEntry)) { + console.log('CHANGELOG:', update.path) + console.log('-'.repeat(40)) + console.log(update.updater.changelogEntry) + console.log('-'.repeat(40)) + } + } } else { core.setOutput(key, JSON.stringify(val)) } @@ -27,5 +38,9 @@ main({ setOutput('release', release) return null }).catch(err => { - core.setFailed(`failed: ${err}`) + if (dryRun) { + console.error(err) + } else { + core.setFailed(`failed: ${err}`) + } }) diff --git a/lib/release-please/changelog.js b/lib/release-please/changelog.js index d7fb56df..766abeaf 100644 --- a/lib/release-please/changelog.js +++ b/lib/release-please/changelog.js @@ -1,21 +1,22 @@ const makeGh = require('./github.js') -const saveFixture = require('./save-fixture.js') -const { link, code, specRe, list } = require('./util') +const { link, code, specRe, list, dateFmt } = require('./util') module.exports = class ChangelogNotes { constructor (options) { this.gh = makeGh(options.github) } - buildEntry (commit, authors) { + buildEntry (commit, authors = []) { const breaking = commit.notes .filter(n => n.title === 'BREAKING CHANGE') .map(n => n.text) const entry = [] - // A link to the commit - entry.push(link(code(commit.sha.slice(0, 7)), this.gh.commit(commit.sha))) + if (commit.sha) { + // A link to the commit + entry.push(link(code(commit.sha.slice(0, 7)), this.gh.commit(commit.sha))) + } // A link to the pull request if the commit has one const prNumber = commit.pullRequest && commit.pullRequest.number @@ -40,14 +41,6 @@ module.exports = class ChangelogNotes { } async buildNotes (rawCommits, { version, previousTag, currentTag, changelogSections }) { - await saveFixture(`changelog-${currentTag}`, { - commits: rawCommits, - version, - previousTag, - currentTag, - changelogSections, - }) - const changelog = changelogSections.reduce((acc, c) => { if (!c.hidden) { acc[c.type] = { @@ -58,7 +51,7 @@ module.exports = class ChangelogNotes { return acc }, { breaking: { - title: 'BREAKING CHANGES', + title: '⚠️ BREAKING CHANGES', entries: [], }, }) @@ -83,8 +76,8 @@ module.exports = class ChangelogNotes { .filter((s) => s.entries.length) .map(({ title, entries }) => [`### ${title}`, entries.map(list).join('\n')].join('\n\n')) - const changelogTitle = this.gh.title(version, currentTag, previousTag) + const title = `## ${link(version, this.gh.compare(previousTag, currentTag))} (${dateFmt()})` - return [changelogTitle, ...sections].join('\n\n').trim() + return [title, ...sections].join('\n\n').trim() } } diff --git a/lib/release-please/github.js b/lib/release-please/github.js index d2ac6d5a..18c033fa 100644 --- a/lib/release-please/github.js +++ b/lib/release-please/github.js @@ -1,60 +1,52 @@ -const { link, dateFmt } = require('./util') - module.exports = (gh) => { - return { - ...gh, - authors: ghAuthors(gh), - ...format(gh), - } -} + const { owner, repo } = gh.repository -const format = (gh) => { - const base = `https://github.com/${gh.repository.owner}/${gh.repository.repo}` - const url = (...p) => `${base}/${p.join('/')}` - const compare = (prev, current) => url('compare', prev, '...', current) - return { - pull: (number) => url('pull', number), - compare: compare, - commit: (sha) => url('commit', sha), - release: (tag) => url('releases', 'tag', tag), - title: (version, currentTag, previousTag) => - `## ${link(version, previousTag && compare(previousTag, currentTag))} (${dateFmt()})`, - tag: (component, version) => [component, `v${version.toString()}`].filter(Boolean).join('-'), - } -} + const authors = async (commits) => { + const response = {} -const ghAuthors = (gh) => async (commits) => { - const response = {} + const shas = commits.map(c => c.sha).filter(Boolean) - if (!commits.length) { - return response - } + if (!shas.length) { + return response + } - const { repository } = await gh.graphql( - `fragment CommitAuthors on GitObject { - ... on Commit { - authors (first:10) { - nodes { - user { login } - name + const { repository } = await gh.graphql( + `fragment CommitAuthors on GitObject { + ... on Commit { + authors (first:10) { + nodes { + user { login } + name + } } } } - } - query { - repository (owner:"${gh.repository.owner}", name:"${gh.repository.repo}") { - ${commits.map(({ sha: s }) => { - return `_${s}: object (expression: "${s}") { ...CommitAuthors }` - })} + query { + repository (owner:"${owner}", name:"${repo}") { + ${shas.map((s) => { + return `_${s}: object (expression: "${s}") { ...CommitAuthors }` + })} + } + }` + ) + + for (const [key, commit] of Object.entries(repository)) { + if (commit) { + response[key.slice(1)] = commit.authors.nodes + .map((a) => a.user && a.user.login ? `@${a.user.login}` : a.name) + .filter(Boolean) } - }` - ) + } - for (const [sha, commit] of Object.entries(repository)) { - response[sha.slice(1)] = commit.authors.nodes - .map((a) => a.user && a.user.login ? `@${a.user.login}` : a.name) - .filter(Boolean) + return response } - return response + const url = (...p) => `https://github.com/${owner}/${repo}/${p.join('/')}` + + return { + authors, + pull: (number) => url('pull', number), + commit: (sha) => url('commit', sha), + compare: (a, b) => a ? url('compare', `${a.toString()}...${b.toString()}`) : null, + } } diff --git a/lib/release-please/index.js b/lib/release-please/index.js index 2464143c..572cc792 100644 --- a/lib/release-please/index.js +++ b/lib/release-please/index.js @@ -1,5 +1,6 @@ const RP = require('release-please') const { CheckpointLogger } = require('release-please/build/src/util/logger.js') +const { ManifestPlugin } = require('release-please/build/src/plugin.js') const ChangelogNotes = require('./changelog.js') const Version = require('./version.js') const NodeWs = require('./node-workspace.js') @@ -8,6 +9,7 @@ RP.setLogger(new CheckpointLogger(true, true)) RP.registerChangelogNotes('default', (o) => new ChangelogNotes(o)) RP.registerVersioningStrategy('default', (o) => new Version(o)) RP.registerPlugin('node-workspace', (o) => new NodeWs(o.github, o.targetBranch, o.repositoryConfig)) +RP.registerPlugin('workspace-deps', () => new ManifestPlugin()) const main = async ({ repo: fullRepo, token, dryRun, branch }) => { if (!token) { diff --git a/lib/release-please/node-workspace.js b/lib/release-please/node-workspace.js index c107ccab..a43b0345 100644 --- a/lib/release-please/node-workspace.js +++ b/lib/release-please/node-workspace.js @@ -1,144 +1,183 @@ -const RP = require('release-please/build/src/plugins/node-workspace.js') -const Version = require('./version.js') +const { NodeWorkspace } = require('release-please/build/src/plugins/node-workspace.js') +const { RawContent } = require('release-please/build/src/updaters/raw-content.js') +const { jsonStringify } = require('release-please/build/src/util/json-stringify.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 makeGh = require('./github.js') -const { code, link } = require('./util.js') -const saveFixture = require('./save-fixture.js') +const { link, code } = require('./util.js') -module.exports = class extends RP.NodeWorkspace { +const SCOPE = '__REPLACE_WORKSPACE_DEP__' +const WORKSPACE_DEP = new RegExp(`${SCOPE}: (\\S+) (\\S+)`, 'gm') + +module.exports = class extends NodeWorkspace { constructor (github, ...args) { super(github, ...args) this.gh = makeGh(github) } - async preconfigure (strategies, ...args) { - saveFixture('strategies', strategies) - - // map the name from each package.json to the path that - // we are releasing. this is so we can - this.paths = {} - this.components = {} - - for (const [path, strategy] of Object.entries(strategies)) { - const component = await strategy.getComponent() - this.paths[component] = path - // Default package name is the package.json name which is how we look - // up the release from a dependency in the workspace. This should not - // be `getPackageName` which we change in our release-please config to - // remove the name from the tags of our root packages. - this.components[await strategy.getDefaultPackageName()] = component + async preconfigure (strategiesByPath, commitsByPath, releasesByPath) { + // First build a list of all releases that will happen based on + // the conventional commits + const candidates = [] + for (const path in strategiesByPath) { + const pullRequest = await strategiesByPath[path].buildReleasePullRequest( + commitsByPath[path], + releasesByPath[path] + ) + if (pullRequest?.version) { + candidates.push({ path, pullRequest }) + } } - return super.preconfigure(strategies, ...args) - } - - postProcessCandidates (candidates, updatedVersions) { - saveFixture('candidates', candidates) - saveFixture('versions', [...updatedVersions.entries()]) - - return super.postProcessCandidates(candidates, updatedVersions).map((candidate) => { - return this.rewriteNotes(candidate, updatedVersions) - }) - } - - rewriteNotes (candidate, versions) { - const changelogByPath = candidate.pullRequest.updates.reduce((acc, u) => { - const changelog = u.path.match(/(^|\/)changelog.md$/i) - if (changelog) { - acc[u.path.replace(changelog[0], '') || '.'] = u + // Then build the graph of all those releases + any other connected workspaces + const { allPackages, candidatesByPackage } = await this.buildAllPackages(candidates) + const orderedPackages = this.buildGraphOrder( + await this.buildGraph(allPackages), + Object.keys(candidatesByPackage) + ) + + // Then build a list of all the updated versions + const updatedVersions = new Map() + for (const pkg of orderedPackages) { + const path = this.pathFromPackage(pkg) + const packageName = this.packageNameFromPackage(pkg) + + let version = null + const existingCandidate = candidatesByPackage[packageName] + if (existingCandidate) { + // If there is an existing pull request use that version + version = existingCandidate.pullRequest.version + } else { + // Otherwise build another pull request (that will be discarded) just + // to see what the version would be if it only contained a deps commit. + // This is to make sure we use any custom versioning or release strategy. + const strategy = strategiesByPath[path] + const depsSection = strategy.changelogSections.find(c => c.section === 'Dependencies') + const releasePullRequest = await strategiesByPath[path].buildReleasePullRequest( + [{ message: `${depsSection.type}:` }], + releasesByPath[path] + ) + version = releasePullRequest.version } - return acc - }, {}) - for (const release of candidate.pullRequest.body.releaseData) { - const releaseNotes = this.rewriteRelease(release, versions) + updatedVersions.set(packageName, version) + } - const changelog = changelogByPath[this.paths[release.component]] - if (!changelog) { - this.logger.warn(`Could not find changelog to update for ${release.component}`) - } else { - changelog.updater.changelogEntry = releaseNotes + // Save some data about the preconfiugred releases so we can look it up later + // when rewriting the changelogs + this.releasesByPackage = new Map() + this.pathsByComponent = new Map() + + // Then go through all the packages again and add deps commits + // for each updated workspace + for (const pkg of orderedPackages) { + const path = this.pathFromPackage(pkg) + const packageName = this.packageNameFromPackage(pkg) + const graphPackage = this.packageGraph.get(pkg.name) + + // Update dependency versions + for (const [depName, resolved] of graphPackage.localDependencies) { + const depVersion = updatedVersions.get(depName) + const isNotDir = resolved.type !== 'directory' + // Changelog entries are only added for dependencies and not any other type + const isDep = Object.prototype.hasOwnProperty.call(pkg.dependencies, depName) + if (depVersion && isNotDir && isDep) { + commitsByPath[path].push({ + message: `deps(${SCOPE}): ${depName} ${depVersion.toString()}`, + }) + } } - release.notes = releaseNotes + const component = await strategiesByPath[path].getComponent() + this.pathsByComponent.set(component, path) + this.releasesByPackage.set(packageName, { + path, + component, + currentTag: releasesByPath[path]?.tag, + }) } - return candidate + return strategiesByPath } - rewriteRelease (release, versions) { - const filteredLines = [] - const lines = release.notes.split('\n') - - // Only collect dependencies, discard dev deps and everything else - const COLLECT_DEP_TYPES = ['dependencies'] - const START = 'The following workspace dependencies were updated' - - let inWorkspaceDeps = false - let collectWorkspaceDeps = false - - for (let line of lines) { - const matchLine = line.replace(/^[*\s]+/, '').trim() - - if (matchLine === START) { - // We are in the section with our workspace deps - // Set the flag and discard this line since we dont want it in the final output - inWorkspaceDeps = true - line = null - } else if (inWorkspaceDeps) { - if (COLLECT_DEP_TYPES.includes(matchLine)) { - collectWorkspaceDeps = true - line = null - } else if (collectWorkspaceDeps) { - const [, dep] = matchLine.match(/^(\S+) bumped from \S+ to \S+$/) || [] - if (dep) { - // This matched a dependency we want in our output so parse and rewrite it - // Link it to the github release for the workspace - const version = versions.get(dep) - const tag = this.gh.tag(this.components[dep], version) - const url = this.gh.release(tag) - line = `* ${link('Workspace', url)} ${code(`${dep}@${version}`)}` - } else { - // Anything else means we are done with this type of dependencies so ignore and continue - collectWorkspaceDeps = false - line = null - } - } else if (!matchLine || matchLine.startsWith('#')) { - // Next whitespace of a header means we are completely done with dependencies - // but we do need to save this line - inWorkspaceDeps = false - } else { - // everything else in the section should be ignored - line = null - } + // This is copied from the release-please node-workspace plugin + // except it only updates the package.json instead of appending + // anything to changelogs since we've already done that in preconfigure. + updateCandidate (candidate, pkg, updatedVersions) { + const graphPackage = this.packageGraph.get(pkg.name) + const updatedPackage = pkg.clone() + + for (const [depName, resolved] of graphPackage.localDependencies) { + const depVersion = updatedVersions.get(depName) + if (depVersion && resolved.type !== 'directory') { + updatedPackage.updateLocalDependency(resolved, depVersion.toString(), '^') } + } - if (line != null) { - filteredLines.push(line) + for (const update of candidate.pullRequest.updates) { + if (update.path === addPath(candidate.path, 'package.json')) { + update.updater = new RawContent( + jsonStringify(updatedPackage.toJSON(), updatedPackage.rawContent) + ) } } - let newNotes = filteredLines.join('\n').trim() + return candidate + } - if (newNotes.endsWith('### Dependencies')) { - newNotes = newNotes.replace(/### Dependencies$/, '') - } + postProcessCandidates (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_DEP, (_, depName, depVersion) => { + const { currentTag, path, component } = this.releasesByPackage.get(depName) + + const url = this.gh.compare(currentTag, new TagName( + depVersion, + component, + this.repositoryConfig[path].tagSeparator, + this.repositoryConfig[path].includeVInTag + )) + + return `${link('Workspace', url)}: ${code(`${depName}@${depVersion}`)}` + }) + + // 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 + } + } + } - const emptyDeps = newNotes.match(/### Dependencies\n+#/m) - if (emptyDeps) { - newNotes = newNotes.replace(emptyDeps[0], '#') + // Sort root release to the top of the pull request + 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 0 + }) } - if (!newNotes.startsWith('## ')) { - newNotes = `${this.gh.title()}\n\n${newNotes}` - } + return candidates + } - return newNotes.trim() + // Stub these methods with errors since the preconfigure method should negate these + // ever being called from the release please base class. If they are called then + // something has changed that would likely break us in other ways. + bumpVersion () { + throw new Error('Should not bump packages. This should be done in preconfigure.') } - bumpVersion (pkg) { - // The default release please node-workspace plugin forces a patch - // bump for the root if it only includes workspace dep updates. - // This does the same thing except it respects the prerelease config. - return new Version(pkg).bump(pkg.version, [{ type: 'fix' }]) + newCandidate () { + throw new Error('Should not create new candidates. This should be done in preconfigure.') } } diff --git a/lib/release-please/save-fixture.js b/lib/release-please/save-fixture.js deleted file mode 100644 index c5366e13..00000000 --- a/lib/release-please/save-fixture.js +++ /dev/null @@ -1,15 +0,0 @@ -const { resolve, join } = require('path') -const fs = require('@npmcli/fs') -const mkdirp = require('mkdirp') - -const FIXTURE_DIR = join(resolve(__dirname, '../../test/fixtures'), Date.now().toString()) - -module.exports = (name, data) => { - if (process.env.SAVE_FIXTURE) { - mkdirp.sync(FIXTURE_DIR) - fs.writeFileSync( - join(FIXTURE_DIR, `${name}.json`), - JSON.stringify(data, null, 2) - ) - } -} diff --git a/lib/release-please/util.js b/lib/release-please/util.js index 3386611c..7fd527e5 100644 --- a/lib/release-please/util.js +++ b/lib/release-please/util.js @@ -4,7 +4,7 @@ module.exports.specRe = new RegExp(`([^\\s]+@${semver.src[semver.tokens.FULLPLAI module.exports.code = (c) => `\`${c}\`` module.exports.link = (text, url) => url ? `[${text}](${url})` : text -module.exports.list = (text) => `* ${text}` +module.exports.list = (text) => `* ${text}` module.exports.dateFmt = (date = new Date()) => { const year = date.getFullYear() diff --git a/package.json b/package.json index f6d2e3a5..e75e5f78 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,6 @@ "json-parse-even-better-errors": "^2.3.1", "just-diff": "^5.0.1", "lodash": "^4.17.21", - "mkdirp": "^1.0.4", "npm-package-arg": "^9.0.1", "proc-log": "^2.0.0", "release-please": "npm:@npmcli/release-please@^14.2.4", diff --git a/tap-snapshots/test/check/diffs.js.test.cjs b/tap-snapshots/test/check/diffs.js.test.cjs index 5da3c33e..fd5bfc2d 100644 --- a/tap-snapshots/test/check/diffs.js.test.cjs +++ b/tap-snapshots/test/check/diffs.js.test.cjs @@ -24,7 +24,6 @@ To correct it: npx template-oss-apply --force exports[`test/check/diffs.js TAP different headers > source after apply 1`] = ` content/index.js ======================================== - module.exports = { rootRepo: { add: { diff --git a/test/release-please/changelog.js b/test/release-please/changelog.js index 4eaed394..4350cfcb 100644 --- a/test/release-please/changelog.js +++ b/test/release-please/changelog.js @@ -1,7 +1,7 @@ const t = require('tap') const ChangelogNotes = require('../../lib/release-please/changelog.js') -const mockChangelog = async ({ authors = true, previousTag = true } = {}) => { +const mockChangelog = async ({ shas = true, authors = true, previousTag = true } = {}) => { const commits = [{ sha: 'a', type: 'feat', @@ -21,16 +21,28 @@ const mockChangelog = async ({ authors = true, previousTag = true } = {}) => { type: 'deps', bareMessage: 'test@1.2.3', notes: [], - }] + }, { + sha: 'd', + type: 'fix', + bareMessage: 'this fixes it', + notes: [], + }].map(({ sha, ...rest }) => shas ? { sha, ...rest } : rest) const github = { repository: { owner: 'npm', repo: 'cli' }, graphql: () => ({ repository: commits.reduce((acc, c, i) => { - const author = i % 2 - ? { user: { login: 'username' } } - : { name: 'Name' } - acc[`_${c.sha}`] = { authors: { nodes: authors ? [author] : [] } } + if (c.sha) { + if (c.sha === 'd') { + // simulate a bad sha passed in that doesnt return a commit + acc[`_${c.sha}`] = null + } else { + const author = i % 2 + ? { user: { login: 'username' } } + : { name: 'Name' } + acc[`_${c.sha}`] = { authors: { nodes: authors ? [author] : [] } } + } + } return acc }, {}), }), @@ -55,28 +67,32 @@ const mockChangelog = async ({ authors = true, previousTag = true } = {}) => { t.test('changelog', async t => { const changelog = await mockChangelog() t.strictSame(changelog, [ - '## [1.0.0](https://github.com/npm/cli/compare/v0.1.0/.../v1.0.0) (DATE)', - '### BREAKING CHANGES', + '## [1.0.0](https://github.com/npm/cli/compare/v0.1.0...v1.0.0) (DATE)', + '### ⚠️ BREAKING CHANGES', '* breaking', '### Features', '* [`a`](https://github.com/npm/cli/commit/a) bin: Hey now (Name)', // eslint-disable-next-line max-len '* [`b`](https://github.com/npm/cli/commit/b) [#100](https://github.com/npm/cli/pull/100) b (@username)', + '### Bug Fixes', + '* [`d`](https://github.com/npm/cli/commit/d) this fixes it', '### Dependencies', '* [`c`](https://github.com/npm/cli/commit/c) `test@1.2.3`', ]) }) -t.test('no tag or authors', async t => { - const changelog = await mockChangelog({ authors: false, previousTag: false }) +t.test('no tag/authors/shas', async t => { + const changelog = await mockChangelog({ authors: false, previousTag: false, shas: false }) t.strictSame(changelog, [ '## 1.0.0 (DATE)', - '### BREAKING CHANGES', + '### ⚠️ BREAKING CHANGES', '* breaking', '### Features', - '* [`a`](https://github.com/npm/cli/commit/a) bin: Hey now', - '* [`b`](https://github.com/npm/cli/commit/b) [#100](https://github.com/npm/cli/pull/100) b', + '* bin: Hey now', + '* [#100](https://github.com/npm/cli/pull/100) b', + '### Bug Fixes', + '* this fixes it', '### Dependencies', - '* [`c`](https://github.com/npm/cli/commit/c) `test@1.2.3`', + '* `test@1.2.3`', ]) }) diff --git a/test/release-please/node-workspace.js b/test/release-please/node-workspace.js deleted file mode 100644 index f60eaa1a..00000000 --- a/test/release-please/node-workspace.js +++ /dev/null @@ -1,177 +0,0 @@ -const t = require('tap') - -// This needs to be required first otherwise, release please creates -// a cycle if we only require the node-workspace plugin. This is only -// an issue when testing. -require('release-please') -const NodeWorkspace = require('../../lib/release-please/node-workspace.js') -const { semverToVersion } = require('../../lib/release-please/version.js') - -const mockWorkspaceDeps = async (notes, { noChangelog = false }) => { - const releases = [{ - path: '.', - component: '', - name: 'the-root', - notes: notes.join('\n'), - }, { - path: 'workspaces/pkg2', - component: 'pkg2', - name: '@scope/pkg2', - version: semverToVersion('2.0.0'), - }, { - path: 'pkg1', - component: 'pkg1', - name: 'pkg1', - version: semverToVersion('1.0.0'), - }, { - path: 'pkg3', - component: 'pkg3', - name: '@scope/pkg3', - version: semverToVersion('3.0.0'), - }].map((r) => ({ - ...r, - notes: r.notes || '', - })) - - const versions = new Map(releases.map(r => [r.name, r.version])) - - const workspace = new NodeWorkspace({ repository: { owner: 'npm', repo: 'cli' } }) - - await workspace.preconfigure(releases.reduce((acc, r) => { - acc[r.path] = { - getComponent: () => r.component, - getDefaultPackageName: () => r.name, - } - return acc - }, {})) - - const [{ pullRequest: { body, updates } }] = workspace.postProcessCandidates([{ - pullRequest: { - body: { - releaseData: releases, - }, - updates: [ - ...releases.filter(r => noChangelog ? r.path !== '.' : true).map(r => ({ - path: `${r.path === '.' ? '' : `${r.path}/`}CHANGELOG.md`, - updater: { changelogEntry: r.notes }, - })), - { path: 'notachangelog' }, - ], - }, - }], versions) - - return { - pr: body.releaseData[0].notes.split('\n'), - changelog: updates[0].updater.changelogEntry.split('\n'), - } -} - -const fixtures = [ - [ - [ - '### Feat', - '', - '* xyz', - '', - '### Dependencies', - '', - '* The following workspace dependencies were updated', - ' * peerDependencies', - ' * pkgA bumped from ^1.0.0 to ^2.0.0', - ' * pkgB bumped from ^1.0.0 to ^2.0.0', - ' * dependencies', - ' * pkg1 bumped from ^0.0.0 to ^1.0.0', - ' * @scope/pkg2 bumped from ^1.0.0 to ^2.0.0', - ' * @scope/pkg3 bumped from ^1.0.0 to ^3.0.0', - ' * devDependencies', - ' * pkgC bumped from ^1.0.0 to ^2.0.0', - ' * pkgD bumped from ^1.0.0 to ^2.0.0', - '', - '### Next', - '', - '* xyz', - ], [ - '### Feat', - '', - '* xyz', - '', - '### Dependencies', - '', - '* [Workspace](https://github.com/npm/cli/releases/tag/pkg1-v1.0.0) `pkg1@1.0.0`', - '* [Workspace](https://github.com/npm/cli/releases/tag/pkg2-v2.0.0) `@scope/pkg2@2.0.0`', - '* [Workspace](https://github.com/npm/cli/releases/tag/pkg3-v3.0.0) `@scope/pkg3@3.0.0`', - '', - '### Next', - '', - '* xyz', - ], - ], - [ - [ - '### Feat', - '', - '* xyz', - '', - '### Dependencies', - '', - '* The following workspace dependencies were updated', - ' * peerDependencies', - ' * pkgA bumped from ^1.0.0 to ^2.0.0', - ' * pkgB bumped from ^1.0.0 to ^2.0.0', - ' * devDependencies', - ' * pkgC bumped from ^1.0.0 to ^2.0.0', - ' * pkgD bumped from ^1.0.0 to ^2.0.0', - '', - '### Other', - '', - '* xyz', - ], [ - '### Feat', - '', - '* xyz', - '', - '### Other', - '', - '* xyz', - ], - ], - [ - [ - '### Feat', - '', - '* xyz', - '', - '### Dependencies', - '* The following workspace dependencies were updated', - ' * peerDependencies', - ' * pkgA bumped from ^1.0.0 to ^2.0.0', - ' * pkgB bumped from ^1.0.0 to ^2.0.0', - ' * devDependencies', - ' * pkgC bumped from ^1.0.0 to ^2.0.0', - ' * pkgD bumped from ^1.0.0 to ^2.0.0', - ], [ - '### Feat', - '', - '* xyz', - ], - { noChangelog: true }, - ], -] - -t.test('rewrite deps', async (t) => { - for (const [input, expected, options = {}] of fixtures) { - const actual = await mockWorkspaceDeps(input, options) - t.strictSame(actual.pr, expected) - if (options.noChangelog) { - t.notSame(actual.changelog, expected) - } else { - t.strictSame(actual.changelog, expected) - } - } -}) - -t.test('can bump version', async (t) => { - const ws = new NodeWorkspace({ repository: { owner: 'npm', repo: 'cli' } }) - const version = { version: '1.0.0' } - t.equal(ws.bumpVersion(version).toString(), '1.0.1') -})