@npmcli/template-oss 4.4.5 → 4.5.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.
@@ -0,0 +1,260 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { Octokit } = require('@octokit/rest')
4
+ const semver = require('semver')
5
+ const mapWorkspaces = require('@npmcli/map-workspaces')
6
+ const { join } = require('path')
7
+
8
+ const log = (...logs) => console.error('LOG', ...logs)
9
+
10
+ const ROOT = process.cwd()
11
+ const pkg = require(join(ROOT, 'package.json'))
12
+
13
+ /* eslint-disable max-len */
14
+ const DEFAULT_RELEASE_PROCESS = `
15
+ 1. Checkout the release branch and test
16
+
17
+ \`\`\`sh
18
+ gh pr checkout <PR-NUMBER> --force
19
+ npm i
20
+ npm test
21
+ gh pr checks --watch
22
+ \`\`\`
23
+
24
+ 1. Publish workspaces
25
+
26
+ \`\`\`sh
27
+ npm publish -w <WS-PKG-N>
28
+ \`\`\`
29
+
30
+ 1. Publish
31
+
32
+ \`\`\`sh
33
+ npm publish <PUBLISH-FLAGS>
34
+ \`\`\`
35
+
36
+ 1. Merge release PR
37
+
38
+ \`\`\`sh
39
+ gh pr merge --rebase
40
+ git checkout <BASE-BRANCH>
41
+ git fetch
42
+ git reset --hard origin/<BASE-BRANCH>
43
+ \`\`\`
44
+
45
+ 1. Check For Release Tags
46
+
47
+ Release Please will run on the just pushed release commit and create GitHub releases and tags for each package.
48
+
49
+ \`\`\`
50
+ gh run watch \`gh run list -w release -b <BASE-BRANCH> -L 1 --json databaseId -q ".[0].databaseId"\`
51
+ \`\`\`
52
+ ` /* eslint-enable max-len */
53
+
54
+ const getReleaseProcess = async ({ owner, repo }) => {
55
+ const RELEASE_LIST_ITEM = /^\d+\.\s/gm
56
+
57
+ log(`Fetching release process from:`, owner, repo, 'wiki')
58
+
59
+ let releaseProcess = ''
60
+ try {
61
+ releaseProcess = await new Promise((resolve, reject) => {
62
+ require('https')
63
+ .get(`https://raw.githubusercontent.com/wiki/${owner}/${repo}/Release-Process.md`, resp => {
64
+ let d = ''
65
+ resp.on('data', c => (d += c))
66
+ resp.on('end', () => {
67
+ if (resp.statusCode !== 200) {
68
+ reject(new Error(`${resp.req.protocol + resp.req.host + resp.req.path}: ${d}`))
69
+ } else {
70
+ resolve(d)
71
+ }
72
+ })
73
+ })
74
+ .on('error', reject)
75
+ })
76
+ } catch (e) {
77
+ log('Release wiki not found', e.message)
78
+ log('Using default release process')
79
+ releaseProcess = DEFAULT_RELEASE_PROCESS.trim() + '\n'
80
+ }
81
+
82
+ // XXX: the release steps need to always be the last thing in the doc for this to work
83
+ const releaseLines = releaseProcess.split('\n')
84
+ const releaseStartLine = releaseLines.reduce((acc, line, index) =>
85
+ line.match(/^#+\s/) ? index : acc, 0)
86
+ const section = releaseLines.slice(releaseStartLine).join('\n')
87
+
88
+ return section.split({
89
+ [Symbol.split] (str) {
90
+ const [, ...matches] = str.split(RELEASE_LIST_ITEM)
91
+ log(`Found ${matches.length} release items`)
92
+ return matches.map((m) => `- [ ] <STEP_INDEX>. ${m}`.trim())
93
+ },
94
+ })
95
+ }
96
+
97
+ const getPrReleases = async (pr) => {
98
+ const RELEASE_SEPARATOR = /<details><summary>.*<\/summary>/g
99
+ const MONO_VERSIONS = /<details><summary>(?:(.*?):\s)?(.*?)<\/summary>/
100
+ const ROOT_VERSION = /\n##\s\[(.*?)\]/
101
+
102
+ const workspaces = [...await mapWorkspaces({ pkg: pkg, cwd: ROOT })].reduce((acc, [k]) => {
103
+ const wsComponentName = k.startsWith('@') ? k.split('/')[1] : k
104
+ acc[wsComponentName] = k
105
+ return acc
106
+ }, {})
107
+
108
+ const getReleaseInfo = ({ name, version: rawVersion }) => {
109
+ const version = semver.parse(rawVersion)
110
+ const prerelease = !!version.prerelease.length
111
+ const tag = `${name ? `${name}-` : ''}v${rawVersion}`
112
+ const workspace = workspaces[name]
113
+
114
+ return {
115
+ name,
116
+ tag,
117
+ prerelease,
118
+ version: rawVersion,
119
+ major: version.major,
120
+ url: `https://github.com/${pr.base.repo.full_name}/releases/tag/${tag}`,
121
+ flags: `${name ? `-w ${workspace}` : ''} ${prerelease ? `--tag prerelease` : ''}`.trim(),
122
+ }
123
+ }
124
+
125
+ const releases = pr.body.match(RELEASE_SEPARATOR)
126
+
127
+ if (!releases) {
128
+ log('Found no monorepo, checking for single root version')
129
+ const [, version] = pr.body.match(ROOT_VERSION) || []
130
+
131
+ if (!version) {
132
+ throw new Error('Could not find version with:', ROOT_VERSION)
133
+ }
134
+
135
+ log('Found version', version)
136
+ return [getReleaseInfo({ version })]
137
+ }
138
+
139
+ log(`Found ${releases.length} releases`)
140
+
141
+ return releases.reduce((acc, r) => {
142
+ const [, name, version] = r.match(MONO_VERSIONS)
143
+ const release = getReleaseInfo({ name, version })
144
+
145
+ if (!name) {
146
+ log('Found root', release)
147
+ acc[0] = release
148
+ } else {
149
+ log('Found workspace', release)
150
+ acc[1].push(release)
151
+ }
152
+
153
+ return acc
154
+ }, [null, []])
155
+ }
156
+
157
+ const appendToComment = async ({ github, commentId, title, body }) => {
158
+ if (!commentId) {
159
+ log(`No comment id, skipping append to comment`)
160
+ return
161
+ }
162
+
163
+ const { data: comment } = await github.rest.issues.getComment({
164
+ ...github.repo,
165
+ comment_id: commentId,
166
+ })
167
+
168
+ const hasAppended = comment.body.includes(title)
169
+
170
+ log('Found comment with id:', commentId)
171
+ log(hasAppended ? 'Comment has aready been appended, replacing' : 'Appending to comment')
172
+
173
+ const prefix = hasAppended
174
+ ? comment.body.split(title)[0]
175
+ : comment.body
176
+
177
+ return github.rest.issues.updateComment({
178
+ ...github.repo,
179
+ comment_id: commentId,
180
+ body: [prefix, title, body].join('\n\n'),
181
+ })
182
+ }
183
+
184
+ const main = async (env) => {
185
+ // These env vars are set by the release.yml workflow from template-oss
186
+ const {
187
+ CI,
188
+ GITHUB_TOKEN,
189
+ GITHUB_REPOSITORY,
190
+ RELEASE_PR_NUMBER,
191
+ RELEASE_COMMENT_ID, // comment is optional for testing
192
+ } = env
193
+
194
+ if (!CI || !GITHUB_TOKEN || !GITHUB_REPOSITORY || !RELEASE_PR_NUMBER) {
195
+ throw new Error('This script is designed to run in CI. If you want to test it, set the ' +
196
+ `following env vars: \`CI, GITHUB_TOKEN, GITHUB_REPOSITORY, RELEASE_PR_NUMBER\``)
197
+ }
198
+
199
+ const [owner, repo] = GITHUB_REPOSITORY.split('/')
200
+ const github = new Octokit({ auth: GITHUB_TOKEN })
201
+ github.repo = { owner, repo }
202
+
203
+ const { data: pr } = await github.rest.pulls.get({
204
+ ...github.repo,
205
+ pull_number: RELEASE_PR_NUMBER,
206
+ })
207
+
208
+ const [release, workspaces = []] = await getPrReleases(pr)
209
+
210
+ const RELEASE_OMIT_PRERELEASE = '> NOT FOR PRERELEASE'
211
+ const RELEASE_OMIT_WORKSPACES = 'Publish workspaces'
212
+ const releaseItems = (await getReleaseProcess({ owner, repo }))
213
+ .filter((item) => {
214
+ if (release.prerelease && item.includes(RELEASE_OMIT_PRERELEASE)) {
215
+ return false
216
+ }
217
+
218
+ if (!workspaces.length && item.includes(RELEASE_OMIT_WORKSPACES)) {
219
+ return false
220
+ }
221
+
222
+ return true
223
+ })
224
+ .map((item, index) => item.replace('<STEP_INDEX>', index + 1))
225
+
226
+ log(
227
+ `Filtered ${releaseItems.length} release process items:\n`,
228
+ releaseItems.map(r => r.split('\n')[0].replace('- [ ] ', '')).join(', ')
229
+ )
230
+
231
+ const releaseTitle = `### Release Checklist for ${release.tag}`
232
+ const releaseChecklist = releaseItems
233
+ .join('\n\n')
234
+ .replace(/<PR-NUMBER>/g, RELEASE_PR_NUMBER)
235
+ .replace(/<RELEASE-BRANCH>/g, pr.head.ref)
236
+ .replace(/<BASE-BRANCH>/g, pr.base.ref)
237
+ .replace(/<MAJOR>/g, release.major)
238
+ .replace(/<X\.Y\.Z>/g, release.version)
239
+ .replace(/<GITHUB-RELEASE-LINK>/g, release.url)
240
+ .replace(/<PUBLISH-FLAGS>/g, release.flags)
241
+ .replace(/^(\s*\S.*)(-w <WS-PKG-N>)$/gm, workspaces.map(w => `$1${w.flags}`).join('\n'))
242
+ .trim()
243
+
244
+ await appendToComment({
245
+ github,
246
+ commentId: RELEASE_COMMENT_ID,
247
+ title: releaseTitle,
248
+ body: releaseChecklist,
249
+ })
250
+
251
+ if (!RELEASE_COMMENT_ID) {
252
+ console.log(releaseChecklist)
253
+ }
254
+ }
255
+
256
+ main(process.env)
257
+ // This is part of the release CI and is for posting a release manager
258
+ // comment to the issue but we dont want it to ever fail the workflow so
259
+ // just log but dont set the error code
260
+ .catch(err => console.error(err))
package/lib/config.js CHANGED
@@ -178,7 +178,7 @@ const getFullConfig = async ({
178
178
  pkgDir: posixDir(pkgPath),
179
179
  pkgGlob: posixGlob(pkgPath),
180
180
  pkgFlags: isRoot ? '-iwr' : `-w ${pkgJson.name}`,
181
- allFlags: '-ws -iwr --if-present',
181
+ allFlags: `${workspaces.length ? '-ws -iwr' : ''} --if-present`.trim(),
182
182
  workspacePaths,
183
183
  workspaceGlobs: workspacePaths.map(posixGlob),
184
184
  // booleans to control application of updates
@@ -11,7 +11,7 @@ jobs:
11
11
  {{> job
12
12
  jobName="template-oss"
13
13
  jobIf="github.actor == 'dependabot[bot]'"
14
- jobCheckout=(obj ref="${{ github.ref_name }}")
14
+ jobCheckout=(obj ref="${{ github.event.pull_request.head.ref }}")
15
15
  }}
16
16
  - name: Fetch Dependabot Metadata
17
17
  id: metadata
@@ -77,6 +77,7 @@ jobs:
77
77
  RELEASE_COMMENT_ID: $\{{ needs.release.outputs.comment-id }}
78
78
  GITHUB_TOKEN: $\{{ secrets.GITHUB_TOKEN }}
79
79
  run: |
80
+ {{ rootNpmPath }} exec --offline -- template-oss-release-manager
80
81
  {{ rootNpmPath }} run rp-pull-request --ignore-scripts {{ allFlags }}
81
82
  - name: Commit
82
83
  id: commit
@@ -81,9 +81,13 @@ module.exports = class extends NodeWorkspace {
81
81
  for (const [depName, resolved] of graphPackage.localDependencies) {
82
82
  const depVersion = updatedVersions.get(depName)
83
83
  const isNotDir = resolved.type !== 'directory'
84
- // Changelog entries are only added for dependencies and not any other type
85
- const isDep = Object.prototype.hasOwnProperty.call(pkg.dependencies, depName)
86
- if (depVersion && isNotDir && isDep) {
84
+
85
+ // Due to some bugs in release-please we need to create `deps:` commits
86
+ // for all dev and prod dependencies even though we normally would only
87
+ // do a `chore:` commit to bump a dev dep. The tradeoff is an extra
88
+ // patch release to workspaces that depend on other workspaces as dev
89
+ // dependencies.
90
+ if (depVersion && isNotDir) {
87
91
  commitsByPath[path].push({
88
92
  message: `deps(${SCOPE}): ${depName} ${depVersion.toString()}`,
89
93
  })
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@npmcli/template-oss",
3
- "version": "4.4.5",
3
+ "version": "4.5.0",
4
4
  "description": "templated files used in npm CLI team oss projects",
5
5
  "main": "lib/content/index.js",
6
6
  "bin": {
7
7
  "template-oss-apply": "bin/apply.js",
8
8
  "template-oss-check": "bin/check.js",
9
- "template-oss-release-please": "bin/release-please.js"
9
+ "template-oss-release-please": "bin/release-please.js",
10
+ "template-oss-release-manager": "bin/release-manager.js"
10
11
  },
11
12
  "scripts": {
12
13
  "lint": "eslint \"**/*.js\"",
@@ -37,6 +38,7 @@
37
38
  "@npmcli/git": "^3.0.0",
38
39
  "@npmcli/map-workspaces": "^2.0.2",
39
40
  "@npmcli/package-json": "^2.0.0",
41
+ "@octokit/rest": "^19.0.4",
40
42
  "diff": "^5.0.0",
41
43
  "glob": "^8.0.1",
42
44
  "handlebars": "^4.7.7",