@npmcli/template-oss 4.17.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.
package/lib/config.js CHANGED
@@ -2,7 +2,8 @@ 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
8
  const { mergeWithArrays } = require('./util/merge.js')
8
9
  const { FILE_KEYS, parseConfig: parseFiles, getAddedFiles, mergeFiles } = require('./util/files.js')
@@ -11,6 +12,7 @@ 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
 
@@ -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}}
@@ -38,14 +38,6 @@ const sharedRootAdd = (name) => ({
38
38
  '.github/dependabot.yml': {
39
39
  file: 'dependabot.yml',
40
40
  filter: (p) => p.config.dependabot,
41
- clean: (p) => p.config.isRoot,
42
- // dependabot takes a single top level config file. this parser
43
- // will run for all configured packages and each one will have
44
- // its item replaced in the updates array based on the directory
45
- parser: (p) => class extends p.YmlMerge {
46
- key = 'updates'
47
- id = 'directory'
48
- },
49
41
  },
50
42
  '.github/workflows/post-dependabot.yml': {
51
43
  file: 'post-dependabot.yml',
@@ -53,6 +45,7 @@ const sharedRootAdd = (name) => ({
53
45
  },
54
46
  '.github/settings.yml': {
55
47
  file: 'settings.yml',
48
+ filter: (p) => !p.config.isReleaseBranch,
56
49
  },
57
50
  })
58
51
 
@@ -60,6 +53,9 @@ const sharedRootRm = () => ({
60
53
  '.github/workflows/pull-request.yml': {
61
54
  filter: (p) => p.config.allPrivate,
62
55
  },
56
+ '.github/settings.yml': {
57
+ filter: (p) => p.config.isReleaseBranch,
58
+ },
63
59
  })
64
60
 
65
61
  // Changes applied to the root of the repo
@@ -139,8 +135,8 @@ module.exports = {
139
135
  workspaceModule,
140
136
  windowsCI: true,
141
137
  macCI: true,
142
- branches: ['main', 'latest', 'release/v*'],
143
- defaultBranch: 'main',
138
+ branches: ['main', 'latest'],
139
+ releaseBranch: 'release/v*',
144
140
  distPaths: [
145
141
  'bin/',
146
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
 
@@ -14,6 +14,7 @@ branches:
14
14
  protection:
15
15
  required_status_checks: null
16
16
  enforce_admins: true
17
+ block_creations: true
17
18
  required_pull_request_reviews:
18
19
  required_approving_review_count: 1
19
20
  require_code_owner_reviews: true
@@ -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
+ }
@@ -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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@npmcli/template-oss",
3
- "version": "4.17.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": {
@@ -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