@npmcli/template-oss 4.0.0 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -9,7 +9,18 @@ const [branch] = process.argv.slice(2)
9
9
  const setOutput = (key, val) => {
10
10
  if (val && (!Array.isArray(val) || val.length)) {
11
11
  if (dryRun) {
12
- console.log(key, JSON.stringify(val, null, 2))
12
+ if (key === 'pr') {
13
+ console.log('PR:', val.title.toString())
14
+ console.log('='.repeat(40))
15
+ console.log(val.body.toString())
16
+ console.log('='.repeat(40))
17
+ for (const update of val.updates.filter(u => u.updater.changelogEntry)) {
18
+ console.log('CHANGELOG:', update.path)
19
+ console.log('-'.repeat(40))
20
+ console.log(update.updater.changelogEntry)
21
+ console.log('-'.repeat(40))
22
+ }
23
+ }
13
24
  } else {
14
25
  core.setOutput(key, JSON.stringify(val))
15
26
  }
@@ -27,5 +38,9 @@ main({
27
38
  setOutput('release', release)
28
39
  return null
29
40
  }).catch(err => {
30
- core.setFailed(`failed: ${err}`)
41
+ if (dryRun) {
42
+ console.error(err)
43
+ } else {
44
+ core.setFailed(`failed: ${err}`)
45
+ }
31
46
  })
@@ -96,6 +96,7 @@ module.exports = {
96
96
  workspaceModule,
97
97
  windowsCI: true,
98
98
  branches: ['main', 'latest'],
99
+ releaseBranches: [],
99
100
  defaultBranch: 'main',
100
101
  // Escape hatch since we write a release test file but the
101
102
  // CLI has a very custom one we dont want to overwrite. This
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "separate-pull-requests": {{{del}}},
3
- "plugins": {{#if isMono}}["node-workspace", "workspace-deps"]{{else}}{{{del}}}{{/if}},
3
+ "plugins": {{#if isMono}}["node-workspace"]{{else}}{{{del}}}{{/if}},
4
4
  "exclude-packages-from-root": true,
5
5
  "group-pull-request-title-pattern": "chore: release ${version}",
6
6
  "pull-request-title-pattern": "chore: release${component} ${version}",
@@ -6,6 +6,9 @@ on:
6
6
  {{#each branches}}
7
7
  - {{.}}
8
8
  {{/each}}
9
+ {{#each releaseBranches}}
10
+ - {{.}}
11
+ {{/each}}
9
12
 
10
13
  permissions:
11
14
  contents: write
@@ -1,225 +1,83 @@
1
- const RP = require('release-please/build/src/changelog-notes/default')
1
+ const makeGh = require('./github.js')
2
+ const { link, code, specRe, list, dateFmt } = require('./util')
2
3
 
3
- module.exports = class DefaultChangelogNotes extends RP.DefaultChangelogNotes {
4
+ module.exports = class ChangelogNotes {
4
5
  constructor (options) {
5
- super(options)
6
- this.github = options.github
6
+ this.gh = makeGh(options.github)
7
7
  }
8
8
 
9
- async buildDefaultNotes (commits, options) {
10
- // The default generator has a title with the version and date
11
- // and a link to the diff between the last two versions
12
- const notes = await super.buildNotes(commits, options)
13
- const lines = notes.split('\n')
14
-
15
- let foundBreakingHeader = false
16
- let foundNextHeader = false
17
- const breaking = lines.reduce((acc, line) => {
18
- if (line.match(/^### .* BREAKING CHANGES$/)) {
19
- foundBreakingHeader = true
20
- } else if (!foundNextHeader && foundBreakingHeader && line.match(/^### /)) {
21
- foundNextHeader = true
22
- }
23
- if (foundBreakingHeader && !foundNextHeader) {
24
- acc.push(line)
25
- }
26
- return acc
27
- }, []).join('\n')
9
+ buildEntry (commit, authors = []) {
10
+ const breaking = commit.notes
11
+ .filter(n => n.title === 'BREAKING CHANGE')
12
+ .map(n => n.text)
28
13
 
29
- return {
30
- title: lines[0],
31
- breaking: breaking.trim(),
32
- }
33
- }
34
-
35
- async buildNotes (commits, options) {
36
- const { title, breaking } = await this.buildDefaultNotes(commits, options)
37
- const body = await generateChangelogBody(commits, { github: this.github, ...options })
38
- return [title, breaking, body].filter(Boolean).join('\n\n')
39
- }
40
- }
14
+ const entry = []
41
15
 
42
- // a naive implementation of console.log/group for indenting console
43
- // output but keeping it in a buffer to be output to a file or console
44
- const logger = (init) => {
45
- let indent = 0
46
- const step = 2
47
- const buffer = [init]
48
- return {
49
- toString () {
50
- return buffer.join('\n').trim()
51
- },
52
- group (s) {
53
- this.log(s)
54
- indent += step
55
- },
56
- groupEnd () {
57
- indent -= step
58
- },
59
- log (s) {
60
- if (!s) {
61
- buffer.push('')
62
- } else {
63
- buffer.push(s.split('\n').map((l) => ' '.repeat(indent) + l).join('\n'))
64
- }
65
- },
66
- }
67
- }
68
-
69
- const generateChangelogBody = async (_commits, { github, changelogSections }) => {
70
- const changelogMap = new Map(
71
- changelogSections.filter(c => !c.hidden).map((c) => [c.type, c.section])
72
- )
73
-
74
- const { repository } = await github.graphql(
75
- `fragment commitCredit on GitObject {
76
- ... on Commit {
77
- message
78
- url
79
- abbreviatedOid
80
- authors (first:10) {
81
- nodes {
82
- user {
83
- login
84
- url
85
- }
86
- email
87
- name
88
- }
89
- }
90
- associatedPullRequests (first:10) {
91
- nodes {
92
- number
93
- url
94
- merged
95
- }
96
- }
97
- }
16
+ if (commit.sha) {
17
+ // A link to the commit
18
+ entry.push(link(code(commit.sha.slice(0, 7)), this.gh.commit(commit.sha)))
98
19
  }
99
20
 
100
- query {
101
- repository (owner:"${github.repository.owner}", name:"${github.repository.repo}") {
102
- ${_commits.map(({ sha: s }) => `_${s}: object (expression: "${s}") { ...commitCredit }`)}
103
- }
104
- }`
105
- )
106
-
107
- // collect commits by valid changelog type
108
- const commits = [...changelogMap.values()].reduce((acc, type) => {
109
- acc[type] = []
110
- return acc
111
- }, {})
112
-
113
- const allCommits = Object.values(repository)
114
-
115
- for (const commit of allCommits) {
116
- // get changelog type of commit or bail if there is not a valid one
117
- const [, type] = /(^\w+)[\s(:]?/.exec(commit.message) || []
118
- const changelogType = changelogMap.get(type)
119
- if (!changelogType) {
120
- continue
21
+ // A link to the pull request if the commit has one
22
+ const prNumber = commit.pullRequest && commit.pullRequest.number
23
+ if (prNumber) {
24
+ entry.push(link(`#${prNumber}`, this.gh.pull(prNumber)))
121
25
  }
122
26
 
123
- const message = commit.message
124
- .trim() // remove leading/trailing spaces
125
- .replace(/(\r?\n)+/gm, '\n') // replace multiple newlines with one
126
- .replace(/([^\s]+@\d+\.\d+\.\d+.*)/gm, '`$1`') // wrap package@version in backticks
127
-
128
- // the title is the first line of the commit, 'let' because we change it later
129
- let [title, ...body] = message.split('\n')
130
-
131
- const prs = commit.associatedPullRequests.nodes.filter((pull) => pull.merged)
132
-
133
- // external squashed PRs dont get the associated pr node set
134
- // so we try to grab it from the end of the commit title
135
- // since thats where it goes by default
136
- const [, titleNumber] = title.match(/\s+\(#(\d+)\)$/) || []
137
- if (titleNumber && !prs.find((pr) => pr.number === +titleNumber)) {
138
- try {
139
- // it could also reference an issue so we do one extra check
140
- // to make sure it is really a pr that has been merged
141
- const { data: realPr } = await github.octokit.pulls.get({
142
- owner: github.repository.owner,
143
- repo: github.repository.repo,
144
- pull_number: titleNumber,
145
- })
146
- if (realPr.state === 'MERGED') {
147
- prs.push(realPr)
148
- }
149
- } catch {
150
- // maybe an issue or something else went wrong
151
- // not super important so keep going
152
- }
27
+ // The title of the commit, with the optional scope as a prefix
28
+ const scope = commit.scope && `${commit.scope}:`
29
+ const subject = commit.bareMessage.replace(specRe, code('$1'))
30
+ entry.push([scope, subject].filter(Boolean).join(' '))
31
+
32
+ // A list og the authors github handles or names
33
+ if (authors.length && commit.type !== 'deps') {
34
+ entry.push(`(${authors.join(', ')})`)
153
35
  }
154
36
 
155
- for (const pr of prs) {
156
- title = title.replace(new RegExp(`\\s*\\(#${pr.number}\\)`, 'g'), '')
37
+ return {
38
+ entry: entry.join(' '),
39
+ breaking,
157
40
  }
41
+ }
158
42
 
159
- body = body
160
- .map((line) => line.trim()) // remove artificial line breaks
161
- .filter(Boolean) // remove blank lines
162
- .join('\n') // rejoin on new lines
163
- .split(/^[*-]/gm) // split on lines starting with bullets
164
- .map((line) => line.trim()) // remove spaces around bullets
165
- .filter((line) => !title.includes(line)) // rm lines that exist in the title
166
- // replace new lines for this bullet with spaces and re-bullet it
167
- .map((line) => `* ${line.trim().replace(/\n/gm, ' ')}`)
168
- .join('\n') // re-join with new lines
169
-
170
- commits[changelogType].push({
171
- hash: commit.abbreviatedOid,
172
- url: commit.url,
173
- title,
174
- type: changelogType,
175
- body,
176
- prs,
177
- credit: commit.authors.nodes.map((author) => {
178
- if (author.user && author.user.login) {
179
- return {
180
- name: `@${author.user.login}`,
181
- url: author.user.url,
182
- }
183
- }
184
- // if the commit used an email that's not associated with a github account
185
- // then the user field will be empty, so we fall back to using the committer's
186
- // name and email as specified by git
187
- return {
188
- name: author.name,
189
- url: `mailto:${author.email}`,
43
+ async buildNotes (rawCommits, { version, previousTag, currentTag, changelogSections }) {
44
+ const changelog = changelogSections.reduce((acc, c) => {
45
+ if (!c.hidden) {
46
+ acc[c.type] = {
47
+ title: c.section,
48
+ entries: [],
190
49
  }
191
- }),
50
+ }
51
+ return acc
52
+ }, {
53
+ breaking: {
54
+ title: '⚠️ BREAKING CHANGES',
55
+ entries: [],
56
+ },
192
57
  })
193
- }
194
58
 
195
- const output = logger()
59
+ // Only continue with commits that will make it to our changelog
60
+ const commits = rawCommits.filter(c => changelog[c.type])
196
61
 
197
- for (const key of Object.keys(commits)) {
198
- if (commits[key].length > 0) {
199
- output.group(`### ${key}\n`)
62
+ const authorsByCommit = await this.gh.authors(commits)
200
63
 
201
- for (const commit of commits[key]) {
202
- let groupCommit = `* [\`${commit.hash}\`](${commit.url})`
64
+ // Group commits by type
65
+ for (const commit of commits) {
66
+ const { entry, breaking } = this.buildEntry(commit, authorsByCommit[commit.sha])
203
67
 
204
- for (const pr of commit.prs) {
205
- groupCommit += ` [#${pr.number}](${pr.url})`
206
- }
68
+ // Collect commits by type
69
+ changelog[commit.type].entries.push(entry)
207
70
 
208
- groupCommit += ` ${commit.title}`
209
- if (key !== 'Dependencies') {
210
- for (const user of commit.credit) {
211
- groupCommit += ` (${user.name})`
212
- }
213
- }
71
+ // And push breaking changes to its own section
72
+ changelog.breaking.entries.push(...breaking)
73
+ }
214
74
 
215
- output.group(groupCommit)
216
- output.groupEnd()
217
- }
75
+ const sections = Object.values(changelog)
76
+ .filter((s) => s.entries.length)
77
+ .map(({ title, entries }) => [`### ${title}`, entries.map(list).join('\n')].join('\n\n'))
218
78
 
219
- output.log()
220
- output.groupEnd()
221
- }
222
- }
79
+ const title = `## ${link(version, this.gh.compare(previousTag, currentTag))} (${dateFmt()})`
223
80
 
224
- return output.toString()
81
+ return [title, ...sections].join('\n\n').trim()
82
+ }
225
83
  }
@@ -0,0 +1,52 @@
1
+ module.exports = (gh) => {
2
+ const { owner, repo } = gh.repository
3
+
4
+ const authors = async (commits) => {
5
+ const response = {}
6
+
7
+ const shas = commits.map(c => c.sha).filter(Boolean)
8
+
9
+ if (!shas.length) {
10
+ return response
11
+ }
12
+
13
+ const { repository } = await gh.graphql(
14
+ `fragment CommitAuthors on GitObject {
15
+ ... on Commit {
16
+ authors (first:10) {
17
+ nodes {
18
+ user { login }
19
+ name
20
+ }
21
+ }
22
+ }
23
+ }
24
+ query {
25
+ repository (owner:"${owner}", name:"${repo}") {
26
+ ${shas.map((s) => {
27
+ return `_${s}: object (expression: "${s}") { ...CommitAuthors }`
28
+ })}
29
+ }
30
+ }`
31
+ )
32
+
33
+ for (const [key, commit] of Object.entries(repository)) {
34
+ if (commit) {
35
+ response[key.slice(1)] = commit.authors.nodes
36
+ .map((a) => a.user && a.user.login ? `@${a.user.login}` : a.name)
37
+ .filter(Boolean)
38
+ }
39
+ }
40
+
41
+ return response
42
+ }
43
+
44
+ const url = (...p) => `https://github.com/${owner}/${repo}/${p.join('/')}`
45
+
46
+ return {
47
+ authors,
48
+ pull: (number) => url('pull', number),
49
+ commit: (sha) => url('commit', sha),
50
+ compare: (a, b) => a ? url('compare', `${a.toString()}...${b.toString()}`) : null,
51
+ }
52
+ }
@@ -1,17 +1,13 @@
1
1
  const RP = require('release-please')
2
- const logger = require('./logger.js')
2
+ const { CheckpointLogger } = require('release-please/build/src/util/logger.js')
3
3
  const ChangelogNotes = require('./changelog.js')
4
4
  const Version = require('./version.js')
5
- const WorkspaceDeps = require('./workspace-deps.js')
6
- const NodeWorkspace = require('./node-workspace.js')
5
+ const NodeWs = require('./node-workspace.js')
7
6
 
8
- RP.setLogger(logger)
9
- RP.registerChangelogNotes('default', (options) => new ChangelogNotes(options))
10
- RP.registerVersioningStrategy('default', (options) => new Version(options))
11
- RP.registerPlugin('workspace-deps', (o) =>
12
- new WorkspaceDeps(o.github, o.targetBranch, o.repositoryConfig))
13
- RP.registerPlugin('node-workspace', (o) =>
14
- new NodeWorkspace(o.github, o.targetBranch, o.repositoryConfig))
7
+ RP.setLogger(new CheckpointLogger(true, true))
8
+ RP.registerChangelogNotes('default', (o) => new ChangelogNotes(o))
9
+ RP.registerVersioningStrategy('default', (o) => new Version(o))
10
+ RP.registerPlugin('node-workspace', (o) => new NodeWs(o.github, o.targetBranch, o.repositoryConfig))
15
11
 
16
12
  const main = async ({ repo: fullRepo, token, dryRun, branch }) => {
17
13
  if (!token) {
@@ -1,11 +1,183 @@
1
- const Version = require('./version.js')
2
- const RP = require('release-please/build/src/plugins/node-workspace')
3
-
4
- module.exports = class NodeWorkspace extends RP.NodeWorkspace {
5
- bumpVersion (pkg) {
6
- // The default release please node-workspace plugin forces a patch
7
- // bump for the root if it only includes workspace dep updates.
8
- // This does the same thing except it respects the prerelease config.
9
- return new Version(pkg).bump(pkg.version, [{ type: 'fix' }])
1
+ const { NodeWorkspace } = require('release-please/build/src/plugins/node-workspace.js')
2
+ const { RawContent } = require('release-please/build/src/updaters/raw-content.js')
3
+ const { jsonStringify } = require('release-please/build/src/util/json-stringify.js')
4
+ const { addPath } = require('release-please/build/src/plugins/workspace.js')
5
+ const { TagName } = require('release-please/build/src/util/tag-name.js')
6
+ const { ROOT_PROJECT_PATH } = require('release-please/build/src/manifest.js')
7
+ const makeGh = require('./github.js')
8
+ const { link, code } = require('./util.js')
9
+
10
+ const SCOPE = '__REPLACE_WORKSPACE_DEP__'
11
+ const WORKSPACE_DEP = new RegExp(`${SCOPE}: (\\S+) (\\S+)`, 'gm')
12
+
13
+ module.exports = class extends NodeWorkspace {
14
+ constructor (github, ...args) {
15
+ super(github, ...args)
16
+ this.gh = makeGh(github)
17
+ }
18
+
19
+ async preconfigure (strategiesByPath, commitsByPath, releasesByPath) {
20
+ // First build a list of all releases that will happen based on
21
+ // the conventional commits
22
+ const candidates = []
23
+ for (const path in strategiesByPath) {
24
+ const pullRequest = await strategiesByPath[path].buildReleasePullRequest(
25
+ commitsByPath[path],
26
+ releasesByPath[path]
27
+ )
28
+ if (pullRequest?.version) {
29
+ candidates.push({ path, pullRequest })
30
+ }
31
+ }
32
+
33
+ // Then build the graph of all those releases + any other connected workspaces
34
+ const { allPackages, candidatesByPackage } = await this.buildAllPackages(candidates)
35
+ const orderedPackages = this.buildGraphOrder(
36
+ await this.buildGraph(allPackages),
37
+ Object.keys(candidatesByPackage)
38
+ )
39
+
40
+ // Then build a list of all the updated versions
41
+ const updatedVersions = new Map()
42
+ for (const pkg of orderedPackages) {
43
+ const path = this.pathFromPackage(pkg)
44
+ const packageName = this.packageNameFromPackage(pkg)
45
+
46
+ let version = null
47
+ const existingCandidate = candidatesByPackage[packageName]
48
+ if (existingCandidate) {
49
+ // If there is an existing pull request use that version
50
+ version = existingCandidate.pullRequest.version
51
+ } else {
52
+ // Otherwise build another pull request (that will be discarded) just
53
+ // to see what the version would be if it only contained a deps commit.
54
+ // This is to make sure we use any custom versioning or release strategy.
55
+ const strategy = strategiesByPath[path]
56
+ const depsSection = strategy.changelogSections.find(c => c.section === 'Dependencies')
57
+ const releasePullRequest = await strategiesByPath[path].buildReleasePullRequest(
58
+ [{ message: `${depsSection.type}:` }],
59
+ releasesByPath[path]
60
+ )
61
+ version = releasePullRequest.version
62
+ }
63
+
64
+ updatedVersions.set(packageName, version)
65
+ }
66
+
67
+ // Save some data about the preconfiugred releases so we can look it up later
68
+ // when rewriting the changelogs
69
+ this.releasesByPackage = new Map()
70
+ this.pathsByComponent = new Map()
71
+
72
+ // Then go through all the packages again and add deps commits
73
+ // for each updated workspace
74
+ for (const pkg of orderedPackages) {
75
+ const path = this.pathFromPackage(pkg)
76
+ const packageName = this.packageNameFromPackage(pkg)
77
+ const graphPackage = this.packageGraph.get(pkg.name)
78
+
79
+ // Update dependency versions
80
+ for (const [depName, resolved] of graphPackage.localDependencies) {
81
+ const depVersion = updatedVersions.get(depName)
82
+ const isNotDir = resolved.type !== 'directory'
83
+ // Changelog entries are only added for dependencies and not any other type
84
+ const isDep = Object.prototype.hasOwnProperty.call(pkg.dependencies, depName)
85
+ if (depVersion && isNotDir && isDep) {
86
+ commitsByPath[path].push({
87
+ message: `deps(${SCOPE}): ${depName} ${depVersion.toString()}`,
88
+ })
89
+ }
90
+ }
91
+
92
+ const component = await strategiesByPath[path].getComponent()
93
+ this.pathsByComponent.set(component, path)
94
+ this.releasesByPackage.set(packageName, {
95
+ path,
96
+ component,
97
+ currentTag: releasesByPath[path]?.tag,
98
+ })
99
+ }
100
+
101
+ return strategiesByPath
102
+ }
103
+
104
+ // This is copied from the release-please node-workspace plugin
105
+ // except it only updates the package.json instead of appending
106
+ // anything to changelogs since we've already done that in preconfigure.
107
+ updateCandidate (candidate, pkg, updatedVersions) {
108
+ const graphPackage = this.packageGraph.get(pkg.name)
109
+ const updatedPackage = pkg.clone()
110
+
111
+ for (const [depName, resolved] of graphPackage.localDependencies) {
112
+ const depVersion = updatedVersions.get(depName)
113
+ if (depVersion && resolved.type !== 'directory') {
114
+ updatedPackage.updateLocalDependency(resolved, depVersion.toString(), '^')
115
+ }
116
+ }
117
+
118
+ for (const update of candidate.pullRequest.updates) {
119
+ if (update.path === addPath(candidate.path, 'package.json')) {
120
+ update.updater = new RawContent(
121
+ jsonStringify(updatedPackage.toJSON(), updatedPackage.rawContent)
122
+ )
123
+ }
124
+ }
125
+
126
+ return candidate
127
+ }
128
+
129
+ postProcessCandidates (candidates) {
130
+ for (const candidate of candidates) {
131
+ for (const release of candidate.pullRequest.body.releaseData) {
132
+ // Update notes with a link to each workspaces release notes
133
+ // now that we have all of the releases in a single pull request
134
+ release.notes = release.notes.replace(WORKSPACE_DEP, (_, depName, depVersion) => {
135
+ const { currentTag, path, component } = this.releasesByPackage.get(depName)
136
+
137
+ const url = this.gh.compare(currentTag, new TagName(
138
+ depVersion,
139
+ component,
140
+ this.repositoryConfig[path].tagSeparator,
141
+ this.repositoryConfig[path].includeVInTag
142
+ ))
143
+
144
+ return `${link('Workspace', url)}: ${code(`${depName}@${depVersion}`)}`
145
+ })
146
+
147
+ // Find the associated changelog and update that too
148
+ const path = this.pathsByComponent.get(release.component)
149
+ for (const update of candidate.pullRequest.updates) {
150
+ if (update.path === addPath(path, 'CHANGELOG.md')) {
151
+ update.updater.changelogEntry = release.notes
152
+ }
153
+ }
154
+ }
155
+
156
+ // Sort root release to the top of the pull request
157
+ candidate.pullRequest.body.releaseData.sort((a, b) => {
158
+ const aPath = this.pathsByComponent.get(a.component)
159
+ const bPath = this.pathsByComponent.get(b.component)
160
+ if (aPath === ROOT_PROJECT_PATH) {
161
+ return -1
162
+ }
163
+ if (bPath === ROOT_PROJECT_PATH) {
164
+ return 1
165
+ }
166
+ return 0
167
+ })
168
+ }
169
+
170
+ return candidates
171
+ }
172
+
173
+ // Stub these methods with errors since the preconfigure method should negate these
174
+ // ever being called from the release please base class. If they are called then
175
+ // something has changed that would likely break us in other ways.
176
+ bumpVersion () {
177
+ throw new Error('Should not bump packages. This should be done in preconfigure.')
178
+ }
179
+
180
+ newCandidate () {
181
+ throw new Error('Should not create new candidates. This should be done in preconfigure.')
10
182
  }
11
183
  }
@@ -0,0 +1,14 @@
1
+ const semver = require('semver')
2
+
3
+ module.exports.specRe = new RegExp(`([^\\s]+@${semver.src[semver.tokens.FULLPLAIN]})`, 'g')
4
+
5
+ module.exports.code = (c) => `\`${c}\``
6
+ module.exports.link = (text, url) => url ? `[${text}](${url})` : text
7
+ module.exports.list = (text) => `* ${text}`
8
+
9
+ module.exports.dateFmt = (date = new Date()) => {
10
+ const year = date.getFullYear()
11
+ const month = (date.getMonth() + 1).toString().padStart(2, '0')
12
+ const day = date.getDate().toString().padStart(2, '0')
13
+ return [year, month, day].join('-')
14
+ }
@@ -1,5 +1,5 @@
1
1
  const semver = require('semver')
2
- const RP = require('release-please/build/src/version.js')
2
+ const { Version } = require('release-please/build/src/version.js')
3
3
 
4
4
  // A way to compare the "level" of a release since we ignore some things during prereleases
5
5
  const LEVELS = new Map([['prerelease', 4], ['major', 3], ['minor', 2], ['patch', 1]]
@@ -7,37 +7,62 @@ const LEVELS = new Map([['prerelease', 4], ['major', 3], ['minor', 2], ['patch',
7
7
 
8
8
  const parseVersion = (v) => {
9
9
  const { prerelease, minor, patch, version } = semver.parse(v)
10
+
11
+ // This looks at whether there are 0s in certain positions of the version
12
+ // 1.0.0 => major
13
+ // 1.5.0 => minor
14
+ // 1.5.6 => patch
15
+ const release = !patch
16
+ ? (minor ? LEVELS.get('minor') : LEVELS.get('major'))
17
+ : LEVELS.get('patch')
18
+
19
+ // Keep track of whether the version has any prerelease identifier
10
20
  const hasPre = prerelease.length > 0
21
+ // Even if it is a prerelease version, this might be an empty string
11
22
  const preId = prerelease.filter(p => typeof p === 'string').join('.')
12
- const release = !patch ? (minor ? LEVELS.get('minor') : LEVELS.get('major')) : LEVELS.get('patch')
13
- return { version, release, prerelease: hasPre, preId }
23
+
24
+ return {
25
+ version,
26
+ release,
27
+ prerelease: hasPre,
28
+ preId,
29
+ }
14
30
  }
15
31
 
16
32
  const parseCommits = (commits, prerelease) => {
33
+ // Default is a patch level change
17
34
  let release = LEVELS.get('patch')
35
+
18
36
  for (const commit of commits) {
19
37
  if (commit.breaking) {
38
+ // If any breaking commit is present, its a major
20
39
  release = LEVELS.get('major')
21
40
  break
22
41
  } else if (['feat', 'feature'].includes(commit.type)) {
42
+ // Otherwise a feature is a minor release
23
43
  release = LEVELS.get('minor')
24
44
  }
25
45
  }
26
- return { release, prerelease: !!prerelease }
46
+
47
+ return {
48
+ release,
49
+ prerelease: !!prerelease,
50
+ }
27
51
  }
28
52
 
29
53
  const preInc = ({ version, prerelease, preId }, release) => {
30
54
  if (!release.startsWith('pre')) {
31
55
  release = `pre${release}`
32
56
  }
57
+
33
58
  // `pre` is the default prerelease identifier when creating a new
34
59
  // prerelease version
35
60
  return semver.inc(version, release, prerelease ? preId : 'pre')
36
61
  }
37
62
 
38
- const releasePleaseVersion = (v) => {
63
+ const semverToVersion = (v) => {
39
64
  const { major, minor, patch, prerelease } = semver.parse(v)
40
- return new RP.Version(major, minor, patch, prerelease.join('.'))
65
+ return new Version(major, minor, patch, prerelease.join('.'))
41
66
  }
42
67
 
43
68
  // This does not account for pre v1 semantics since we don't publish those
@@ -71,6 +96,8 @@ module.exports = class DefaultVersioningStrategy {
71
96
  const releaseVersion = next.prerelease
72
97
  ? preInc(current, release)
73
98
  : semver.inc(current.version, release)
74
- return releasePleaseVersion(releaseVersion)
99
+ return semverToVersion(releaseVersion)
75
100
  }
76
101
  }
102
+
103
+ module.exports.semverToVersion = semverToVersion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@npmcli/template-oss",
3
- "version": "4.0.0",
3
+ "version": "4.1.0",
4
4
  "description": "templated files used in npm CLI team oss projects",
5
5
  "main": "lib/content/index.js",
6
6
  "bin": {
@@ -1,3 +0,0 @@
1
- const { CheckpointLogger } = require('release-please/build/src/util/logger')
2
-
3
- module.exports = new CheckpointLogger(true, true)
@@ -1,99 +0,0 @@
1
- const { ManifestPlugin } = require('release-please/build/src/plugin')
2
- const { Changelog } = require('release-please/build/src/updaters/changelog.js')
3
- const { PackageJson } = require('release-please/build/src/updaters/node/package-json.js')
4
-
5
- const matchLine = (line, re) => {
6
- const trimmed = line.trim().replace(/^[*\s]+/, '')
7
- if (typeof re === 'string') {
8
- return trimmed === re
9
- }
10
- return trimmed.match(re)
11
- }
12
-
13
- module.exports = class WorkspaceDeps extends ManifestPlugin {
14
- run (pullRequests) {
15
- try {
16
- for (const { pullRequest } of pullRequests) {
17
- const getChangelog = (release) => pullRequest.updates.find((u) => {
18
- const isChangelog = u.updater instanceof Changelog
19
- const isComponent = release.component && u.path.startsWith(release.component)
20
- const isRoot = !release.component && !u.path.includes('/')
21
- return isChangelog && (isComponent || isRoot)
22
- })
23
-
24
- const getComponent = (pkgName) => pullRequest.updates.find((u) => {
25
- const isPkg = u.updater instanceof PackageJson
26
- return isPkg && JSON.parse(u.updater.rawContent).name === pkgName
27
- }).path.replace(/\/package\.json$/, '')
28
-
29
- const depLinksByComponent = pullRequest.body.releaseData.reduce((acc, release) => {
30
- if (release.component) {
31
- const path = [
32
- this.github.repository.owner,
33
- this.github.repository.repo,
34
- 'releases',
35
- 'tag',
36
- release.tag.toString(),
37
- ]
38
- acc[release.component] = `https://github.com/${path.join('/')}`
39
- }
40
- return acc
41
- }, {})
42
-
43
- for (const release of pullRequest.body.releaseData) {
44
- const lines = release.notes.split('\n')
45
- const newLines = []
46
-
47
- let inWorkspaceDeps = false
48
- let collectWorkspaceDeps = false
49
-
50
- for (const line of lines) {
51
- if (matchLine(line, 'The following workspace dependencies were updated')) {
52
- // We are in the section with our workspace deps
53
- // Set the flag and discard this line since we dont want it in the final output
54
- inWorkspaceDeps = true
55
- } else if (inWorkspaceDeps) {
56
- if (collectWorkspaceDeps) {
57
- const depMatch = matchLine(line, /^(\S+) bumped from \S+ to (\S+)$/)
58
- if (depMatch) {
59
- // If we have a line that is a workspace dep update, then reformat
60
- // it and save it to the new lines
61
- const [, depName, newVersion] = depMatch
62
- const depSpec = `\`${depName}@${newVersion}\``
63
- const url = depLinksByComponent[getComponent(depName)]
64
- newLines.push(` * deps: [${depSpec}](${url})`)
65
- } else {
66
- // Anything else means we are done with dependencies so ignore
67
- // this line and dont look for any more
68
- collectWorkspaceDeps = false
69
- }
70
- } else if (matchLine(line, 'dependencies')) {
71
- // Only collect dependencies discard dev deps and everything else
72
- collectWorkspaceDeps = true
73
- } else if (matchLine(line, '') || matchLine(line, /^#/)) {
74
- inWorkspaceDeps = false
75
- newLines.push(line)
76
- }
77
- } else {
78
- newLines.push(line)
79
- }
80
- }
81
-
82
- let newNotes = newLines.join('\n').trim()
83
- const emptyDeps = newNotes.match(/### Dependencies[\n]+(### .*)/m)
84
- if (emptyDeps) {
85
- newNotes = newNotes.replace(emptyDeps[0], emptyDeps[1])
86
- }
87
-
88
- release.notes = newNotes
89
- getChangelog(release).updater.changelogEntry = newNotes
90
- }
91
- }
92
- } catch {
93
- // Always return pull requests even if we failed so
94
- // we dont fail the release
95
- }
96
-
97
- return pullRequests
98
- }
99
- }