@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.
- package/README.md +77 -68
- package/bin/apply.js +22 -0
- package/bin/check.js +26 -0
- package/lib/apply/apply-files.js +31 -0
- package/lib/apply/index.js +5 -0
- package/lib/check/check-apply.js +73 -0
- package/lib/check/check-changelog.js +31 -0
- package/lib/check/check-gitignore.js +67 -0
- package/lib/check/check-required.js +36 -0
- package/lib/check/check-unwanted.js +23 -0
- package/lib/check/index.js +9 -0
- package/lib/config.js +151 -40
- package/lib/content/CODEOWNERS +1 -1
- package/lib/content/LICENSE.md +0 -2
- package/lib/content/SECURITY.md +0 -2
- package/lib/content/audit.yml +5 -12
- package/lib/content/bug.yml +45 -46
- package/lib/content/ci.yml +35 -38
- package/lib/content/codeql-analysis.yml +11 -9
- package/lib/content/commitlintrc.js +1 -4
- package/lib/content/config.yml +0 -2
- package/lib/content/dependabot.yml +13 -14
- package/lib/content/eslintrc.js +0 -2
- package/lib/content/gitignore +8 -14
- package/lib/content/index.js +90 -0
- package/lib/content/npmrc +0 -2
- package/lib/content/package.json +27 -0
- package/lib/content/post-dependabot.yml +12 -15
- package/lib/content/pull-request.yml +12 -11
- package/lib/content/release-please.yml +18 -11
- package/lib/content/setup-git.yml +11 -0
- package/lib/content/setup-node.yml +21 -0
- package/lib/index.js +100 -0
- package/lib/util/files.js +43 -0
- package/lib/util/get-git-url.js +24 -0
- package/lib/util/has-package.js +30 -0
- package/lib/util/json-diff.js +38 -0
- package/lib/util/output.js +35 -0
- package/lib/util/parse-ci-versions.js +78 -0
- package/lib/util/parser.js +279 -0
- package/lib/util/template.js +41 -0
- package/package.json +29 -26
- package/bin/.gitattributes +0 -3
- package/bin/npm-template-check.js +0 -44
- package/bin/postinstall.js +0 -31
- package/lib/content/ci-no-windows.yml +0 -48
- package/lib/content/ci-workspace.yml +0 -63
- package/lib/content/release-please-workspace.yml +0 -29
- package/lib/postinstall/copy-content.js +0 -133
- package/lib/postinstall/update-package.js +0 -100
- package/lib/postlint/check-gitignore.js +0 -59
- 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
|
-
|
|
1
|
+
name: Post Dependabot Actions
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
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:
|
|
13
|
+
if: github.actor == 'dependabot[bot]'
|
|
14
14
|
steps:
|
|
15
|
-
|
|
16
|
-
|
|
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: "
|
|
21
|
+
github-token: "$\{{ secrets.GITHUB_TOKEN }}"
|
|
24
22
|
- name: npm install and commit
|
|
25
|
-
if:
|
|
23
|
+
if: contains(steps.metadata.outputs.dependency-names, '@npmcli/template-oss')
|
|
26
24
|
env:
|
|
27
|
-
GITHUB_TOKEN:
|
|
25
|
+
GITHUB_TOKEN: $\{{ secrets.GITHUB_TOKEN }}
|
|
28
26
|
run: |
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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:
|
|
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
|
-
|
|
15
|
-
|
|
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:
|
|
23
|
+
PR_TITLE: $\{{ github.event.pull_request.title }}
|
|
26
24
|
run: |
|
|
27
|
-
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
|
-
|
|
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
|
-
|
|
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@
|
|
18
|
+
- uses: google-github-actions/release-please-action@v3
|
|
15
19
|
id: release
|
|
16
20
|
with:
|
|
17
21
|
release-type: node
|
|
18
|
-
#
|
|
22
|
+
{{#if pkgRelPath}}
|
|
23
|
+
monorepo-tags: true
|
|
24
|
+
paths: {{pkgRelPath}}
|
|
25
|
+
{{/if}}
|
|
19
26
|
changelog-types: >
|
|
20
|
-
[
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
27
|
+
[
|
|
28
|
+
{{#each changelogTypes}}
|
|
29
|
+
{{{json .}}}{{#unless @last}},{{/unless}}
|
|
30
|
+
{{/each}}
|
|
31
|
+
]
|
|
@@ -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
|