@npmcli/template-oss 4.1.2 → 4.3.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 +42 -28
- package/lib/apply/apply-files.js +1 -1
- package/lib/apply/index.js +1 -1
- package/lib/check/check-apply.js +5 -4
- package/lib/check/index.js +1 -1
- package/lib/config.js +178 -121
- package/lib/content/_job-matrix.yml +29 -0
- package/lib/content/_job.yml +8 -0
- package/lib/content/_on-ci.yml +30 -0
- package/lib/content/_step-checks.yml +24 -0
- package/lib/content/_step-deps.yml +2 -0
- package/lib/content/_step-git.yml +12 -0
- package/lib/content/_step-lint.yml +4 -0
- package/lib/content/{setup-node.yml → _step-node.yml} +12 -9
- package/lib/content/_step-test.yml +4 -0
- package/lib/content/_steps-setup.yml +6 -0
- package/lib/content/audit.yml +3 -6
- package/lib/content/ci-release.yml +31 -0
- package/lib/content/ci.yml +6 -54
- package/lib/content/codeql-analysis.yml +10 -17
- package/lib/content/commitlintrc.js +1 -1
- package/lib/content/dependabot.yml +2 -2
- package/lib/content/eslintrc.js +7 -0
- package/lib/content/gitignore +1 -14
- package/lib/content/index.js +62 -27
- package/lib/content/npmrc +1 -1
- package/lib/content/pkg.json +34 -14
- package/lib/content/post-dependabot.yml +55 -16
- package/lib/content/pull-request.yml +11 -13
- package/lib/content/release-please-config.json +5 -5
- package/lib/content/release-please-manifest.json +1 -1
- package/lib/content/release.yml +125 -0
- package/lib/index.js +27 -30
- package/lib/release-please/index.js +26 -5
- package/lib/util/files.js +71 -27
- package/lib/util/gitignore.js +34 -0
- package/lib/util/merge.js +21 -0
- package/lib/util/parser.js +76 -18
- package/lib/util/template.js +30 -21
- package/package.json +7 -2
- package/lib/content/release-please.yml +0 -73
- package/lib/content/release-test.yml +0 -46
- package/lib/content/setup-deps.yml +0 -1
- package/lib/content/setup-git.yml +0 -11
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
name: Release
|
|
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
|
+
checks: write
|
|
17
|
+
|
|
18
|
+
jobs:
|
|
19
|
+
release:
|
|
20
|
+
outputs:
|
|
21
|
+
pr: $\{{ steps.release.outputs.pr }}
|
|
22
|
+
releases: $\{{ steps.release.outputs.releases }}
|
|
23
|
+
release-flags: $\{{ steps.release.outputs.release-flags }}
|
|
24
|
+
branch: $\{{ steps.release.outputs.pr-branch }}
|
|
25
|
+
pr-number: $\{{ steps.release.outputs.pr-number }}
|
|
26
|
+
comment-id: $\{{ steps.pr-comment.outputs.result }}
|
|
27
|
+
check-id: $\{{ steps.check.outputs.check_id }}
|
|
28
|
+
{{> job jobName="Release" }}
|
|
29
|
+
- name: Release Please
|
|
30
|
+
id: release
|
|
31
|
+
env:
|
|
32
|
+
GITHUB_TOKEN: $\{{ secrets.GITHUB_TOKEN }}
|
|
33
|
+
run: |
|
|
34
|
+
{{ rootNpxPath }} --offline template-oss-release-please $\{{ github.ref_name }}
|
|
35
|
+
- name: Post Pull Request Comment
|
|
36
|
+
if: steps.release.outputs.pr-number
|
|
37
|
+
uses: actions/github-script@v6
|
|
38
|
+
id: pr-comment
|
|
39
|
+
env:
|
|
40
|
+
PR_NUMBER: $\{{ steps.release.outputs.pr-number }}
|
|
41
|
+
with:
|
|
42
|
+
script: |
|
|
43
|
+
const repo = { owner: context.repo.owner, repo: context.repo.repo }
|
|
44
|
+
const issue = { ...repo, issue_number: process.env.PR_NUMBER }
|
|
45
|
+
|
|
46
|
+
const { data: workflow } = await github.rest.actions.getWorkflowRun({ ...repo, run_id: context.runId })
|
|
47
|
+
|
|
48
|
+
let body = '## Release Manager\n\n'
|
|
49
|
+
|
|
50
|
+
const comments = await github.paginate(github.rest.issues.listComments, issue)
|
|
51
|
+
let commentId = comments?.find(c => c.user.login === 'github-actions[bot]' && c.body.startsWith(body))?.id
|
|
52
|
+
|
|
53
|
+
body += `- Release workflow run: ${workflow.html_url}`
|
|
54
|
+
if (commentId) {
|
|
55
|
+
await github.rest.issues.updateComment({ ...repo, comment_id: commentId, body })
|
|
56
|
+
} else {
|
|
57
|
+
const { data: comment } = await github.rest.issues.createComment({ ...issue, body })
|
|
58
|
+
commentId = comment?.id
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return commentId
|
|
62
|
+
{{> stepChecks jobCheck=(obj name="Release" sha="${{ steps.release.outputs.pr-sha }}" if="steps.release.outputs.pr-number") }}
|
|
63
|
+
|
|
64
|
+
update:
|
|
65
|
+
needs: release
|
|
66
|
+
outputs:
|
|
67
|
+
sha: $\{{ steps.commit.outputs.sha }}
|
|
68
|
+
check-id: $\{{ steps.check.outputs.check_id }}
|
|
69
|
+
{{> job
|
|
70
|
+
jobName="Update - Release"
|
|
71
|
+
jobIf="needs.release.outputs.pr"
|
|
72
|
+
jobCheckout=(obj ref="${{ needs.release.outputs.branch }}" fetch-depth=0)
|
|
73
|
+
}}
|
|
74
|
+
- name: Run Post Pull Request Actions
|
|
75
|
+
env:
|
|
76
|
+
RELEASE_PR_NUMBER: $\{{ needs.release.outputs.pr-number }}
|
|
77
|
+
RELEASE_COMMENT_ID: $\{{ needs.release.outputs.comment-id }}
|
|
78
|
+
GITHUB_TOKEN: $\{{ secrets.GITHUB_TOKEN }}
|
|
79
|
+
run: |
|
|
80
|
+
{{ rootNpmPath }} run rp-pull-request --ignore-scripts {{ allFlags }}
|
|
81
|
+
- name: Commit
|
|
82
|
+
id: commit
|
|
83
|
+
env:
|
|
84
|
+
GITHUB_TOKEN: $\{{ secrets.GITHUB_TOKEN }}
|
|
85
|
+
run: |
|
|
86
|
+
git commit --all --amend --no-edit || true
|
|
87
|
+
git push --force-with-lease
|
|
88
|
+
echo "::set-output name=sha::$(git rev-parse HEAD)"
|
|
89
|
+
{{> stepChecks jobCheck=(obj sha="${{ steps.commit.outputs.sha }}" name="Release" )}}
|
|
90
|
+
{{> stepChecks jobCheck=(obj id="${{ needs.release.outputs.check-id }}" )}}
|
|
91
|
+
|
|
92
|
+
ci:
|
|
93
|
+
name: CI - Release
|
|
94
|
+
needs: [release, update]
|
|
95
|
+
if: needs.release.outputs.pr
|
|
96
|
+
uses: ./.github/workflows/ci-release.yml
|
|
97
|
+
with:
|
|
98
|
+
ref: $\{{ needs.release.outputs.branch }}
|
|
99
|
+
check-sha: $\{{ needs.update.outputs.sha }}
|
|
100
|
+
|
|
101
|
+
post-ci:
|
|
102
|
+
needs: [release, update, ci]
|
|
103
|
+
{{> job jobName="Post CI - Release" jobIf="needs.release.outputs.pr && always()" jobSkipSetup=true }}
|
|
104
|
+
- name: Get Needs Result
|
|
105
|
+
id: needs-result
|
|
106
|
+
run: |
|
|
107
|
+
result=""
|
|
108
|
+
if [[ "$\{{ contains(needs.*.result, 'failure') }}" == "true" ]]; then
|
|
109
|
+
result="failure"
|
|
110
|
+
elif [[ "$\{{ contains(needs.*.result, 'cancelled') }}" == "true" ]]; then
|
|
111
|
+
result="cancelled"
|
|
112
|
+
else
|
|
113
|
+
result="success"
|
|
114
|
+
fi
|
|
115
|
+
echo "::set-output name=result::$result"
|
|
116
|
+
{{> stepChecks jobCheck=(obj id="${{ needs.update.outputs.check-id }}" status="${{ steps.needs-result.outputs.result }}") }}
|
|
117
|
+
|
|
118
|
+
post-release:
|
|
119
|
+
needs: release
|
|
120
|
+
{{> job jobName="Post Release - Release" jobIf="needs.release.outputs.releases" }}
|
|
121
|
+
- name: Run Post Release Actions
|
|
122
|
+
env:
|
|
123
|
+
RELEASES: $\{{ needs.release.outputs.releases }}
|
|
124
|
+
run: |
|
|
125
|
+
{{ rootNpmPath }} run rp-release --ignore-scripts --if-present $\{{ join(fromJSON(needs.release.outputs.release-flags), ' ') }}
|
package/lib/index.js
CHANGED
|
@@ -1,37 +1,42 @@
|
|
|
1
1
|
const log = require('proc-log')
|
|
2
|
-
const {
|
|
2
|
+
const { resolve } = require('path')
|
|
3
3
|
const getConfig = require('./config.js')
|
|
4
4
|
const PackageJson = require('@npmcli/package-json')
|
|
5
5
|
const mapWorkspaces = require('@npmcli/map-workspaces')
|
|
6
6
|
|
|
7
|
-
const getPkg = async (path
|
|
7
|
+
const getPkg = async (path) => {
|
|
8
8
|
log.verbose('get-pkg', path)
|
|
9
9
|
|
|
10
|
-
const
|
|
11
|
-
const pkgConfig = getConfig.getPkgConfig(
|
|
10
|
+
const pkgJson = (await PackageJson.load(path)).content
|
|
11
|
+
const pkgConfig = getConfig.getPkgConfig(pkgJson)
|
|
12
12
|
log.verbose('get-pkg', pkgConfig)
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
if (pkgConfig.content) {
|
|
15
|
+
pkgConfig.content = resolve(path, pkgConfig.content)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
pkgJson,
|
|
20
|
+
path,
|
|
21
|
+
config: pkgConfig,
|
|
22
|
+
}
|
|
15
23
|
}
|
|
16
24
|
|
|
17
25
|
const getWsPkgs = async (root, rootPkg) => {
|
|
18
26
|
const wsPkgs = []
|
|
19
27
|
|
|
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
28
|
// Include all by default
|
|
29
|
+
const { workspaces } = rootPkg.config
|
|
25
30
|
const include = (name) => Array.isArray(workspaces) ? workspaces.includes(name) : true
|
|
26
31
|
|
|
27
32
|
// Look through all workspaces on the root pkg
|
|
28
|
-
const rootWorkspaces = await mapWorkspaces({ pkg: rootPkg.
|
|
33
|
+
const rootWorkspaces = await mapWorkspaces({ pkg: rootPkg.pkgJson, cwd: root })
|
|
29
34
|
|
|
30
35
|
for (const [wsName, wsPath] of rootWorkspaces.entries()) {
|
|
31
36
|
if (include(wsName)) {
|
|
32
37
|
// A workspace can control its own workspaceRepo and workspaceModule settings
|
|
33
38
|
// which are true by default on the root config
|
|
34
|
-
wsPkgs.push(await getPkg(wsPath
|
|
39
|
+
wsPkgs.push(await getPkg(wsPath))
|
|
35
40
|
}
|
|
36
41
|
}
|
|
37
42
|
|
|
@@ -45,41 +50,33 @@ const getPkgs = async (root) => {
|
|
|
45
50
|
log.verbose('get-pkgs', 'root', root)
|
|
46
51
|
|
|
47
52
|
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
53
|
|
|
58
54
|
const ws = await getWsPkgs(root, rootPkg)
|
|
59
55
|
|
|
60
56
|
return {
|
|
61
|
-
|
|
57
|
+
rootPkg,
|
|
58
|
+
pkgs: [rootPkg].concat(ws.pkgs),
|
|
62
59
|
workspaces: ws.paths,
|
|
63
60
|
}
|
|
64
61
|
}
|
|
65
62
|
|
|
66
|
-
const runAll = async (root,
|
|
63
|
+
const runAll = async (root, checks) => {
|
|
67
64
|
const results = []
|
|
68
|
-
const { pkgs, workspaces } = await getPkgs(root)
|
|
65
|
+
const { pkgs, workspaces, rootPkg: { config: rootConfig } } = await getPkgs(root)
|
|
69
66
|
|
|
70
|
-
for (const {
|
|
67
|
+
for (const { pkgJson, path, config: pkgConfig } of pkgs) {
|
|
71
68
|
// full config includes original config values
|
|
72
69
|
const fullConfig = await getConfig({
|
|
73
|
-
pkgs,
|
|
74
|
-
workspaces,
|
|
75
70
|
root,
|
|
76
|
-
pkg,
|
|
77
71
|
path,
|
|
78
|
-
|
|
79
|
-
|
|
72
|
+
pkgJson,
|
|
73
|
+
pkgs,
|
|
74
|
+
workspaces,
|
|
75
|
+
rootConfig,
|
|
76
|
+
pkgConfig,
|
|
80
77
|
})
|
|
81
78
|
|
|
82
|
-
const options = { root,
|
|
79
|
+
const options = { root, path, pkg: pkgJson, config: fullConfig }
|
|
83
80
|
log.verbose('run-all', options)
|
|
84
81
|
|
|
85
82
|
// files can export multiple checks so flatten first
|
|
@@ -26,13 +26,34 @@ const main = async ({ repo: fullRepo, token, dryRun, branch }) => {
|
|
|
26
26
|
)
|
|
27
27
|
|
|
28
28
|
const pullRequests = await (dryRun ? manifest.buildPullRequests() : manifest.createPullRequests())
|
|
29
|
-
const
|
|
29
|
+
const allReleases = await (dryRun ? manifest.buildReleases() : manifest.createReleases())
|
|
30
|
+
|
|
31
|
+
// We only ever get a single pull request with our current release-please settings
|
|
32
|
+
const rootPr = pullRequests.filter(Boolean)[0]
|
|
33
|
+
if (rootPr?.number) {
|
|
34
|
+
const commits = await github.octokit.paginate(github.octokit.rest.pulls.listCommits, {
|
|
35
|
+
owner: github.repository.owner,
|
|
36
|
+
repo: github.repository.repo,
|
|
37
|
+
pull_number: rootPr.number,
|
|
38
|
+
})
|
|
39
|
+
rootPr.sha = commits?.[commits.length - 1]?.sha
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const releases = allReleases.filter(Boolean)
|
|
43
|
+
const [rootRelease, workspaceReleases] = releases.reduce((acc, r) => {
|
|
44
|
+
if (r.path === '.') {
|
|
45
|
+
acc[0] = r
|
|
46
|
+
} else {
|
|
47
|
+
acc[1].push(r)
|
|
48
|
+
}
|
|
49
|
+
return acc
|
|
50
|
+
}, [null, []])
|
|
30
51
|
|
|
31
52
|
return {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
releases: releases.
|
|
35
|
-
|
|
53
|
+
pr: rootPr,
|
|
54
|
+
release: rootRelease,
|
|
55
|
+
releases: releases.length ? releases : null,
|
|
56
|
+
workspaceReleases: workspaceReleases.length ? workspaceReleases : null,
|
|
36
57
|
}
|
|
37
58
|
}
|
|
38
59
|
|
package/lib/util/files.js
CHANGED
|
@@ -1,43 +1,57 @@
|
|
|
1
1
|
const { join } = require('path')
|
|
2
|
+
const { defaultsDeep } = require('lodash')
|
|
2
3
|
const { promisify } = require('util')
|
|
4
|
+
const merge = require('./merge.js')
|
|
5
|
+
const deepMapValues = require('just-deep-map-values')
|
|
3
6
|
const glob = promisify(require('glob'))
|
|
4
7
|
const Parser = require('./parser.js')
|
|
5
8
|
const template = require('./template.js')
|
|
6
9
|
|
|
10
|
+
const FILE_KEYS = ['rootRepo', 'rootModule', 'workspaceRepo', 'workspaceModule']
|
|
11
|
+
|
|
7
12
|
const globify = pattern => pattern.split('\\').join('/')
|
|
8
13
|
|
|
9
|
-
|
|
10
|
-
|
|
14
|
+
const fileEntries = (dir, files, options) => Object.entries(files)
|
|
15
|
+
// remove any false values
|
|
16
|
+
.filter(([_, v]) => v !== false)
|
|
17
|
+
// target paths need to be joinsed with dir and templated
|
|
18
|
+
.map(([k, source]) => {
|
|
19
|
+
const target = join(dir, template(k, options))
|
|
20
|
+
return [target, source]
|
|
21
|
+
})
|
|
11
22
|
|
|
12
23
|
// given an obj of files, return the full target/source paths and associated parser
|
|
13
|
-
const getParsers = (dir, files, options) =>
|
|
14
|
-
|
|
15
|
-
file,
|
|
16
|
-
|
|
17
|
-
filter
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
if (typeof filter === 'function' && !filter(options)) {
|
|
24
|
-
return null
|
|
25
|
-
}
|
|
24
|
+
const getParsers = (dir, files, options) => {
|
|
25
|
+
const parsers = fileEntries(dir, files, options).map(([target, source]) => {
|
|
26
|
+
const { file, parser, filter, clean: shouldClean } = source
|
|
27
|
+
|
|
28
|
+
if (typeof filter === 'function' && !filter(options)) {
|
|
29
|
+
return null
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const clean = typeof shouldClean === 'function' ? shouldClean(options) : false
|
|
26
33
|
|
|
27
|
-
|
|
34
|
+
if (parser) {
|
|
28
35
|
// allow files to extend base parsers or create new ones
|
|
29
|
-
|
|
30
|
-
|
|
36
|
+
return new (parser(Parser.Parsers))(target, file, options, { clean })
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return new (Parser(file))(target, file, options, { clean })
|
|
40
|
+
})
|
|
31
41
|
|
|
32
|
-
return
|
|
33
|
-
}
|
|
42
|
+
return parsers.filter(Boolean)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const getRemovals = async (dir, files, options) => {
|
|
46
|
+
const targets = fileEntries(dir, files, options).map(([t]) => globify(t))
|
|
47
|
+
const globs = await Promise.all(targets.map(t => glob(t, { cwd: dir })))
|
|
48
|
+
return globs.flat()
|
|
49
|
+
}
|
|
34
50
|
|
|
35
51
|
const rmEach = async (dir, files, options, fn) => {
|
|
36
52
|
const res = []
|
|
37
|
-
for (const
|
|
38
|
-
|
|
39
|
-
res.push(await fn(file))
|
|
40
|
-
}
|
|
53
|
+
for (const file of await getRemovals(dir, files, options)) {
|
|
54
|
+
res.push(await fn(file))
|
|
41
55
|
}
|
|
42
56
|
return res.filter(Boolean)
|
|
43
57
|
}
|
|
@@ -45,14 +59,44 @@ const rmEach = async (dir, files, options, fn) => {
|
|
|
45
59
|
const parseEach = async (dir, files, options, fn) => {
|
|
46
60
|
const res = []
|
|
47
61
|
for (const parser of getParsers(dir, files, options)) {
|
|
48
|
-
|
|
49
|
-
res.push(await fn(parser))
|
|
50
|
-
}
|
|
62
|
+
res.push(await fn(parser))
|
|
51
63
|
}
|
|
52
64
|
return res.filter(Boolean)
|
|
53
65
|
}
|
|
54
66
|
|
|
67
|
+
const parseConfig = (files, dir, overrides) => {
|
|
68
|
+
const normalizeFiles = (v) => deepMapValues(v, (value, key) => {
|
|
69
|
+
if (key === 'rm' && Array.isArray(value)) {
|
|
70
|
+
return value.reduce((acc, k) => {
|
|
71
|
+
acc[k] = true
|
|
72
|
+
return acc
|
|
73
|
+
}, {})
|
|
74
|
+
}
|
|
75
|
+
if (typeof value === 'string') {
|
|
76
|
+
const file = join(dir, value)
|
|
77
|
+
return key === 'file' ? file : { file }
|
|
78
|
+
}
|
|
79
|
+
if (value === true && FILE_KEYS.includes(key)) {
|
|
80
|
+
return {}
|
|
81
|
+
}
|
|
82
|
+
return value
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
const merged = merge(normalizeFiles(files), normalizeFiles(overrides))
|
|
86
|
+
const withDefaults = defaultsDeep(merged, FILE_KEYS.reduce((acc, k) => {
|
|
87
|
+
acc[k] = { add: {}, rm: {} }
|
|
88
|
+
return acc
|
|
89
|
+
}, {}))
|
|
90
|
+
|
|
91
|
+
return withDefaults
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const getAddedFiles = (files) => files ? Object.keys(files.add || {}) : []
|
|
95
|
+
|
|
55
96
|
module.exports = {
|
|
56
97
|
rmEach,
|
|
57
98
|
parseEach,
|
|
99
|
+
FILE_KEYS,
|
|
100
|
+
parseConfig,
|
|
101
|
+
getAddedFiles,
|
|
58
102
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const { posix } = require('path')
|
|
2
|
+
const { uniq } = require('lodash')
|
|
3
|
+
const localeCompare = require('@isaacs/string-locale-compare')('en')
|
|
4
|
+
|
|
5
|
+
const sortGitPaths = (a, b) => localeCompare(a.replace(/^!/g, ''), b.replace(/^!/g, ''))
|
|
6
|
+
|
|
7
|
+
const allowDir = (p) => {
|
|
8
|
+
const parts = p.split(posix.sep)
|
|
9
|
+
return parts.flatMap((part, index, list) => {
|
|
10
|
+
const prev = list.slice(0, index)
|
|
11
|
+
const isLast = index === list.length - 1
|
|
12
|
+
const ignorePart = ['', ...prev, part, ''].join(posix.sep)
|
|
13
|
+
return [`!${ignorePart}`, !isLast && `${ignorePart}*`]
|
|
14
|
+
}).filter(Boolean)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const allowRootDir = (p) => {
|
|
18
|
+
// This negates the first part of each path for the gitignore
|
|
19
|
+
// files. It should be used to allow directories where everything
|
|
20
|
+
// should be allowed inside such as .github/. It shouldn't be used on
|
|
21
|
+
// directories like `workspaces/` since we want to be explicit and
|
|
22
|
+
// only allow each workspace directory individually. For those use
|
|
23
|
+
// the allowDir method above.
|
|
24
|
+
const [first, hasChildren] = p.split(posix.sep)
|
|
25
|
+
return `${first}${hasChildren ? posix.sep : ''}`
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const gitignore = {
|
|
29
|
+
allowDir: (dirs) => uniq(dirs.map(allowDir).flat()),
|
|
30
|
+
allowRootDir: (dirs) => dirs.map(allowRootDir).map((p) => `!${posix.sep}${p}`),
|
|
31
|
+
sort: (arr) => uniq(arr.sort(sortGitPaths)),
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
module.exports = gitignore
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const { mergeWith } = require('lodash')
|
|
2
|
+
|
|
3
|
+
const merge = (...objects) => mergeWith({}, ...objects, (value, srcValue, key) => {
|
|
4
|
+
if (Array.isArray(srcValue)) {
|
|
5
|
+
// Dont merge arrays, last array wins
|
|
6
|
+
return srcValue
|
|
7
|
+
}
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
const mergeWithArrays = (...keys) =>
|
|
11
|
+
(...objects) => mergeWith({}, ...objects, (value, srcValue, key) => {
|
|
12
|
+
if (Array.isArray(srcValue)) {
|
|
13
|
+
if (keys.includes(key)) {
|
|
14
|
+
return (Array.isArray(value) ? value : []).concat(srcValue)
|
|
15
|
+
}
|
|
16
|
+
return srcValue
|
|
17
|
+
}
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
module.exports = merge
|
|
21
|
+
module.exports.withArrays = mergeWithArrays
|
package/lib/util/parser.js
CHANGED
|
@@ -4,10 +4,13 @@ const yaml = require('yaml')
|
|
|
4
4
|
const NpmPackageJson = require('@npmcli/package-json')
|
|
5
5
|
const jsonParse = require('json-parse-even-better-errors')
|
|
6
6
|
const Diff = require('diff')
|
|
7
|
-
const { unset
|
|
7
|
+
const { unset } = require('lodash')
|
|
8
8
|
const template = require('./template.js')
|
|
9
9
|
const jsonDiff = require('./json-diff')
|
|
10
|
+
const merge = require('./merge.js')
|
|
11
|
+
|
|
10
12
|
const setFirst = (first, rest) => ({ ...first, ...rest })
|
|
13
|
+
|
|
11
14
|
const traverse = (value, visit, keys = []) => {
|
|
12
15
|
if (keys.length) {
|
|
13
16
|
const res = visit(keys, value)
|
|
@@ -22,17 +25,25 @@ const traverse = (value, visit, keys = []) => {
|
|
|
22
25
|
}
|
|
23
26
|
}
|
|
24
27
|
|
|
28
|
+
const fsOk = (code) => (error) => {
|
|
29
|
+
if (error.code === 'ENOENT') {
|
|
30
|
+
return null
|
|
31
|
+
}
|
|
32
|
+
return Object.assign(error, { code })
|
|
33
|
+
}
|
|
34
|
+
|
|
25
35
|
class Base {
|
|
26
36
|
static types = []
|
|
27
|
-
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.'
|
|
28
38
|
comment = (v) => v
|
|
29
39
|
merge = false // supply a merge function which runs on prepare for certain types
|
|
30
40
|
DELETE = template.DELETE
|
|
31
41
|
|
|
32
|
-
constructor (target, source, options) {
|
|
42
|
+
constructor (target, source, options, fileOptions) {
|
|
33
43
|
this.target = target
|
|
34
44
|
this.source = source
|
|
35
45
|
this.options = options
|
|
46
|
+
this.fileOptions = fileOptions
|
|
36
47
|
}
|
|
37
48
|
|
|
38
49
|
header () {
|
|
@@ -41,6 +52,13 @@ class Base {
|
|
|
41
52
|
}
|
|
42
53
|
}
|
|
43
54
|
|
|
55
|
+
clean () {
|
|
56
|
+
if (this.fileOptions.clean) {
|
|
57
|
+
return fs.rm(this.target).catch(fsOk())
|
|
58
|
+
}
|
|
59
|
+
return null
|
|
60
|
+
}
|
|
61
|
+
|
|
44
62
|
read (s) {
|
|
45
63
|
return fs.readFile(s, { encoding: 'utf-8' })
|
|
46
64
|
}
|
|
@@ -87,13 +105,17 @@ class Base {
|
|
|
87
105
|
// XXX: everything is allowed to be overridden in base classes but we could
|
|
88
106
|
// find a different solution than making everything public
|
|
89
107
|
applyWrite () {
|
|
90
|
-
return Promise.resolve(this.
|
|
108
|
+
return Promise.resolve(this.clean())
|
|
109
|
+
.then(() => this.read(this.source))
|
|
91
110
|
// replace template vars first, this will throw for nonexistant vars
|
|
92
111
|
// because it must be parseable after this step
|
|
93
112
|
.then((s) => this.template(s))
|
|
94
113
|
// parse into whatever data structure is necessary for maniuplating
|
|
95
114
|
// diffing, merging, etc. by default its a string
|
|
96
|
-
.then((s) =>
|
|
115
|
+
.then((s) => {
|
|
116
|
+
this.sourcePreParse = s
|
|
117
|
+
return this.parse(s)
|
|
118
|
+
})
|
|
97
119
|
// prepare the source for writing and diffing, pass in current
|
|
98
120
|
// target for merging. errors parsing or preparing targets are ok here
|
|
99
121
|
.then((s) => this.applyTarget().catch(() => null).then((t) => this.prepare(s, t)))
|
|
@@ -108,14 +130,9 @@ class Base {
|
|
|
108
130
|
}
|
|
109
131
|
|
|
110
132
|
async applyDiff () {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
return null
|
|
115
|
-
} else {
|
|
116
|
-
return { code: 'ETARGETERROR', error: e }
|
|
117
|
-
}
|
|
118
|
-
})
|
|
133
|
+
// handle if old does not exist
|
|
134
|
+
const targetError = 'ETARGETERROR'
|
|
135
|
+
const target = await this.applyTarget().catch(fsOk(targetError))
|
|
119
136
|
|
|
120
137
|
// no need to diff if current file does not exist
|
|
121
138
|
if (target === null) {
|
|
@@ -130,11 +147,11 @@ class Base {
|
|
|
130
147
|
|
|
131
148
|
// if there was a target error then there is no need to diff
|
|
132
149
|
// so we just show the source with an error message
|
|
133
|
-
if (target.code ===
|
|
150
|
+
if (target.code === targetError) {
|
|
134
151
|
const msg = `[${this.options.config.__NAME__} ERROR]`
|
|
135
152
|
return [
|
|
136
153
|
`${msg} There was an erroring getting the target file`,
|
|
137
|
-
`${msg} ${target
|
|
154
|
+
`${msg} ${target}`,
|
|
138
155
|
`${msg} It will be overwritten with the following source:`,
|
|
139
156
|
'-'.repeat(40),
|
|
140
157
|
this.toString(source),
|
|
@@ -174,7 +191,12 @@ class Yml extends Base {
|
|
|
174
191
|
comment = (c) => ` ${c}`
|
|
175
192
|
|
|
176
193
|
toString (s) {
|
|
177
|
-
|
|
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
|
+
}
|
|
178
200
|
}
|
|
179
201
|
|
|
180
202
|
parse (s) {
|
|
@@ -191,6 +213,41 @@ class Yml extends Base {
|
|
|
191
213
|
}
|
|
192
214
|
}
|
|
193
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
|
+
|
|
194
251
|
class Json extends Base {
|
|
195
252
|
static types = ['json']
|
|
196
253
|
// its a json comment! not really but we do add a special key
|
|
@@ -219,8 +276,8 @@ class Json extends Base {
|
|
|
219
276
|
}
|
|
220
277
|
|
|
221
278
|
class JsonMerge extends Json {
|
|
222
|
-
static header = 'This file is partially managed by {{__NAME__}}. Edits may be overwritten.'
|
|
223
|
-
merge = (t, s) => merge(
|
|
279
|
+
static header = 'This file is partially managed by {{ __NAME__ }}. Edits may be overwritten.'
|
|
280
|
+
merge = (t, s) => merge(t, s)
|
|
224
281
|
}
|
|
225
282
|
|
|
226
283
|
class PackageJson extends JsonMerge {
|
|
@@ -259,6 +316,7 @@ const Parsers = {
|
|
|
259
316
|
Ini,
|
|
260
317
|
Markdown,
|
|
261
318
|
Yml,
|
|
319
|
+
YmlMerge,
|
|
262
320
|
Json,
|
|
263
321
|
JsonMerge,
|
|
264
322
|
PackageJson,
|