@npmcli/template-oss 3.7.1 → 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.
@@ -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,
@@ -12,5 +12,5 @@ jobs:
12
12
  steps:
13
13
  {{> setupGit}}
14
14
  {{> setupNode}}
15
- - run: npm i --ignore-scripts --no-audit --no-fund --package-lock
15
+ {{> setupDeps flags="--package-lock"}}
16
16
  - run: npm audit
@@ -28,7 +28,7 @@ jobs:
28
28
  steps:
29
29
  {{> setupGit}}
30
30
  {{> setupNode}}
31
- - run: npm i --ignore-scripts --no-audit --no-fund
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}}
@@ -5,6 +5,10 @@ const releasePlease = () => ({
5
5
  file: 'release-please.yml',
6
6
  filter: (o) => !o.pkg.private,
7
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
+ },
8
12
  '.release-please-manifest.json': {
9
13
  file: 'release-please-manifest.json',
10
14
  filter: (o) => !o.pkg.private,
@@ -92,6 +96,11 @@ module.exports = {
92
96
  workspaceModule,
93
97
  windowsCI: true,
94
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',
95
104
  distPaths: ['bin/', 'lib/'],
96
105
  ciVersions: ['12.13.0', '12.x', '14.15.0', '14.x', '16.0.0', '16.x'],
97
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: npm install and commit
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
- - name: Install deps
19
- run: npm i -D @commitlint/cli @commitlint/config-conventional
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/main --to $\{{ github.event.pull_request.head.sha }} \
23
+ npx --offline commitlint -V --from origin/{{defaultBranch}} --to $\{{ github.event.pull_request.head.sha }} \
25
24
  || echo $PR_TITLE | npx --offline commitlint -V
@@ -1,12 +1,10 @@
1
1
  {
2
- "separate-pull-requests": true,
3
- "changelog-sections": [
4
- {"type":"feat","section":"Features","hidden":false},
5
- {"type":"fix","section":"Bug Fixes","hidden":false},
6
- {"type":"docs","section":"Documentation","hidden":false},
7
- {"type":"deps","section":"Dependencies","hidden":false},
8
- {"type":"chore","hidden":true}
9
- ],
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}}},
10
8
  "packages": {
11
9
  "{{#unless pkgRelPath}}.{{/unless}}{{pkgRelPath}}": {
12
10
  {{#unless pkgRelPath}}"package-name": ""{{/unless}}
@@ -15,32 +15,56 @@ jobs:
15
15
  release-please:
16
16
  runs-on: ubuntu-latest
17
17
  outputs:
18
- prs: $\{{ steps.release.outputs.prs }}
18
+ pr: $\{{ steps.release.outputs.pr }}
19
+ release: $\{{ steps.release.outputs.release }}
19
20
  steps:
20
- - uses: google-github-actions/release-please-action@v3
21
+ {{> setupGit}}
22
+ {{> setupNode}}
23
+ {{> setupDeps}}
24
+ - name: Release Please
21
25
  id: release
22
- with:
23
- command: manifest
26
+ run: npx --offline template-oss-release-please
27
+ env:
28
+ GITHUB_TOKEN: $\{{ secrets.GITHUB_TOKEN }}
24
29
 
25
- update-prs:
30
+ post-pr:
26
31
  needs: release-please
27
- if: needs.release-please.outputs.prs
32
+ if: needs.release-please.outputs.pr
28
33
  runs-on: ubuntu-latest
29
- strategy:
30
- matrix:
31
- pr: $\{{ fromJSON(needs.release-please.outputs.prs) }}
34
+ outputs:
35
+ ref: $\{{ steps.ref.outputs.branch }}
32
36
  steps:
33
- {{> setupGit}}
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 }}")}}
34
41
  {{> setupNode}}
35
- - name: Update PR $\{{ matrix.pr.number }} dependencies and commit
42
+ {{> setupDeps}}
43
+ - name: Post pull request actions
36
44
  env:
37
45
  GITHUB_TOKEN: $\{{ secrets.GITHUB_TOKEN }}
38
46
  run: |
39
- gh pr checkout $\{{ matrix.pr.number }}
40
- npm run resetdeps
41
- title="$\{{ matrix.pr.title }}"
42
- # get the dependency spec from the pr title
43
- # get everything after ': release ' + replace space with @
44
- dep_spec=$(echo "${title##*: release }" | tr ' ' @)
45
- git commit -am "deps: $dep_spec"
47
+ npm run rp-pull-request --ignore-scripts --if-present -ws -iwr
48
+ git commit -am "chore: post pull request" || true
46
49
  git push
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,3 @@
1
+ const { CheckpointLogger } = require('release-please/build/src/util/logger')
2
+
3
+ module.exports = new CheckpointLogger(true, true)
@@ -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
+ }
@@ -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/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "@npmcli/template-oss",
3
- "version": "3.7.1",
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
  },