@npmcli/template-oss 4.16.0 → 4.18.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.
@@ -9,7 +9,7 @@ const run = async (dir, files, options) => {
9
9
  await rmEach(dir, rm, options, (f) => fs.rm(f))
10
10
 
11
11
  log.verbose('apply-files', 'add', add)
12
- await parseEach(dir, add, options, (p) => p.applyWrite())
12
+ await parseEach(dir, add, options, {}, (p) => p.applyWrite())
13
13
  }
14
14
 
15
15
  module.exports = [{
@@ -12,7 +12,8 @@ const run = async (type, dir, files, options) => {
12
12
  const { add: addFiles, rm: rmFiles } = files
13
13
 
14
14
  const rm = await rmEach(dir, rmFiles, options, (f) => rel(f))
15
- const [add, update] = partition(await parseEach(dir, addFiles, options, async (p) => {
15
+ const parseOpts = { allowMultipleSources: false }
16
+ const [add, update] = partition(await parseEach(dir, addFiles, options, parseOpts, async (p) => {
16
17
  const diff = await p.applyDiff()
17
18
  const target = rel(p.target)
18
19
  if (diff === null) {
package/lib/config.js CHANGED
@@ -2,19 +2,21 @@ const { relative, dirname, join, extname, posix, win32 } = require('path')
2
2
  const { defaults, pick, omit, uniq } = require('lodash')
3
3
  const semver = require('semver')
4
4
  const parseCIVersions = require('./util/parse-ci-versions.js')
5
- const getGitUrl = require('./util/get-git-url.js')
5
+ const parseDependabot = require('./util/dependabot.js')
6
+ const git = require('./util/git.js')
6
7
  const gitignore = require('./util/gitignore.js')
7
- const { withArrays } = require('./util/merge.js')
8
- const { FILE_KEYS, parseConfig: parseFiles, getAddedFiles } = require('./util/files.js')
8
+ const { mergeWithArrays } = require('./util/merge.js')
9
+ const { FILE_KEYS, parseConfig: parseFiles, getAddedFiles, mergeFiles } = require('./util/files.js')
9
10
 
10
11
  const CONFIG_KEY = 'templateOSS'
11
12
  const getPkgConfig = (pkg) => pkg[CONFIG_KEY] || {}
12
13
 
13
14
  const { name: NAME, version: LATEST_VERSION } = require('../package.json')
15
+ const { minimatch } = require('minimatch')
14
16
  const MERGE_KEYS = [...FILE_KEYS, 'defaultContent', 'content']
15
17
  const DEFAULT_CONTENT = require.resolve(NAME)
16
18
 
17
- const merge = withArrays('branches', 'distPaths', 'allowPaths', 'ignorePaths')
19
+ const merge = mergeWithArrays('branches', 'distPaths', 'allowPaths', 'ignorePaths')
18
20
 
19
21
  const makePosix = (v) => v.split(win32.sep).join(posix.sep)
20
22
  const deglob = (v) => makePosix(v).replace(/[/*]+$/, '')
@@ -120,7 +122,7 @@ const getFullConfig = async ({
120
122
  // Files get merged in from the default content (that template-oss provides) as well
121
123
  // as any content paths provided from the root or the workspace
122
124
  const fileDirs = uniq([useDefault && defaultDir, rootDir, pkgDir].filter(Boolean))
123
- const files = merge(useDefault && defaultFiles, rootFiles, pkgFiles)
125
+ const files = mergeFiles(useDefault && defaultFiles, rootFiles, pkgFiles)
124
126
  const repoFiles = isRoot ? files.rootRepo : files.workspaceRepo
125
127
  const moduleFiles = isRoot ? files.rootModule : files.workspaceModule
126
128
 
@@ -153,6 +155,12 @@ const getFullConfig = async ({
153
155
  const publicPkgs = pkgs.filter(p => !p.pkgJson.private)
154
156
  const allPrivate = pkgs.every(p => p.pkgJson.private)
155
157
 
158
+ const branches = uniq([...pkgConfig.branches ?? [], pkgConfig.releaseBranch]).filter(Boolean)
159
+ const gitBranches = await git.getBranches(rootPkg.path, branches)
160
+ const currentBranch = await git.currentBranch(rootPkg.path)
161
+ const isReleaseBranch = currentBranch ? minimatch(currentBranch, pkgConfig.releaseBranch) : false
162
+ const defaultBranch = await git.defaultBranch(rootPkg.path) ?? 'main'
163
+
156
164
  // all derived keys
157
165
  const derived = {
158
166
  isRoot,
@@ -170,6 +178,14 @@ const getFullConfig = async ({
170
178
  allPrivate,
171
179
  // controls whether we are in a monorepo with any public workspaces
172
180
  isMonoPublic: isMono && !!publicPkgs.filter(p => p.path !== rootPkg.path).length,
181
+ // git
182
+ defaultBranch,
183
+ baseBranch: isReleaseBranch ? currentBranch : defaultBranch,
184
+ branches: gitBranches.branches,
185
+ branchPatterns: gitBranches.patterns,
186
+ isReleaseBranch,
187
+ // dependabot
188
+ dependabot: parseDependabot(pkgConfig, defaultConfig, gitBranches.branches),
173
189
  // repo
174
190
  repoDir: rootPkg.path,
175
191
  repoFiles,
@@ -261,7 +277,7 @@ const getFullConfig = async ({
261
277
  }
262
278
  }
263
279
 
264
- const gitUrl = await getGitUrl(rootPkg.path)
280
+ const gitUrl = await git.getUrl(rootPkg.path)
265
281
  if (gitUrl) {
266
282
  derived.repository = {
267
283
  type: 'git',
@@ -12,7 +12,7 @@ pull_request:
12
12
  {{/if}}
13
13
  push:
14
14
  branches:
15
- {{#each branches}}
15
+ {{#each branchPatterns}}
16
16
  - {{ . }}
17
17
  {{/each}}
18
18
  {{#if isWorkspace}}
@@ -7,7 +7,7 @@ on:
7
7
  ref:
8
8
  required: true
9
9
  type: string
10
- default: {{ defaultBranch }}
10
+ default: {{ baseBranch }}
11
11
  workflow_call:
12
12
  inputs:
13
13
  ref:
@@ -3,12 +3,12 @@ name: CodeQL
3
3
  on:
4
4
  push:
5
5
  branches:
6
- {{#each branches}}
6
+ {{#each branchPatterns}}
7
7
  - {{ . }}
8
8
  {{/each}}
9
9
  pull_request:
10
10
  branches:
11
- {{#each branches}}
11
+ {{#each branchPatterns}}
12
12
  - {{ . }}
13
13
  {{/each}}
14
14
  schedule:
@@ -1,15 +1,24 @@
1
1
  version: 2
2
2
 
3
3
  updates:
4
+ {{#each dependabot}}
4
5
  - package-ecosystem: npm
5
- directory: {{ pkgDir }}
6
+ directory: /
6
7
  schedule:
7
8
  interval: daily
9
+ target-branch: "{{ branch }}"
8
10
  allow:
9
11
  - dependency-type: direct
10
- versioning-strategy: {{ dependabot }}
12
+ {{#each allowNames }}
13
+ dependency-name: "{{ . }}"
14
+ {{/each}}
15
+ versioning-strategy: {{ strategy }}
11
16
  commit-message:
12
17
  prefix: deps
13
18
  prefix-development: chore
14
19
  labels:
15
20
  - "Dependencies"
21
+ {{#each labels }}
22
+ - "{{ . }}"
23
+ {{/each}}
24
+ {{/each}}
@@ -37,20 +37,15 @@ const sharedRootAdd = (name) => ({
37
37
  // dependabot
38
38
  '.github/dependabot.yml': {
39
39
  file: 'dependabot.yml',
40
- clean: (p) => p.config.isRoot,
41
- // dependabot takes a single top level config file. this parser
42
- // will run for all configured packages and each one will have
43
- // its item replaced in the updates array based on the directory
44
- parser: (p) => class extends p.YmlMerge {
45
- key = 'updates'
46
- id = 'directory'
47
- },
40
+ filter: (p) => p.config.dependabot,
48
41
  },
49
42
  '.github/workflows/post-dependabot.yml': {
50
43
  file: 'post-dependabot.yml',
44
+ filter: (p) => p.config.dependabot,
51
45
  },
52
46
  '.github/settings.yml': {
53
47
  file: 'settings.yml',
48
+ filter: (p) => !p.config.isReleaseBranch,
54
49
  },
55
50
  })
56
51
 
@@ -58,6 +53,9 @@ const sharedRootRm = () => ({
58
53
  '.github/workflows/pull-request.yml': {
59
54
  filter: (p) => p.config.allPrivate,
60
55
  },
56
+ '.github/settings.yml': {
57
+ filter: (p) => p.config.isReleaseBranch,
58
+ },
61
59
  })
62
60
 
63
61
  // Changes applied to the root of the repo
@@ -137,8 +135,8 @@ module.exports = {
137
135
  workspaceModule,
138
136
  windowsCI: true,
139
137
  macCI: true,
140
- branches: ['main', 'latest', 'release/v*'],
141
- defaultBranch: 'main',
138
+ branches: ['main', 'latest'],
139
+ releaseBranch: 'release/v*',
142
140
  distPaths: [
143
141
  'bin/',
144
142
  'lib/',
@@ -21,4 +21,4 @@ jobs:
21
21
  env:
22
22
  PR_TITLE: $\{{ github.event.pull_request.title }}
23
23
  run: |
24
- echo '$PR_TITLE' | {{ rootNpxPath }} --offline commitlint -V
24
+ echo "$PR_TITLE" | {{ rootNpxPath }} --offline commitlint -V
@@ -8,7 +8,7 @@ on:
8
8
  type: string
9
9
  push:
10
10
  branches:
11
- {{#each branches}}
11
+ {{#each branchPatterns}}
12
12
  - {{ . }}
13
13
  {{/each}}
14
14
 
@@ -54,7 +54,7 @@ jobs:
54
54
  let commentId = comments.find(c => c.user.login === 'github-actions[bot]' && c.body.startsWith(body))?.id
55
55
 
56
56
  body += `Release workflow run: ${workflow.html_url}\n\n#### Force CI to Update This Release\n\n`
57
- body += `This PR will be updated and CI will run for every non-\`chore:\` commit that is pushed to \`{{ defaultBranch }}\`. `
57
+ body += `This PR will be updated and CI will run for every non-\`chore:\` commit that is pushed to \`${REF_NAME}\`. `
58
58
  body += `To force CI to update this PR, run this command:\n\n`
59
59
  body += `\`\`\`\ngh workflow run release.yml -r ${REF_NAME} -R ${owner}/${repo} -f release-pr=${issue_number}\n\`\`\``
60
60
 
@@ -9,10 +9,12 @@ repository:
9
9
  enable_vulnerability_alerts: true
10
10
 
11
11
  branches:
12
- - name: {{ defaultBranch }}
12
+ {{#each branches}}
13
+ - name: {{ . }}
13
14
  protection:
14
15
  required_status_checks: null
15
16
  enforce_admins: true
17
+ block_creations: true
16
18
  required_pull_request_reviews:
17
19
  required_approving_review_count: 1
18
20
  require_code_owner_reviews: true
@@ -22,3 +24,4 @@ branches:
22
24
  apps: []
23
25
  users: []
24
26
  teams: ["cli-team"]
27
+ {{/each}}
@@ -0,0 +1,27 @@
1
+ const { name: NAME } = require('../../package.json')
2
+ const { minimatch } = require('minimatch')
3
+
4
+ const parseDependabotConfig = (v) => typeof v === 'string' ? { strategy: v } : (v ?? {})
5
+
6
+ module.exports = (config, defaultConfig, branches) => {
7
+ const { dependabot } = config
8
+ const { dependabot: defaultDependabot } = defaultConfig
9
+
10
+ if (!dependabot) {
11
+ return false
12
+ }
13
+
14
+ return branches
15
+ .filter((b) => dependabot[b] !== false)
16
+ .map(branch => {
17
+ const isReleaseBranch = minimatch(branch, config.releaseBranch)
18
+ return {
19
+ branch,
20
+ allowNames: isReleaseBranch ? [NAME] : [],
21
+ labels: isReleaseBranch ? ['Backport', branch] : [],
22
+ ...parseDependabotConfig(defaultDependabot),
23
+ ...parseDependabotConfig(dependabot),
24
+ ...parseDependabotConfig(dependabot[branch]),
25
+ }
26
+ })
27
+ }
package/lib/util/files.js CHANGED
@@ -1,27 +1,62 @@
1
1
  const { join } = require('path')
2
- const { defaultsDeep } = require('lodash')
3
- const merge = require('./merge.js')
2
+ const { defaultsDeep, omit } = require('lodash')
4
3
  const deepMapValues = require('just-deep-map-values')
5
4
  const { glob } = require('glob')
5
+ const { mergeWithCustomizers, customizers } = require('./merge.js')
6
6
  const Parser = require('./parser.js')
7
7
  const template = require('./template.js')
8
8
 
9
+ const ADD_KEY = 'add'
10
+ const RM_KEY = 'rm'
9
11
  const FILE_KEYS = ['rootRepo', 'rootModule', 'workspaceRepo', 'workspaceModule']
10
12
 
11
13
  const globify = pattern => pattern.split('\\').join('/')
12
14
 
13
- const fileEntries = (dir, files, options) => Object.entries(files)
14
- // remove any false values
15
- .filter(([_, v]) => v !== false)
16
- // target paths need to be joinsed with dir and templated
17
- .map(([k, source]) => {
18
- const target = join(dir, template(k, options))
19
- return [target, source]
20
- })
15
+ const mergeFiles = mergeWithCustomizers((value, srcValue, key, target, source, stack) => {
16
+ // This will merge all files except if the src file has overwrite:false. Then
17
+ // the files will be turned into an array so they can be applied on top of
18
+ // each other in the parser.
19
+ if (
20
+ stack[0] === ADD_KEY &&
21
+ FILE_KEYS.includes(stack[1]) &&
22
+ value?.file &&
23
+ srcValue?.overwrite === false
24
+ ) {
25
+ return [value, omit(srcValue, 'overwrite')]
26
+ }
27
+ }, customizers.overwriteArrays)
28
+
29
+ const fileEntries = (dir, files, options, { allowMultipleSources = true } = {}) => {
30
+ const results = []
31
+
32
+ for (const [key, source] of Object.entries(files)) {
33
+ // remove any false values first since that means those targets are skipped
34
+ if (source === false) {
35
+ continue
36
+ }
37
+
38
+ // target paths need to be joinsed with dir and templated
39
+ const target = join(dir, template(key, options))
40
+
41
+ if (Array.isArray(source)) {
42
+ // When turning an object of files into all its entries, we allow
43
+ // multiples when applying changes, but not when checking for changes
44
+ // since earlier files would always return as needing an update. So we
45
+ // either allow multiples and return the array or only return the last
46
+ // source file in the array.
47
+ const sources = allowMultipleSources ? source : source.slice(-1)
48
+ results.push(...sources.map(s => [target, s]))
49
+ } else {
50
+ results.push([target, source])
51
+ }
52
+ }
53
+
54
+ return results
55
+ }
21
56
 
22
57
  // given an obj of files, return the full target/source paths and associated parser
23
- const getParsers = (dir, files, options) => {
24
- const parsers = fileEntries(dir, files, options).map(([target, source]) => {
58
+ const getParsers = (dir, files, options, parseOptions) => {
59
+ const parsers = fileEntries(dir, files, options, parseOptions).map(([target, source]) => {
25
60
  const { file, parser, filter, clean: shouldClean } = source
26
61
 
27
62
  if (typeof filter === 'function' && !filter(options)) {
@@ -35,7 +70,7 @@ const getParsers = (dir, files, options) => {
35
70
  return new (parser(Parser.Parsers))(target, file, options, { clean })
36
71
  }
37
72
 
38
- return new (Parser(file))(target, file, options, { clean })
73
+ return new (Parser(target))(target, file, options, { clean })
39
74
  })
40
75
 
41
76
  return parsers.filter(Boolean)
@@ -62,9 +97,9 @@ const rmEach = async (dir, files, options, fn) => {
62
97
  return res.filter(Boolean)
63
98
  }
64
99
 
65
- const parseEach = async (dir, files, options, fn) => {
100
+ const parseEach = async (dir, files, options, parseOptions, fn) => {
66
101
  const res = []
67
- for (const parser of getParsers(dir, files, options)) {
102
+ for (const parser of getParsers(dir, files, options, parseOptions)) {
68
103
  res.push(await fn(parser))
69
104
  }
70
105
  return res.filter(Boolean)
@@ -72,7 +107,7 @@ const parseEach = async (dir, files, options, fn) => {
72
107
 
73
108
  const parseConfig = (files, dir, overrides) => {
74
109
  const normalizeFiles = (v) => deepMapValues(v, (value, key) => {
75
- if (key === 'rm' && Array.isArray(value)) {
110
+ if (key === RM_KEY && Array.isArray(value)) {
76
111
  return value.reduce((acc, k) => {
77
112
  acc[k] = true
78
113
  return acc
@@ -88,16 +123,16 @@ const parseConfig = (files, dir, overrides) => {
88
123
  return value
89
124
  })
90
125
 
91
- const merged = merge(normalizeFiles(files), normalizeFiles(overrides))
126
+ const merged = mergeFiles(normalizeFiles(files), normalizeFiles(overrides))
92
127
  const withDefaults = defaultsDeep(merged, FILE_KEYS.reduce((acc, k) => {
93
- acc[k] = { add: {}, rm: {} }
128
+ acc[k] = { [ADD_KEY]: {}, [RM_KEY]: {} }
94
129
  return acc
95
130
  }, {}))
96
131
 
97
132
  return withDefaults
98
133
  }
99
134
 
100
- const getAddedFiles = (files) => files ? Object.keys(files.add || {}) : []
135
+ const getAddedFiles = (files) => files ? Object.keys(files[ADD_KEY] || {}) : []
101
136
 
102
137
  module.exports = {
103
138
  rmEach,
@@ -105,4 +140,5 @@ module.exports = {
105
140
  FILE_KEYS,
106
141
  parseConfig,
107
142
  getAddedFiles,
143
+ mergeFiles,
108
144
  }
@@ -0,0 +1,82 @@
1
+ const hgi = require('hosted-git-info')
2
+ const git = require('@npmcli/git')
3
+ const { minimatch } = require('minimatch')
4
+
5
+ const cache = new Map()
6
+
7
+ const tryGit = async (path, ...args) => {
8
+ if (!await git.is({ cwd: path })) {
9
+ throw new Error('no git')
10
+ }
11
+ const key = [path, ...args].join(',')
12
+ if (cache.has(key)) {
13
+ return cache.get(key)
14
+ }
15
+ const res = git.spawn(args, { cwd: path }).then(r => r.stdout.trim())
16
+ cache.set(key, res)
17
+ return res
18
+ }
19
+
20
+ // parse a repo from a git origin into a format
21
+ // for a package.json#repository object
22
+ const getUrl = async (path) => {
23
+ try {
24
+ const urlStr = await tryGit(path, 'remote', 'get-url', 'origin')
25
+ const { domain, user, project } = hgi.fromUrl(urlStr)
26
+ const url = new URL(`https://${domain}`)
27
+ url.pathname = `/${user}/${project}.git`
28
+ return url.toString()
29
+ } catch {
30
+ // errors are ignored
31
+ }
32
+ }
33
+
34
+ const getBranches = async (path, branchPatterns) => {
35
+ let matchingBranches = new Set()
36
+ let matchingPatterns = new Set()
37
+
38
+ try {
39
+ const res = await tryGit(path, 'ls-remote', '--heads', 'origin').then(r => r.split('\n'))
40
+ const remotes = res.map((h) => h.match(/refs\/heads\/(.*)$/)).filter(Boolean).map(h => h[1])
41
+ for (const branch of remotes) {
42
+ for (const pattern of branchPatterns) {
43
+ if (minimatch(branch, pattern)) {
44
+ matchingBranches.add(branch)
45
+ matchingPatterns.add(pattern)
46
+ }
47
+ }
48
+ }
49
+ } catch {
50
+ matchingBranches = new Set(branchPatterns.filter(b => !b.includes('*')))
51
+ matchingPatterns = new Set(branchPatterns)
52
+ }
53
+
54
+ return {
55
+ branches: [...matchingBranches],
56
+ patterns: [...matchingPatterns],
57
+ }
58
+ }
59
+
60
+ const defaultBranch = async (path) => {
61
+ try {
62
+ const remotes = await tryGit(path, 'remote', 'show', 'origin')
63
+ return remotes.match(/HEAD branch: (.*)$/m)?.[1]
64
+ } catch {
65
+ // ignore errors
66
+ }
67
+ }
68
+
69
+ const currentBranch = async (path) => {
70
+ try {
71
+ return await tryGit(path, 'rev-parse', '--abbrev-ref', 'HEAD')
72
+ } catch {
73
+ // ignore errors
74
+ }
75
+ }
76
+
77
+ module.exports = {
78
+ getUrl,
79
+ getBranches,
80
+ defaultBranch,
81
+ currentBranch,
82
+ }
package/lib/util/merge.js CHANGED
@@ -1,21 +1,72 @@
1
- const { mergeWith } = require('lodash')
1
+ const { mergeWith: _mergeWith } = require('lodash')
2
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
- })
3
+ // Adapted from https://github.com/lodash/lodash/issues/3901#issuecomment-517983996
4
+ // Allows us to keep track of the current key during each merge so a customizer
5
+ // can make different merges based on the parent keys.
6
+ const mergeWith = (...args) => {
7
+ const customizer = args.pop()
8
+ const objects = args
9
+ const sourceStack = []
10
+ const keyStack = []
11
+ return _mergeWith({}, ...objects, (value, srcValue, key, target, source) => {
12
+ let currentKeys
13
+ while (true) {
14
+ if (!sourceStack.length) {
15
+ sourceStack.push(source)
16
+ keyStack.push([])
17
+ }
18
+ if (source === sourceStack[sourceStack.length - 1]) {
19
+ currentKeys = keyStack[keyStack.length - 1].concat(key)
20
+ sourceStack.push(srcValue)
21
+ keyStack.push(currentKeys)
22
+ break
23
+ }
24
+ sourceStack.pop()
25
+ keyStack.pop()
26
+ }
27
+ // Remove the last key since that is the current one and reverse the whole
28
+ // array so that the first entry is the parent, 2nd grandparent, etc
29
+ return customizer(value, srcValue, key, target, source, currentKeys.slice(0, -1).reverse())
30
+ })
31
+ }
32
+
33
+ // Create a merge function that will run a set of customizer functions
34
+ const mergeWithCustomizers = (...customizers) => {
35
+ return (...objects) => mergeWith({}, ...objects, (...args) => {
36
+ for (const customizer of customizers) {
37
+ const result = customizer(...args)
38
+ // undefined means the customizer will defer to the next one
39
+ // the default behavior of undefined in lodash is to merge
40
+ if (result !== undefined) {
41
+ return result
42
+ }
43
+ }
44
+ })
45
+ }
9
46
 
10
- const mergeWithArrays = (...keys) =>
11
- (...objects) => mergeWith({}, ...objects, (value, srcValue, key) => {
47
+ const customizers = {
48
+ // Dont merge arrays, last array wins
49
+ overwriteArrays: (value, srcValue) => {
50
+ if (Array.isArray(srcValue)) {
51
+ return srcValue
52
+ }
53
+ },
54
+ // Merge arrays if their key matches one of the passed in keys
55
+ mergeArrays: (...keys) => (value, srcValue, key) => {
12
56
  if (Array.isArray(srcValue)) {
13
57
  if (keys.includes(key)) {
14
58
  return (Array.isArray(value) ? value : []).concat(srcValue)
15
59
  }
16
60
  return srcValue
17
61
  }
18
- })
62
+ },
63
+ }
19
64
 
20
- module.exports = merge
21
- module.exports.withArrays = mergeWithArrays
65
+ module.exports = {
66
+ // default merge is to overwrite arrays
67
+ merge: mergeWithCustomizers(customizers.overwriteArrays),
68
+ mergeWithArrays: (...keys) => mergeWithCustomizers(customizers.mergeArrays(...keys)),
69
+ mergeWithCustomizers,
70
+ mergeWith,
71
+ customizers,
72
+ }
@@ -1,14 +1,15 @@
1
1
  const fs = require('fs/promises')
2
- const { basename, extname, dirname } = require('path')
2
+ const { dirname } = require('path')
3
3
  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
7
  const { unset } = require('lodash')
8
8
  const ini = require('ini')
9
+ const { minimatch } = require('minimatch')
9
10
  const template = require('./template.js')
10
11
  const jsonDiff = require('./json-diff')
11
- const merge = require('./merge.js')
12
+ const { merge } = require('./merge.js')
12
13
 
13
14
  const setFirst = (first, rest) => ({ ...first, ...rest })
14
15
 
@@ -167,17 +168,17 @@ class Base {
167
168
  }
168
169
 
169
170
  class Gitignore extends Base {
170
- static types = ['codeowners', 'gitignore']
171
+ static types = ['codeowners', '.gitignore']
171
172
  comment = (c) => `# ${c}`
172
173
  }
173
174
 
174
175
  class Js extends Base {
175
- static types = ['js']
176
+ static types = ['*.js']
176
177
  comment = (c) => `/* ${c} */`
177
178
  }
178
179
 
179
180
  class Ini extends Base {
180
- static types = ['ini']
181
+ static types = ['*.ini']
181
182
  comment = (c) => `; ${c}`
182
183
 
183
184
  toString (s) {
@@ -202,17 +203,17 @@ class Ini extends Base {
202
203
  }
203
204
 
204
205
  class IniMerge extends Ini {
205
- static types = ['npmrc']
206
+ static types = ['.npmrc']
206
207
  merge = (t, s) => merge(t, s)
207
208
  }
208
209
 
209
210
  class Markdown extends Base {
210
- static types = ['md']
211
+ static types = ['*.md']
211
212
  comment = (c) => `<!-- ${c} -->`
212
213
  }
213
214
 
214
215
  class Yml extends Base {
215
- static types = ['yml']
216
+ static types = ['*.yml']
216
217
  comment = (c) => ` ${c}`
217
218
 
218
219
  toString (s) {
@@ -274,7 +275,7 @@ class YmlMerge extends Yml {
274
275
  }
275
276
 
276
277
  class Json extends Base {
277
- static types = ['json']
278
+ static types = ['*.json']
278
279
  // its a json comment! not really but we do add a special key
279
280
  // to json objects
280
281
  comment = (c) => ({ [`//${this.options.config.__NAME__}`]: c })
@@ -306,7 +307,7 @@ class JsonMerge extends Json {
306
307
  }
307
308
 
308
309
  class PackageJson extends JsonMerge {
309
- static types = ['pkg.json']
310
+ static types = ['package.json']
310
311
 
311
312
  async prepare (s, t) {
312
313
  // merge new source with current pkg content
@@ -348,15 +349,28 @@ const Parsers = {
348
349
  PackageJson,
349
350
  }
350
351
 
351
- const parserLookup = Object.values(Parsers)
352
+ // Create an order to lookup parsers based on filename the only important part
353
+ // of ordering is that we want to match types by exact match first, then globs,
354
+ // so we always sort globs to the bottom
355
+ const parserLookup = []
356
+ for (const parser of Object.values(Parsers)) {
357
+ for (const type of parser.types) {
358
+ const parserEntry = [type, parser]
359
+ if (type.includes('*')) {
360
+ parserLookup.push(parserEntry)
361
+ } else {
362
+ parserLookup.unshift(parserEntry)
363
+ }
364
+ }
365
+ }
352
366
 
353
367
  const getParser = (file) => {
354
- const base = basename(file).toLowerCase()
355
- const ext = extname(file).slice(1).toLowerCase()
356
-
357
- return parserLookup.find((p) => p.types.includes(base))
358
- || parserLookup.find((p) => p.types.includes(ext))
359
- || Parsers.Base
368
+ for (const [type, parser] of parserLookup) {
369
+ if (minimatch(file, type, { nocase: true, dot: true, matchBase: true })) {
370
+ return parser
371
+ }
372
+ }
373
+ return Parsers.Base
360
374
  }
361
375
 
362
376
  module.exports = getParser
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@npmcli/template-oss",
3
- "version": "4.16.0",
3
+ "version": "4.18.0",
4
4
  "description": "templated files used in npm CLI team oss projects",
5
5
  "main": "lib/content/index.js",
6
6
  "bin": {
@@ -39,7 +39,7 @@
39
39
  "@npmcli/arborist": "^6.0.0",
40
40
  "@npmcli/git": "^4.0.0",
41
41
  "@npmcli/map-workspaces": "^3.0.0",
42
- "@npmcli/package-json": "^3.0.0",
42
+ "@npmcli/package-json": "^4.0.0",
43
43
  "@octokit/rest": "^19.0.4",
44
44
  "diff": "^5.0.0",
45
45
  "glob": "^10.1.0",
@@ -50,6 +50,7 @@
50
50
  "just-deep-map-values": "^1.1.1",
51
51
  "just-diff": "^6.0.0",
52
52
  "lodash": "^4.17.21",
53
+ "minimatch": "^9.0.2",
53
54
  "npm-package-arg": "^10.0.0",
54
55
  "proc-log": "^3.0.0",
55
56
  "release-please": "npm:@npmcli/release-please@^14.2.6",
@@ -1,26 +0,0 @@
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
- // errors are ignored
23
- }
24
- }
25
-
26
- module.exports = getRepo