@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.
- package/bin/release-please.js +17 -2
- package/lib/content/index.js +1 -0
- package/lib/content/release-please-config.json +1 -1
- package/lib/content/release-please.yml +3 -0
- package/lib/release-please/changelog.js +58 -200
- package/lib/release-please/github.js +52 -0
- package/lib/release-please/index.js +6 -10
- package/lib/release-please/node-workspace.js +181 -9
- package/lib/release-please/util.js +14 -0
- package/lib/release-please/version.js +34 -7
- package/package.json +1 -1
- package/lib/release-please/logger.js +0 -3
- package/lib/release-please/workspace-deps.js +0 -99
package/bin/release-please.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
41
|
+
if (dryRun) {
|
|
42
|
+
console.error(err)
|
|
43
|
+
} else {
|
|
44
|
+
core.setFailed(`failed: ${err}`)
|
|
45
|
+
}
|
|
31
46
|
})
|
package/lib/content/index.js
CHANGED
|
@@ -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"
|
|
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}",
|
|
@@ -1,225 +1,83 @@
|
|
|
1
|
-
const
|
|
1
|
+
const makeGh = require('./github.js')
|
|
2
|
+
const { link, code, specRe, list, dateFmt } = require('./util')
|
|
2
3
|
|
|
3
|
-
module.exports = class
|
|
4
|
+
module.exports = class ChangelogNotes {
|
|
4
5
|
constructor (options) {
|
|
5
|
-
|
|
6
|
-
this.github = options.github
|
|
6
|
+
this.gh = makeGh(options.github)
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
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
|
-
|
|
43
|
-
//
|
|
44
|
-
|
|
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
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
//
|
|
129
|
-
|
|
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
|
-
|
|
156
|
-
|
|
37
|
+
return {
|
|
38
|
+
entry: entry.join(' '),
|
|
39
|
+
breaking,
|
|
157
40
|
}
|
|
41
|
+
}
|
|
158
42
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
.
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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
|
-
|
|
59
|
+
// Only continue with commits that will make it to our changelog
|
|
60
|
+
const commits = rawCommits.filter(c => changelog[c.type])
|
|
196
61
|
|
|
197
|
-
|
|
198
|
-
if (commits[key].length > 0) {
|
|
199
|
-
output.group(`### ${key}\n`)
|
|
62
|
+
const authorsByCommit = await this.gh.authors(commits)
|
|
200
63
|
|
|
201
|
-
|
|
202
|
-
|
|
64
|
+
// Group commits by type
|
|
65
|
+
for (const commit of commits) {
|
|
66
|
+
const { entry, breaking } = this.buildEntry(commit, authorsByCommit[commit.sha])
|
|
203
67
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
}
|
|
68
|
+
// Collect commits by type
|
|
69
|
+
changelog[commit.type].entries.push(entry)
|
|
207
70
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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
|
-
|
|
216
|
-
|
|
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
|
-
|
|
220
|
-
output.groupEnd()
|
|
221
|
-
}
|
|
222
|
-
}
|
|
79
|
+
const title = `## ${link(version, this.gh.compare(previousTag, currentTag))} (${dateFmt()})`
|
|
223
80
|
|
|
224
|
-
|
|
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
|
|
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
|
|
6
|
-
const NodeWorkspace = require('./node-workspace.js')
|
|
5
|
+
const NodeWs = require('./node-workspace.js')
|
|
7
6
|
|
|
8
|
-
RP.setLogger(
|
|
9
|
-
RP.registerChangelogNotes('default', (
|
|
10
|
-
RP.registerVersioningStrategy('default', (
|
|
11
|
-
RP.registerPlugin('workspace
|
|
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
|
|
2
|
-
const
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
|
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
|
-
|
|
13
|
-
return {
|
|
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
|
-
|
|
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
|
|
63
|
+
const semverToVersion = (v) => {
|
|
39
64
|
const { major, minor, patch, prerelease } = semver.parse(v)
|
|
40
|
-
return new
|
|
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
|
|
99
|
+
return semverToVersion(releaseVersion)
|
|
75
100
|
}
|
|
76
101
|
}
|
|
102
|
+
|
|
103
|
+
module.exports.semverToVersion = semverToVersion
|
package/package.json
CHANGED
|
@@ -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
|
-
}
|