@npmcli/template-oss 4.1.1 → 4.2.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.
@@ -5,9 +5,9 @@ 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',
8
+ '.github/workflows/release.yml': {
9
+ file: 'release.yml',
10
+ filter: (o) => !o.pkg.private,
11
11
  },
12
12
  '.release-please-manifest.json': {
13
13
  file: 'release-please-manifest.json',
@@ -25,22 +25,29 @@ const releasePlease = () => ({
25
25
  },
26
26
  })
27
27
 
28
+ const tap = (name) => ({
29
+ '.github/matchers/tap.json': 'tap.json',
30
+ [`.github/workflows/ci${name ? `-${name}` : ''}.yml`]: 'ci.yml',
31
+ })
32
+
28
33
  // Changes applied to the root of the repo
29
34
  const rootRepo = {
30
35
  add: {
31
36
  '.commitlintrc.js': 'commitlintrc.js',
32
- '.github/workflows/ci.yml': 'ci.yml',
33
37
  '.github/ISSUE_TEMPLATE/bug.yml': 'bug.yml',
34
38
  '.github/ISSUE_TEMPLATE/config.yml': 'config.yml',
35
39
  '.github/CODEOWNERS': 'CODEOWNERS',
36
40
  '.github/dependabot.yml': 'dependabot.yml',
37
- '.github/matchers/tap.json': 'tap.json',
38
41
  '.github/workflows/audit.yml': 'audit.yml',
39
42
  '.github/workflows/codeql-analysis.yml': 'codeql-analysis.yml',
40
43
  '.github/workflows/post-dependabot.yml': 'post-dependabot.yml',
41
44
  '.github/workflows/pull-request.yml': 'pull-request.yml',
42
45
  ...releasePlease(),
46
+ ...tap(),
43
47
  },
48
+ rm: [
49
+ '.github/workflows/release-test.yml',
50
+ ],
44
51
  }
45
52
 
46
53
  // These are also applied to the root of the repo
@@ -65,9 +72,8 @@ const rootModule = {
65
72
  // Changes for each workspace but applied to the root of the repo
66
73
  const workspaceRepo = {
67
74
  add: {
68
- ...releasePlease(true),
69
- '.github/matchers/tap.json': 'tap.json',
70
- '.github/workflows/ci-{{pkgNameFs}}.yml': 'ci.yml',
75
+ ...releasePlease(),
76
+ ...tap('{{pkgNameFs}}'),
71
77
  },
72
78
  rm: [
73
79
  // These are the old release please files that should be removed now
@@ -95,17 +101,33 @@ module.exports = {
95
101
  workspaceRepo,
96
102
  workspaceModule,
97
103
  windowsCI: true,
104
+ macCI: true,
98
105
  branches: ['main', 'latest'],
99
106
  releaseBranches: [],
100
107
  defaultBranch: 'main',
101
- // Escape hatch since we write a release test file but the
102
- // CLI has a very custom one we dont want to overwrite. This
103
- // setting allows us to call a workflow by any name during release
104
- releaseTest: 'release-test.yml',
105
- distPaths: ['bin/', 'lib/'],
108
+ distPaths: [
109
+ 'bin/',
110
+ 'lib/',
111
+ ],
112
+ allowPaths: [
113
+ '/bin/',
114
+ '/lib/',
115
+ '/.eslintrc.local.*',
116
+ '**/.gitignore',
117
+ '/docs/',
118
+ '/tap-snapshots/',
119
+ '/test/',
120
+ '/map.js',
121
+ '/scripts/',
122
+ '/README*',
123
+ '/LICENSE*',
124
+ '/CHANGELOG*',
125
+ ],
126
+ ignorePaths: [],
106
127
  ciVersions: ['14.17.0', '14.x', '16.13.0', '16.x', '18.0.0', '18.x'],
107
128
  lockfile: false,
108
- npmBin: 'npm',
129
+ npm: 'npm',
130
+ npx: 'npx',
109
131
  unwantedPackages: [
110
132
  'eslint',
111
133
  'eslint-plugin-node',
@@ -5,23 +5,41 @@
5
5
  "lint": "eslint \"**/*.js\"",
6
6
  "postlint": "template-oss-check",
7
7
  "template-oss-apply": "template-oss-apply --force",
8
- "lintfix": "{{npmBin}} run lint -- --fix",
8
+ "lintfix": "{{localNpmPath}} run lint -- --fix",
9
9
  "preversion": {{{del}}},
10
10
  "postversion": {{{del}}},
11
11
  "prepublishOnly": {{{del}}},
12
+ "postpublish": {{{del}}},
12
13
  "snap": "tap",
13
14
  "test": "tap",
14
- "posttest": "{{npmBin}} run lint",
15
+ "posttest": "{{localNpmPath}} run lint",
16
+ {{#if isRootMono}}
17
+ "test-all": "{{localNpmPath}} run test -ws -iwr --if-present",
18
+ "lint-all": "{{localNpmPath}} run lint -ws -iwr --if-present",
19
+ {{/if}}
15
20
  "template-copy": {{{del}}},
16
21
  "lint:fix": {{{del}}}
17
22
  },
18
23
  "repository": {{#if repository}}{{{json repository}}}{{else}}{{{del}}}{{/if}},
19
24
  "engines": {
25
+ {{#if engines}}
20
26
  "node": {{{json engines}}}
27
+ {{/if}}
21
28
  },
22
29
  {{{json __CONFIG_KEY__}}}: {
23
30
  "version": {{#if isDogFood}}{{{del}}}{{else}}{{{json __VERSION__}}}{{/if}}
24
31
  },
25
32
  "templateVersion": {{{del}}},
26
- "standard": {{{del}}}
33
+ "standard": {{{del}}},
34
+ "tap": {
35
+ {{#if isRootMono}}
36
+ "test-ignore": "^({{#each workspaceGlobs}}{{this}}{{#unless @last}}|{{/unless}}{{/each}})/",
37
+ {{/if}}
38
+ "nyc-arg": [
39
+ {{#if isRootMono}}
40
+ {{#each workspaceGlobs}}"--exclude", "{{this}}/**",{{/each}}
41
+ {{/if}}
42
+ "--exclude", "tap-snapshots/**"
43
+ ]
44
+ }
27
45
  }
@@ -9,12 +9,7 @@ permissions:
9
9
 
10
10
  jobs:
11
11
  template-oss-apply:
12
- runs-on: ubuntu-latest
13
- if: github.actor == 'dependabot[bot]'
14
- steps:
15
- {{> setupGit with=(obj ref="${{ github.event.pull_request.head_ref }}")}}
16
- {{> setupNode}}
17
- {{> setupDeps}}
12
+ {{> setupJob jobIf="github.actor == 'dependabot[bot]'" checkout=(obj ref="${{ github.event.pull_request.head_ref }}")}}
18
13
  - name: Dependabot metadata
19
14
  id: metadata
20
15
  uses: dependabot/fetch-metadata@v1.1.1
@@ -25,7 +20,7 @@ jobs:
25
20
  env:
26
21
  GITHUB_TOKEN: $\{{ secrets.GITHUB_TOKEN }}
27
22
  run: |
28
- npm run template-oss-apply
23
+ {{rootNpmPath}} run template-oss-apply
29
24
  git commit -am "chore: postinstall for dependabot template-oss PR"
30
25
  git push
31
- npm run lint
26
+ {{rootNpmPath}} run lint
@@ -11,14 +11,10 @@ on:
11
11
  jobs:
12
12
  check:
13
13
  name: Check PR Title or Commits
14
- runs-on: ubuntu-latest
15
- steps:
16
- {{> setupGit with=(obj fetch-depth=0)}}
17
- {{> setupNode}}
18
- {{> setupDeps}}
14
+ {{> setupJob checkout=(obj fetch-depth=0)}}
19
15
  - name: Check commits or PR title
20
16
  env:
21
17
  PR_TITLE: $\{{ github.event.pull_request.title }}
22
18
  run: |
23
- npx --offline commitlint -V --from origin/{{defaultBranch}} --to $\{{ github.event.pull_request.head.sha }} \
19
+ {{rootNpxPath}} --offline commitlint -V --from origin/{{defaultBranch}} --to $\{{ github.event.pull_request.head.sha }} \
24
20
  || echo $PR_TITLE | npx --offline commitlint -V
@@ -16,14 +16,10 @@ permissions:
16
16
 
17
17
  jobs:
18
18
  release-please:
19
- runs-on: ubuntu-latest
20
19
  outputs:
21
20
  pr: $\{{ steps.release.outputs.pr }}
22
21
  release: $\{{ steps.release.outputs.release }}
23
- steps:
24
- {{> setupGit}}
25
- {{> setupNode}}
26
- {{> setupDeps}}
22
+ {{> setupJob }}
27
23
  - name: Release Please
28
24
  id: release
29
25
  run: npx --offline template-oss-release-please $\{{ github.ref_name }}
@@ -40,34 +36,29 @@ jobs:
40
36
  - name: Output ref
41
37
  id: ref
42
38
  run: echo "::set-output name=branch::$\{{ fromJSON(needs.release-please.outputs.pr).headBranchName }}"
43
- {{> setupGit with=(obj ref="${{ steps.ref.outputs.branch }}" fetch-depth=0)}}
39
+ {{> setupGit checkout=(obj ref="${{ steps.ref.outputs.branch }}" fetch-depth=0)}}
44
40
  {{> setupNode}}
45
41
  {{> setupDeps}}
46
42
  - name: Post pull request actions
47
43
  env:
48
44
  GITHUB_TOKEN: $\{{ secrets.GITHUB_TOKEN }}
49
45
  run: |
50
- npm run rp-pull-request --ignore-scripts --if-present -ws -iwr
46
+ {{rootNpmPath}} run rp-pull-request --ignore-scripts --if-present -ws -iwr
51
47
  git commit -am "chore: post pull request" || true
52
48
  git push
53
49
 
54
50
  release-test:
55
51
  needs: post-pr
56
52
  if: needs.post-pr.outputs.ref
57
- uses: ./.github/workflows/{{releaseTest}}
53
+ uses: ./.github/workflows/release.yml
58
54
  with:
59
55
  ref: $\{{ needs.post-pr.outputs.ref }}
60
56
 
61
57
  post-release:
62
58
  needs: release-please
63
- if: needs.release-please.outputs.release
64
- runs-on: ubuntu-latest
65
- steps:
66
- {{> setupGit}}
67
- {{> setupNode}}
68
- {{> setupDeps}}
59
+ {{> setupJob jobIf="needs.release-please.outputs.release" }}
69
60
  - name: Post release actions
70
61
  env:
71
62
  GITHUB_TOKEN: $\{{ secrets.GITHUB_TOKEN }}
72
63
  run: |
73
- npm run rp-release --ignore-scripts --if-present -ws -iwr
64
+ {{rootNpmPath}} run rp-release --ignore-scripts --if-present -ws -iwr
@@ -0,0 +1,20 @@
1
+
2
+ name: Release
3
+
4
+ on:
5
+ workflow_call:
6
+ inputs:
7
+ ref:
8
+ required: true
9
+ type: string
10
+
11
+ jobs:
12
+ lint-all:
13
+ {{> setupJob checkout=(obj ref="${{ inputs.ref }}")}}
14
+ - run: {{rootNpmPath}} run lint -ws -iwr --if-present
15
+
16
+ test-all:
17
+ {{> setupJobMatrix checkout=(obj ref="${{ inputs.ref }}")}}
18
+ - name: add tap problem matcher
19
+ run: echo "::add-matcher::.github/matchers/tap.json"
20
+ - run: {{rootNpmPath}} run test -ws -iwr --if-present
package/lib/index.js CHANGED
@@ -1,17 +1,25 @@
1
1
  const log = require('proc-log')
2
- const { defaults } = require('lodash')
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, baseConfig) => {
7
+ const getPkg = async (path) => {
8
8
  log.verbose('get-pkg', path)
9
9
 
10
10
  const pkg = (await PackageJson.load(path)).content
11
11
  const pkgConfig = getConfig.getPkgConfig(pkg)
12
12
  log.verbose('get-pkg', pkgConfig)
13
13
 
14
- return { pkg, path, config: { ...baseConfig, ...pkgConfig } }
14
+ if (pkgConfig.content) {
15
+ pkgConfig.content = resolve(path, pkgConfig.content)
16
+ }
17
+
18
+ return {
19
+ pkg,
20
+ path,
21
+ config: pkgConfig,
22
+ }
15
23
  }
16
24
 
17
25
  const getWsPkgs = async (root, rootPkg) => {
@@ -45,38 +53,30 @@ const getPkgs = async (root) => {
45
53
  log.verbose('get-pkgs', 'root', root)
46
54
 
47
55
  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
56
 
58
57
  const ws = await getWsPkgs(root, rootPkg)
59
58
 
60
59
  return {
61
- pkgs: pkgs.concat(ws.pkgs),
60
+ rootPkg,
61
+ pkgs: [rootPkg].concat(ws.pkgs),
62
62
  workspaces: ws.paths,
63
63
  }
64
64
  }
65
65
 
66
- const runAll = async (root, content, checks) => {
66
+ const runAll = async (root, checks) => {
67
67
  const results = []
68
- const { pkgs, workspaces } = await getPkgs(root)
68
+ const { pkgs, workspaces, rootPkg: { config: rootConfig } } = await getPkgs(root)
69
69
 
70
- for (const { pkg, path, config } of pkgs) {
70
+ for (const { pkg, path, config: pkgConfig } of pkgs) {
71
71
  // full config includes original config values
72
72
  const fullConfig = await getConfig({
73
- pkgs,
74
- workspaces,
75
73
  root,
76
- pkg,
77
74
  path,
78
- config,
79
- content,
75
+ pkg,
76
+ pkgs,
77
+ workspaces,
78
+ rootConfig,
79
+ pkgConfig,
80
80
  })
81
81
 
82
82
  const options = { root, pkg, path, config: fullConfig }
@@ -10,35 +10,39 @@ module.exports = (gh) => {
10
10
  return response
11
11
  }
12
12
 
13
- const { repository } = await gh.graphql(
14
- `fragment CommitAuthors on GitObject {
15
- ... on Commit {
16
- authors (first:10) {
17
- nodes {
18
- user { login }
19
- name
13
+ try {
14
+ const { repository } = await gh.graphql(
15
+ `fragment CommitAuthors on GitObject {
16
+ ... on Commit {
17
+ authors (first:10) {
18
+ nodes {
19
+ user { login }
20
+ name
21
+ }
20
22
  }
21
23
  }
22
24
  }
23
- }
24
- query {
25
- repository (owner:"${owner}", name:"${repo}") {
26
- ${shas.map((s) => {
27
- return `_${s}: object (expression: "${s}") { ...CommitAuthors }`
28
- })}
25
+ query {
26
+ repository (owner:"${owner}", name:"${repo}") {
27
+ ${shas.map((s) => {
28
+ return `_${s}: object (expression: "${s}") { ...CommitAuthors }`
29
+ })}
30
+ }
31
+ }`
32
+ )
33
+
34
+ for (const [key, commit] of Object.entries(repository)) {
35
+ if (commit) {
36
+ response[key.slice(1)] = commit.authors.nodes
37
+ .map((a) => a.user && a.user.login ? `@${a.user.login}` : a.name)
38
+ .filter(Boolean)
29
39
  }
30
- }`
31
- )
32
-
33
- for (const [key, commit] of Object.entries(repository)) {
34
- if (commit) {
35
- response[key.slice(1)] = commit.authors.nodes
36
- .map((a) => a.user && a.user.login ? `@${a.user.login}` : a.name)
37
- .filter(Boolean)
38
40
  }
39
- }
40
41
 
41
- return response
42
+ return response
43
+ } catch {
44
+ return response
45
+ }
42
46
  }
43
47
 
44
48
  const url = (...p) => `https://github.com/${owner}/${repo}/${p.join('/')}`
@@ -1,3 +1,4 @@
1
+ const localeCompare = require('@isaacs/string-locale-compare')('en')
1
2
  const { NodeWorkspace } = require('release-please/build/src/plugins/node-workspace.js')
2
3
  const { RawContent } = require('release-please/build/src/updaters/raw-content.js')
3
4
  const { jsonStringify } = require('release-please/build/src/util/json-stringify.js')
@@ -105,9 +106,11 @@ module.exports = class extends NodeWorkspace {
105
106
  // except it only updates the package.json instead of appending
106
107
  // anything to changelogs since we've already done that in preconfigure.
107
108
  updateCandidate (candidate, pkg, updatedVersions) {
109
+ const newVersion = updatedVersions.get(pkg.name)
108
110
  const graphPackage = this.packageGraph.get(pkg.name)
109
- const updatedPackage = pkg.clone()
110
111
 
112
+ const updatedPackage = pkg.clone()
113
+ updatedPackage.version = newVersion.toString()
111
114
  for (const [depName, resolved] of graphPackage.localDependencies) {
112
115
  const depVersion = updatedVersions.get(depName)
113
116
  if (depVersion && resolved.type !== 'directory') {
@@ -160,10 +163,13 @@ module.exports = class extends NodeWorkspace {
160
163
  if (aPath === ROOT_PROJECT_PATH) {
161
164
  return -1
162
165
  }
166
+ // release please pre sorts based on graph order so
167
+ // this is never called in normal circumstances
168
+ /* istanbul ignore next */
163
169
  if (bPath === ROOT_PROJECT_PATH) {
164
170
  return 1
165
171
  }
166
- return 0
172
+ return localeCompare(aPath, bPath)
167
173
  })
168
174
  }
169
175
 
package/lib/util/files.js CHANGED
@@ -1,43 +1,55 @@
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
- // target paths need to be joinsed with dir and templated
10
- const fullTarget = (dir, file, options) => join(dir, template(file, options))
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) => Object.entries(files).map(([t, s]) => {
14
- let {
15
- file,
16
- parser: fileParser,
17
- filter,
18
- } = typeof s === 'string' ? { file: s } : s
19
-
20
- file = join(options.config.sourceDir, file)
21
- const target = fullTarget(dir, t, options)
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 } = source
27
+
28
+ if (typeof filter === 'function' && !filter(options)) {
29
+ return null
30
+ }
26
31
 
27
- if (fileParser) {
32
+ if (parser) {
28
33
  // allow files to extend base parsers or create new ones
29
- return new (fileParser(Parser.Parsers))(target, file, options)
30
- }
34
+ return new (parser(Parser.Parsers))(target, file, options)
35
+ }
36
+
37
+ return new (Parser(file))(target, file, options)
38
+ })
31
39
 
32
- return new (Parser(file))(target, file, options)
33
- })
40
+ return parsers.filter(Boolean)
41
+ }
42
+
43
+ const getRemovals = async (dir, files, options) => {
44
+ const targets = fileEntries(dir, files, options).map(([t]) => globify(t))
45
+ const globs = await Promise.all(targets.map(t => glob(t, { cwd: dir })))
46
+ return globs.flat()
47
+ }
34
48
 
35
49
  const rmEach = async (dir, files, options, fn) => {
36
50
  const res = []
37
- for (const target of files.map((t) => fullTarget(dir, t, options))) {
38
- for (const file of await glob(globify(target), { cwd: dir })) {
39
- res.push(await fn(file))
40
- }
51
+ for (const file of await getRemovals(dir, files, options)) {
52
+ res.push(await fn(file))
41
53
  }
42
54
  return res.filter(Boolean)
43
55
  }
@@ -45,14 +57,44 @@ const rmEach = async (dir, files, options, fn) => {
45
57
  const parseEach = async (dir, files, options, fn) => {
46
58
  const res = []
47
59
  for (const parser of getParsers(dir, files, options)) {
48
- if (parser) {
49
- res.push(await fn(parser))
50
- }
60
+ res.push(await fn(parser))
51
61
  }
52
62
  return res.filter(Boolean)
53
63
  }
54
64
 
65
+ const parseConfig = (files, dir, overrides) => {
66
+ const normalizeFiles = (v) => deepMapValues(v, (value, key) => {
67
+ if (key === 'rm' && Array.isArray(value)) {
68
+ return value.reduce((acc, k) => {
69
+ acc[k] = true
70
+ return acc
71
+ }, {})
72
+ }
73
+ if (typeof value === 'string') {
74
+ const file = join(dir, value)
75
+ return key === 'file' ? file : { file }
76
+ }
77
+ if (value === true && FILE_KEYS.includes(key)) {
78
+ return {}
79
+ }
80
+ return value
81
+ })
82
+
83
+ const merged = merge(normalizeFiles(files), normalizeFiles(overrides))
84
+ const withDefaults = defaultsDeep(merged, FILE_KEYS.reduce((acc, k) => {
85
+ acc[k] = { add: {}, rm: {} }
86
+ return acc
87
+ }, {}))
88
+
89
+ return withDefaults
90
+ }
91
+
92
+ const getAddedFiles = (files) => files ? Object.keys(files.add || {}) : []
93
+
55
94
  module.exports = {
56
95
  rmEach,
57
96
  parseEach,
97
+ FILE_KEYS,
98
+ parseConfig,
99
+ getAddedFiles,
58
100
  }
@@ -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
@@ -4,9 +4,10 @@ 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, merge } = require('lodash')
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')
10
11
  const setFirst = (first, rest) => ({ ...first, ...rest })
11
12
  const traverse = (value, visit, keys = []) => {
12
13
  if (keys.length) {
@@ -220,7 +221,7 @@ class Json extends Base {
220
221
 
221
222
  class JsonMerge extends Json {
222
223
  static header = 'This file is partially managed by {{__NAME__}}. Edits may be overwritten.'
223
- merge = (t, s) => merge({}, t, s)
224
+ merge = (t, s) => merge(t, s)
224
225
  }
225
226
 
226
227
  class PackageJson extends JsonMerge {