@npmcli/template-oss 3.6.0 → 3.8.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 +31 -0
- package/lib/config.js +6 -0
- package/lib/content/audit.yml +1 -1
- package/lib/content/ci.yml +2 -2
- package/lib/content/index.js +37 -8
- package/lib/content/post-dependabot.yml +3 -5
- package/lib/content/pull-request.yml +3 -4
- package/lib/content/release-please-config.json +13 -0
- package/lib/content/release-please-manifest.json +3 -0
- package/lib/content/release-please.yml +48 -34
- package/lib/content/release-test.yml +46 -0
- package/lib/content/setup-deps.yml +1 -0
- package/lib/release-please/changelog.js +225 -0
- package/lib/release-please/index.js +39 -0
- package/lib/release-please/logger.js +3 -0
- package/lib/release-please/version.js +76 -0
- package/lib/release-please/workspace-deps.js +72 -0
- package/lib/util/has-package.js +8 -0
- package/lib/util/parser.js +1 -1
- package/package.json +10 -2
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const core = require('@actions/core')
|
|
4
|
+
const main = require('../lib/release-please/index.js')
|
|
5
|
+
|
|
6
|
+
const dryRun = !process.env.CI
|
|
7
|
+
const [branch] = process.argv.slice(2)
|
|
8
|
+
|
|
9
|
+
const setOutput = (key, val) => {
|
|
10
|
+
if (val && (!Array.isArray(val) || val.length)) {
|
|
11
|
+
if (dryRun) {
|
|
12
|
+
console.log(key, JSON.stringify(val, null, 2))
|
|
13
|
+
} else {
|
|
14
|
+
core.setOutput(key, JSON.stringify(val))
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
main({
|
|
20
|
+
token: process.env.GITHUB_TOKEN,
|
|
21
|
+
repo: process.env.GITHUB_REPOSITORY,
|
|
22
|
+
dryRun,
|
|
23
|
+
branch,
|
|
24
|
+
}).then(({ pr, releases, release }) => {
|
|
25
|
+
setOutput('pr', pr)
|
|
26
|
+
setOutput('releases', releases)
|
|
27
|
+
setOutput('release', release)
|
|
28
|
+
return null
|
|
29
|
+
}).catch(err => {
|
|
30
|
+
core.setFailed(`failed: ${err}`)
|
|
31
|
+
})
|
package/lib/config.js
CHANGED
|
@@ -101,6 +101,12 @@ const getConfig = async ({
|
|
|
101
101
|
const derived = {
|
|
102
102
|
isRoot,
|
|
103
103
|
isWorkspace: !isRoot,
|
|
104
|
+
// Some files are written to the base of a repo but will
|
|
105
|
+
// include configuration for all packages within a monorepo
|
|
106
|
+
// For these cases it is helpful to know if we are in a
|
|
107
|
+
// monorepo since template-oss might be used only for
|
|
108
|
+
// workspaces and not the root or vice versa.
|
|
109
|
+
isMono: (isRoot && workspaces.length > 0) || !isRoot,
|
|
104
110
|
// repo
|
|
105
111
|
repoDir: root,
|
|
106
112
|
repoFiles,
|
package/lib/content/audit.yml
CHANGED
package/lib/content/ci.yml
CHANGED
|
@@ -28,7 +28,7 @@ jobs:
|
|
|
28
28
|
steps:
|
|
29
29
|
{{> setupGit}}
|
|
30
30
|
{{> setupNode}}
|
|
31
|
-
|
|
31
|
+
{{> setupDeps}}
|
|
32
32
|
- run: npm run lint {{~#if isWorkspace}} -w {{pkgName}}{{/if}}
|
|
33
33
|
|
|
34
34
|
test:
|
|
@@ -55,7 +55,7 @@ jobs:
|
|
|
55
55
|
steps:
|
|
56
56
|
{{> setupGit}}
|
|
57
57
|
{{> setupNode useMatrix=true}}
|
|
58
|
+
{{> setupDeps}}
|
|
58
59
|
- name: add tap problem matcher
|
|
59
60
|
run: echo "::add-matcher::.github/matchers/tap.json"
|
|
60
|
-
- run: npm i --ignore-scripts --no-audit --no-fund
|
|
61
61
|
- run: npm test --ignore-scripts {{~#if isWorkspace}} -w {{pkgName}}{{/if}}
|
package/lib/content/index.js
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
const { name: NAME, version: LATEST_VERSION } = require('../../package.json')
|
|
2
2
|
|
|
3
|
+
const releasePlease = () => ({
|
|
4
|
+
'.github/workflows/release-please.yml': {
|
|
5
|
+
file: 'release-please.yml',
|
|
6
|
+
filter: (o) => !o.pkg.private,
|
|
7
|
+
},
|
|
8
|
+
'.github/workflows/release-test.yml': {
|
|
9
|
+
file: 'release-test.yml',
|
|
10
|
+
filter: (o) => !o.pkg.private && o.config.releaseTest === 'release-test.yml',
|
|
11
|
+
},
|
|
12
|
+
'.release-please-manifest.json': {
|
|
13
|
+
file: 'release-please-manifest.json',
|
|
14
|
+
filter: (o) => !o.pkg.private,
|
|
15
|
+
parser: (p) => class NoCommentJson extends p.JsonMerge {
|
|
16
|
+
comment = null
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
'release-please-config.json': {
|
|
20
|
+
file: 'release-please-config.json',
|
|
21
|
+
filter: (o) => !o.pkg.private,
|
|
22
|
+
parser: (p) => class NoCommentJson extends p.JsonMerge {
|
|
23
|
+
comment = null
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
})
|
|
27
|
+
|
|
3
28
|
// Changes applied to the root of the repo
|
|
4
29
|
const rootRepo = {
|
|
5
30
|
add: {
|
|
@@ -14,10 +39,7 @@ const rootRepo = {
|
|
|
14
39
|
'.github/workflows/codeql-analysis.yml': 'codeql-analysis.yml',
|
|
15
40
|
'.github/workflows/post-dependabot.yml': 'post-dependabot.yml',
|
|
16
41
|
'.github/workflows/pull-request.yml': 'pull-request.yml',
|
|
17
|
-
|
|
18
|
-
file: 'release-please.yml',
|
|
19
|
-
filter: (o) => !o.pkg.private,
|
|
20
|
-
},
|
|
42
|
+
...releasePlease(),
|
|
21
43
|
},
|
|
22
44
|
}
|
|
23
45
|
|
|
@@ -43,12 +65,14 @@ const rootModule = {
|
|
|
43
65
|
// Changes for each workspace but applied to the root of the repo
|
|
44
66
|
const workspaceRepo = {
|
|
45
67
|
add: {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
filter: (o) => !o.pkg.private,
|
|
49
|
-
},
|
|
68
|
+
...releasePlease(true),
|
|
69
|
+
'.github/matchers/tap.json': 'tap.json',
|
|
50
70
|
'.github/workflows/ci-{{pkgNameFs}}.yml': 'ci.yml',
|
|
51
71
|
},
|
|
72
|
+
rm: [
|
|
73
|
+
// These are the old release please files that should be removed now
|
|
74
|
+
'.github/workflows/release-please-{{pkgNameFs}}.yml',
|
|
75
|
+
],
|
|
52
76
|
}
|
|
53
77
|
|
|
54
78
|
// Changes for each workspace but applied to the relative workspace dir
|
|
@@ -72,6 +96,11 @@ module.exports = {
|
|
|
72
96
|
workspaceModule,
|
|
73
97
|
windowsCI: true,
|
|
74
98
|
branches: ['main', 'latest'],
|
|
99
|
+
defaultBranch: 'main',
|
|
100
|
+
// Escape hatch since we write a release test file but the
|
|
101
|
+
// CLI has a very custom one we dont want to overwrite. This
|
|
102
|
+
// setting allows us to call a workflow by any name during release
|
|
103
|
+
releaseTest: 'release-test.yml',
|
|
75
104
|
distPaths: ['bin/', 'lib/'],
|
|
76
105
|
ciVersions: ['12.13.0', '12.x', '14.15.0', '14.x', '16.0.0', '16.x'],
|
|
77
106
|
lockfile: false,
|
|
@@ -12,22 +12,20 @@ jobs:
|
|
|
12
12
|
runs-on: ubuntu-latest
|
|
13
13
|
if: github.actor == 'dependabot[bot]'
|
|
14
14
|
steps:
|
|
15
|
-
{{> setupGit}}
|
|
15
|
+
{{> setupGit with=(obj ref="${{ github.event.pull_request.head_ref }}")}}
|
|
16
16
|
{{> setupNode}}
|
|
17
|
+
{{> setupDeps}}
|
|
17
18
|
- name: Dependabot metadata
|
|
18
19
|
id: metadata
|
|
19
20
|
uses: dependabot/fetch-metadata@v1.1.1
|
|
20
21
|
with:
|
|
21
22
|
github-token: "$\{{ secrets.GITHUB_TOKEN }}"
|
|
22
|
-
- name:
|
|
23
|
+
- name: Apply {{__NAME__}} changes and lint
|
|
23
24
|
if: contains(steps.metadata.outputs.dependency-names, '{{__NAME__}}')
|
|
24
25
|
env:
|
|
25
26
|
GITHUB_TOKEN: $\{{ secrets.GITHUB_TOKEN }}
|
|
26
27
|
run: |
|
|
27
|
-
gh pr checkout $\{{ github.event.pull_request.number }}
|
|
28
|
-
npm install --ignore-scripts --no-audit --no-fund
|
|
29
28
|
npm run template-oss-apply
|
|
30
|
-
git add .
|
|
31
29
|
git commit -am "chore: postinstall for dependabot template-oss PR"
|
|
32
30
|
git push
|
|
33
31
|
npm run lint
|
|
@@ -15,11 +15,10 @@ jobs:
|
|
|
15
15
|
steps:
|
|
16
16
|
{{> setupGit with=(obj fetch-depth=0)}}
|
|
17
17
|
{{> setupNode}}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
- name: Check commits OR PR title
|
|
18
|
+
{{> setupDeps}}
|
|
19
|
+
- name: Check commits or PR title
|
|
21
20
|
env:
|
|
22
21
|
PR_TITLE: $\{{ github.event.pull_request.title }}
|
|
23
22
|
run: |
|
|
24
|
-
npx --offline commitlint -V --from origin/
|
|
23
|
+
npx --offline commitlint -V --from origin/{{defaultBranch}} --to $\{{ github.event.pull_request.head.sha }} \
|
|
25
24
|
|| echo $PR_TITLE | npx --offline commitlint -V
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"separate-pull-requests": {{{del}}},
|
|
3
|
+
"plugins": {{#if isMono}}["node-workspace", "workspace-deps"]{{else}}{{{del}}}{{/if}},
|
|
4
|
+
"exclude-packages-from-root": true,
|
|
5
|
+
"group-pull-request-title-pattern": "chore: release ${version}",
|
|
6
|
+
"pull-request-title-pattern": "chore: release${component} ${version}",
|
|
7
|
+
"changelog-sections": {{{json changelogTypes}}},
|
|
8
|
+
"packages": {
|
|
9
|
+
"{{#unless pkgRelPath}}.{{/unless}}{{pkgRelPath}}": {
|
|
10
|
+
{{#unless pkgRelPath}}"package-name": ""{{/unless}}
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -1,56 +1,70 @@
|
|
|
1
|
-
name: Release Please
|
|
1
|
+
name: Release Please
|
|
2
2
|
|
|
3
3
|
on:
|
|
4
4
|
push:
|
|
5
|
-
{{#if pkgRelPath}}
|
|
6
|
-
paths:
|
|
7
|
-
- {{pkgRelPath}}/**
|
|
8
|
-
{{/if}}
|
|
9
5
|
branches:
|
|
10
6
|
{{#each branches}}
|
|
11
7
|
- {{.}}
|
|
12
8
|
{{/each}}
|
|
13
9
|
|
|
14
|
-
{{#if isWorkspace}}
|
|
15
10
|
permissions:
|
|
16
11
|
contents: write
|
|
17
12
|
pull-requests: write
|
|
18
|
-
{{/if}}
|
|
19
13
|
|
|
20
14
|
jobs:
|
|
21
15
|
release-please:
|
|
22
16
|
runs-on: ubuntu-latest
|
|
17
|
+
outputs:
|
|
18
|
+
pr: $\{{ steps.release.outputs.pr }}
|
|
19
|
+
release: $\{{ steps.release.outputs.release }}
|
|
23
20
|
steps:
|
|
24
|
-
- uses: google-github-actions/release-please-action@v3
|
|
25
|
-
id: release
|
|
26
|
-
with:
|
|
27
|
-
release-type: node
|
|
28
|
-
{{#if pkgRelPath}}
|
|
29
|
-
monorepo-tags: true
|
|
30
|
-
path: {{pkgRelPath}}
|
|
31
|
-
# name can be removed after this is merged
|
|
32
|
-
# https://github.com/google-github-actions/release-please-action/pull/459
|
|
33
|
-
package-name: "{{pkgName}}"
|
|
34
|
-
{{/if}}
|
|
35
|
-
changelog-types: >
|
|
36
|
-
[
|
|
37
|
-
{{#each changelogTypes}}
|
|
38
|
-
{{{json .}}}{{#unless @last}},{{/unless}}
|
|
39
|
-
{{/each}}
|
|
40
|
-
]
|
|
41
|
-
{{#if isWorkspace}}
|
|
42
21
|
{{> setupGit}}
|
|
43
22
|
{{> setupNode}}
|
|
44
|
-
|
|
45
|
-
|
|
23
|
+
{{> setupDeps}}
|
|
24
|
+
- name: Release Please
|
|
25
|
+
id: release
|
|
26
|
+
run: npx --offline template-oss-release-please
|
|
27
|
+
env:
|
|
28
|
+
GITHUB_TOKEN: $\{{ secrets.GITHUB_TOKEN }}
|
|
29
|
+
|
|
30
|
+
post-pr:
|
|
31
|
+
needs: release-please
|
|
32
|
+
if: needs.release-please.outputs.pr
|
|
33
|
+
runs-on: ubuntu-latest
|
|
34
|
+
outputs:
|
|
35
|
+
ref: $\{{ steps.ref.outputs.branch }}
|
|
36
|
+
steps:
|
|
37
|
+
- name: Output ref
|
|
38
|
+
id: ref
|
|
39
|
+
run: echo "::set-output name=branch::$\{{ fromJSON(needs.release-please.outputs.pr).headBranchName }}"
|
|
40
|
+
{{> setupGit with=(obj ref="${{ steps.ref.outputs.branch }}")}}
|
|
41
|
+
{{> setupNode}}
|
|
42
|
+
{{> setupDeps}}
|
|
43
|
+
- name: Post pull request actions
|
|
46
44
|
env:
|
|
47
45
|
GITHUB_TOKEN: $\{{ secrets.GITHUB_TOKEN }}
|
|
48
46
|
run: |
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
title="$\{{ fromJSON(steps.release.outputs.pr).title }}"
|
|
52
|
-
# get the version from the pr title
|
|
53
|
-
# get everything after the last space
|
|
54
|
-
git commit -am "deps: {{pkgName}}@${title##* }"
|
|
47
|
+
npm run rp-pull-request --ignore-scripts --if-present -ws -iwr
|
|
48
|
+
git commit -am "chore: post pull request" || true
|
|
55
49
|
git push
|
|
56
|
-
|
|
50
|
+
|
|
51
|
+
release-test:
|
|
52
|
+
needs: post-pr
|
|
53
|
+
if: needs.post-pr.outputs.ref
|
|
54
|
+
uses: ./.github/workflows/{{releaseTest}}
|
|
55
|
+
with:
|
|
56
|
+
ref: $\{{ needs.post-pr.outputs.ref }}
|
|
57
|
+
|
|
58
|
+
post-release:
|
|
59
|
+
needs: release-please
|
|
60
|
+
if: needs.release-please.outputs.release
|
|
61
|
+
runs-on: ubuntu-latest
|
|
62
|
+
steps:
|
|
63
|
+
{{> setupGit}}
|
|
64
|
+
{{> setupNode}}
|
|
65
|
+
{{> setupDeps}}
|
|
66
|
+
- name: Post release actions
|
|
67
|
+
env:
|
|
68
|
+
GITHUB_TOKEN: $\{{ secrets.GITHUB_TOKEN }}
|
|
69
|
+
run: |
|
|
70
|
+
npm run rp-release --ignore-scripts --if-present -ws -iwr
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_call:
|
|
5
|
+
inputs:
|
|
6
|
+
ref:
|
|
7
|
+
required: true
|
|
8
|
+
type: string
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
lint-all:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
steps:
|
|
14
|
+
{{> setupGit with=(obj ref="${{ inputs.ref }}")}}
|
|
15
|
+
{{> setupNode}}
|
|
16
|
+
{{> setupDeps}}
|
|
17
|
+
- run: npm run lint --if-present --workspaces --include-workspace-root
|
|
18
|
+
|
|
19
|
+
test-all:
|
|
20
|
+
strategy:
|
|
21
|
+
fail-fast: false
|
|
22
|
+
matrix:
|
|
23
|
+
node-version:
|
|
24
|
+
{{#each ciVersions}}
|
|
25
|
+
- {{.}}
|
|
26
|
+
{{/each}}
|
|
27
|
+
platform:
|
|
28
|
+
- os: ubuntu-latest
|
|
29
|
+
shell: bash
|
|
30
|
+
- os: macos-latest
|
|
31
|
+
shell: bash
|
|
32
|
+
{{#if windowsCI}}
|
|
33
|
+
- os: windows-latest
|
|
34
|
+
shell: cmd
|
|
35
|
+
{{/if}}
|
|
36
|
+
runs-on: $\{{ matrix.platform.os }}
|
|
37
|
+
defaults:
|
|
38
|
+
run:
|
|
39
|
+
shell: $\{{ matrix.platform.shell }}
|
|
40
|
+
steps:
|
|
41
|
+
{{> setupGit with=(obj ref="${{ inputs.ref }}")}}
|
|
42
|
+
{{> setupNode useMatrix=true}}
|
|
43
|
+
{{> setupDeps}}
|
|
44
|
+
- name: add tap problem matcher
|
|
45
|
+
run: echo "::add-matcher::.github/matchers/tap.json"
|
|
46
|
+
- run: npm run test --if-present --workspaces --include-workspace-root
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
- run: npm i --ignore-scripts --no-audit --no-fund {{~#if flags}} {{flags}}{{/if}}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
const RP = require('release-please/build/src/changelog-notes/default')
|
|
2
|
+
|
|
3
|
+
module.exports = class DefaultChangelogNotes extends RP.DefaultChangelogNotes {
|
|
4
|
+
constructor (options) {
|
|
5
|
+
super(options)
|
|
6
|
+
this.github = options.github
|
|
7
|
+
}
|
|
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')
|
|
28
|
+
|
|
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
|
+
}
|
|
41
|
+
|
|
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
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
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
|
|
121
|
+
}
|
|
122
|
+
|
|
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
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
for (const pr of prs) {
|
|
156
|
+
title = title.replace(new RegExp(`\\s*\\(#${pr.number}\\)`, 'g'), '')
|
|
157
|
+
}
|
|
158
|
+
|
|
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}`,
|
|
190
|
+
}
|
|
191
|
+
}),
|
|
192
|
+
})
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const output = logger()
|
|
196
|
+
|
|
197
|
+
for (const key of Object.keys(commits)) {
|
|
198
|
+
if (commits[key].length > 0) {
|
|
199
|
+
output.group(`### ${key}\n`)
|
|
200
|
+
|
|
201
|
+
for (const commit of commits[key]) {
|
|
202
|
+
let groupCommit = `* [\`${commit.hash}\`](${commit.url})`
|
|
203
|
+
|
|
204
|
+
for (const pr of commit.prs) {
|
|
205
|
+
groupCommit += ` [#${pr.number}](${pr.url})`
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
groupCommit += ` ${commit.title}`
|
|
209
|
+
if (key !== 'Dependencies') {
|
|
210
|
+
for (const user of commit.credit) {
|
|
211
|
+
groupCommit += ` (${user.name})`
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
output.group(groupCommit)
|
|
216
|
+
output.groupEnd()
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
output.log()
|
|
220
|
+
output.groupEnd()
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return output.toString()
|
|
225
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const RP = require('release-please')
|
|
2
|
+
const logger = require('./logger.js')
|
|
3
|
+
const ChangelogNotes = require('./changelog.js')
|
|
4
|
+
const Version = require('./version.js')
|
|
5
|
+
const WorkspaceDeps = require('./workspace-deps.js')
|
|
6
|
+
|
|
7
|
+
RP.setLogger(logger)
|
|
8
|
+
RP.registerChangelogNotes('default', (options) => new ChangelogNotes(options))
|
|
9
|
+
RP.registerVersioningStrategy('default', (options) => new Version(options))
|
|
10
|
+
RP.registerPlugin('workspace-deps', (options) => new WorkspaceDeps(options))
|
|
11
|
+
|
|
12
|
+
const main = async ({ repo: fullRepo, token, dryRun, branch }) => {
|
|
13
|
+
if (!token) {
|
|
14
|
+
throw new Error('Token is required')
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (!fullRepo) {
|
|
18
|
+
throw new Error('Repo is required')
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const [owner, repo] = fullRepo.split('/')
|
|
22
|
+
const github = await RP.GitHub.create({ owner, repo, token })
|
|
23
|
+
const manifest = await RP.Manifest.fromManifest(
|
|
24
|
+
github,
|
|
25
|
+
branch ?? github.repository.defaultBranch
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
const pullRequests = await (dryRun ? manifest.buildPullRequests() : manifest.createPullRequests())
|
|
29
|
+
const releases = await (dryRun ? manifest.buildReleases() : manifest.createReleases())
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
// We only ever get a single pull request with our current release-please settings
|
|
33
|
+
pr: pullRequests.filter(Boolean)[0],
|
|
34
|
+
releases: releases.filter(Boolean),
|
|
35
|
+
release: releases.find(r => r.path === '.'),
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
module.exports = main
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
const semver = require('semver')
|
|
2
|
+
const RP = require('release-please/build/src/version.js')
|
|
3
|
+
|
|
4
|
+
// A way to compare the "level" of a release since we ignore some things during prereleases
|
|
5
|
+
const LEVELS = new Map([['prerelease', 4], ['major', 3], ['minor', 2], ['patch', 1]]
|
|
6
|
+
.flatMap((kv) => [kv, kv.slice().reverse()]))
|
|
7
|
+
|
|
8
|
+
const parseVersion = (v) => {
|
|
9
|
+
const { prerelease, minor, patch, version } = semver.parse(v)
|
|
10
|
+
const hasPre = prerelease.length > 0
|
|
11
|
+
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 }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const parseCommits = (commits, prerelease) => {
|
|
17
|
+
let release = LEVELS.get('patch')
|
|
18
|
+
for (const commit of commits) {
|
|
19
|
+
if (commit.breaking) {
|
|
20
|
+
release = LEVELS.get('major')
|
|
21
|
+
break
|
|
22
|
+
} else if (['feat', 'feature'].includes(commit.type)) {
|
|
23
|
+
release = LEVELS.get('minor')
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return { release, prerelease: !!prerelease }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const preInc = ({ version, prerelease, preId }, release) => {
|
|
30
|
+
if (!release.startsWith('pre')) {
|
|
31
|
+
release = `pre${release}`
|
|
32
|
+
}
|
|
33
|
+
// `pre` is the default prerelease identifier when creating a new
|
|
34
|
+
// prerelease version
|
|
35
|
+
return semver.inc(version, release, prerelease ? preId : 'pre')
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const releasePleaseVersion = (v) => {
|
|
39
|
+
const { major, minor, patch, prerelease } = semver.parse(v)
|
|
40
|
+
return new RP.Version(major, minor, patch, prerelease.join('.'))
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// This does not account for pre v1 semantics since we don't publish those
|
|
44
|
+
// Always 1.0.0 your initial versions!
|
|
45
|
+
module.exports = class DefaultVersioningStrategy {
|
|
46
|
+
constructor (options) {
|
|
47
|
+
this.prerelease = options.prerelease
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
bump (currentVersion, commits) {
|
|
51
|
+
const next = parseCommits(commits, this.prerelease)
|
|
52
|
+
// Release please passes in a version class with a toString() method
|
|
53
|
+
const current = parseVersion(currentVersion.toString())
|
|
54
|
+
|
|
55
|
+
// This is a special case where semver doesn't align exactly with what we want.
|
|
56
|
+
// We are currently at a prerelease and our next is also a prerelease.
|
|
57
|
+
// In this case we want to ignore the release type we got from our conventional
|
|
58
|
+
// commits if the "level" of the next release is <= the level of the current one.
|
|
59
|
+
//
|
|
60
|
+
// This has the effect of only bumping the prerelease identifier and nothing else
|
|
61
|
+
// when we are actively working (and breaking) a prerelease. For example:
|
|
62
|
+
//
|
|
63
|
+
// `9.0.0-pre.4` + breaking changes = `9.0.0-pre.5`
|
|
64
|
+
// `8.5.0-pre.4` + breaking changes = `9.0.0-pre.0`
|
|
65
|
+
// `8.5.0-pre.4` + feature or patch changes = `8.5.0-pre.5`
|
|
66
|
+
if (current.prerelease && next.prerelease && next.release <= current.release) {
|
|
67
|
+
next.release = LEVELS.get('prerelease')
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const release = LEVELS.get(next.release)
|
|
71
|
+
const releaseVersion = next.prerelease
|
|
72
|
+
? preInc(current, release)
|
|
73
|
+
: semver.inc(current.version, release)
|
|
74
|
+
return releasePleaseVersion(releaseVersion)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
const { ManifestPlugin } = require('release-please/build/src/plugin')
|
|
2
|
+
|
|
3
|
+
const matchLine = (line, re) => {
|
|
4
|
+
const trimmed = line.trim().replace(/^[*\s]+/, '')
|
|
5
|
+
if (typeof re === 'string') {
|
|
6
|
+
return trimmed === re
|
|
7
|
+
}
|
|
8
|
+
return trimmed.match(re)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
module.exports = class WorkspaceDeps extends ManifestPlugin {
|
|
12
|
+
run (pullRequests) {
|
|
13
|
+
for (const { pullRequest } of pullRequests) {
|
|
14
|
+
const depLinks = pullRequest.body.releaseData.reduce((acc, release) => {
|
|
15
|
+
if (release.component) {
|
|
16
|
+
const url = matchLine(release.notes.split('\n')[0], /\[[^\]]+\]\((.*?)\)/)
|
|
17
|
+
if (url) {
|
|
18
|
+
acc[release.component] = url[1]
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return acc
|
|
22
|
+
}, {})
|
|
23
|
+
|
|
24
|
+
for (const release of pullRequest.body.releaseData) {
|
|
25
|
+
const lines = release.notes.split('\n')
|
|
26
|
+
const newLines = []
|
|
27
|
+
|
|
28
|
+
let inWorkspaceDeps = false
|
|
29
|
+
let collectWorkspaceDeps = false
|
|
30
|
+
|
|
31
|
+
for (const line of lines) {
|
|
32
|
+
if (matchLine(line, 'The following workspace dependencies were updated')) {
|
|
33
|
+
// We are in the section with our workspace deps
|
|
34
|
+
// Set the flag and discard this line since we dont want it in the final output
|
|
35
|
+
inWorkspaceDeps = true
|
|
36
|
+
} else if (inWorkspaceDeps) {
|
|
37
|
+
if (collectWorkspaceDeps) {
|
|
38
|
+
const depMatch = matchLine(line, /^(\S+) bumped from \S+ to (\S+)$/)
|
|
39
|
+
if (depMatch) {
|
|
40
|
+
// If we have a line that is a workspace dep update, then reformat
|
|
41
|
+
// it and save it to the new lines
|
|
42
|
+
const [, depName, newVersion] = depMatch
|
|
43
|
+
const depSpec = `\`${depName}@${newVersion}\``
|
|
44
|
+
const url = depLinks[depName]
|
|
45
|
+
newLines.push(` * ${url ? `[${depSpec}](${url})` : depSpec}`)
|
|
46
|
+
} else {
|
|
47
|
+
// Anything else means we are done with dependencies so ignore
|
|
48
|
+
// this line and dont look for any more
|
|
49
|
+
collectWorkspaceDeps = false
|
|
50
|
+
}
|
|
51
|
+
} else if (matchLine(line, 'dependencies')) {
|
|
52
|
+
// Only collect dependencies discard dev deps and everything else
|
|
53
|
+
collectWorkspaceDeps = true
|
|
54
|
+
} else if (matchLine(line, '') || matchLine(line, /^#/)) {
|
|
55
|
+
inWorkspaceDeps = false
|
|
56
|
+
newLines.push(line)
|
|
57
|
+
}
|
|
58
|
+
} else {
|
|
59
|
+
newLines.push(line)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const newNotes = newLines.join('\n').trim()
|
|
64
|
+
const emptyDeps = newNotes.match(/### Dependencies[\n]+(### .*)/m)
|
|
65
|
+
|
|
66
|
+
release.notes = emptyDeps ? newNotes.replace(emptyDeps[0], emptyDeps[1]) : newNotes
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return pullRequests
|
|
71
|
+
}
|
|
72
|
+
}
|
package/lib/util/has-package.js
CHANGED
|
@@ -2,6 +2,7 @@ const semver = require('semver')
|
|
|
2
2
|
const npa = require('npm-package-arg')
|
|
3
3
|
const { has } = require('lodash')
|
|
4
4
|
const { join } = require('path')
|
|
5
|
+
const { name: NAME } = require('../../package.json')
|
|
5
6
|
|
|
6
7
|
const installLocations = [
|
|
7
8
|
'dependencies',
|
|
@@ -30,6 +31,10 @@ const getSpecVersion = (spec, where) => {
|
|
|
30
31
|
const pkg = require(join(arg.fetchSpec, 'package.json'))
|
|
31
32
|
return new semver.SemVer(pkg.version)
|
|
32
33
|
}
|
|
34
|
+
case 'git': {
|
|
35
|
+
// allow installing only this project from git to test in other projects
|
|
36
|
+
return arg.name === NAME
|
|
37
|
+
}
|
|
33
38
|
}
|
|
34
39
|
return null
|
|
35
40
|
}
|
|
@@ -58,6 +63,9 @@ const hasPackage = (
|
|
|
58
63
|
.filter(Boolean)
|
|
59
64
|
|
|
60
65
|
return existingByLocation.some((existing) => {
|
|
66
|
+
if (existing === true) {
|
|
67
|
+
return true
|
|
68
|
+
}
|
|
61
69
|
switch ([existing, requested].map((t) => isVersion(t) ? 'VER' : 'RNG').join('-')) {
|
|
62
70
|
case `VER-VER`:
|
|
63
71
|
// two versions, use semver.eq to check equality
|
package/lib/util/parser.js
CHANGED
|
@@ -198,7 +198,7 @@ class Json extends Base {
|
|
|
198
198
|
comment = (c) => ({ [`//${this.options.config.__NAME__}`]: c })
|
|
199
199
|
|
|
200
200
|
toString (s) {
|
|
201
|
-
return JSON.stringify(s, (_, v) => v === this.DELETE ? undefined : v, 2)
|
|
201
|
+
return JSON.stringify(s, (_, v) => v === this.DELETE ? undefined : v, 2).trim() + '\n'
|
|
202
202
|
}
|
|
203
203
|
|
|
204
204
|
parse (s) {
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@npmcli/template-oss",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.8.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
|
-
"template-oss-check": "bin/check.js"
|
|
8
|
+
"template-oss-check": "bin/check.js",
|
|
9
|
+
"template-oss-release-please": "bin/release-please.js"
|
|
9
10
|
},
|
|
10
11
|
"scripts": {
|
|
11
12
|
"lint": "eslint \"**/*.js\"",
|
|
@@ -31,6 +32,9 @@
|
|
|
31
32
|
"author": "GitHub Inc.",
|
|
32
33
|
"license": "ISC",
|
|
33
34
|
"dependencies": {
|
|
35
|
+
"@actions/core": "^1.9.1",
|
|
36
|
+
"@commitlint/cli": "^17.1.1",
|
|
37
|
+
"@commitlint/config-conventional": "^17.1.0",
|
|
34
38
|
"@npmcli/fs": "^2.0.1",
|
|
35
39
|
"@npmcli/git": "^3.0.0",
|
|
36
40
|
"@npmcli/map-workspaces": "^2.0.2",
|
|
@@ -44,6 +48,7 @@
|
|
|
44
48
|
"lodash": "^4.17.21",
|
|
45
49
|
"npm-package-arg": "^9.0.1",
|
|
46
50
|
"proc-log": "^2.0.0",
|
|
51
|
+
"release-please": "npm:@npmcli/release-please@^14.2.4",
|
|
47
52
|
"semver": "^7.3.5",
|
|
48
53
|
"yaml": "2.0.0-11"
|
|
49
54
|
},
|
|
@@ -56,6 +61,9 @@
|
|
|
56
61
|
"@npmcli/template-oss": "file:./",
|
|
57
62
|
"tap": "^16.0.0"
|
|
58
63
|
},
|
|
64
|
+
"tap": {
|
|
65
|
+
"timeout": 600
|
|
66
|
+
},
|
|
59
67
|
"templateOSS": {
|
|
60
68
|
"//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten."
|
|
61
69
|
},
|