@npmcli/template-oss 4.18.1 → 4.19.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/lib/config.js CHANGED
@@ -1,41 +1,63 @@
1
1
  const { relative, dirname, join, extname, posix, win32 } = require('path')
2
- const { defaults, pick, omit, uniq } = require('lodash')
3
- const semver = require('semver')
4
- const parseCIVersions = require('./util/parse-ci-versions.js')
2
+ const { defaults, pick, omit, uniq, isPlainObject } = require('lodash')
3
+ const ciVersions = require('./util/ci-versions.js')
5
4
  const parseDependabot = require('./util/dependabot.js')
6
5
  const git = require('./util/git.js')
7
6
  const gitignore = require('./util/gitignore.js')
8
- const { mergeWithArrays } = require('./util/merge.js')
7
+ const { mergeWithCustomizers, customizers } = require('./util/merge.js')
9
8
  const { FILE_KEYS, parseConfig: parseFiles, getAddedFiles, mergeFiles } = require('./util/files.js')
10
9
 
11
10
  const CONFIG_KEY = 'templateOSS'
12
11
  const getPkgConfig = (pkg) => pkg[CONFIG_KEY] || {}
13
12
 
14
13
  const { name: NAME, version: LATEST_VERSION } = require('../package.json')
15
- const { minimatch } = require('minimatch')
16
14
  const MERGE_KEYS = [...FILE_KEYS, 'defaultContent', 'content']
17
15
  const DEFAULT_CONTENT = require.resolve(NAME)
18
16
 
19
- const merge = mergeWithArrays('branches', 'distPaths', 'allowPaths', 'ignorePaths')
17
+ const merge = mergeWithCustomizers(
18
+ customizers.mergeArrays('branches', 'distPaths', 'allowPaths', 'ignorePaths'),
19
+ (value, srcValue, key) => {
20
+ if (key === 'ciVersions' && (Array.isArray(srcValue) || isPlainObject(srcValue))) {
21
+ return { ...ciVersions.parse(value), ...ciVersions.parse(srcValue) }
22
+ }
23
+ }
24
+ )
20
25
 
21
26
  const makePosix = (v) => v.split(win32.sep).join(posix.sep)
22
27
  const deglob = (v) => makePosix(v).replace(/[/*]+$/, '')
23
28
  const posixDir = (v) => `${v === '.' ? '' : deglob(v).replace(/\/$/, '')}${posix.sep}`
24
29
  const posixGlob = (str) => `${posixDir(str)}**`
25
30
 
26
- const getCmdPath = (key, { rootConfig, defaultConfig, isRoot, pkg, rootPkg }) => {
27
- // Make a path relative from a workspace to the root if we are in a workspace
28
- const wsToRoot = (p) => isRoot ? p : makePosix(join(relative(pkg.path, rootPkg.path), p))
31
+ const getCmdPath = (key, { pkgConfig, rootConfig, isRoot, pkg, rootPkg }) => {
32
+ const result = (local, isRelative) => {
33
+ let root = local
34
+ const isLocal = local.startsWith('.') || local.startsWith('/')
35
+
36
+ if (isLocal) {
37
+ if (isRelative) {
38
+ // Make a path relative from a workspace to the root if we are in a workspace
39
+ local = makePosix(join(relative(pkg.path, rootPkg.path), local))
40
+ }
41
+ local = `node ${local}`
42
+ root = `node ${root}`
43
+ }
44
+
45
+ return {
46
+ isLocal,
47
+ local,
48
+ root,
49
+ }
50
+ }
29
51
 
30
- const rootPath = rootConfig[key]
31
- const defaultPath = defaultConfig[key]
32
- const isLocal = rootPath && rootPath !== defaultPath
52
+ if (pkgConfig[key]) {
53
+ return result(pkgConfig[key])
54
+ }
33
55
 
34
- return {
35
- isLocal,
36
- root: !isLocal ? defaultPath : `node ${rootPath}`,
37
- local: !isLocal ? defaultPath : `node ${wsToRoot(rootPath)}`,
56
+ if (rootConfig[key]) {
57
+ return result(rootConfig[key], !isRoot)
38
58
  }
59
+
60
+ return result(key)
39
61
  }
40
62
 
41
63
  const mergeConfigs = (...configs) => {
@@ -139,9 +161,8 @@ const getFullConfig = async ({
139
161
  ] : [],
140
162
  ]
141
163
 
142
- // root only configs
143
- const npmPath = getCmdPath('npm', { rootConfig, defaultConfig, isRoot, pkg, rootPkg })
144
- const npxPath = getCmdPath('npx', { rootConfig, defaultConfig, isRoot, pkg, rootPkg })
164
+ const npmPath = getCmdPath('npm', { pkgConfig, rootConfig, isRoot, pkg, rootPkg })
165
+ const npxPath = getCmdPath('npx', { pkgConfig, rootConfig, isRoot, pkg, rootPkg })
145
166
 
146
167
  // these are written to ci yml files so it needs to always use posix
147
168
  const pkgPath = makePosix(relative(rootPkg.path, pkg.path)) || '.'
@@ -157,9 +178,12 @@ const getFullConfig = async ({
157
178
 
158
179
  const branches = uniq([...pkgConfig.branches ?? [], pkgConfig.releaseBranch]).filter(Boolean)
159
180
  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
181
  const defaultBranch = await git.defaultBranch(rootPkg.path) ?? 'main'
182
+ const isReleaseBranch = !!pkgConfig.backport
183
+ const publishTag = isReleaseBranch ? `next-${pkgConfig.backport}` : 'latest'
184
+ const releaseBranch = isReleaseBranch
185
+ ? pkgConfig.releaseBranch.replace(/\*/g, pkgConfig.backport)
186
+ : defaultBranch
163
187
 
164
188
  // all derived keys
165
189
  const derived = {
@@ -179,12 +203,11 @@ const getFullConfig = async ({
179
203
  // controls whether we are in a monorepo with any public workspaces
180
204
  isMonoPublic: isMono && !!publicPkgs.filter(p => p.path !== rootPkg.path).length,
181
205
  // git
182
- defaultBranch,
183
- baseBranch: isReleaseBranch ? currentBranch : defaultBranch,
184
206
  branches: gitBranches.branches,
185
207
  branchPatterns: gitBranches.patterns,
186
208
  isReleaseBranch,
187
- // dependabot
209
+ releaseBranch,
210
+ publishTag,
188
211
  dependabot: parseDependabot(pkgConfig, defaultConfig, gitBranches.branches),
189
212
  // repo
190
213
  repoDir: rootPkg.path,
@@ -218,6 +241,8 @@ const getFullConfig = async ({
218
241
  // lockfiles are only present at the root, so this only should be set for
219
242
  // all workspaces based on the root
220
243
  lockfile: rootPkgConfig.lockfile,
244
+ // ci versions / engines
245
+ ciVersions: ciVersions.get(pkg.pkgJson.engines?.node, pkgConfig),
221
246
  // gitignore
222
247
  ignorePaths: [
223
248
  ...gitignore.sort([
@@ -242,33 +267,6 @@ const getFullConfig = async ({
242
267
  __PARTIAL_DIRS__: fileDirs,
243
268
  }
244
269
 
245
- if (pkgConfig.ciVersions) {
246
- let versions = pkgConfig.ciVersions
247
- if (versions === 'latest' || (Array.isArray(versions) && versions.includes('latest'))) {
248
- const { ciVersions } = [isWorkspace ? rootPkgConfig : {}, defaultConfig]
249
- .find(c => Array.isArray(c.ciVersions))
250
- const defaultLatest = ciVersions[ciVersions.length - 1]
251
- versions = [].concat(versions).map(v => v === 'latest' ? defaultLatest : v)
252
- }
253
-
254
- const { targets, engines } = parseCIVersions(versions)
255
-
256
- // get just a list of the target versions (not ranges)
257
- // these are used for the node version when doing engines checks
258
- // since we want to test in the lowest version of each major
259
- let targetVersions = targets.filter(t => semver.valid(t))
260
- // if the versions are all ranges then convert them to the lower bound of each range
261
- if (!targetVersions.length) {
262
- targetVersions = targets.filter(t => semver.validRange(t)).map(t => {
263
- return new semver.Range(t).set[0][0].semver.version
264
- })
265
- }
266
-
267
- derived.ciVersions = targets
268
- derived.baseCiVersions = targetVersions
269
- derived.engines = pkgConfig.engines || engines
270
- }
271
-
272
270
  if (!pkgConfig.eslint) {
273
271
  derived.ignorePaths = derived.ignorePaths.filter(p => !p.includes('eslint'))
274
272
  if (Array.isArray(pkgConfig.requiredPackages?.devDependencies)) {
@@ -22,7 +22,7 @@ steps:
22
22
  - name: Publish
23
23
  env:
24
24
  PUBLISH_TOKEN: $\{{ secrets.PUBLISH_TOKEN }}
25
- run: npm publish --provenance
25
+ run: npm publish --provenance --tag={{ publishTag }}
26
26
  {{else}}
27
27
  runs-on: ubuntu-latest
28
28
  defaults:
@@ -7,7 +7,7 @@ on:
7
7
  ref:
8
8
  required: true
9
9
  type: string
10
- default: {{ baseBranch }}
10
+ default: {{ releaseBranch }}
11
11
  workflow_call:
12
12
  inputs:
13
13
  ref:
@@ -136,6 +136,8 @@ module.exports = {
136
136
  windowsCI: true,
137
137
  macCI: true,
138
138
  branches: ['main', 'latest'],
139
+ // set this to the major version to backport
140
+ backport: null,
139
141
  releaseBranch: 'release/v*',
140
142
  distPaths: [
141
143
  'bin/',
@@ -156,13 +158,12 @@ module.exports = {
156
158
  '/CHANGELOG*',
157
159
  ],
158
160
  ignorePaths: [],
159
- ciVersions: ['14.17.0', '14.x', '16.13.0', '16.x', '18.0.0', '18.x'],
161
+ ciVersions: {},
162
+ latestCiVersion: 20,
160
163
  lockfile: false,
161
164
  codeowner: '@npm/cli-team',
162
165
  eslint: true,
163
166
  publish: false,
164
- npm: 'npm',
165
- npx: 'npx',
166
167
  updateNpm: true,
167
168
  dependabot: 'increase-if-necessary',
168
169
  unwantedPackages: [
@@ -21,11 +21,6 @@
21
21
  "postpublish": {{{ del }}}
22
22
  },
23
23
  "repository": {{#if repository}}{{{ json repository }}}{{else}}{{{ del }}}{{/if}},
24
- "engines": {
25
- {{#if engines}}
26
- "node": {{{ json engines }}}
27
- {{/if}}
28
- },
29
24
  {{{ json __CONFIG_KEY__ }}}: {
30
25
  "version": {{#if isDogFood}}{{{ del }}}{{else}}{{{ json __VERSION__ }}}{{/if}}
31
26
  },
@@ -0,0 +1,80 @@
1
+ const { uniq, range, isPlainObject } = require('lodash')
2
+ const semver = require('semver')
3
+
4
+ const parseCiVersions = (ciVersions) => {
5
+ if (Array.isArray(ciVersions)) {
6
+ return Object.fromEntries(ciVersions.map((v) => [v, true]))
7
+ }
8
+ if (isPlainObject(ciVersions)) {
9
+ return ciVersions
10
+ }
11
+ }
12
+
13
+ const getLowerBounds = (sRange) => {
14
+ return new semver.Range(sRange).set.map(c => c[0])
15
+ }
16
+
17
+ const getCiVersions = (nodeEngines, pkgConfig) => {
18
+ let allCiVersions = {}
19
+
20
+ // get ci versions
21
+ const { latestCiVersion, ciVersions } = pkgConfig
22
+
23
+ if (latestCiVersion) {
24
+ allCiVersions[`${latestCiVersion}.x`] = true
25
+ }
26
+
27
+ // determine the ci versions from the node engines set
28
+ if (nodeEngines) {
29
+ const lowerBounds = getLowerBounds(nodeEngines)
30
+ .map(v => v.semver)
31
+ .filter(v => v.version)
32
+
33
+ for (const version of lowerBounds) {
34
+ allCiVersions[version.version] = true
35
+ allCiVersions[`${version.major}.x`] = true
36
+ }
37
+
38
+ const lowestCiVersion = semver.sort(lowerBounds)[0]?.major
39
+ if (lowestCiVersion && latestCiVersion) {
40
+ for (const major of range(lowestCiVersion, latestCiVersion, 2)) {
41
+ allCiVersions[`${major}.x`] = true
42
+ }
43
+ }
44
+ }
45
+
46
+ if (ciVersions === 'latest' && latestCiVersion) {
47
+ // the plain string 'latest' means latest only and everything else is removed
48
+ allCiVersions = { [`${latestCiVersion}.x`]: true }
49
+ } else {
50
+ // this allows ciVersions to turn off default versions by setting them to a falsy value
51
+ Object.assign(allCiVersions, parseCiVersions(ciVersions))
52
+ }
53
+
54
+ if (allCiVersions.latest && latestCiVersion) {
55
+ delete allCiVersions.latest
56
+ allCiVersions[`${latestCiVersion}.x`] = true
57
+ }
58
+
59
+ const filteredCiVersions = Object.entries(allCiVersions)
60
+ .filter(([, v]) => v)
61
+ .map(([k]) => k)
62
+
63
+ return uniq(filteredCiVersions).sort((a, b) => {
64
+ const aComp = getLowerBounds(a)[0]
65
+ const bComp = getLowerBounds(b)[0]
66
+
67
+ if (aComp.semver.major > bComp.semver.major) {
68
+ return 1
69
+ } else if (aComp.semver.major < bComp.semver.major) {
70
+ return -1
71
+ }
72
+
73
+ return aComp.operator ? 1 : -1
74
+ })
75
+ }
76
+
77
+ module.exports = {
78
+ parse: parseCiVersions,
79
+ get: getCiVersions,
80
+ }
package/lib/util/git.js CHANGED
@@ -66,17 +66,8 @@ const defaultBranch = async (path) => {
66
66
  }
67
67
  }
68
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
69
  module.exports = {
78
70
  getUrl,
79
71
  getBranches,
80
72
  defaultBranch,
81
- currentBranch,
82
73
  }
package/lib/util/merge.js CHANGED
@@ -65,7 +65,6 @@ const customizers = {
65
65
  module.exports = {
66
66
  // default merge is to overwrite arrays
67
67
  merge: mergeWithCustomizers(customizers.overwriteArrays),
68
- mergeWithArrays: (...keys) => mergeWithCustomizers(customizers.mergeArrays(...keys)),
69
68
  mergeWithCustomizers,
70
69
  mergeWith,
71
70
  customizers,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@npmcli/template-oss",
3
- "version": "4.18.1",
3
+ "version": "4.19.0",
4
4
  "description": "templated files used in npm CLI team oss projects",
5
5
  "main": "lib/content/index.js",
6
6
  "bin": {
@@ -1,78 +0,0 @@
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