@npmcli/template-oss 2.3.1 → 2.4.3

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/README.md CHANGED
@@ -5,6 +5,28 @@ single devDependency.
5
5
 
6
6
  **CAUTION: THESE CHANGES WILL OVERWRITE ANY LOCAL FILES AND SETTINGS**
7
7
 
8
+ ### Configuration
9
+
10
+ Configure the use of `template-oss` in the root `package.json`.
11
+
12
+ ```js
13
+ {
14
+ name: 'my-package',
15
+ // ...
16
+ templateOSS: {
17
+ // copy repo specific files for the root pkg
18
+ applyRootRepoFiles: true,
19
+ // modify package.json and copy module specific files for the root pkg
20
+ applyRootModuleFiles: true,
21
+ // copy repo files for each whitelisted workspaces
22
+ applyWorkspaceRepoFiles: true,
23
+ // whitelist workspace by package name to modify package.json
24
+ // and copy module files
25
+ workspaces: ['workspace-package-name'],
26
+ version: '2.3.1'
27
+ }
28
+ }
29
+
8
30
  ### `package.json` patches
9
31
 
10
32
  These fields will be set in the project's `package.json`:
@@ -58,13 +80,18 @@ These files will be copied, overwriting any existing files:
58
80
  - `LICENSE.md`
59
81
  - `SECURITY.md`
60
82
 
83
+ ### Dynamic Files
84
+
85
+ Currently, the only dynamic file generated is a github workflow for a given workspace.
86
+ `.github/workflows/ci-$$package-name$$.yml`
87
+
61
88
  #### Extending
62
89
 
63
90
  Place files in the `lib/content/` directory, use only the file name and remove
64
91
  any leading `.` characters (i.e. `.github/workflows/ci.yml` becomes `ci.yml`
65
92
  and `.gitignore` becomes `gitignore`).
66
93
 
67
- Modify the `content` object at the top of `lib/postinstall/copy-content.js` to include
94
+ Modify the `repoFiles` and `moduleFiles` objects at the top of `lib/postinstall/copy-content.js` to include
68
95
  your new file. The object keys are destination paths, and values are source.
69
96
 
70
97
  ### `package.json` checks
@@ -76,4 +103,4 @@ is not configured properly, with steps to run to correct any problems.
76
103
 
77
104
  Add any unwanted packages to `unwantedPackages` in `lib/check.js`. Currently
78
105
  the best way to install any packages is to include them as `peerDependencies`
79
- in this repo.
106
+ in this repo.
@@ -2,6 +2,7 @@
2
2
 
3
3
  const checkPackage = require('../lib/postlint/check-package.js')
4
4
  const checkGitIgnore = require('../lib/postlint/check-gitignore.js')
5
+ const getConfig = require('../lib/config.js')
5
6
 
6
7
  const main = async () => {
7
8
  const {
@@ -12,10 +13,19 @@ const main = async () => {
12
13
  throw new Error('This package requires npm >7.21.1')
13
14
  }
14
15
 
15
- const problems = [
16
- ...(await checkPackage(root)),
17
- ...(await checkGitIgnore(root)),
18
- ]
16
+ const config = await getConfig(root)
17
+
18
+ const problemSets = []
19
+ for (const path of config.paths) {
20
+ if (path !== root || config.applyRootModuleFiles) {
21
+ problemSets.push(await checkPackage(path))
22
+ }
23
+ if (path !== root || config.applyRootRepoFiles) {
24
+ problemSets.push(await checkGitIgnore(path))
25
+ }
26
+ }
27
+
28
+ const problems = problemSets.flat()
19
29
 
20
30
  if (problems.length) {
21
31
  console.error('Some problems were detected:')
@@ -2,6 +2,7 @@
2
2
 
3
3
  const copyContent = require('../lib/postinstall/copy-content.js')
4
4
  const patchPackage = require('../lib/postinstall/update-package.js')
5
+ const getConfig = require('../lib/config.js')
5
6
 
6
7
  const main = async () => {
7
8
  const {
@@ -14,12 +15,14 @@ const main = async () => {
14
15
  return
15
16
  }
16
17
 
17
- const needsAction = await patchPackage(root)
18
- if (!needsAction) {
19
- return
20
- }
18
+ const config = await getConfig(root)
19
+ for (const path of config.paths) {
20
+ if (!await patchPackage(path, root, config)) {
21
+ continue
22
+ }
21
23
 
22
- await copyContent(root)
24
+ await copyContent(path, root, config)
25
+ }
23
26
  }
24
27
 
25
28
  module.exports = main().catch((err) => {
package/lib/config.js ADDED
@@ -0,0 +1,48 @@
1
+ const PackageJson = require('@npmcli/package-json')
2
+ const mapWorkspaces = require('@npmcli/map-workspaces')
3
+
4
+ const defaultConfig = {
5
+ applyRootRepoFiles: true,
6
+ applyWorkspaceRepoFiles: true,
7
+ applyRootModuleFiles: true,
8
+ workspaces: [],
9
+ paths: [],
10
+ }
11
+
12
+ module.exports = async (root) => {
13
+ let pkg
14
+ let pkgError = false
15
+ try {
16
+ pkg = (await PackageJson.load(root)).content
17
+ } catch (e) {
18
+ pkgError = true
19
+ }
20
+ if (pkgError || !pkg.templateOSS) {
21
+ return {
22
+ ...defaultConfig,
23
+ paths: [root],
24
+ }
25
+ }
26
+ const config = {
27
+ ...defaultConfig,
28
+ ...pkg.templateOSS,
29
+ }
30
+ const workspaceMap = await mapWorkspaces({
31
+ pkg,
32
+ cwd: root,
33
+ })
34
+ const wsPaths = []
35
+ const workspaceSet = new Set(config.workspaces)
36
+ for (const [name, path] of workspaceMap.entries()) {
37
+ if (workspaceSet.has(name)) {
38
+ wsPaths.push(path)
39
+ }
40
+ }
41
+ config.workspacePaths = wsPaths
42
+
43
+ config.paths = config.paths.concat(config.workspacePaths)
44
+
45
+ config.paths.push(root)
46
+
47
+ return config
48
+ }
@@ -0,0 +1,23 @@
1
+ # This file is automatically added by @npmcli/template-oss. Do not edit.
2
+
3
+ name: Audit
4
+
5
+ on:
6
+ schedule:
7
+ # "At 01:00 on Monday" https://crontab.guru/#0_1_*_*_1
8
+ - cron: "0 1 * * 1"
9
+ workflow_dispatch:
10
+
11
+ jobs:
12
+ audit:
13
+ name: npm audit
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@v2
17
+ - uses: actions/setup-node@v2
18
+ with:
19
+ node-version: '16'
20
+ - name: Install deps
21
+ run: npm i --package-lock
22
+ - name: Audit
23
+ run: npm audit
@@ -0,0 +1,76 @@
1
+ name: Node Workspace CI %%pkgname%%
2
+
3
+ on:
4
+ pull_request:
5
+ paths:
6
+ - %%pkgpath%%/**
7
+ branches:
8
+ - '*'
9
+ push:
10
+ paths:
11
+ - %%pkgpath%%/**
12
+ branches:
13
+ - release-next
14
+ - latest
15
+ workflow_dispatch:
16
+
17
+ jobs:
18
+ lint:
19
+ runs-on: ubuntu-latest
20
+ steps:
21
+ # Checkout the npm/cli repo
22
+ - uses: actions/checkout@v2
23
+ - name: Use Node.js 16.x
24
+ uses: actions/setup-node@v2
25
+ with:
26
+ node-version: 16.x
27
+ cache: npm
28
+ - name: Install dependencies
29
+ run: |
30
+ node ./bin/npm-cli.js install --ignore-scripts --no-audit
31
+ node ./bin/npm-cli.js rebuild
32
+ - name: Run linting
33
+ run: node ./bin/npm-cli.js run posttest -w %%pkgpath%%
34
+ env:
35
+ DEPLOY_VERSION: testing
36
+
37
+ test:
38
+ strategy:
39
+ fail-fast: false
40
+ matrix:
41
+ node-version: ['12.13.0', 12.x, '14.15.0', 14.x, '16.0.0', 16.x]
42
+ platform:
43
+ - os: ubuntu-latest
44
+ shell: bash
45
+ - os: macos-latest
46
+ shell: bash
47
+ - os: windows-latest
48
+ shell: bash
49
+ - os: windows-latest
50
+ shell: powershell
51
+
52
+ runs-on: ${{ matrix.platform.os }}
53
+ defaults:
54
+ run:
55
+ shell: ${{ matrix.platform.shell }}
56
+
57
+ steps:
58
+ # Checkout the npm/cli repo
59
+ - uses: actions/checkout@v2
60
+
61
+ # Installs the specific version of Node.js
62
+ - name: Use Node.js ${{ matrix.node-version }}
63
+ uses: actions/setup-node@v2
64
+ with:
65
+ node-version: ${{ matrix.node-version }}
66
+ cache: npm
67
+
68
+ # Run the installer script
69
+ - name: Install dependencies
70
+ run: |
71
+ node ./bin/npm-cli.js install --ignore-scripts --no-audit
72
+ node ./bin/npm-cli.js rebuild
73
+
74
+ # Run the tests, but not if we're just gonna do coveralls later anyway
75
+ - name: Run Tap tests
76
+ run: node ./bin/npm-cli.js run -w %%pkgpath%% --ignore-scripts test -- -t600 -Rbase -c
@@ -8,6 +8,9 @@ on:
8
8
  branches:
9
9
  - main
10
10
  - latest
11
+ schedule:
12
+ # "At 02:00 on Monday" https://crontab.guru/#0_1_*_*_1
13
+ - cron: "0 2 * * 1"
11
14
 
12
15
  jobs:
13
16
  lint:
@@ -17,9 +20,8 @@ jobs:
17
20
  - uses: actions/setup-node@v2
18
21
  with:
19
22
  node-version: '16'
20
- cache: npm
21
23
  - run: npm i --prefer-online -g npm@latest
22
- - run: npm ci
24
+ - run: npm i
23
25
  - run: npm run lint
24
26
 
25
27
  test:
@@ -47,7 +49,6 @@ jobs:
47
49
  - uses: actions/setup-node@v2
48
50
  with:
49
51
  node-version: ${{ matrix.node-version }}
50
- cache: npm
51
52
  - run: npm i --prefer-online -g npm@latest
52
- - run: npm ci
53
+ - run: npm i
53
54
  - run: npm test --ignore-scripts
@@ -0,0 +1,11 @@
1
+ // This file is automatically added by @npmcli/template-oss. Do not edit.
2
+
3
+ module.exports = {
4
+ extends: ['@commitlint/config-conventional'],
5
+ // If you change rules be sure to also update release-please.yml
6
+ rules: {
7
+ 'type-enum': [2, 'always', ['feat', 'fix', 'docs', 'chore', 'deps']],
8
+ 'header-max-length': [2, 'always', 80],
9
+ 'subject-case': [2, 'always', ['lower-case', 'sentence-case', 'start-case']],
10
+ },
11
+ }
@@ -0,0 +1,16 @@
1
+ # This file is automatically added by @npmcli/template-oss. Do not edit.
2
+
3
+ version: 2
4
+ updates:
5
+ - package-ecosystem: npm
6
+ directory: "/"
7
+ schedule:
8
+ interval: daily
9
+ allow:
10
+ - dependency-type: direct
11
+ versioning-strategy: increase-if-necessary
12
+ commit-message:
13
+ prefix: deps
14
+ prefix-development: chore
15
+ labels:
16
+ - "dependencies"
@@ -4,11 +4,12 @@
4
4
  /*
5
5
 
6
6
  # keep these
7
+ !/.commitlintrc.js
8
+ !/.npmrc
7
9
  !/.eslintrc*
8
10
  !/.github
9
11
  !**/.gitignore
10
12
  !/package.json
11
- !/package-lock.json
12
13
  !/docs
13
14
  !/bin
14
15
  !/lib
@@ -0,0 +1,3 @@
1
+ ;This file is automatically added by @npmcli/template-oss. Do not edit.
2
+
3
+ package-lock=false
@@ -0,0 +1,27 @@
1
+ # This file is automatically added by @npmcli/template-oss. Do not edit.
2
+
3
+ name: Pull Request Linting
4
+
5
+ on:
6
+ pull_request:
7
+ types: [opened, reopened, edited, synchronize]
8
+
9
+ jobs:
10
+ check:
11
+ name: Check PR Title or Commits
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - uses: actions/checkout@v2
15
+ with:
16
+ fetch-depth: 0
17
+ - uses: actions/setup-node@v2
18
+ with:
19
+ node-version: '16'
20
+ - name: Install deps
21
+ run: |
22
+ npm i -D @commitlint/cli @commitlint/config-conventional
23
+ - name: Check commits OR PR title
24
+ env:
25
+ PR_TITLE: ${{ github.event.pull_request.title }}
26
+ run: |
27
+ npx commitlint -x @commitlint/config-conventional -V --from origin/main --to ${{ github.event.pull_request.head.sha }} || echo $PR_TITLE | npx commitlint -x @commitlint/config-conventional -V
@@ -0,0 +1,25 @@
1
+ # This file is automatically added by @npmcli/template-oss. Do not edit.
2
+
3
+ name: Release Please
4
+
5
+ on:
6
+ push:
7
+ branches:
8
+ - main
9
+
10
+ jobs:
11
+ release-please:
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - uses: google-github-actions/release-please-action@v2
15
+ id: release
16
+ with:
17
+ package-name: conventional-test
18
+ release-type: node
19
+ # If you change changelog-types be sure to also update commitlintrc.js
20
+ changelog-types: >
21
+ [{"type":"feat","section":"Features","hidden":false},
22
+ {"type":"fix","section":"Bug Fixes","hidden":false},
23
+ {"type":"docs","section":"Documentation","hidden":false},
24
+ {"type":"deps","section":"dependencies","hidden":false},
25
+ {"type":"chore","hidden":true}]
@@ -1,44 +1,51 @@
1
1
  const { dirname, join, resolve } = require('path')
2
2
  const fs = require('@npmcli/fs')
3
+ const PackageJson = require('@npmcli/package-json')
3
4
 
4
5
  const contentDir = resolve(__dirname, '..', 'content')
5
6
 
6
7
  // keys are destination paths in the target project
7
8
  // values are paths to contents relative to '../content/'
8
- const content = {
9
+ const moduleFiles = {
9
10
  '.eslintrc.js': './eslintrc.js',
11
+ '.gitignore': './gitignore',
12
+ '.npmrc': './npmrc',
13
+ 'SECURITY.md': './SECURITY.md',
14
+ }
15
+
16
+ const repoFiles = {
17
+ '.commitlintrc.js': './commitlintrc.js',
10
18
  '.github/workflows/ci.yml': './ci.yml',
11
19
  '.github/ISSUE_TEMPLATE/bug.yml': './bug.yml',
12
20
  '.github/ISSUE_TEMPLATE/config.yml': './config.yml',
13
21
  '.github/CODEOWNERS': './CODEOWNERS',
14
- '.gitignore': './gitignore',
15
- 'LICENSE.md': './LICENSE.md',
16
- 'SECURITY.md': './SECURITY.md',
22
+ '.github/dependabot.yml': './dependabot.yml',
23
+ '.github/workflows/audit.yml': './audit.yml',
24
+ '.github/workflows/pull-request.yml': './pull-request.yml',
25
+ '.github/workflows/release-please.yml': './release-please.yml',
17
26
  }
18
27
 
28
+ // currently no workspace moduleFiles
29
+ // const workspaceContent = {}
30
+ // const workspaceRootContent = {}
31
+
19
32
  const filesToDelete = [
20
- // remove any other license files
21
- /^LICENSE*/,
22
33
  // remove any eslint config files that aren't local to the project
23
34
  /^\.eslintrc\.(?!(local\.)).*/,
24
35
  ]
25
36
 
26
- // given a root directory, copy all files in the content map
27
- // after purging any files we need to delete
28
- const copyContent = async (root) => {
29
- const contents = await fs.readdir(root)
30
-
31
- for (const file of contents) {
32
- if (filesToDelete.some((p) => p.test(file))) {
33
- await fs.rm(join(root, file))
34
- }
35
- }
37
+ const defaultConfig = {
38
+ applyRootRepoFiles: true,
39
+ applyWorkspaceRepoFiles: true,
40
+ applyRootModuleFiles: true,
41
+ }
36
42
 
37
- for (let [target, source] of Object.entries(content)) {
43
+ const copyFiles = async (targetDir, files) => {
44
+ for (let [target, source] of Object.entries(files)) {
38
45
  source = join(contentDir, source)
39
- target = join(root, target)
40
- // if the target is a subdirectory of the root, mkdirp it first
41
- if (dirname(target) !== root) {
46
+ target = join(targetDir, target)
47
+ // if the target is a subdirectory of the path, mkdirp it first
48
+ if (dirname(target) !== targetDir) {
42
49
  await fs.mkdir(dirname(target), {
43
50
  owner: 'inherit',
44
51
  recursive: true,
@@ -48,6 +55,62 @@ const copyContent = async (root) => {
48
55
  await fs.copyFile(source, target, { owner: 'inherit' })
49
56
  }
50
57
  }
51
- copyContent.content = content
58
+
59
+ // given a root directory, copy all files in the content map
60
+ // after purging any files we need to delete
61
+ const copyContent = async (path, rootPath, config) => {
62
+ config = { ...defaultConfig, ...config }
63
+ const isWorkspace = path !== rootPath
64
+
65
+ const contents = await fs.readdir(path)
66
+
67
+ if (isWorkspace || config.applyRootModuleFiles) {
68
+ // delete files and copy moduleFiles if it's a workspace
69
+ // or if we enabled doing so for the root
70
+ for (const file of contents) {
71
+ if (filesToDelete.some((p) => p.test(file))) {
72
+ await fs.rm(join(path, file))
73
+ }
74
+ }
75
+ await copyFiles(path, moduleFiles)
76
+ }
77
+
78
+ if (!isWorkspace) {
79
+ if (config.applyRootRepoFiles) {
80
+ await copyFiles(rootPath, repoFiles)
81
+ }
82
+ return
83
+ } // only workspace now
84
+
85
+ // TODO: await copyFiles(path, workspaceFiles)
86
+ // if we ever have workspace specific files
87
+
88
+ if (config.applyWorkspaceRepoFiles) {
89
+ // copy and edit workspace repo file (ci github action)
90
+ const workspacePkg = (await PackageJson.load(path)).content
91
+ const workspaceName = workspacePkg.name
92
+ let workspaceFile = `ci-${workspaceName.replace(/\//g, '-')}.yml`
93
+ workspaceFile = workspaceFile.replace(/@/g, '')
94
+ const workflowPath = join(rootPath, '.github', 'workflows')
95
+ await fs.mkdir(workflowPath, {
96
+ owner: 'inherit',
97
+ recursive: true,
98
+ force: true,
99
+ })
100
+
101
+ let workflowData = await fs.readFile(
102
+ join(contentDir, './ci-workspace.yml'),
103
+ { encoding: 'utf-8' }
104
+ )
105
+
106
+ const relPath = path.substring(rootPath.length + 1)
107
+ workflowData = workflowData.replace(/%%pkgpath%%/g, relPath)
108
+ workflowData = workflowData.replace(/%%pkgname%%/g, workspaceName)
109
+
110
+ await fs.writeFile(join(workflowPath, workspaceFile), workflowData)
111
+ }
112
+ }
113
+ copyContent.moduleFiles = moduleFiles
114
+ copyContent.repoFiles = repoFiles
52
115
 
53
116
  module.exports = copyContent
@@ -8,8 +8,6 @@ const {
8
8
  const changes = {
9
9
  author: 'GitHub Inc.',
10
10
  files: ['bin', 'lib'],
11
- license: 'ISC',
12
- templateVersion: TEMPLATE_VERSION,
13
11
  scripts: {
14
12
  lint: `eslint '**/*.js'`,
15
13
  postlint: 'npm-template-check',
@@ -26,33 +24,57 @@ const changes = {
26
24
  },
27
25
  }
28
26
 
29
- const patchPackage = async (root) => {
30
- const pkg = await PackageJson.load(root)
27
+ const patchPackage = async (path, root, config) => {
28
+ const pkg = await PackageJson.load(path)
31
29
 
32
30
  // If we are running this on itself, we always run the script.
33
31
  // We also don't set templateVersion in package.json because
34
32
  // its not relavent and would cause git churn after running
35
33
  // `npm version`.
36
34
  const isDogfood = pkg.content.name === TEMPLATE_NAME
35
+ const currentVersion = (pkg.content.templateOSS === undefined) ?
36
+ pkg.content.templateVersion : pkg.content.templateOSS.version
37
37
 
38
38
  // if the target package.json has a templateVersion field matching our own
39
39
  // current version, we return false here so the postinstall script knows to
40
40
  // exit early instead of running everything again
41
- if (pkg.content.templateVersion === TEMPLATE_VERSION && !isDogfood) {
41
+ if (currentVersion === TEMPLATE_VERSION && !isDogfood) {
42
42
  return false
43
43
  }
44
44
 
45
- // we build a new object here so our exported set of changes is not modified
46
- const update = {
47
- ...changes,
48
- scripts: {
49
- ...pkg.content.scripts,
50
- ...changes.scripts,
45
+ const templateConfig = {
46
+ templateOSS: {
47
+ ...pkg.content.templateOSS,
48
+ ...{ version: TEMPLATE_VERSION },
51
49
  },
52
50
  }
53
51
 
52
+ let update
53
+
54
+ if (path === root && !config.applyRootModuleFiles) {
55
+ // only update templateVersion if we're skipping root module files
56
+ update = {
57
+ ...templateConfig,
58
+ }
59
+ } else {
60
+ // we build a new object here so our exported set of changes is not modified
61
+ update = {
62
+ ...changes,
63
+ scripts: {
64
+ ...pkg.content.scripts,
65
+ ...changes.scripts,
66
+ },
67
+ ...templateConfig,
68
+ }
69
+ }
70
+
54
71
  if (isDogfood) {
55
72
  delete update.templateVersion
73
+ delete update.templateOSS
74
+ } else {
75
+ if (pkg.content.templateVersion) {
76
+ update.templateVersion = undefined
77
+ }
56
78
  }
57
79
 
58
80
  pkg.update(update)
@@ -1,6 +1,4 @@
1
1
  const PackageJson = require('@npmcli/package-json')
2
-
3
- const { name: TEMPLATE_NAME } = require('../../package.json')
4
2
  const patchPackage = require('../postinstall/update-package.js')
5
3
 
6
4
  const unwantedPackages = [
@@ -14,18 +12,8 @@ const hasOwn = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key)
14
12
 
15
13
  const check = async (root) => {
16
14
  const pkg = (await PackageJson.load(root)).content
17
-
18
- // templateVersion doesn't apply if we're on this repo
19
- // since we always run the scripts here
20
- const changes = Object.entries(patchPackage.changes).filter(([key]) => {
21
- if (pkg.name === TEMPLATE_NAME && key === 'templateVersion') {
22
- return false
23
- }
24
- return true
25
- })
26
-
15
+ const changes = Object.entries(patchPackage.changes)
27
16
  const problems = []
28
-
29
17
  const incorrectFields = []
30
18
  // 1. ensure package.json changes have been applied
31
19
  for (const [key, value] of changes) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@npmcli/template-oss",
3
- "version": "2.3.1",
3
+ "version": "2.4.3",
4
4
  "description": "templated files used in npm CLI team oss projects",
5
5
  "main": "lib/index.js",
6
6
  "bin": {
@@ -30,7 +30,9 @@
30
30
  "license": "ISC",
31
31
  "dependencies": {
32
32
  "@npmcli/fs": "^1.0.0",
33
+ "@npmcli/map-workspaces": "^2.0.0",
33
34
  "@npmcli/package-json": "^1.0.1",
35
+ "json-parse-even-better-errors": "^2.3.1",
34
36
  "which": "^2.0.2"
35
37
  },
36
38
  "files": [
@@ -46,7 +48,7 @@
46
48
  "tap": "*"
47
49
  },
48
50
  "peerDependencies": {
49
- "@npmcli/eslint-config": "^1.0.0",
51
+ "@npmcli/eslint-config": "^2.0.0",
50
52
  "tap": "^15.0.9"
51
53
  },
52
54
  "tap": {