@npmcli/template-oss 2.9.2 → 3.0.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.
Files changed (52) hide show
  1. package/README.md +77 -68
  2. package/bin/apply.js +22 -0
  3. package/bin/check.js +26 -0
  4. package/lib/apply/apply-files.js +31 -0
  5. package/lib/apply/index.js +5 -0
  6. package/lib/check/check-apply.js +73 -0
  7. package/lib/check/check-changelog.js +31 -0
  8. package/lib/check/check-gitignore.js +67 -0
  9. package/lib/check/check-required.js +36 -0
  10. package/lib/check/check-unwanted.js +23 -0
  11. package/lib/check/index.js +9 -0
  12. package/lib/config.js +151 -40
  13. package/lib/content/CODEOWNERS +1 -1
  14. package/lib/content/LICENSE.md +0 -2
  15. package/lib/content/SECURITY.md +0 -2
  16. package/lib/content/audit.yml +5 -12
  17. package/lib/content/bug.yml +45 -46
  18. package/lib/content/ci.yml +35 -38
  19. package/lib/content/codeql-analysis.yml +11 -9
  20. package/lib/content/commitlintrc.js +1 -4
  21. package/lib/content/config.yml +0 -2
  22. package/lib/content/dependabot.yml +13 -14
  23. package/lib/content/eslintrc.js +0 -2
  24. package/lib/content/gitignore +8 -14
  25. package/lib/content/index.js +90 -0
  26. package/lib/content/npmrc +0 -2
  27. package/lib/content/package.json +27 -0
  28. package/lib/content/post-dependabot.yml +12 -15
  29. package/lib/content/pull-request.yml +12 -11
  30. package/lib/content/release-please.yml +18 -11
  31. package/lib/content/setup-git.yml +11 -0
  32. package/lib/content/setup-node.yml +21 -0
  33. package/lib/index.js +100 -0
  34. package/lib/util/files.js +43 -0
  35. package/lib/util/get-git-url.js +24 -0
  36. package/lib/util/has-package.js +30 -0
  37. package/lib/util/json-diff.js +38 -0
  38. package/lib/util/output.js +35 -0
  39. package/lib/util/parse-ci-versions.js +78 -0
  40. package/lib/util/parser.js +279 -0
  41. package/lib/util/template.js +41 -0
  42. package/package.json +29 -26
  43. package/bin/.gitattributes +0 -3
  44. package/bin/npm-template-check.js +0 -44
  45. package/bin/postinstall.js +0 -31
  46. package/lib/content/ci-no-windows.yml +0 -48
  47. package/lib/content/ci-workspace.yml +0 -63
  48. package/lib/content/release-please-workspace.yml +0 -29
  49. package/lib/postinstall/copy-content.js +0 -133
  50. package/lib/postinstall/update-package.js +0 -100
  51. package/lib/postlint/check-gitignore.js +0 -59
  52. package/lib/postlint/check-package.js +0 -90
@@ -0,0 +1,27 @@
1
+ {
2
+ "author": "GitHub Inc.",
3
+ "files": {{{json distPaths}}},
4
+ "scripts": {
5
+ "lint": "eslint \"**/*.js\"",
6
+ "postlint": "template-oss-check",
7
+ "template-oss-apply": "template-oss-apply --force",
8
+ "lintfix": "npm run lint -- --fix",
9
+ "preversion": "npm test",
10
+ "postversion": "npm publish",
11
+ "prepublishOnly": "git push origin --follow-tags",
12
+ "snap": "tap",
13
+ "test": "tap",
14
+ "posttest": "npm run lint",
15
+ "template-copy": {{{del}}},
16
+ "lint:fix": {{{del}}}
17
+ },
18
+ "repository": {{#if repository}}{{{json repository}}}{{else}}{{{del}}}{{/if}},
19
+ "engines": {
20
+ "node": {{{json engines}}}
21
+ },
22
+ {{{json __CONFIG_KEY__}}}: {
23
+ "version": {{#if __DOGFOOD__}}{{{del}}}{{else}}{{{json __VERSION__}}}{{/if}}
24
+ },
25
+ "templateVersion": {{{del}}},
26
+ "standard": {{{del}}}
27
+ }
@@ -1,7 +1,7 @@
1
- # This file is automatically added by @npmcli/template-oss. Do not edit.
1
+ name: Post Dependabot Actions
2
2
 
3
- name: "Post Dependabot Actions"
4
- on: pull_request
3
+ on:
4
+ pull_request
5
5
 
6
6
  # https://docs.github.com/en/rest/overview/permissions-required-for-github-apps
7
7
  permissions:
@@ -10,26 +10,23 @@ permissions:
10
10
  jobs:
11
11
  Install:
12
12
  runs-on: ubuntu-latest
13
- if: ${{ github.actor == 'dependabot[bot]' }}
13
+ if: github.actor == 'dependabot[bot]'
14
14
  steps:
15
- - uses: actions/checkout@v2
16
- - uses: actions/setup-node@v2
17
- with:
18
- node-version: '16'
15
+ {{> setupGit}}
16
+ {{> setupNode}}
19
17
  - name: Dependabot metadata
20
18
  id: metadata
21
19
  uses: dependabot/fetch-metadata@v1.1.1
22
20
  with:
23
- github-token: "${{ secrets.GITHUB_TOKEN }}"
21
+ github-token: "$\{{ secrets.GITHUB_TOKEN }}"
24
22
  - name: npm install and commit
25
- if: ${{contains(steps.metadata.outputs.dependency-names, '@npmcli/template-oss')}}
23
+ if: contains(steps.metadata.outputs.dependency-names, '@npmcli/template-oss')
26
24
  env:
27
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
25
+ GITHUB_TOKEN: $\{{ secrets.GITHUB_TOKEN }}
28
26
  run: |
29
- git config --local user.email "ops+npm-cli@npmjs.com"
30
- git config --local user.name "npm cli ops bot"
31
- gh pr checkout ${{ github.event.pull_request.number }}
32
- npm install
27
+ gh pr checkout $\{{ github.event.pull_request.number }}
28
+ npm install --no-scripts
29
+ npm run template-oss-apply
33
30
  git add .
34
31
  git commit -am "chore: postinstall for dependabot template-oss PR"
35
32
  git push
@@ -1,27 +1,28 @@
1
- # This file is automatically added by @npmcli/template-oss. Do not edit.
2
-
3
1
  name: Pull Request Linting
4
2
 
5
3
  on:
6
4
  pull_request:
7
- types: [opened, reopened, edited, synchronize]
5
+ types:
6
+ - opened
7
+ - reopened
8
+ - edited
9
+ - synchronize
8
10
 
9
11
  jobs:
10
12
  check:
11
13
  name: Check PR Title or Commits
12
14
  runs-on: ubuntu-latest
13
15
  steps:
14
- - uses: actions/checkout@v2
15
- with:
16
- fetch-depth: 0
17
- - uses: actions/setup-node@v2
18
- with:
19
- node-version: '16'
16
+ {{> setupGit with=(obj fetch-depth=0)}}
17
+ {{> setupNode}}
20
18
  - name: Install deps
21
19
  run: |
22
20
  npm i -D @commitlint/cli @commitlint/config-conventional
23
21
  - name: Check commits OR PR title
24
22
  env:
25
- PR_TITLE: ${{ github.event.pull_request.title }}
23
+ PR_TITLE: $\{{ github.event.pull_request.title }}
26
24
  run: |
27
- npx commitlint -x @commitlint/config-conventional -V --from origin/main --to ${{ github.event.pull_request.head.sha }} || echo $PR_TITLE | npx commitlint -x @commitlint/config-conventional -V
25
+ npx commitlint -x @commitlint/config-conventional -V \
26
+ --from origin/main --to $\{{ github.event.pull_request.head.sha }} \
27
+ || echo $PR_TITLE | \
28
+ npx commitlint -x @commitlint/config-conventional -V
@@ -1,24 +1,31 @@
1
- # This file is automatically added by @npmcli/template-oss. Do not edit.
2
-
3
- name: Release Please
1
+ name: Release Please {{~#if isWorkspace}} - {{pkgName}}{{/if}}
4
2
 
5
3
  on:
6
4
  push:
5
+ {{#if pkgRelPath}}
6
+ paths:
7
+ - {{pkgRelPath}}/**
8
+ {{/if}}
7
9
  branches:
8
- - main
10
+ {{#each branches}}
11
+ - {{.}}
12
+ {{/each}}
9
13
 
10
14
  jobs:
11
15
  release-please:
12
16
  runs-on: ubuntu-latest
13
17
  steps:
14
- - uses: google-github-actions/release-please-action@v2
18
+ - uses: google-github-actions/release-please-action@v3
15
19
  id: release
16
20
  with:
17
21
  release-type: node
18
- # If you change changelog-types be sure to also update commitlintrc.js
22
+ {{#if pkgRelPath}}
23
+ monorepo-tags: true
24
+ paths: {{pkgRelPath}}
25
+ {{/if}}
19
26
  changelog-types: >
20
- [{"type":"feat","section":"Features","hidden":false},
21
- {"type":"fix","section":"Bug Fixes","hidden":false},
22
- {"type":"docs","section":"Documentation","hidden":false},
23
- {"type":"deps","section":"Dependencies","hidden":false},
24
- {"type":"chore","hidden":true}]
27
+ [
28
+ {{#each changelogTypes}}
29
+ {{{json .}}}{{#unless @last}},{{/unless}}
30
+ {{/each}}
31
+ ]
@@ -0,0 +1,11 @@
1
+ - uses: actions/checkout@v3
2
+ {{#if with}}
3
+ with:
4
+ {{#each with}}
5
+ {{@key}}: {{this}}
6
+ {{/each}}
7
+ {{/if}}
8
+ - name: Setup git user
9
+ run: |
10
+ git config --global user.email "ops+npm-cli@npmjs.com"
11
+ git config --global user.name "npm cli ops bot"
@@ -0,0 +1,21 @@
1
+ - uses: actions/setup-node@v3
2
+ with:
3
+ node-version: {{#each ciVersions}}{{#if @last}}{{.}}{{/if}}{{/each}}
4
+ - name: Update to workable npm (windows)
5
+ # node 12 and 14 ship with npm@6, which is known to fail when updating itself in windows
6
+ if: matrix.platform.os == 'windows-latest' && (startsWith(matrix.node-version, '12') || startsWith(matrix.node-version, '14'))
7
+ run: |
8
+ curl -sO https://registry.npmjs.org/npm/-/npm-7.5.4.tgz
9
+ tar xf npm-7.5.4.tgz
10
+ cd package
11
+ node lib/npm.js install --no-fund --no-audit -g ..\npm-7.5.4.tgz
12
+ cd ..
13
+ rmdir /s /q package
14
+ - name: Update npm to 7
15
+ # If we do test on npm 10 it needs npm7
16
+ if: matrix.node-version <= 10
17
+ run: npm i --prefer-online --no-fund --no-audit -g npm@7
18
+ - name: Update npm to latest
19
+ if: matrix.node-version > 10
20
+ run: npm i --prefer-online --no-fund --no-audit -g npm@latest
21
+ - run: npm -v
package/lib/index.js ADDED
@@ -0,0 +1,100 @@
1
+ const log = require('proc-log')
2
+ const { defaults } = require('lodash')
3
+ const getConfig = require('./config.js')
4
+ const PackageJson = require('@npmcli/package-json')
5
+ const mapWorkspaces = require('@npmcli/map-workspaces')
6
+
7
+ const getPkg = async (path, baseConfig) => {
8
+ log.verbose('get-pkg', path)
9
+
10
+ const pkg = (await PackageJson.load(path)).content
11
+ const pkgConfig = getConfig.getPkgConfig(pkg)
12
+ log.verbose('get-pkg', pkgConfig)
13
+
14
+ return { pkg, path, config: { ...baseConfig, ...pkgConfig } }
15
+ }
16
+
17
+ const getWsPkgs = async (root, rootPkg) => {
18
+ const wsPkgs = []
19
+
20
+ // workspaces are only used to filter paths and control changes to workspaces
21
+ // so dont pass it along with the rest of the config
22
+ const { workspaces, ...baseConfig } = rootPkg.config
23
+
24
+ // Include all by default
25
+ const include = (name) => Array.isArray(workspaces) ? workspaces.includes(name) : true
26
+
27
+ // Look through all workspaces on the root pkg
28
+ const rootWorkspaces = await mapWorkspaces({ pkg: rootPkg.pkg, cwd: root })
29
+
30
+ for (const [wsName, wsPath] of rootWorkspaces.entries()) {
31
+ if (include(wsName)) {
32
+ // A workspace can control its own workspaceRepo and workspaceModule settings
33
+ // which are true by default on the root config
34
+ wsPkgs.push(await getPkg(wsPath, baseConfig))
35
+ }
36
+ }
37
+
38
+ return {
39
+ pkgs: wsPkgs,
40
+ paths: [...rootWorkspaces.values()],
41
+ }
42
+ }
43
+
44
+ const getPkgs = async (root) => {
45
+ log.verbose('get-pkgs', 'root', root)
46
+
47
+ const rootPkg = await getPkg(root)
48
+ const pkgs = [rootPkg]
49
+
50
+ defaults(rootPkg.config, {
51
+ rootRepo: true,
52
+ rootModule: true,
53
+ workspaceRepo: true,
54
+ workspaceModule: true,
55
+ workspaces: null,
56
+ })
57
+
58
+ const ws = await getWsPkgs(root, rootPkg)
59
+
60
+ return {
61
+ pkgs: pkgs.concat(ws.pkgs),
62
+ workspaces: ws.paths,
63
+ }
64
+ }
65
+
66
+ const runAll = async (root, content, checks) => {
67
+ const results = []
68
+ const { pkgs, workspaces } = await getPkgs(root)
69
+
70
+ for (const { pkg, path, config } of pkgs) {
71
+ // full config includes original config values
72
+ const fullConfig = await getConfig({
73
+ pkgs,
74
+ workspaces,
75
+ root,
76
+ pkg,
77
+ path,
78
+ config,
79
+ content,
80
+ })
81
+
82
+ const options = { root, pkg, path, config: fullConfig }
83
+ log.verbose('run-all', options)
84
+
85
+ // files can export multiple checks so flatten first
86
+ for (const { when, run, name } of checks.flat()) {
87
+ log.info('attempting to run', name)
88
+ if (await when(options)) {
89
+ log.info('running', name)
90
+ results.push(await run(options))
91
+ }
92
+ }
93
+ }
94
+
95
+ // checks can return multiple results or nothing
96
+ // so flatten first and remove nulls before returning
97
+ return results.flat().filter(Boolean)
98
+ }
99
+
100
+ module.exports = runAll
@@ -0,0 +1,43 @@
1
+ const { join } = require('path')
2
+ const { promisify } = require('util')
3
+ const glob = promisify(require('glob'))
4
+ const Parser = require('./parser.js')
5
+ const template = require('./template.js')
6
+
7
+ // target paths need to be joinsed with dir and templated
8
+ const fullTarget = (dir, file, options) => join(dir, template(file, options))
9
+
10
+ // given an obj of files, return the full target/source paths and associated parser
11
+ const getParsers = (dir, files, options) => Object.entries(files).map(([t, s]) => {
12
+ let { file, parser: fileParser } = typeof s === 'string' ? { file: s } : s
13
+ const target = fullTarget(dir, t, options)
14
+ file = join(options.config.sourceDir, file)
15
+ if (fileParser) {
16
+ // allow files to extend base parsers or create new ones
17
+ return new (fileParser(Parser.Parsers))(target, file, options)
18
+ }
19
+ return new (Parser(file))(target, file, options)
20
+ })
21
+
22
+ const rmEach = async (dir, files, options, fn) => {
23
+ const res = []
24
+ for (const target of files.map((t) => fullTarget(dir, t, options))) {
25
+ for (const file of await glob(target, { cwd: dir })) {
26
+ res.push(await fn(file))
27
+ }
28
+ }
29
+ return res.filter(Boolean)
30
+ }
31
+
32
+ const parseEach = async (dir, files, options, fn) => {
33
+ const res = []
34
+ for (const parser of getParsers(dir, files, options)) {
35
+ res.push(await fn(parser))
36
+ }
37
+ return res.filter(Boolean)
38
+ }
39
+
40
+ module.exports = {
41
+ rmEach,
42
+ parseEach,
43
+ }
@@ -0,0 +1,24 @@
1
+ const hgi = require('hosted-git-info')
2
+ const git = require('@npmcli/git')
3
+
4
+ // parse a repo from a git origin into a format
5
+ // for a package.json#repository object
6
+ const getRepo = async (path) => {
7
+ if (!await git.is({ cwd: path })) {
8
+ return
9
+ }
10
+
11
+ try {
12
+ const res = await git.spawn([
13
+ 'remote',
14
+ 'get-url',
15
+ 'origin',
16
+ ], { cwd: path })
17
+ const { domain, user, project } = hgi.fromUrl(res.stdout.trim())
18
+ const url = new URL(`https://${domain}`)
19
+ url.pathname = `/${user}/${project}.git`
20
+ return url.toString()
21
+ } catch {}
22
+ }
23
+
24
+ module.exports = getRepo
@@ -0,0 +1,30 @@
1
+ const intersects = require('semver/ranges/intersects')
2
+ const { has } = require('lodash')
3
+
4
+ const installLocations = [
5
+ 'dependencies',
6
+ 'devDependencies',
7
+ 'peerDependencies',
8
+ 'bundledDependencies',
9
+ 'optionalDependencies',
10
+ ]
11
+
12
+ const hasPackage = (
13
+ pkg,
14
+ name,
15
+ version = '*',
16
+ locations = installLocations
17
+ ) => locations
18
+ .map((l) => pkg[l])
19
+ .some((deps) =>
20
+ has(deps, name) &&
21
+ (version === '*' || intersects(deps[name], version))
22
+ )
23
+
24
+ module.exports = hasPackage
25
+
26
+ module.exports.flags = installLocations.reduce((acc, location) => {
27
+ const type = location.replace(/dependencies/i, '')
28
+ acc[location] = '--save' + (type ? `-${type}` : '')
29
+ return acc
30
+ }, {})
@@ -0,0 +1,38 @@
1
+ const { format } = require('util')
2
+ const { get } = require('lodash')
3
+ const { diff } = require('just-diff')
4
+
5
+ const j = (obj, replacer = null) => JSON.stringify(obj, replacer, 2)
6
+
7
+ const jsonDiff = (s1, s2, DELETE) => {
8
+ // DELETE is a special string that will be the value of updated if it exists
9
+ // but should be deleted
10
+
11
+ const ops = diff(s1, s2).map(({ op, path, value }) => {
12
+ // there could be cases where a whole object is reported
13
+ // as missing and the expected value does not need to show
14
+ // special DELETED values so filter those out here
15
+ const msgVal = j(value, (_, v) => v === DELETE ? undefined : v)
16
+ const prev = j(get(s1, path))
17
+ const key = j(path.reduce((acc, p) => acc + (typeof p === 'number' ? `[${p}]` : `.${p}`)))
18
+
19
+ const msg = (...args) => format('%s is %s, expected %s', ...args)
20
+ const AD = msg(key, 'missing', msgVal)
21
+ const RM = msg(key, prev, 'to be removed')
22
+ const UP = msg(key, prev, msgVal)
23
+
24
+ if (op === 'replace') {
25
+ return value === DELETE ? RM : UP
26
+ } else if (op === 'add' && value !== DELETE) {
27
+ return AD
28
+ }
29
+ }).filter(Boolean).sort((a, b) => a.localeCompare(b))
30
+
31
+ if (!ops.length) {
32
+ return true
33
+ }
34
+
35
+ return ops.join('\n')
36
+ }
37
+
38
+ module.exports = jsonDiff
@@ -0,0 +1,35 @@
1
+ const indent = (v, i = 2) => {
2
+ if (Array.isArray(v)) {
3
+ return v.map((a) => indent(a, i)).join('\n')
4
+ }
5
+ return v.toString().split('\n').map((l) => ' '.repeat(i) + l).join('\n')
6
+ }
7
+
8
+ const output = () => {
9
+ const res = []
10
+ const push = (...parts) => res.push(parts.join('\n'))
11
+ return {
12
+ toString: () => res.join('\n'),
13
+ sep: () => push('', '-'.repeat(67), ''),
14
+ push,
15
+ }
16
+ }
17
+
18
+ const outputProblems = (problems) => {
19
+ const o = output()
20
+ o.push('', 'Some problems were detected:')
21
+ o.sep()
22
+ for (const { title, body, solution } of problems) {
23
+ const [solutionTitle, ...solutionRest] = Array.isArray(solution)
24
+ ? solution : [solution]
25
+ o.push(title, '', indent(body), '', `To correct it: ${solutionTitle}`)
26
+ if (solutionRest.length) {
27
+ o.push('', indent(solutionRest))
28
+ }
29
+ o.sep()
30
+ }
31
+
32
+ return o.toString()
33
+ }
34
+
35
+ module.exports = outputProblems
@@ -0,0 +1,78 @@
1
+ const semver = require('semver')
2
+ const { partition, uniq, groupBy } = require('lodash')
3
+
4
+ // try to parse a version. if its invalid then
5
+ // try to parse it as a range instead
6
+ const versionOrRange = (v) => semver.parse(v) || new semver.Range(v)
7
+
8
+ // get the version or the upper bound of the range
9
+ // used for sorting to give the latest ci target
10
+ const getMaxVersion = (v) => v.version || v.set[0][1].semver.version
11
+
12
+ // given an array of versions, returns an object where
13
+ // each key is a major and each value is a sorted list of versions
14
+ const versionsByMajor = (versions) => {
15
+ const majors = groupBy(versions, (v) => semver.major(v))
16
+ for (const [k, vs] of Object.entries(majors)) {
17
+ majors[k] = semver.sort(vs)[0]
18
+ }
19
+ return majors
20
+ }
21
+
22
+ // given a list of semver ci targets like:
23
+ // ['12.13.0', '12.x', '14.15.0', '14.x', '16.0.0', '16.x']
24
+ // this will parse into a uniq list of lowest "supported"
25
+ // versions. In our cases so that will return
26
+ // '^12.13.0 || ^14.15.0 || >=16'. This is not super generic but fits
27
+ // our use case for now where we want to test on a bunch of
28
+ // specific versions/ranges and map them to somewhat loose
29
+ // semver range for package.json#engines.node. This only supports
30
+ // returning ^ ranges and makes the last version >= currently.
31
+ //
32
+ // Assumptions:
33
+ // - ranges span a single major version
34
+ // - specific versions are lower then the upper bound of
35
+ // ranges within the same major version
36
+ const parseCITargets = (targets = []) => {
37
+ const [versions, ranges] = partition(
38
+ targets.map((t) => versionOrRange(t)),
39
+ (t) => t.version
40
+ )
41
+
42
+ const sorted = [...versions, ...ranges]
43
+ .sort((a, b) => semver.compareBuild(getMaxVersion(a), getMaxVersion(b)))
44
+ .map((v) => v.version || v.raw)
45
+
46
+ // object of {major: lowestVersion } for all passed in versions
47
+ const minVersions = versionsByMajor(versions)
48
+
49
+ // object of {major: lowestVersionInRange } for all passed in ranges
50
+ const minRanges = versionsByMajor(ranges.map((r) => semver.minVersion(r)))
51
+
52
+ // Given all the uniq major versions in targets...
53
+ const parsedRanges = uniq([...Object.keys(minVersions), ...Object.keys(minRanges)])
54
+ // first sort by major to make it display nicer
55
+ .sort((a, b) => Number(a) - Number(b))
56
+ .map((major) => {
57
+ const minVersion = minVersions[major]
58
+ const minRange = minRanges[major]
59
+ // if we only have one then return that
60
+ if (!minVersion || !minRange) {
61
+ return minVersion || minRange
62
+ }
63
+ // otherwise return min version
64
+ // XXX: this assumes the versions are lower than the upper
65
+ // bound for any range for the same major. This is ok for
66
+ // now but will break with more complex/specific semver ranges
67
+ return minVersion
68
+ })
69
+ // make the last version allow all greater than
70
+ .map((v, index, list) => (index === list.length - 1 ? '>=' : '^') + v)
71
+
72
+ return {
73
+ targets: sorted,
74
+ engines: parsedRanges.join(' || '),
75
+ }
76
+ }
77
+
78
+ module.exports = parseCITargets