@npmcli/template-oss 4.20.0 → 4.21.1

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,180 @@
1
+ const RP = require('release-please')
2
+ const {
3
+ DefaultVersioningStrategy,
4
+ } = require('release-please/build/src/versioning-strategies/default.js')
5
+ const {
6
+ PrereleaseVersioningStrategy,
7
+ } = require('release-please/build/src/versioning-strategies/prerelease.js')
8
+ const { ROOT_PROJECT_PATH } = require('release-please/build/src/manifest.js')
9
+ const { CheckpointLogger, logger } = require('release-please/build/src/util/logger.js')
10
+ const assert = require('assert')
11
+ const core = require('@actions/core')
12
+ const omit = require('just-omit')
13
+ const ChangelogNotes = require('./changelog.js')
14
+ const NodeWorkspace = require('./node-workspace.js')
15
+ const NodeWorkspaceFormat = require('./node-workspace-format.js')
16
+ const { getPublishTag, noop } = require('./util.js')
17
+
18
+ class ReleasePlease {
19
+ #token
20
+ #owner
21
+ #repo
22
+ #branch
23
+ #backport
24
+ #defaultTag
25
+ #overrides
26
+ #silent
27
+ #trace
28
+ #info
29
+
30
+ #github
31
+ #octokit
32
+ #manifest
33
+
34
+ constructor ({
35
+ token,
36
+ repo,
37
+ branch,
38
+ backport,
39
+ defaultTag,
40
+ overrides,
41
+ silent,
42
+ trace,
43
+ }) {
44
+ assert(token, 'token is required')
45
+ assert(repo, 'repo is required')
46
+ assert(branch, 'branch is required')
47
+ assert(defaultTag, 'defaultTag is required')
48
+
49
+ this.#token = token
50
+ this.#owner = repo.split('/')[0]
51
+ this.#repo = repo.split('/')[1]
52
+ this.#branch = branch
53
+ this.#backport = backport
54
+ this.#defaultTag = defaultTag
55
+ this.#overrides = overrides
56
+ this.#silent = silent
57
+ this.#trace = trace
58
+ }
59
+
60
+ static async run (options) {
61
+ const releasePlease = new ReleasePlease(options)
62
+ await releasePlease.init()
63
+ return releasePlease.run()
64
+ }
65
+
66
+ async init () {
67
+ RP.registerChangelogNotes('default', ({ github, ...o }) =>
68
+ new ChangelogNotes(github, o))
69
+ RP.registerVersioningStrategy('default', (o) =>
70
+ o.prerelease ? new PrereleaseVersioningStrategy(o) : new DefaultVersioningStrategy(o))
71
+ RP.registerPlugin('node-workspace', ({ github, targetBranch, repositoryConfig, ...o }) =>
72
+ new NodeWorkspace(github, targetBranch, repositoryConfig, o))
73
+ RP.registerPlugin('node-workspace-format', ({ github, targetBranch, repositoryConfig, ...o }) =>
74
+ new NodeWorkspaceFormat(github, targetBranch, repositoryConfig, o))
75
+
76
+ if (this.#silent) {
77
+ this.#info = noop
78
+ RP.setLogger(Object.entries(logger).reduce((acc, [k, v]) => {
79
+ if (typeof v === 'function') {
80
+ acc[k] = noop
81
+ }
82
+ return acc
83
+ }, {}))
84
+ } else {
85
+ this.#info = core.info
86
+ RP.setLogger(new CheckpointLogger(true, !!this.#trace))
87
+ }
88
+
89
+ this.#github = await RP.GitHub.create({
90
+ owner: this.#owner,
91
+ repo: this.#repo,
92
+ token: this.#token,
93
+ })
94
+ this.#octokit = this.#github.octokit
95
+ this.#manifest = await RP.Manifest.fromManifest(
96
+ this.#github,
97
+ this.#branch,
98
+ undefined,
99
+ undefined,
100
+ this.#overrides
101
+ )
102
+ }
103
+
104
+ async run () {
105
+ const rootPr = await this.#getRootPullRequest()
106
+ const releases = await this.#getReleases()
107
+
108
+ if (rootPr) {
109
+ this.#info(`root pr: ${JSON.stringify(omit(rootPr, 'body'))}`)
110
+
111
+ // release please does not guarantee that the release PR will have the latest sha,
112
+ // but we always need it so we can attach the relevant checks to the sha.
113
+ rootPr.sha = await this.#octokit.paginate(this.#octokit.rest.pulls.listCommits, {
114
+ owner: this.#owner,
115
+ repo: this.#repo,
116
+ pull_number: rootPr.number,
117
+ }).then(r => r[r.length - 1].sha)
118
+ }
119
+
120
+ if (releases) {
121
+ this.#info(`found releases: ${releases.length}`)
122
+
123
+ for (const release of releases) {
124
+ this.#info(`release: ${JSON.stringify(omit(release, 'notes'))}`)
125
+
126
+ release.publishTag = getPublishTag(release.version, {
127
+ backport: this.#backport,
128
+ defaultTag: this.#defaultTag,
129
+ })
130
+
131
+ release.prNumber = await this.#octokit.rest.repos.listPullRequestsAssociatedWithCommit({
132
+ owner: this.#owner,
133
+ repo: this.#repo,
134
+ commit_sha: release.sha,
135
+ per_page: 1,
136
+ }).then(r => r.data[0]?.number)
137
+
138
+ release.pkgName = await this.#octokit.rest.repos.getContent({
139
+ owner: this.#owner,
140
+ repo: this.#repo,
141
+ ref: this.#branch,
142
+ path: `${release.path === '.' ? '' : release.path}/package.json`,
143
+ }).then(r => JSON.parse(Buffer.from(r.data.content, r.data.encoding)).name)
144
+ }
145
+ }
146
+
147
+ return {
148
+ pr: rootPr,
149
+ releases: releases,
150
+ }
151
+ }
152
+
153
+ async #getRootPullRequest () {
154
+ // We only ever get a single pull request with our current release-please settings
155
+ // Update this if we start creating individual PRs per workspace release
156
+ const pullRequests = await this.#manifest.createPullRequests()
157
+ return pullRequests.filter(Boolean)[0] ?? null
158
+ }
159
+
160
+ async #getReleases () {
161
+ // if we have a root release, always put it as the first item in the array
162
+ const rawReleases = await this.#manifest.createReleases().then(r => r.filter(Boolean))
163
+ let rootRelease = null
164
+ const workspaceReleases = []
165
+
166
+ for (const release of rawReleases) {
167
+ if (release.path === ROOT_PROJECT_PATH) {
168
+ assert(!rootRelease, 'Multiple root releases detected. This should never happen.')
169
+ rootRelease = release
170
+ } else {
171
+ workspaceReleases.push(release)
172
+ }
173
+ }
174
+
175
+ const releases = [rootRelease, ...workspaceReleases].filter(Boolean)
176
+ return releases.length ? releases : null
177
+ }
178
+ }
179
+
180
+ module.exports = ReleasePlease
@@ -0,0 +1,42 @@
1
+ const semver = require('semver')
2
+
3
+ const SPEC = new RegExp(`([^\\s]+@${semver.src[semver.tokens.FULLPLAIN]})`, 'g')
4
+ const code = (c) => `\`${c}\``
5
+ const wrapSpecs = (str) => str.replace(SPEC, code('$1'))
6
+ const block = (lang) => `\`\`\`${lang ? `${lang}` : ''}`
7
+ const link = (text, url) => `[${text}](${url})`
8
+ const list = (text) => `* ${text}`
9
+ const formatDate = (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
+ }
15
+
16
+ const getPublishTag = (v, { backport, defaultTag }) => {
17
+ const version = semver.parse(v)
18
+ return version.prerelease.length
19
+ ? `prerelease-${version.major}`
20
+ : backport ? `latest-${backport}`
21
+ : defaultTag.replace(/{{\s*major\s*}}/, version.major)
22
+ }
23
+
24
+ const makeGitHubUrl = (owner, repo) =>
25
+ (...p) => `https://github.com/${owner}/${repo}/${p.join('/')}`
26
+
27
+ const noop = () => {}
28
+
29
+ module.exports = {
30
+ // we use this string a lot, its duplicated in the changelog sections but its hard
31
+ // to access from within release please so we keep it here also.
32
+ DEPS: 'deps',
33
+ wrapSpecs,
34
+ code,
35
+ block,
36
+ link,
37
+ list,
38
+ formatDate,
39
+ getPublishTag,
40
+ makeGitHubUrl,
41
+ noop,
42
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@npmcli/template-oss",
3
- "version": "4.20.0",
3
+ "version": "4.21.1",
4
4
  "description": "templated files used in npm CLI team oss projects",
5
5
  "main": "lib/content/index.js",
6
6
  "bin": {
@@ -19,7 +19,8 @@
19
19
  "postlint": "template-oss-check",
20
20
  "postinstall": "template-oss-apply",
21
21
  "test-all": "npm run test -ws -iwr --if-present",
22
- "lint-all": "npm run lint -ws -iwr --if-present"
22
+ "lint-all": "npm run lint -ws -iwr --if-present",
23
+ "test:record": "TAP_SNAPSHOT=1 NOCK_RECORD=1 tap"
23
24
  },
24
25
  "repository": {
25
26
  "type": "git",
@@ -41,6 +42,7 @@
41
42
  "@npmcli/map-workspaces": "^3.0.0",
42
43
  "@npmcli/package-json": "^5.0.0",
43
44
  "@octokit/rest": "^19.0.4",
45
+ "dedent": "^1.5.1",
44
46
  "diff": "^5.0.0",
45
47
  "glob": "^10.1.0",
46
48
  "handlebars": "^4.7.7",
@@ -49,12 +51,14 @@
49
51
  "json-parse-even-better-errors": "^3.0.0",
50
52
  "just-deep-map-values": "^1.1.1",
51
53
  "just-diff": "^6.0.0",
54
+ "just-omit": "^2.2.0",
52
55
  "lodash": "^4.17.21",
53
56
  "minimatch": "^9.0.2",
54
57
  "npm-package-arg": "^11.0.1",
55
58
  "proc-log": "^3.0.0",
56
- "release-please": "npm:@npmcli/release-please@^14.2.6",
59
+ "release-please": "16.3.1",
57
60
  "semver": "^7.3.5",
61
+ "undici": "^5.27.2",
58
62
  "yaml": "^2.1.1"
59
63
  },
60
64
  "files": [
@@ -64,6 +68,7 @@
64
68
  "devDependencies": {
65
69
  "@npmcli/eslint-config": "^4.0.0",
66
70
  "@npmcli/template-oss": "file:./",
71
+ "nock": "^13.3.8",
67
72
  "tap": "^16.0.0"
68
73
  },
69
74
  "tap": {
@@ -74,7 +79,10 @@
74
79
  "--exclude",
75
80
  "tap-snapshots/**"
76
81
  ],
77
- "test-ignore": "^(workspace/test-workspace)/"
82
+ "test-ignore": "^(workspace/test-workspace)/",
83
+ "node-arg": [
84
+ "--no-experimental-fetch"
85
+ ]
78
86
  },
79
87
  "templateOSS": {
80
88
  "//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.",
@@ -1,54 +0,0 @@
1
- {{#if jobCheck.sha}}
2
- - name: Get Workflow Job
3
- uses: actions/github-script@v6
4
- if: {{ jobCheck.sha }}
5
- id: check-output
6
- env:
7
- JOB_NAME: "{{#if jobName}}{{ jobName }}{{else}}{{ jobCheck.name }}{{/if}}"
8
- MATRIX_NAME: "{{#if jobIsMatrix}} - $\{{ matrix.platform.name }} - $\{{ matrix.node-version }}{{/if}}"
9
- with:
10
- script: |
11
- const { owner, repo } = context.repo
12
-
13
- const { data } = await github.rest.actions.listJobsForWorkflowRun({
14
- owner,
15
- repo,
16
- run_id: context.runId,
17
- per_page: 100
18
- })
19
-
20
- const jobName = process.env.JOB_NAME + process.env.MATRIX_NAME
21
- const job = data.jobs.find(j => j.name.endsWith(jobName))
22
- const jobUrl = job?.html_url
23
-
24
- const shaUrl = `${context.serverUrl}/${owner}/${repo}/commit/$\{{ {{ jobCheck.sha }} }}`
25
-
26
- let summary = `This check is assosciated with ${shaUrl}\n\n`
27
-
28
- if (jobUrl) {
29
- summary += `For run logs, click here: ${jobUrl}`
30
- } else {
31
- summary += `Run logs could not be found for a job with name: "${jobName}"`
32
- }
33
-
34
- return { summary }
35
- {{/if}}
36
- - name: {{#if jobCheck.sha}}Create{{else}}Conclude{{/if}} Check
37
- uses: LouisBrunner/checks-action@v1.6.0
38
- {{#if jobCheck.sha}}
39
- id: check
40
- if: {{ jobCheck.sha }}
41
- {{else}}
42
- if: {{#if jobCheck.id}}{{ jobCheck.id }}{{else}}steps.check.outputs.check_id{{/if}} && always()
43
- {{/if}}
44
- with:
45
- token: $\{{ secrets.GITHUB_TOKEN }}
46
- {{#if jobCheck.sha}}
47
- status: {{#if jobCheck.status}}{{ jobCheck.status }}{{else}}in_progress{{/if}}
48
- name: {{#if jobCheck.name}}{{ jobCheck.name }}{{else}}{{ jobName }}{{/if}}{{#if jobIsMatrix}} - $\{{ matrix.platform.name }} - $\{{ matrix.node-version }}{{/if}}
49
- sha: $\{{ {{ jobCheck.sha }} }}
50
- output: $\{{ steps.check-output.outputs.result }}
51
- {{else}}
52
- conclusion: $\{{ {{#if jobCheck.status}}{{ jobCheck.status }}{{else}}job.status{{/if}} }}
53
- check_id: $\{{ {{#if jobCheck.id}}{{ jobCheck.id }}{{else}}steps.check.outputs.check_id{{/if}} }}
54
- {{/if}}
@@ -1,92 +0,0 @@
1
- const makeGh = require('./github.js')
2
- const { link, code, specRe, list, dateFmt } = require('./util')
3
-
4
- module.exports = class ChangelogNotes {
5
- constructor (options) {
6
- this.gh = makeGh(options.github)
7
- }
8
-
9
- buildEntry (commit, authors = []) {
10
- const breaking = commit.notes
11
- .filter(n => n.title === 'BREAKING CHANGE')
12
- .map(n => n.text)
13
-
14
- const entry = []
15
-
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)))
19
- }
20
-
21
- // A link to the pull request if the commit has one
22
- const prNumber = commit.pullRequest?.number
23
- if (prNumber) {
24
- entry.push(link(`#${prNumber}`, this.gh.pull(prNumber)))
25
- }
26
-
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(', ')})`)
35
- }
36
-
37
- return {
38
- entry: entry.join(' '),
39
- breaking,
40
- }
41
- }
42
-
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: [],
49
- }
50
- }
51
- return acc
52
- }, {
53
- breaking: {
54
- title: '⚠️ BREAKING CHANGES',
55
- entries: [],
56
- },
57
- })
58
-
59
- // Only continue with commits that will make it to our changelog
60
- const commits = rawCommits.filter(c => changelog[c.type])
61
-
62
- const authorsByCommit = await this.gh.authors(commits)
63
-
64
- // Group commits by type
65
- for (const commit of commits) {
66
- // when rebase merging multiple commits with a single PR, only the first commit
67
- // will have a pr number when coming from release-please. this check will manually
68
- // lookup commits without a pr number and find one if it exists
69
- if (!commit.pullRequest?.number) {
70
- commit.pullRequest = { number: await this.gh.commitPrNumber(commit) }
71
- }
72
- const { entry, breaking } = this.buildEntry(
73
- commit,
74
- authorsByCommit[commit.sha]
75
- )
76
-
77
- // Collect commits by type
78
- changelog[commit.type].entries.push(entry)
79
-
80
- // And push breaking changes to its own section
81
- changelog.breaking.entries.push(...breaking)
82
- }
83
-
84
- const sections = Object.values(changelog)
85
- .filter((s) => s.entries.length)
86
- .map(({ title, entries }) => [`### ${title}`, entries.map(list).join('\n')].join('\n\n'))
87
-
88
- const title = `## ${link(version, this.gh.compare(previousTag, currentTag))} (${dateFmt()})`
89
-
90
- return [title, ...sections].join('\n\n').trim()
91
- }
92
- }
@@ -1,72 +0,0 @@
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
- try {
14
- const { repository } = await gh.graphql(
15
- `fragment CommitAuthors on GitObject {
16
- ... on Commit {
17
- authors (first:10) {
18
- nodes {
19
- user { login }
20
- name
21
- }
22
- }
23
- }
24
- }
25
- query {
26
- repository (owner:"${owner}", name:"${repo}") {
27
- ${shas.map((s) => {
28
- return `_${s}: object (expression: "${s}") { ...CommitAuthors }`
29
- })}
30
- }
31
- }`
32
- )
33
-
34
- for (const [key, commit] of Object.entries(repository)) {
35
- if (commit) {
36
- response[key.slice(1)] = commit.authors.nodes
37
- .map((a) => a.user && a.user.login ? `@${a.user.login}` : a.name)
38
- .filter(Boolean)
39
- }
40
- }
41
-
42
- return response
43
- } catch {
44
- return response
45
- }
46
- }
47
-
48
- const commitPrNumber = async (commit) => {
49
- try {
50
- const res = await gh.octokit.rest.repos.listPullRequestsAssociatedWithCommit({
51
- owner,
52
- repo,
53
- commit_sha: commit.sha,
54
- per_page: 1,
55
- })
56
- return res.data[0].number
57
- } catch {
58
- return null
59
- }
60
- }
61
-
62
- const url = (...p) => `https://github.com/${owner}/${repo}/${p.join('/')}`
63
-
64
- return {
65
- authors,
66
- commitPrNumber,
67
- pull: (number) => url('pull', number),
68
- commit: (sha) => url('commit', sha),
69
- compare: (a, b) => a ? url('compare', `${a.toString()}...${b.toString()}`) : null,
70
- release: (tag) => url('releases', 'tag', tag.toString()),
71
- }
72
- }