@npmcli/template-oss 4.2.0 → 4.3.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.
Files changed (38) hide show
  1. package/bin/release-please.js +42 -28
  2. package/lib/config.js +44 -26
  3. package/lib/content/{_setup-job-matrix.yml → _job-matrix.yml} +12 -10
  4. package/lib/content/_job.yml +8 -0
  5. package/lib/content/{_setup-ci-on.yml → _on-ci.yml} +11 -13
  6. package/lib/content/_step-checks.yml +24 -0
  7. package/lib/content/_step-deps.yml +2 -0
  8. package/lib/content/_step-git.yml +12 -0
  9. package/lib/content/_step-lint.yml +4 -0
  10. package/lib/content/{_setup-node.yml → _step-node.yml} +10 -9
  11. package/lib/content/_step-test.yml +4 -0
  12. package/lib/content/_steps-setup.yml +6 -0
  13. package/lib/content/audit.yml +3 -2
  14. package/lib/content/ci-release.yml +31 -0
  15. package/lib/content/ci.yml +6 -8
  16. package/lib/content/codeql-analysis.yml +10 -17
  17. package/lib/content/commitlintrc.js +1 -1
  18. package/lib/content/dependabot.yml +2 -20
  19. package/lib/content/eslintrc.js +3 -3
  20. package/lib/content/gitignore +1 -1
  21. package/lib/content/index.js +34 -21
  22. package/lib/content/npmrc +1 -1
  23. package/lib/content/pkg.json +25 -23
  24. package/lib/content/post-dependabot.yml +55 -11
  25. package/lib/content/pull-request.yml +11 -9
  26. package/lib/content/release-please-config.json +5 -5
  27. package/lib/content/release-please-manifest.json +1 -1
  28. package/lib/content/release.yml +120 -15
  29. package/lib/index.js +9 -12
  30. package/lib/release-please/index.js +26 -5
  31. package/lib/util/files.js +5 -3
  32. package/lib/util/parser.js +73 -16
  33. package/lib/util/template.js +8 -1
  34. package/package.json +1 -1
  35. package/lib/content/_setup-deps.yml +0 -1
  36. package/lib/content/_setup-git.yml +0 -11
  37. package/lib/content/_setup-job.yml +0 -6
  38. package/lib/content/release-please.yml +0 -64
@@ -8,7 +8,9 @@ const { unset } = require('lodash')
8
8
  const template = require('./template.js')
9
9
  const jsonDiff = require('./json-diff')
10
10
  const merge = require('./merge.js')
11
+
11
12
  const setFirst = (first, rest) => ({ ...first, ...rest })
13
+
12
14
  const traverse = (value, visit, keys = []) => {
13
15
  if (keys.length) {
14
16
  const res = visit(keys, value)
@@ -23,17 +25,25 @@ const traverse = (value, visit, keys = []) => {
23
25
  }
24
26
  }
25
27
 
28
+ const fsOk = (code) => (error) => {
29
+ if (error.code === 'ENOENT') {
30
+ return null
31
+ }
32
+ return Object.assign(error, { code })
33
+ }
34
+
26
35
  class Base {
27
36
  static types = []
28
- static header = 'This file is automatically added by {{__NAME__}}. Do not edit.'
37
+ static header = 'This file is automatically added by {{ __NAME__ }}. Do not edit.'
29
38
  comment = (v) => v
30
39
  merge = false // supply a merge function which runs on prepare for certain types
31
40
  DELETE = template.DELETE
32
41
 
33
- constructor (target, source, options) {
42
+ constructor (target, source, options, fileOptions) {
34
43
  this.target = target
35
44
  this.source = source
36
45
  this.options = options
46
+ this.fileOptions = fileOptions
37
47
  }
38
48
 
39
49
  header () {
@@ -42,6 +52,13 @@ class Base {
42
52
  }
43
53
  }
44
54
 
55
+ clean () {
56
+ if (this.fileOptions.clean) {
57
+ return fs.rm(this.target).catch(fsOk())
58
+ }
59
+ return null
60
+ }
61
+
45
62
  read (s) {
46
63
  return fs.readFile(s, { encoding: 'utf-8' })
47
64
  }
@@ -88,13 +105,17 @@ class Base {
88
105
  // XXX: everything is allowed to be overridden in base classes but we could
89
106
  // find a different solution than making everything public
90
107
  applyWrite () {
91
- return Promise.resolve(this.read(this.source))
108
+ return Promise.resolve(this.clean())
109
+ .then(() => this.read(this.source))
92
110
  // replace template vars first, this will throw for nonexistant vars
93
111
  // because it must be parseable after this step
94
112
  .then((s) => this.template(s))
95
113
  // parse into whatever data structure is necessary for maniuplating
96
114
  // diffing, merging, etc. by default its a string
97
- .then((s) => this.parse(s))
115
+ .then((s) => {
116
+ this.sourcePreParse = s
117
+ return this.parse(s)
118
+ })
98
119
  // prepare the source for writing and diffing, pass in current
99
120
  // target for merging. errors parsing or preparing targets are ok here
100
121
  .then((s) => this.applyTarget().catch(() => null).then((t) => this.prepare(s, t)))
@@ -109,14 +130,9 @@ class Base {
109
130
  }
110
131
 
111
132
  async applyDiff () {
112
- const target = await this.applyTarget().catch((e) => {
113
- // handle if old does not exist
114
- if (e.code === 'ENOENT') {
115
- return null
116
- } else {
117
- return { code: 'ETARGETERROR', error: e }
118
- }
119
- })
133
+ // handle if old does not exist
134
+ const targetError = 'ETARGETERROR'
135
+ const target = await this.applyTarget().catch(fsOk(targetError))
120
136
 
121
137
  // no need to diff if current file does not exist
122
138
  if (target === null) {
@@ -131,11 +147,11 @@ class Base {
131
147
 
132
148
  // if there was a target error then there is no need to diff
133
149
  // so we just show the source with an error message
134
- if (target.code === 'ETARGETERROR') {
150
+ if (target.code === targetError) {
135
151
  const msg = `[${this.options.config.__NAME__} ERROR]`
136
152
  return [
137
153
  `${msg} There was an erroring getting the target file`,
138
- `${msg} ${target.error}`,
154
+ `${msg} ${target}`,
139
155
  `${msg} It will be overwritten with the following source:`,
140
156
  '-'.repeat(40),
141
157
  this.toString(source),
@@ -175,7 +191,12 @@ class Yml extends Base {
175
191
  comment = (c) => ` ${c}`
176
192
 
177
193
  toString (s) {
178
- return s.toString({ lineWidth: 0, indent: 2 })
194
+ try {
195
+ return s.toString({ lineWidth: 0, indent: 2 })
196
+ } catch (err) {
197
+ err.message = [this.target, this.sourcePreParse, ...s.errors, err.message].join('\n')
198
+ throw err
199
+ }
179
200
  }
180
201
 
181
202
  parse (s) {
@@ -192,6 +213,41 @@ class Yml extends Base {
192
213
  }
193
214
  }
194
215
 
216
+ class YmlMerge extends Yml {
217
+ prepare (source, t) {
218
+ if (t === null) {
219
+ // If target does not exist or is in an
220
+ // error state, we cant do anything but write
221
+ // the whole document
222
+ return super.prepare(source)
223
+ }
224
+
225
+ const key = [].concat(this.key)
226
+
227
+ const getId = (node) => {
228
+ const index = node.items.findIndex(p => p.key?.value === this.id)
229
+ return index !== -1 ? node.items[index].value?.value : node.toJSON()
230
+ }
231
+
232
+ const target = this.parse(t)
233
+ const targetNodes = target.getIn(key).items.reduce((acc, node, index) => {
234
+ acc[getId(node)] = { node, index }
235
+ return acc
236
+ }, {})
237
+
238
+ for (const node of source.getIn(key).items) {
239
+ const index = targetNodes[getId(node)]?.index
240
+ if (typeof index === 'number' && index !== -1) {
241
+ target.setIn([...key, index], node)
242
+ } else {
243
+ target.addIn(key, node)
244
+ }
245
+ }
246
+
247
+ return super.prepare(target)
248
+ }
249
+ }
250
+
195
251
  class Json extends Base {
196
252
  static types = ['json']
197
253
  // its a json comment! not really but we do add a special key
@@ -220,7 +276,7 @@ class Json extends Base {
220
276
  }
221
277
 
222
278
  class JsonMerge extends Json {
223
- static header = 'This file is partially managed by {{__NAME__}}. Edits may be overwritten.'
279
+ static header = 'This file is partially managed by {{ __NAME__ }}. Edits may be overwritten.'
224
280
  merge = (t, s) => merge(t, s)
225
281
  }
226
282
 
@@ -260,6 +316,7 @@ const Parsers = {
260
316
  Ini,
261
317
  Markdown,
262
318
  Yml,
319
+ YmlMerge,
263
320
  Json,
264
321
  JsonMerge,
265
322
  PackageJson,
@@ -3,12 +3,19 @@ const { basename, extname, join } = require('path')
3
3
  const fs = require('fs')
4
4
  const DELETE = '__DELETE__'
5
5
 
6
+ const safeValues = (obj) => Object.entries(obj).map(([key, value]) =>
7
+ [key, new Handlebars.SafeString(value)])
8
+
6
9
  const partialName = (s) => basename(s, extname(s)) // remove extension
7
10
  .replace(/^_/, '') // remove leading underscore
8
11
  .replace(/-([a-z])/g, (_, g) => g.toUpperCase()) // camelcase
9
12
 
10
13
  const setupHandlebars = (...partialDirs) => {
11
- Handlebars.registerHelper('obj', ({ hash }) => hash)
14
+ Handlebars.registerHelper('obj', ({ hash }) => Object.fromEntries(safeValues(hash)))
15
+ Handlebars.registerHelper('join', (arr, sep) => arr.join(typeof sep === 'string' ? sep : ', '))
16
+ Handlebars.registerHelper('pluck', (arr, key) => arr.map(a => a[key]))
17
+ Handlebars.registerHelper('quote', (arr) => arr.map(a => `'${a}'`))
18
+ Handlebars.registerHelper('last', (arr) => arr[arr.length - 1])
12
19
  Handlebars.registerHelper('json', (c) => JSON.stringify(c))
13
20
  Handlebars.registerHelper('del', () => JSON.stringify(DELETE))
14
21
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@npmcli/template-oss",
3
- "version": "4.2.0",
3
+ "version": "4.3.1",
4
4
  "description": "templated files used in npm CLI team oss projects",
5
5
  "main": "lib/content/index.js",
6
6
  "bin": {
@@ -1 +0,0 @@
1
- - run: {{rootNpmPath}} i --ignore-scripts --no-audit --no-fund {{~#if flags}} {{flags}}{{/if}}
@@ -1,11 +0,0 @@
1
- - uses: actions/checkout@v3
2
- {{#if checkout}}
3
- with:
4
- {{#each checkout}}
5
- {{@key}}: {{this}}
6
- {{/each}}
7
- {{/if}}
8
- - name: Setup git user
9
- run: |
10
- git config --global user.email "npm-cli+bot@github.com"
11
- git config --global user.name "npm CLI robot"
@@ -1,6 +0,0 @@
1
- if: github.repository_owner == 'npm' {{~#if jobIf}} && {{{jobIf}}}{{/if}}
2
- runs-on: ubuntu-latest
3
- steps:
4
- {{> setupGit}}
5
- {{> setupNode}}
6
- {{> setupDeps}}
@@ -1,64 +0,0 @@
1
- name: Release Please
2
-
3
- on:
4
- push:
5
- branches:
6
- {{#each branches}}
7
- - {{.}}
8
- {{/each}}
9
- {{#each releaseBranches}}
10
- - {{.}}
11
- {{/each}}
12
-
13
- permissions:
14
- contents: write
15
- pull-requests: write
16
-
17
- jobs:
18
- release-please:
19
- outputs:
20
- pr: $\{{ steps.release.outputs.pr }}
21
- release: $\{{ steps.release.outputs.release }}
22
- {{> setupJob }}
23
- - name: Release Please
24
- id: release
25
- run: npx --offline template-oss-release-please $\{{ github.ref_name }}
26
- env:
27
- GITHUB_TOKEN: $\{{ secrets.GITHUB_TOKEN }}
28
-
29
- post-pr:
30
- needs: release-please
31
- if: needs.release-please.outputs.pr
32
- runs-on: ubuntu-latest
33
- outputs:
34
- ref: $\{{ steps.ref.outputs.branch }}
35
- steps:
36
- - name: Output ref
37
- id: ref
38
- run: echo "::set-output name=branch::$\{{ fromJSON(needs.release-please.outputs.pr).headBranchName }}"
39
- {{> setupGit checkout=(obj ref="${{ steps.ref.outputs.branch }}" fetch-depth=0)}}
40
- {{> setupNode}}
41
- {{> setupDeps}}
42
- - name: Post pull request actions
43
- env:
44
- GITHUB_TOKEN: $\{{ secrets.GITHUB_TOKEN }}
45
- run: |
46
- {{rootNpmPath}} run rp-pull-request --ignore-scripts --if-present -ws -iwr
47
- git commit -am "chore: post pull request" || true
48
- git push
49
-
50
- release-test:
51
- needs: post-pr
52
- if: needs.post-pr.outputs.ref
53
- uses: ./.github/workflows/release.yml
54
- with:
55
- ref: $\{{ needs.post-pr.outputs.ref }}
56
-
57
- post-release:
58
- needs: release-please
59
- {{> setupJob jobIf="needs.release-please.outputs.release" }}
60
- - name: Post release actions
61
- env:
62
- GITHUB_TOKEN: $\{{ secrets.GITHUB_TOKEN }}
63
- run: |
64
- {{rootNpmPath}} run rp-release --ignore-scripts --if-present -ws -iwr