@npmcli/template-oss 4.11.3 → 4.11.4

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.
@@ -4,7 +4,7 @@ const core = require('@actions/core')
4
4
  const main = require('../lib/release-please/index.js')
5
5
 
6
6
  const dryRun = !process.env.CI
7
- const [branch, eventName] = process.argv.slice(2)
7
+ const [branch, forcePullRequest] = process.argv.slice(2)
8
8
 
9
9
  const debugPr = (val) => {
10
10
  if (dryRun) {
@@ -45,7 +45,7 @@ main({
45
45
  repo: process.env.GITHUB_REPOSITORY,
46
46
  dryRun,
47
47
  branch,
48
- force: eventName === 'workflow_dispatch',
48
+ forcePullRequest: forcePullRequest ? +forcePullRequest : null,
49
49
  }).then(({ pr, release, releases }) => {
50
50
  if (pr) {
51
51
  debugPr(pr)
@@ -2,6 +2,10 @@ name: Release
2
2
 
3
3
  on:
4
4
  workflow_dispatch:
5
+ inputs:
6
+ release-pr:
7
+ description: a release PR number to rerun release jobs on
8
+ type: string
5
9
  push:
6
10
  branches:
7
11
  {{#each branches}}
@@ -30,7 +34,7 @@ jobs:
30
34
  env:
31
35
  GITHUB_TOKEN: $\{{ secrets.GITHUB_TOKEN }}
32
36
  run: |
33
- {{ rootNpxPath }} --offline template-oss-release-please $\{{ github.ref_name }} $\{{ github.event_name }}
37
+ {{ rootNpxPath }} --offline template-oss-release-please "$\{{ github.ref_name }}" "$\{{ inputs.release-pr }}"
34
38
  - name: Post Pull Request Comment
35
39
  if: steps.release.outputs.pr-number
36
40
  uses: actions/github-script@v6
@@ -53,7 +57,7 @@ jobs:
53
57
  body += `Release workflow run: ${workflow.html_url}\n\n#### Force CI to Update This Release\n\n`
54
58
  body += `This PR will be updated and CI will run for every non-\`chore:\` commit that is pushed to \`{{ defaultBranch }}\`. `
55
59
  body += `To force CI to update this PR, run this command:\n\n`
56
- body += `\`\`\`\ngh workflow run release.yml -r ${REF_NAME} -R ${owner}/${repo}\n\`\`\``
60
+ body += `\`\`\`\ngh workflow run release.yml -r ${REF_NAME} -R ${owner}/${repo} -f release-pr=${issue_number}\n\`\`\``
57
61
 
58
62
  if (commentId) {
59
63
  await github.rest.issues.updateComment({ owner, repo, comment_id: commentId, body })
@@ -139,14 +143,17 @@ jobs:
139
143
  }
140
144
 
141
145
  const comments = await github.paginate(github.rest.issues.listComments, { owner, repo, issue_number })
142
- const releaseComments = comments.filter(c => c.user.login === 'github-actions[bot]' && c.body.includes('Release is at'))
146
+ .then(cs => cs.map(c => ({ id: c.id, login: c.user.login, body: c.body })))
147
+ console.log(`Found comments: ${JSON.stringify(comments, null, 2)}`)
148
+ const releaseComments = comments.filter(c => c.login === 'github-actions[bot]' && c.body.includes('Release is at'))
143
149
 
144
150
  for (const comment of releaseComments) {
151
+ console.log(`Release comment: ${JSON.stringify(comment, null, 2)}`)
145
152
  await github.rest.issues.deleteComment({ owner, repo, comment_id: comment.id })
146
153
  }
147
154
 
148
155
  const runUrl = `https://github.com/${owner}/${repo}/actions/runs/${runId}`
149
- await github.rest.issues.createComment({
156
+ await github.rest.issues.createComment({
150
157
  owner,
151
158
  repo,
152
159
  issue_number,
@@ -182,10 +189,14 @@ jobs:
182
189
  with:
183
190
  script: |
184
191
  const { PR_NUMBER: issue_number, RESULT } = process.env
185
- const { repo: { owner, repo } } = context
192
+ const { runId, repo: { owner, repo } } = context
186
193
 
187
194
  const comments = await github.paginate(github.rest.issues.listComments, { owner, repo, issue_number })
188
- const updateComment = comments.find(c => c.user.login === 'github-actions[bot]' && c.body.startsWith('## Release Workflow\n\n'))
195
+ const updateComment = comments.find(c =>
196
+ c.user.login === 'github-actions[bot]' &&
197
+ c.body.startsWith('## Release Workflow\n\n') &&
198
+ c.body.includes(runId)
199
+ )
189
200
 
190
201
  if (updateComment) {
191
202
  console.log('Found comment to update:', JSON.stringify(updateComment, null, 2))
@@ -4,26 +4,33 @@ const ChangelogNotes = require('./changelog.js')
4
4
  const Version = require('./version.js')
5
5
  const NodeWs = require('./node-workspace.js')
6
6
 
7
- RP.setLogger(new CheckpointLogger(true, true))
7
+ const logger = new CheckpointLogger(true, true)
8
+ RP.setLogger(logger)
8
9
  RP.registerChangelogNotes('default', (o) => new ChangelogNotes(o))
9
10
  RP.registerVersioningStrategy('default', (o) => new Version(o))
10
11
  RP.registerPlugin('node-workspace', (o) => new NodeWs(o.github, o.targetBranch, o.repositoryConfig))
11
12
 
12
- const main = async ({ repo: _fullRepo, token, dryRun, branch, force }) => {
13
- if (!token) {
14
- throw new Error('Token is required')
13
+ const omit = (obj, ...keys) => {
14
+ const res = {}
15
+ for (const [key, value] of Object.entries(obj)) {
16
+ if (!keys.includes(key)) {
17
+ res[key] = value
18
+ }
15
19
  }
20
+ return res
21
+ }
16
22
 
17
- if (!_fullRepo) {
18
- throw new Error('Repo is required')
19
- }
23
+ const getManifest = async ({ repo: fullRepo, token, branch }) => {
24
+ const fullRepoParts = fullRepo.split('/')
25
+ const github = await RP.GitHub.create({
26
+ owner: fullRepoParts[0],
27
+ repo: fullRepoParts[1],
28
+ token,
29
+ })
20
30
 
21
- const fullRepo = _fullRepo.split('/')
22
- const github = await RP.GitHub.create({ owner: fullRepo[0], repo: fullRepo[1], token })
23
- const {
24
- octokit,
25
- repository: { owner, repo, defaultBranch },
26
- } = github
31
+ const { octokit, repository: { owner, repo, defaultBranch } } = github
32
+
33
+ const baseBranch = branch ?? defaultBranch
27
34
 
28
35
  // This is mostly for testing and debugging. Use environs with the
29
36
  // format `RELEASE_PLEASE_<manfiestOverrideConfigName>` (eg
@@ -33,8 +40,6 @@ const main = async ({ repo: _fullRepo, token, dryRun, branch, force }) => {
33
40
  .filter(([k, v]) => k.startsWith('RELEASE_PLEASE_') && v != null)
34
41
  .map(([k, v]) => [k.replace('RELEASE_PLEASE_', ''), v])
35
42
 
36
- const baseBranch = branch ?? defaultBranch
37
-
38
43
  const manifest = await RP.Manifest.fromManifest(
39
44
  github,
40
45
  baseBranch,
@@ -43,79 +48,213 @@ const main = async ({ repo: _fullRepo, token, dryRun, branch, force }) => {
43
48
  Object.fromEntries(manifestOverrides)
44
49
  )
45
50
 
46
- if (force) {
47
- const { data: releasePrs } = await octokit.pulls.list({
48
- owner,
49
- repo,
50
- head: `release-please--branches--${baseBranch}`,
51
- })
51
+ return {
52
+ github,
53
+ manifest,
54
+ octokit,
55
+ owner,
56
+ repo,
57
+ baseBranch,
58
+ }
59
+ }
52
60
 
53
- if (releasePrs.length !== 1) {
54
- throw new Error(`Found ${releasePrs.length} matching PRs, expected 1`)
61
+ const getReleasesFromPr = async ({ manifest, github, number }) => {
62
+ const baseUrl = `https://github.com/${github.repository.owner}/${github.repository.repo}`
63
+ // get the release please formatted pull request
64
+ let pullRequest
65
+ const prGenerator = github.pullRequestIterator(this.targetBranch, 'MERGED', 200, false)
66
+ for await (const pr of prGenerator) {
67
+ if (pr.number === number) {
68
+ pullRequest = pr
69
+ break
55
70
  }
71
+ }
72
+ const strategiesByPath = await manifest.getStrategiesByPath()
73
+ const releases = []
74
+ for (const path in manifest.repositoryConfig) {
75
+ const config = manifest.repositoryConfig[path]
76
+ const release = await strategiesByPath[path].buildRelease(pullRequest)
77
+ if (release) {
78
+ const { tag, ...rest } = release
79
+ releases.push({
80
+ ...rest,
81
+ ...tag.version,
82
+ tagName: tag.toString(),
83
+ version: tag.version.toString(),
84
+ path,
85
+ draft: false,
86
+ url: `${baseUrl}/releases/tag/${tag.toString()}`,
87
+ prerelease: config.prerelease && !!tag.version.preRelease,
88
+ })
89
+ }
90
+ }
91
+ return releases
92
+ }
93
+
94
+ const getReleaseArtifacts = async ({ dryRun, manifest, forceReleases }) => {
95
+ let pullRequests = []
96
+ let releases = []
97
+
98
+ if (forceReleases) {
99
+ releases = forceReleases
100
+ } else if (dryRun) {
101
+ pullRequests = await manifest.buildPullRequests()
102
+ releases = await manifest.buildReleases()
103
+ } else {
104
+ pullRequests = await manifest.createPullRequests()
105
+ releases = await manifest.createReleases()
106
+ }
107
+
108
+ return {
109
+ pullRequests: pullRequests.filter(Boolean),
110
+ releases: releases.filter(Boolean),
111
+ }
112
+ }
113
+
114
+ // XXX(hack): to get release please to recreate a pull request it needs
115
+ // to have a different body string so we append a message a message that CI
116
+ // is running. This will force release-please to rebase the PR but it
117
+ // wont update the body again, so we only append to it.
118
+ const touchPullRequest = async ({ octokit, owner, repo, releasePr }) => {
119
+ const id = process.env.GITHUB_RUN_ID
120
+ ? `by https://github.com/${owner}/${repo}/actions/runs/${process.env.GITHUB_RUN_ID}`
121
+ : `manually starting at ${new Date().toJSON()}`
122
+
123
+ await octokit.pulls.update({
124
+ owner,
125
+ repo,
126
+ pull_number: releasePr.number,
127
+ body: `${releasePr.body.trim()}\n- This PR is being recreated ${id}`,
128
+ })
129
+ }
130
+
131
+ const main = async ({ repo: fullRepo, token, dryRun, branch, forcePullRequest }) => {
132
+ if (!token) {
133
+ throw new Error('Token is required')
134
+ }
135
+
136
+ if (!fullRepo) {
137
+ throw new Error('Repo is required')
138
+ }
139
+
140
+ const {
141
+ github,
142
+ octokit,
143
+ manifest,
144
+ owner,
145
+ repo,
146
+ baseBranch,
147
+ } = await getManifest({ repo: fullRepo, token, branch })
56
148
 
57
- const [releasePr] = releasePrs
58
- const id = process.env.GITHUB_RUN_ID
59
- ? `by https://github.com/${owner}/${repo}/actions/runs/${process.env.GITHUB_RUN_ID}`
60
- : `manually starting at ${new Date().toJSON()}`
149
+ let forceReleases = null
61
150
 
62
- // XXX(hack): to get release please to recreate a pull request it needs
63
- // to have a different body string so we append a message a message that CI
64
- // is running. This will force release-please to rebase the PR but it
65
- // wont update the body again, so we only append to it.
66
- await octokit.pulls.update({
151
+ if (forcePullRequest) {
152
+ const { data: releasePr } = await octokit.rest.pulls.get({
67
153
  owner,
68
154
  repo,
69
- pull_number: releasePr.number,
70
- body: `${releasePr.body.trim()}\n- This PR is being recreated ${id}`,
155
+ pull_number: forcePullRequest,
71
156
  })
157
+
158
+ if (!releasePr) {
159
+ throw new Error(`Could not find PR from number: ${forcePullRequest}`)
160
+ }
161
+
162
+ if (releasePr.state === 'open') {
163
+ await touchPullRequest({ octokit, owner, repo, releasePr })
164
+ } else if (releasePr.state === 'closed' && releasePr.merged) {
165
+ forceReleases = await getReleasesFromPr({ manifest, github, number: releasePr.number })
166
+ } else {
167
+ throw new Error(`Could not run workflow on PR with wrong state: ${JSON.stringify(
168
+ releasePr,
169
+ null,
170
+ 2
171
+ )}`)
172
+ }
72
173
  }
73
174
 
74
- const pullRequests = await (dryRun ? manifest.buildPullRequests() : manifest.createPullRequests())
75
- const allReleases = await (dryRun ? manifest.buildReleases() : manifest.createReleases())
175
+ const { pullRequests, releases } = await getReleaseArtifacts({ dryRun, manifest, forceReleases })
76
176
 
77
177
  // We only ever get a single pull request with our current release-please settings
78
- const rootPr = pullRequests.filter(Boolean)?.[0]
178
+ // Update this if we start creating individual PRs per workspace release
179
+ const rootPr = pullRequests[0]
180
+ let rootRelease = releases[0]
181
+
182
+ logger.debug(`pull requests: ${pullRequests.length}`)
183
+ logger.debug(`releases: ${releases.length}`)
184
+
185
+ if (rootPr) {
186
+ logger.debug(`root pr: ${JSON.stringify(omit(rootPr, 'body'), null, 2)}`)
187
+ }
188
+
79
189
  if (rootPr?.number) {
80
190
  const commits = await octokit.paginate(octokit.rest.pulls.listCommits, {
81
191
  owner,
82
192
  repo,
83
193
  pull_number: rootPr.number,
84
194
  })
85
- rootPr.sha = commits?.[commits.length - 1]?.sha
86
- }
87
195
 
88
- const releases = allReleases.filter(Boolean)
89
- let rootRelease = releases[0]
196
+ const prSha = commits?.[commits.length - 1]?.sha
197
+ if (!prSha) {
198
+ throw new Error(`Could not find a latest sha for pull request: ${rootPr.number}`)
199
+ }
200
+
201
+ rootPr.sha = prSha
202
+ }
90
203
 
91
204
  for (const release of releases) {
92
- const prefix = release.path === '.' ? '' : release.path
205
+ const { path, sha } = release
206
+ const prefix = path === '.' ? '' : path
207
+ const isRoot = !prefix
208
+ const packagePath = `${prefix}/package.json`
93
209
 
94
- if (!prefix) {
95
- rootRelease = release
210
+ logger.debug(`release: ${JSON.stringify({
211
+ ...omit(release, 'notes'),
212
+ isRoot,
213
+ prefix,
214
+ }, null, 2)}`)
215
+
216
+ const releasePrNumber = await octokit.rest.repos.listPullRequestsAssociatedWithCommit({
217
+ owner,
218
+ repo,
219
+ commit_sha: sha,
220
+ per_page: 1,
221
+ }).then(r => r.data[0]?.number)
222
+
223
+ if (!releasePrNumber) {
224
+ throw new Error(`Could not find release PR number from commit: "${sha}"`)
96
225
  }
97
226
 
98
- const [releasePr, releasePkg] = await Promise.all([
99
- octokit.rest.repos.listPullRequestsAssociatedWithCommit({
100
- owner,
101
- repo,
102
- commit_sha: release.sha,
103
- }).then(r => r.data[0]),
104
- octokit.rest.repos.getContent({
105
- owner,
106
- repo,
107
- ref: baseBranch,
108
- path: `${prefix}/package.json`,
109
- }).then(r => JSON.parse(Buffer.from(r.data.content, r.data.encoding))),
110
- ])
111
-
112
- release.prNumber = releasePr.number
113
- release.pkgName = releasePkg.name
227
+ logger.debug(`pr from ${sha}: ${releasePrNumber}`)
228
+
229
+ const releasePkgName = await octokit.rest.repos.getContent({
230
+ owner,
231
+ repo,
232
+ ref: baseBranch,
233
+ path: packagePath,
234
+ }).then(r => {
235
+ try {
236
+ return JSON.parse(Buffer.from(r.data.content, r.data.encoding)).name
237
+ } catch {
238
+ return null
239
+ }
240
+ })
241
+
242
+ if (!releasePkgName) {
243
+ throw new Error(`Could not find package name for release at: "${packagePath}#${baseBranch}"`)
244
+ }
245
+
246
+ logger.debug(`pkg name from ${packagePath}#${baseBranch}: "${releasePkgName}"`)
247
+
248
+ release.prNumber = releasePrNumber
249
+ release.pkgName = releasePkgName
250
+ if (isRoot) {
251
+ rootRelease = release
252
+ }
114
253
  }
115
254
 
116
255
  return {
117
- pr: rootPr,
118
- release: rootRelease,
256
+ pr: rootPr ?? null,
257
+ release: rootRelease ?? null,
119
258
  releases: releases.length ? releases : null,
120
259
  }
121
260
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@npmcli/template-oss",
3
- "version": "4.11.3",
3
+ "version": "4.11.4",
4
4
  "description": "templated files used in npm CLI team oss projects",
5
5
  "main": "lib/content/index.js",
6
6
  "bin": {