diff --git a/.commitlintrc.js b/.commitlintrc.js
index cf8f3d76..5b0b1a52 100644
--- a/.commitlintrc.js
+++ b/.commitlintrc.js
@@ -1,10 +1,9 @@
-// This file is automatically added by @npmcli/template-oss. Do not edit.
+/* This file is automatically added by @npmcli/template-oss. Do not edit. */
module.exports = {
extends: ['@commitlint/config-conventional'],
- // If you change rules be sure to also update release-please.yml
rules: {
- 'type-enum': [2, 'always', ['feat', 'fix', 'docs', 'chore', 'deps']],
+ 'type-enum': [2, 'always', ['feat', 'fix', 'docs', 'deps', 'chore']],
'header-max-length': [2, 'always', 80],
'subject-case': [0, 'always', ['lower-case', 'sentence-case', 'start-case']],
},
diff --git a/.eslintrc.js b/.eslintrc.js
index 022767bc..8204f234 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -1,4 +1,4 @@
-// This file is automatically added by @npmcli/template-oss. Do not edit.
+/* This file is automatically added by @npmcli/template-oss. Do not edit. */
const { readdirSync: readdir } = require('fs')
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index ef874313..2c54b0d2 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -1 +1,3 @@
-* @npm/cli-team
+# This file is automatically added by @npmcli/template-oss. Do not edit.
+
+* @npm/cli-team
diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml
index fa80b2de..d043192f 100644
--- a/.github/ISSUE_TEMPLATE/bug.yml
+++ b/.github/ISSUE_TEMPLATE/bug.yml
@@ -3,52 +3,52 @@
name: Bug
description: File a bug/issue
title: "[BUG]
"
-labels: [Bug, Needs Triage]
+labels: [ Bug, Needs Triage ]
+
body:
-- type: checkboxes
- attributes:
- label: Is there an existing issue for this?
- description: Please [search here](./issues) to see if an issue already exists for your problem.
- options:
- - label: I have searched the existing issues
- required: true
-- type: textarea
- attributes:
- label: Current Behavior
- description: A clear & concise description of what you're experiencing.
- validations:
- required: false
-- type: textarea
- attributes:
- label: Expected Behavior
- description: A clear & concise description of what you expected to happen.
- validations:
- required: false
-- type: textarea
- attributes:
- label: Steps To Reproduce
- description: Steps to reproduce the behavior.
- value: |
- 1. In this environment...
- 2. With this config...
- 3. Run '...'
- 4. See error...
- validations:
- required: false
-- type: textarea
- attributes:
- label: Environment
- description: |
- examples:
- - **npm**: 7.6.3
- - **Node**: 13.14.0
- - **OS**: Ubuntu 20.04
- - **platform**: Macbook Pro
- value: |
+ - type: checkboxes
+ attributes:
+ label: Is there an existing issue for this?
+ description: Please [search here](./issues) to see if an issue already exists for your problem.
+ options:
+ - label: I have searched the existing issues
+ required: true
+ - type: textarea
+ attributes:
+ label: Current Behavior
+ description: A clear & concise description of what you're experiencing.
+ validations:
+ required: false
+ - type: textarea
+ attributes:
+ label: Expected Behavior
+ description: A clear & concise description of what you expected to happen.
+ validations:
+ required: false
+ - type: textarea
+ attributes:
+ label: Steps To Reproduce
+ description: Steps to reproduce the behavior.
+ value: |
+ 1. In this environment...
+ 2. With this config...
+ 3. Run '...'
+ 4. See error...
+ validations:
+ required: false
+ - type: textarea
+ attributes:
+ label: Environment
+ description: |
+ examples:
+ - **npm**: 7.6.3
+ - **Node**: 13.14.0
+ - **OS**: Ubuntu 20.04
+ - **platform**: Macbook Pro
+ value: |
- npm:
- Node:
- OS:
- platform:
- validations:
- required: false
-
+ validations:
+ required: false
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 4c8e23f4..d4d25432 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -1,16 +1,17 @@
# This file is automatically added by @npmcli/template-oss. Do not edit.
version: 2
+
updates:
-- package-ecosystem: npm
- directory: "/"
- schedule:
- interval: daily
- allow:
- - dependency-type: direct
- versioning-strategy: increase
- commit-message:
- prefix: deps
- prefix-development: chore
- labels:
- - "Dependencies"
+ - package-ecosystem: npm
+ directory: "/"
+ schedule:
+ interval: daily
+ allow:
+ - dependency-type: direct
+ versioning-strategy: increase
+ commit-message:
+ prefix: deps
+ prefix-development: chore
+ labels:
+ - "Dependencies"
diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml
index 5f07e044..7797a00e 100644
--- a/.github/workflows/audit.yml
+++ b/.github/workflows/audit.yml
@@ -3,21 +3,40 @@
name: Audit
on:
+ workflow_dispatch: null
schedule:
# "At 01:00 on Monday" https://crontab.guru/#0_1_*_*_1
- cron: "0 1 * * 1"
- workflow_dispatch:
jobs:
audit:
- name: npm audit
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
- - uses: actions/setup-node@v2
+ - uses: actions/checkout@v3
+ - name: Setup git user
+ run: |
+ git config --global user.email "ops+npm-cli@npmjs.com"
+ git config --global user.name "npm cli ops bot"
+ - uses: actions/setup-node@v3
with:
- node-version: '16'
- - name: Install deps
- run: npm i --package-lock
- - name: Audit
- run: npm audit
+ node-version: 16.x
+ - name: Update to workable npm (windows)
+ # node 12 and 14 ship with npm@6, which is known to fail when updating itself in windows
+ if: matrix.platform.os == 'windows-latest' && (startsWith(matrix.node-version, '12') || startsWith(matrix.node-version, '14'))
+ run: |
+ curl -sO https://registry.npmjs.org/npm/-/npm-7.5.4.tgz
+ tar xf npm-7.5.4.tgz
+ cd package
+ node lib/npm.js install --no-fund --no-audit -g ..\npm-7.5.4.tgz
+ cd ..
+ rmdir /s /q package
+ - name: Update npm to 7
+ # If we do test on npm 10 it needs npm7
+ if: matrix.node-version <= 10
+ run: npm i --prefer-online --no-fund --no-audit -g npm@7
+ - name: Update npm to latest
+ if: matrix.node-version > 10
+ run: npm i --prefer-online --no-fund --no-audit -g npm@latest
+ - run: npm -v
+ - run: npm i --package-lock
+ - run: npm audit
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 175457e2..7669ba98 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -3,7 +3,10 @@
name: CI
on:
+ workflow_dispatch: null
pull_request:
+ branches:
+ - '*'
push:
branches:
- main
@@ -16,11 +19,32 @@ jobs:
lint:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
- - uses: actions/setup-node@v2
+ - uses: actions/checkout@v3
+ - name: Setup git user
+ run: |
+ git config --global user.email "ops+npm-cli@npmjs.com"
+ git config --global user.name "npm cli ops bot"
+ - uses: actions/setup-node@v3
with:
- node-version: '16'
- - run: npm i --prefer-online -g npm@latest
+ node-version: 16.x
+ - name: Update to workable npm (windows)
+ # node 12 and 14 ship with npm@6, which is known to fail when updating itself in windows
+ if: matrix.platform.os == 'windows-latest' && (startsWith(matrix.node-version, '12') || startsWith(matrix.node-version, '14'))
+ run: |
+ curl -sO https://registry.npmjs.org/npm/-/npm-7.5.4.tgz
+ tar xf npm-7.5.4.tgz
+ cd package
+ node lib/npm.js install --no-fund --no-audit -g ..\npm-7.5.4.tgz
+ cd ..
+ rmdir /s /q package
+ - name: Update npm to 7
+ # If we do test on npm 10 it needs npm7
+ if: matrix.node-version <= 10
+ run: npm i --prefer-online --no-fund --no-audit -g npm@7
+ - name: Update npm to latest
+ if: matrix.node-version > 10
+ run: npm i --prefer-online --no-fund --no-audit -g npm@latest
+ - run: npm -v
- run: npm i
- run: npm run lint
@@ -28,25 +52,35 @@ jobs:
strategy:
fail-fast: false
matrix:
- node-version: [12.13.0, 12.x, 14.15.0, 14.x, 16.13.0, 16.x]
+ node-version:
+ - 12.13.0
+ - 12.x
+ - 14.15.0
+ - 14.x
+ - 16.0.0
+ - 16.x
platform:
- - os: ubuntu-latest
- shell: bash
- - os: macos-latest
- shell: bash
- - os: windows-latest
- shell: cmd
+ - os: ubuntu-latest
+ shell: bash
+ - os: macos-latest
+ shell: bash
+ - os: windows-latest
+ shell: cmd
runs-on: ${{ matrix.platform.os }}
defaults:
run:
shell: ${{ matrix.platform.shell }}
steps:
- - uses: actions/checkout@v2
- - uses: actions/setup-node@v2
+ - uses: actions/checkout@v3
+ - name: Setup git user
+ run: |
+ git config --global user.email "ops+npm-cli@npmjs.com"
+ git config --global user.name "npm cli ops bot"
+ - uses: actions/setup-node@v3
with:
- node-version: ${{ matrix.node-version }}
- # node 12 and 14 ship with npm@6, which is known to fail when updating itself in windows
+ node-version: 16.x
- name: Update to workable npm (windows)
+ # node 12 and 14 ship with npm@6, which is known to fail when updating itself in windows
if: matrix.platform.os == 'windows-latest' && (startsWith(matrix.node-version, '12') || startsWith(matrix.node-version, '14'))
run: |
curl -sO https://registry.npmjs.org/npm/-/npm-7.5.4.tgz
@@ -55,7 +89,12 @@ jobs:
node lib/npm.js install --no-fund --no-audit -g ..\npm-7.5.4.tgz
cd ..
rmdir /s /q package
- - name: Update npm
+ - name: Update npm to 7
+ # If we do test on npm 10 it needs npm7
+ if: matrix.node-version <= 10
+ run: npm i --prefer-online --no-fund --no-audit -g npm@7
+ - name: Update npm to latest
+ if: matrix.node-version > 10
run: npm i --prefer-online --no-fund --no-audit -g npm@latest
- run: npm -v
- run: npm i
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index c7694c49..56cd7b9c 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -4,10 +4,14 @@ name: "CodeQL"
on:
push:
- branches: [ main ]
+ branches:
+ - main
+ - latest
pull_request:
# The branches below must be a subset of the branches above
- branches: [ main ]
+ branches:
+ - main
+ - latest
schedule:
# "At 03:00 on Monday" https://crontab.guru/#0_3_*_*_1
- cron: "0 3 * * 1"
@@ -24,15 +28,17 @@ jobs:
strategy:
fail-fast: false
matrix:
- language: [ 'javascript' ]
+ language: [ javascript ]
steps:
- - name: Checkout repository
- uses: actions/checkout@v2
-
- - name: Initialize CodeQL
- uses: github/codeql-action/init@v1
- with:
- languages: ${{ matrix.language }}
- - name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@v1
+ - uses: actions/checkout@v3
+ - name: Setup git user
+ run: |
+ git config --global user.email "ops+npm-cli@npmjs.com"
+ git config --global user.name "npm cli ops bot"
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v1
+ with:
+ languages: ${{ matrix.language }}
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v1
diff --git a/.github/workflows/post-dependabot.yml b/.github/workflows/post-dependabot.yml
index d3c12f40..ca91a05a 100644
--- a/.github/workflows/post-dependabot.yml
+++ b/.github/workflows/post-dependabot.yml
@@ -1,6 +1,7 @@
# This file is automatically added by @npmcli/template-oss. Do not edit.
-name: "Post Dependabot Actions"
+name: Post Dependabot Actions
+
on: pull_request
# https://docs.github.com/en/rest/overview/permissions-required-for-github-apps
@@ -10,27 +11,47 @@ permissions:
jobs:
Install:
runs-on: ubuntu-latest
- if: ${{ github.actor == 'dependabot[bot]' }}
+ if: github.actor == 'dependabot[bot]'
steps:
- - uses: actions/checkout@v2
- - uses: actions/setup-node@v2
+ - uses: actions/checkout@v3
+ - name: Setup git user
+ run: |
+ git config --global user.email "ops+npm-cli@npmjs.com"
+ git config --global user.name "npm cli ops bot"
+ - uses: actions/setup-node@v3
with:
- node-version: '16'
+ node-version: 16.x
+ - name: Update to workable npm (windows)
+ # node 12 and 14 ship with npm@6, which is known to fail when updating itself in windows
+ if: matrix.platform.os == 'windows-latest' && (startsWith(matrix.node-version, '12') || startsWith(matrix.node-version, '14'))
+ run: |
+ curl -sO https://registry.npmjs.org/npm/-/npm-7.5.4.tgz
+ tar xf npm-7.5.4.tgz
+ cd package
+ node lib/npm.js install --no-fund --no-audit -g ..\npm-7.5.4.tgz
+ cd ..
+ rmdir /s /q package
+ - name: Update npm to 7
+ # If we do test on npm 10 it needs npm7
+ if: matrix.node-version <= 10
+ run: npm i --prefer-online --no-fund --no-audit -g npm@7
+ - name: Update npm to latest
+ if: matrix.node-version > 10
+ run: npm i --prefer-online --no-fund --no-audit -g npm@latest
+ - run: npm -v
- name: Dependabot metadata
id: metadata
uses: dependabot/fetch-metadata@v1.1.1
with:
github-token: "${{ secrets.GITHUB_TOKEN }}"
- name: npm install and commit
- if: ${{contains(steps.metadata.outputs.dependency-names, '@npmcli/template-oss')}}
+ if: contains(steps.metadata.outputs.dependency-names, '@npmcli/template-oss')
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
- git config --local user.email "ops+npm-cli@npmjs.com"
- git config --local user.name "npm cli ops bot"
gh pr checkout ${{ github.event.pull_request.number }}
npm install --no-scripts
- npm run template-copy
+ npm run template-oss-apply
git add .
git commit -am "chore: postinstall for dependabot template-oss PR"
git push
diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml
index 948960e7..215850b7 100644
--- a/.github/workflows/pull-request.yml
+++ b/.github/workflows/pull-request.yml
@@ -4,19 +4,45 @@ name: Pull Request Linting
on:
pull_request:
- types: [opened, reopened, edited, synchronize]
+ types:
+ - opened
+ - reopened
+ - edited
+ - synchronize
jobs:
check:
name: Check PR Title or Commits
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
with:
fetch-depth: 0
- - uses: actions/setup-node@v2
+ - name: Setup git user
+ run: |
+ git config --global user.email "ops+npm-cli@npmjs.com"
+ git config --global user.name "npm cli ops bot"
+ - uses: actions/setup-node@v3
with:
- node-version: '16'
+ node-version: 16.x
+ - name: Update to workable npm (windows)
+ # node 12 and 14 ship with npm@6, which is known to fail when updating itself in windows
+ if: matrix.platform.os == 'windows-latest' && (startsWith(matrix.node-version, '12') || startsWith(matrix.node-version, '14'))
+ run: |
+ curl -sO https://registry.npmjs.org/npm/-/npm-7.5.4.tgz
+ tar xf npm-7.5.4.tgz
+ cd package
+ node lib/npm.js install --no-fund --no-audit -g ..\npm-7.5.4.tgz
+ cd ..
+ rmdir /s /q package
+ - name: Update npm to 7
+ # If we do test on npm 10 it needs npm7
+ if: matrix.node-version <= 10
+ run: npm i --prefer-online --no-fund --no-audit -g npm@7
+ - name: Update npm to latest
+ if: matrix.node-version > 10
+ run: npm i --prefer-online --no-fund --no-audit -g npm@latest
+ - run: npm -v
- name: Install deps
run: |
npm i -D @commitlint/cli @commitlint/config-conventional
@@ -24,4 +50,7 @@ jobs:
env:
PR_TITLE: ${{ github.event.pull_request.title }}
run: |
- 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
+ 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
diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml
index c5a165f3..34ebf789 100644
--- a/.github/workflows/release-please.yml
+++ b/.github/workflows/release-please.yml
@@ -6,19 +6,22 @@ on:
push:
branches:
- main
+ - latest
jobs:
release-please:
runs-on: ubuntu-latest
steps:
- - uses: google-github-actions/release-please-action@v2
+ - uses: google-github-actions/release-please-action@v3
id: release
with:
release-type: node
- # If you change changelog-types be sure to also update commitlintrc.js
+ changelog-type: github
changelog-types: >
- [{"type":"feat","section":"Features","hidden":false},
- {"type":"fix","section":"Bug Fixes","hidden":false},
- {"type":"docs","section":"Documentation","hidden":false},
- {"type":"deps","section":"Dependencies","hidden":false},
- {"type":"chore","hidden":true}]
+ [
+ {"type":"feat","section":"Features","hidden":false},
+ {"type":"fix","section":"Bug Fixes","hidden":false},
+ {"type":"docs","section":"Documentation","hidden":false},
+ {"type":"deps","section":"Dependencies","hidden":false},
+ {"type":"chore","hidden":true}
+ ]
diff --git a/.gitignore b/.gitignore
index 6ed44c72..bf011b1d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,20 +4,22 @@
/*
# keep these
-!/.commitlintrc.js
-!/.npmrc
-!/.eslintrc*
-!/.github
+!/.eslintrc.local.*
!**/.gitignore
-!/package.json
-!/docs
-!/bin
-!/lib
+!/docs/
+!/tap-snapshots/
+!/test/
!/map.js
-!/tap-snapshots
-!/test
-!/scripts
+!/scripts/
!/README*
!/LICENSE*
-!/SECURITY*
!/CHANGELOG*
+!/.commitlintrc.js
+!/.eslintrc.js
+!/.github/
+!/.gitignore
+!/.npmrc
+!/SECURITY.md
+!/bin/
+!/lib/
+!/package.json
diff --git a/.npmrc b/.npmrc
index 878b7dde..529f93e7 100644
--- a/.npmrc
+++ b/.npmrc
@@ -1,3 +1,3 @@
-;This file is automatically added by @npmcli/template-oss. Do not edit.
+; This file is automatically added by @npmcli/template-oss. Do not edit.
package-lock=false
diff --git a/README.md b/README.md
index 06a244e9..1a423510 100644
--- a/README.md
+++ b/README.md
@@ -7,106 +7,115 @@ single devDependency.
### Configuration
-Configure the use of `template-oss` in the root `package.json`.
+Configure the use of `@npmcli/template-oss` in your `package.json` using the
+`templateOSS` property.
+
```js
{
name: 'my-package',
- // ...
templateOSS: {
// copy repo specific files for the root pkg
- applyRootRepoFiles: true,
+ rootRepo: true,
// modify package.json and copy module specific files for the root pkg
- applyRootModuleFiles: true,
- // copy repo files for each whitelisted workspaces
- applyWorkspaceRepoFiles: true,
- // whitelist workspace by package name to modify package.json
- // and copy module files
+ rootModule: true,
+ // copy repo files for all workspaces
+ workspaceRepo: true,
+ // copy module files for all workspaces
+ workspaceModule: true,
+ // filter allowed workspaces by package name
+ // defaults to all workspaces
workspaces: ['workspace-package-name'],
- version: '2.3.1'
+ // The rest of the config is passed in as variables
+ // that can be used to template files in the content
+ // directory. Some common ones are:
+ // Turns off ci in windows
+ windowsCI: false,
+ // Change the versions tested in CI and engines
+ ciVersions: ['10', '12', '14']
}
}
+```
-### `package.json` patches
+#### Workspaces
-These fields will be set in the project's `package.json`:
+Individual workspaces can also supply their own config, if they are included by
+the root package's `templateOSS.workspaces` array. These settings will override
+any of the same settings in the root.
```js
{
- author: 'GitHub Inc.',
- files: ['bin', 'lib'],
- license: 'ISC',
- templateVersion: $TEMPLATE_VERSION,
- scripts: {
- lint: 'eslint "**/*.js"',
- postlint: 'npm-template-check',
- lintfix: 'npm run lint -- --fix',
- 'template-copy': 'npm-template-copy --force',
- preversion: 'npm test',
- postversion: 'npm publish',
- prepublishOnly: 'git push origin --follow-tags',
- snap: 'tap',
- test: 'tap',
- posttest: 'npm run lint',
- },
- engines: {
- node: '^12.13.0 || ^14.15.0 || >=16',
- },
+ name: 'my-workspace',
+ templateOSS: {
+ // copy repo files for this workspace
+ workspaceRepo: true,
+ // copy module files for this workspace
+ moduleRepo: true,
+ // Changes windowsCI setting for this workspace
+ windowsCI: false,
+ }
}
```
-The `"templateVersion"` field will be set to the version of this package being
-installed. This is used to determine if the postinstall script should take any
-action.
+### Content
+
+All the templated content for this repo lives in
+[`lib/content/`](./lib/content/). The `index.js`[./lib/content/index.js] file
+controls how and where this content is written.
+
+Content files can be overwritten or merged with the existing target file.
+Currently mergining is only supported for `package.json` files.
-#### Extending
+Each content file goes through the following pipeline:
-The `changes` constant located in `lib/postinstall/update-package.js` should contain
-all patches for the `package.json` file. Be sure to correctly expand any object/array
-based values with the original package content.
+1. It is read from its source location
+1. It is are templated using Handlebars with the variables from each packages's
+ config (with some derived values generated in [`config.js`](./lib/config.js)
+1. It is parsed based on its file extension in
+ [`parser.js`](./lib/util/parser.js)
+1. Additional logic is applied by the parser
+1. It is written to its target location
-### Static files
+### Usage
-Any existing `.eslintrc.*` files will be removed, unless they also match the
-pattern `.eslintrc.local.*`
+This package provides two bin scripts:
-These files will be copied, overwriting any existing files:
+#### `template-oss-check`
-- `.eslintrc.js`
-- `.github/workflows/ci.yml`
-- `.github/ISSUE_TEMPLATE/bug.yml`
-- `.github/ISSUE_TEMPLATE/config.yml`
-- `.github/CODEOWNERS`
-- `.gitignore`
-- `LICENSE.md`
-- `SECURITY.md`
+This will check if any of the applied files different from the target content,
+or if any of the other associated checks fail. The diffs of each file or check
+will be reported with instructions on how to fix it.
-### Dynamic Files
+#### `template-oss-apply [--force]`
-Currently, the only dynamic file generated is a github workflow for a given workspace.
-`.github/workflows/ci-$$package-name$$.yml`
+This will write all source files to their target locations in the cwd. It will
+do nothing if `package.json#templateOSS.version` is the same as the version
+being run. This can be overridden by `--force`.
-#### Extending
+This is the script that is run on `postinsall`.
-Place files in the `lib/content/` directory, use only the file name and remove
-any leading `.` characters (i.e. `.github/workflows/ci.yml` becomes `ci.yml`
-and `.gitignore` becomes `gitignore`).
+### Extending
-Modify the `repoFiles` and `moduleFiles` objects at the top of `lib/postinstall/copy-content.js` to include
-your new file. The object keys are destination paths, and values are source.
+#### `lib/apply`
-### `package.json` checks
+This directory is where all the logic for applying files lives. It should be
+possible to add new files without modifying anything in this directory. To add a
+file, add the templated file to `lib/content/$FILENAME` and add entry for it in
+`lib/content/index.js` depending on where and when it should be written (root vs
+workspace, repo vs module, add vs remove, etc).
-`npm-template-check` is run by `postlint` and will error if the `package.json`
-is not configured properly, with steps to run to correct any problems.
+#### `lib/check`
-### Manual copy
+All checks live in this directory and have the same signature. A check must be
+added to `lib/check/index.js` for it to be run.
-Template files will be copied automatically when `template-oss` is updated.
-You can force an update with `npm run template-copy`.
+#### Generic vs specific extensions
-#### Extending
+This repo is designed so that all (fine, most) of the logic in `lib/` is generic
+and could be applied across projects of many different types.
-Add any unwanted packages to `unwantedPackages` in `lib/check.js`. Currently
-the best way to install any packages is to include them as `peerDependencies`
-in this repo.
+The files in `lib/content` are extremely specific to the npm CLI. It would be
+trivial to swap out this content directory for a different one as it is only
+referenced in a single place in `lib/config.js`. However, it's not currently
+possible to change this value at runtime, but that might become possible in
+future versions of this package.
diff --git a/bin/.gitattributes b/bin/.gitattributes
deleted file mode 100644
index 2d6e55eb..00000000
--- a/bin/.gitattributes
+++ /dev/null
@@ -1,3 +0,0 @@
-# Dont modify line endings of our bin scripts
-# so git status stays clean for windows tests
-*.js -crlf
diff --git a/bin/apply.js b/bin/apply.js
new file mode 100755
index 00000000..2405a3e0
--- /dev/null
+++ b/bin/apply.js
@@ -0,0 +1,22 @@
+#!/usr/bin/env node
+
+const apply = require('../lib/apply/index.js')
+
+const main = async () => {
+ const {
+ npm_config_global: globalMode,
+ npm_config_local_prefix: root,
+ } = process.env
+
+ // do nothing in global mode or when the local prefix isn't set
+ if (globalMode === 'true' || !root) {
+ return
+ }
+
+ await apply(root)
+}
+
+module.exports = main().catch((err) => {
+ console.error(err.stack)
+ process.exitCode = 1
+})
diff --git a/bin/check.js b/bin/check.js
new file mode 100755
index 00000000..8b3417cb
--- /dev/null
+++ b/bin/check.js
@@ -0,0 +1,26 @@
+#!/usr/bin/env node
+
+const check = require('../lib/check/index.js')
+const output = require('../lib/util/output.js')
+
+const main = async () => {
+ const {
+ npm_config_local_prefix: root,
+ } = process.env
+
+ if (!root) {
+ throw new Error('This package requires npm >7.21.1')
+ }
+
+ const problems = await check(root)
+
+ if (problems.length) {
+ process.exitCode = 1
+ console.error(output(problems))
+ }
+}
+
+module.exports = main().catch((err) => {
+ console.error(err.stack)
+ process.exitCode = 1
+})
diff --git a/bin/npm-template-check.js b/bin/npm-template-check.js
deleted file mode 100755
index 0f9b2599..00000000
--- a/bin/npm-template-check.js
+++ /dev/null
@@ -1,44 +0,0 @@
-#!/usr/bin/env node
-
-const checkPackage = require('../lib/postlint/check-package.js')
-const checkGitIgnore = require('../lib/postlint/check-gitignore.js')
-const getConfig = require('../lib/config.js')
-
-const main = async () => {
- const {
- npm_config_local_prefix: root,
- } = process.env
-
- if (!root) {
- throw new Error('This package requires npm >7.21.1')
- }
-
- const config = await getConfig(root)
-
- const problemSets = []
- for (const path of config.paths) {
- if (path !== root || config.applyRootModuleFiles) {
- problemSets.push(await checkPackage(path))
- }
- if (path !== root || config.applyRootRepoFiles) {
- problemSets.push(await checkGitIgnore(path))
- }
- }
-
- const problems = problemSets.flat()
-
- if (problems.length) {
- console.error('Some problems were detected:')
- console.error()
- console.error(problems.map((problem) => problem.message).join('\n'))
- console.error()
- console.error('To correct them:')
- console.error(problems.map((problem) => problem.solution).join('\n'))
- process.exitCode = 1
- }
-}
-
-module.exports = main().catch((err) => {
- console.error(err.stack)
- process.exitCode = 1
-})
diff --git a/bin/postinstall.js b/bin/postinstall.js
deleted file mode 100755
index 0022257d..00000000
--- a/bin/postinstall.js
+++ /dev/null
@@ -1,31 +0,0 @@
-#!/usr/bin/env node
-
-const copyContent = require('../lib/postinstall/copy-content.js')
-const patchPackage = require('../lib/postinstall/update-package.js')
-const getConfig = require('../lib/config.js')
-
-const main = async () => {
- const {
- npm_config_global: globalMode,
- npm_config_local_prefix: root,
- } = process.env
-
- // do nothing in global mode or when the local prefix isn't set
- if (globalMode === 'true' || !root) {
- return
- }
-
- const config = await getConfig(root)
- for (const path of config.paths) {
- if (!await patchPackage(path, root, config)) {
- continue
- }
-
- await copyContent(path, root, config)
- }
-}
-
-module.exports = main().catch((err) => {
- console.error(err.stack)
- process.exitCode = 1
-})
diff --git a/lib/apply/apply-files.js b/lib/apply/apply-files.js
new file mode 100644
index 00000000..383e2380
--- /dev/null
+++ b/lib/apply/apply-files.js
@@ -0,0 +1,31 @@
+const fs = require('@npmcli/fs')
+const log = require('proc-log')
+const { rmEach, parseEach } = require('../util/files.js')
+
+const run = async (dir, files, options) => {
+ const { rm = [], add = {} } = files
+
+ log.verbose('apply-files', 'rm', rm)
+ await rmEach(dir, rm, options, (f) => fs.rm(f))
+
+ log.verbose('apply-files', 'add', add)
+ await parseEach(dir, add, options, (p) => p.applyWrite())
+}
+
+module.exports = [{
+ run: (options) => run(
+ options.config.repoDir,
+ options.config.repoFiles,
+ options
+ ),
+ when: ({ config: c }) => c.isForce || (c.needsUpdate && c.applyRepo),
+ name: 'apply-repo',
+}, {
+ run: (options) => run(
+ options.config.moduleDir,
+ options.config.moduleFiles,
+ options
+ ),
+ when: ({ config: c }) => c.isForce || (c.needsUpdate && c.applyModule),
+ name: 'apply-module',
+}]
diff --git a/lib/apply/index.js b/lib/apply/index.js
new file mode 100644
index 00000000..4917332b
--- /dev/null
+++ b/lib/apply/index.js
@@ -0,0 +1,5 @@
+const run = require('../index.js')
+
+module.exports = (root, content) => run(root, content, [
+ require('./apply-files.js'),
+])
diff --git a/lib/check/check-apply.js b/lib/check/check-apply.js
new file mode 100644
index 00000000..aeae7b54
--- /dev/null
+++ b/lib/check/check-apply.js
@@ -0,0 +1,73 @@
+const log = require('proc-log')
+const { relative, basename } = require('path')
+const { rmEach, parseEach } = require('../util/files.js')
+const { partition } = require('lodash')
+
+const solution = 'npx template-oss-apply --force'
+
+const run = async (type, dir, files, options) => {
+ const res = []
+ const rel = (f) => relative(options.root, f)
+ const { add: addFiles = {}, rm: rmFiles = [] } = files
+
+ const rm = await rmEach(dir, rmFiles, options, (f) => rel(f))
+ const [add, update] = partition(await parseEach(dir, addFiles, options, async (p) => {
+ const diff = await p.applyDiff()
+ const target = rel(p.target)
+ if (diff === null) {
+ // needs to be added
+ return target
+ } else if (diff === true) {
+ // its ok, no diff, this is filtered out
+ return null
+ }
+ return { file: target, diff }
+ }), (d) => typeof d === 'string')
+
+ log.verbose('check-apply', 'rm', rm)
+ if (rm.length) {
+ res.push({
+ title: `The following ${type} files need to be deleted:`,
+ body: rm,
+ solution,
+ })
+ }
+
+ log.verbose('check-apply', 'add', add)
+ if (add.length) {
+ res.push({
+ title: `The following ${type} files need to be added:`,
+ body: add,
+ solution,
+ })
+ }
+
+ log.verbose('check-apply', 'update', update)
+ res.push(...update.map(({ file, diff }) => ({
+ title: `The ${type} file ${basename(file)} needs to be updated:`,
+ body: [`${file}\n${'='.repeat(40)}\n${diff}`],
+ solution,
+ })))
+
+ return res
+}
+
+module.exports = [{
+ run: (options) => run(
+ 'repo',
+ options.config.repoDir,
+ options.config.repoFiles,
+ options
+ ),
+ when: ({ config: c }) => c.applyRepo,
+ name: 'check-repo',
+}, {
+ run: (options) => run(
+ 'module',
+ options.config.moduleDir,
+ options.config.moduleFiles,
+ options
+ ),
+ when: ({ config: c }) => c.applyModule,
+ name: 'check-module',
+}]
diff --git a/lib/check/check-changelog.js b/lib/check/check-changelog.js
new file mode 100644
index 00000000..dc603ec9
--- /dev/null
+++ b/lib/check/check-changelog.js
@@ -0,0 +1,31 @@
+const fs = require('@npmcli/fs')
+const { EOL } = require('os')
+const { join, relative } = require('path')
+
+const run = async ({ root, path }) => {
+ // XXX: our changelogs are always markdown
+ // but they could be other extensions so
+ // make this glob for possible matches
+ const changelog = join(path, 'CHANGELOG.md')
+
+ if (await fs.exists(changelog)) {
+ const content = await fs.readFile(changelog, { encoding: 'utf8' })
+ const mustStart = `# Changelog${EOL}${EOL}#`
+ if (!content.startsWith(mustStart)) {
+ return {
+ title: `The ${relative(root, changelog)} is incorrect:`,
+ body: [
+ 'The changelog should start with',
+ `"${mustStart}"`,
+ ],
+ solution: 'reformat the changelog to have the correct heading',
+ }
+ }
+ }
+}
+
+module.exports = {
+ run,
+ when: ({ config: c }) => c.applyModule,
+ name: 'check-changelog',
+}
diff --git a/lib/check/check-gitignore.js b/lib/check/check-gitignore.js
new file mode 100644
index 00000000..5d39248a
--- /dev/null
+++ b/lib/check/check-gitignore.js
@@ -0,0 +1,67 @@
+const log = require('proc-log')
+const { EOL } = require('os')
+const { resolve, relative, basename } = require('path')
+const fs = require('@npmcli/fs')
+const git = require('@npmcli/git')
+
+const NAME = 'check-gitignore'
+
+// The problem we are trying to solve is when a new .gitignore file
+// is copied into an existing repo, there could be files already checked in
+// to git that are now ignored by new gitignore rules. We want to warn
+// about these files.
+const run = async ({ root, path, config }) => {
+ log.verbose(NAME, { root, path })
+
+ const relativeToRoot = (f) => relative(root, resolve(path, f))
+
+ // use the root to detect a git repo but the project directory (path) for the
+ // ignore check
+ const ignoreFile = resolve(path, '.gitignore')
+ if (!await git.is({ cwd: root }) || !await fs.exists(ignoreFile)) {
+ log.verbose(NAME, 'no git or no gitignore')
+ return null
+ }
+
+ log.verbose(NAME, `using ignore file ${ignoreFile}`)
+
+ const res = await git.spawn([
+ 'ls-files',
+ '--cached',
+ '--ignored',
+ // https://git-scm.com/docs/git-ls-files#_exclude_patterns
+ `--${config.isRoot ? 'exclude-from' : 'exclude-per-directory'}=${basename(ignoreFile)}`,
+ ], { cwd: path })
+
+ log.verbose(NAME, 'ls-files', res)
+
+ // TODO: files should be filtered if they have already been moved/deleted
+ // but not committed. Currently you must commit for this check to pass.
+ const files = res.stdout
+ .trim()
+ .split('\n')
+ .filter(Boolean)
+
+ if (!files.length) {
+ return null
+ }
+
+ const ignores = (await fs.readFile(ignoreFile))
+ .toString()
+ .split(EOL)
+ .filter((l) => l && !l.trim().startsWith('#'))
+
+ const relIgnore = relativeToRoot(ignoreFile)
+
+ return {
+ title: `The following files are tracked by git but matching a pattern in ${relIgnore}:`,
+ body: files.map(relativeToRoot),
+ solution: ['move files to not match one of the following patterns:', ...ignores],
+ }
+}
+
+module.exports = {
+ run,
+ when: ({ config: c }) => c.applyModule,
+ name: NAME,
+}
diff --git a/lib/check/check-required.js b/lib/check/check-required.js
new file mode 100644
index 00000000..60feea3e
--- /dev/null
+++ b/lib/check/check-required.js
@@ -0,0 +1,36 @@
+const hasPackage = require('../util/has-package.js')
+const { groupBy } = require('lodash')
+
+const run = ({ pkg, config: { requiredPackages = {} } }) => {
+ // ensure required packages are present in the correct place
+
+ const mustHave = Object.entries(requiredPackages).flatMap(([location, pkgs]) =>
+ // first make a flat array of {name, version, location}
+ Object.entries(pkgs).map(([name, version]) => ({
+ name,
+ version,
+ location,
+ })))
+ .filter(({ name, version, location }) => !hasPackage(pkg, name, version, [location]))
+
+ if (mustHave.length) {
+ return Object.entries(groupBy(mustHave, 'location')).map(([location, specs]) => {
+ const rm = specs.map(({ name }) => name).join(' ')
+ const install = specs.map(({ name, version }) => `${name}@${version}`).join(' ')
+ const installLocation = hasPackage.flags[location]
+
+ return {
+ title: `The following required ${location} were not found:`,
+ body: specs.map(({ name, version }) => `${name}@${version}`),
+ // solution is to remove any existing all at once but add back in by --save-
+ solution: [`npm rm ${rm}`, `npm i ${install} ${installLocation}`].join(' && '),
+ }
+ })
+ }
+}
+
+module.exports = {
+ run,
+ when: ({ config: c }) => c.applyModule,
+ name: 'check-required-packages',
+}
diff --git a/lib/check/check-unwanted.js b/lib/check/check-unwanted.js
new file mode 100644
index 00000000..13eb27fb
--- /dev/null
+++ b/lib/check/check-unwanted.js
@@ -0,0 +1,23 @@
+
+const hasPackage = require('../util/has-package.js')
+
+const run = ({ pkg, config: { allowedPackages = [], unwantedPackages = [] } }) => {
+ // ensure packages that should not be present are removed
+ const hasUnwanted = unwantedPackages
+ .filter((name) => !allowedPackages.includes(name))
+ .filter((name) => hasPackage(pkg, name))
+
+ if (hasUnwanted.length) {
+ return {
+ title: 'The following unwanted packages were found:',
+ body: hasUnwanted,
+ solution: `npm rm ${hasUnwanted.join(' ')}`,
+ }
+ }
+}
+
+module.exports = {
+ run,
+ when: ({ config: c }) => c.applyModule,
+ name: 'check-unwanted-packages',
+}
diff --git a/lib/check/index.js b/lib/check/index.js
new file mode 100644
index 00000000..b3a24345
--- /dev/null
+++ b/lib/check/index.js
@@ -0,0 +1,9 @@
+const run = require('../index.js')
+
+module.exports = (root, content) => run(root, content, [
+ require('./check-apply.js'),
+ require('./check-required.js'),
+ require('./check-unwanted.js'),
+ require('./check-gitignore.js'),
+ require('./check-changelog.js'),
+])
diff --git a/lib/config.js b/lib/config.js
index d378ec2e..83465259 100644
--- a/lib/config.js
+++ b/lib/config.js
@@ -1,51 +1,162 @@
-const PackageJson = require('@npmcli/package-json')
-const mapWorkspaces = require('@npmcli/map-workspaces')
-
-const defaultConfig = {
- applyRootRepoFiles: true,
- applyWorkspaceRepoFiles: true,
- applyRootModuleFiles: true,
- workspaces: [],
- paths: [],
- force: false,
+const { relative, dirname, posix, win32 } = require('path')
+const log = require('proc-log')
+const { uniq, defaults } = require('lodash')
+const parseCIVersions = require('./util/parse-ci-versions.js')
+const getGitUrl = require('./util/get-git-url.js')
+const { name: NAME, version: LATEST_VERSION } = require('../package.json')
+
+const CONFIG_KEY = 'templateOSS'
+const getPkgConfig = (pkg) => pkg[CONFIG_KEY] || {}
+
+const getContent = (contentPath) => {
+ if (typeof contentPath === 'string') {
+ return defaults(require(contentPath), {
+ sourceDir: dirname(require.resolve(contentPath)),
+ })
+ } else {
+ // allow passing in content directly for tests
+ return contentPath
+ }
+}
+
+// falsy means no content of this type
+const getFiles = (config, content) => config ? content : null
+const getFileKeys = (files) => files ? Object.keys(files.add || {}) : []
+const negatePath = (p) => {
+ // XXX: this negates the first part of each path for the gitignore
+ // files. it might make sense to negate more specific portions of the
+ // path for some paths like workspaces. so instead of ignoring !/workspaces
+ // it would only ignore !/workspaces/a, !/workspaces/b, etc
+ const [first, ...parts] = p.split(posix.sep)
+ const isDir = parts.length > 0
+ return `!${posix.sep}${first}${isDir ? posix.sep : ''}`
}
-module.exports = async (root) => {
- let pkg
- let pkgError = false
- try {
- pkg = (await PackageJson.load(root)).content
- } catch (e) {
- pkgError = true
+const makePosix = (str) => str.split(win32.sep).join(posix.sep)
+
+const getConfig = async ({
+ pkgs,
+ workspaces,
+ root,
+ path,
+ pkg,
+ // default content path is looked up via require.resolve
+ // so use the name of this module since package.json#main
+ // points to the content dir
+ content: contentPath = NAME,
+ config: {
+ rootRepo,
+ rootModule,
+ workspaceRepo,
+ workspaceModule,
+ version,
+ ...pkgContent
+ },
+}) => {
+ const isRoot = root === path
+ const isLatest = version === LATEST_VERSION
+ const isDogFood = pkg.name === NAME
+
+ // this is written to ci yml files so it needs to always use posix
+ const pkgRelPath = makePosix(relative(root, path))
+ const gitUrl = await getGitUrl(root)
+
+ const {
+ rootRepo: rootRepoContent,
+ rootModule: rootModuleContent,
+ workspaceRepo: workspaceRepoContent,
+ workspaceModule: workspaceModuleContent,
+ ...baseContent
+ } = getContent(contentPath)
+
+ let repoFiles, moduleFiles
+ const ignorePaths = []
+
+ if (isRoot) {
+ repoFiles = getFiles(rootRepo, rootRepoContent)
+ moduleFiles = getFiles(rootModule, rootModuleContent)
+ ignorePaths.push(
+ // allow workspace paths if they are set, this is directly from
+ // map-workspaces so normalize to posix paths for gitignore
+ ...workspaces.map((p) => makePosix(relative(root, p))),
+ // allow both the repo and module files since this is the root
+ ...getFileKeys(repoFiles),
+ ...getFileKeys(moduleFiles),
+ // allow all workspace repo level files
+ ...pkgs.filter((p) => p.path !== path).flatMap((p) =>
+ getFileKeys(getFiles(p.config.workspaceRepo, workspaceRepoContent))
+ )
+ )
+ } else {
+ repoFiles = getFiles(workspaceRepo, workspaceRepoContent)
+ moduleFiles = getFiles(workspaceModule, workspaceModuleContent)
+ // In a workspace gitignores are relative to the workspace dir
+ // so we should only allow added module files
+ ignorePaths.push(...getFileKeys(moduleFiles))
}
- if (pkgError || !pkg.templateOSS) {
- return {
- ...defaultConfig,
- paths: [root],
- }
+
+ // all derived keys
+ const derived = {
+ isRoot,
+ isWorkspace: !isRoot,
+ // repo
+ repoDir: root,
+ repoFiles,
+ applyRepo: !!repoFiles,
+ // module
+ moduleDir: path,
+ moduleFiles,
+ applyModule: !!moduleFiles,
+ // package
+ pkgName: pkg.name,
+ pkgNameFs: pkg.name.replace(/\//g, '-').replace(/@/g, ''),
+ pkgRelPath: pkgRelPath,
+ // force changes if we are dogfooding this repo or with force argv
+ // XXX: setup proper cli arg parsing
+ isForce: isDogFood || process.argv.includes('--force'),
+ isLatest,
+ needsUpdate: !isLatest,
+ // templateoss specific values
+ __NAME__: NAME,
+ __CONFIG_KEY__: CONFIG_KEY,
+ __VERSION__: LATEST_VERSION,
+ __DOGFOOD__: isDogFood,
}
- const config = {
- ...defaultConfig,
- ...pkg.templateOSS,
+
+ // merge the rest of base and pkg content to make the
+ // full content object
+ const content = { ...baseContent, ...pkgContent }
+
+ // set some defaults on content that can be overwritten unlike
+ // derived values which are calculated from other config
+ const contentDefaults = {}
+
+ if (Array.isArray(content.ciVersions)) {
+ const parsed = parseCIVersions(content.ciVersions)
+ contentDefaults.engines = parsed.engines
+ content.ciVersions = parsed.targets
+ log.verbose('config ci', parsed)
}
- const workspaceMap = await mapWorkspaces({
- pkg,
- cwd: root,
- })
- const wsPaths = []
- const workspaceSet = new Set(config.workspaces)
- for (const [name, path] of workspaceMap.entries()) {
- if (workspaceSet.has(name)) {
- wsPaths.push(path)
+
+ if (gitUrl) {
+ contentDefaults.repository = {
+ type: 'git',
+ url: gitUrl,
+ ...(pkgRelPath ? { directory: pkgRelPath } : {}),
}
}
- config.workspacePaths = wsPaths
-
- config.paths = config.paths.concat(config.workspacePaths)
- config.paths.push(root)
+ contentDefaults.ignorePaths = uniq(
+ [...ignorePaths, ...(content.distPaths || [])].map(negatePath)
+ ).sort()
- config.force = process.argv.indexOf('--force') !== -1
+ log.verbose('config', 'defaults', contentDefaults)
- return config
+ return {
+ ...defaults(content, contentDefaults),
+ ...derived,
+ }
}
+
+module.exports = getConfig
+module.exports.getPkgConfig = getPkgConfig
diff --git a/lib/content/CODEOWNERS b/lib/content/CODEOWNERS
index ef874313..3bee8753 100644
--- a/lib/content/CODEOWNERS
+++ b/lib/content/CODEOWNERS
@@ -1 +1 @@
-* @npm/cli-team
+* @npm/cli-team
diff --git a/lib/content/LICENSE.md b/lib/content/LICENSE.md
index 5fc208ff..845be76f 100644
--- a/lib/content/LICENSE.md
+++ b/lib/content/LICENSE.md
@@ -1,5 +1,3 @@
-
-
ISC License
Copyright npm, Inc.
diff --git a/lib/content/SECURITY.md b/lib/content/SECURITY.md
index a93106d0..41b76c24 100644
--- a/lib/content/SECURITY.md
+++ b/lib/content/SECURITY.md
@@ -1,3 +1 @@
-
-
Please send vulnerability reports through [hackerone](https://hackerone.com/github).
diff --git a/lib/content/audit.yml b/lib/content/audit.yml
index 5f07e044..ccd4421e 100644
--- a/lib/content/audit.yml
+++ b/lib/content/audit.yml
@@ -1,23 +1,16 @@
-# This file is automatically added by @npmcli/template-oss. Do not edit.
-
name: Audit
on:
+ workflow_dispatch:
schedule:
# "At 01:00 on Monday" https://crontab.guru/#0_1_*_*_1
- cron: "0 1 * * 1"
- workflow_dispatch:
jobs:
audit:
- name: npm audit
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
- - uses: actions/setup-node@v2
- with:
- node-version: '16'
- - name: Install deps
- run: npm i --package-lock
- - name: Audit
- run: npm audit
+ {{> setupGit}}
+ {{> setupNode }}
+ - run: npm i --package-lock
+ - run: npm audit
diff --git a/lib/content/bug.yml b/lib/content/bug.yml
index fa80b2de..8803685f 100644
--- a/lib/content/bug.yml
+++ b/lib/content/bug.yml
@@ -1,54 +1,53 @@
-# This file is automatically added by @npmcli/template-oss. Do not edit.
-
name: Bug
description: File a bug/issue
title: "[BUG] "
-labels: [Bug, Needs Triage]
+labels: [ Bug, Needs Triage ]
+
body:
-- type: checkboxes
- attributes:
- label: Is there an existing issue for this?
- description: Please [search here](./issues) to see if an issue already exists for your problem.
- options:
- - label: I have searched the existing issues
- required: true
-- type: textarea
- attributes:
- label: Current Behavior
- description: A clear & concise description of what you're experiencing.
- validations:
- required: false
-- type: textarea
- attributes:
- label: Expected Behavior
- description: A clear & concise description of what you expected to happen.
- validations:
- required: false
-- type: textarea
- attributes:
- label: Steps To Reproduce
- description: Steps to reproduce the behavior.
- value: |
- 1. In this environment...
- 2. With this config...
- 3. Run '...'
- 4. See error...
- validations:
- required: false
-- type: textarea
- attributes:
- label: Environment
- description: |
- examples:
- - **npm**: 7.6.3
- - **Node**: 13.14.0
- - **OS**: Ubuntu 20.04
- - **platform**: Macbook Pro
- value: |
+ - type: checkboxes
+ attributes:
+ label: Is there an existing issue for this?
+ description: Please [search here](./issues) to see if an issue already exists
+ for your problem.
+ options:
+ - label: I have searched the existing issues
+ required: true
+ - type: textarea
+ attributes:
+ label: Current Behavior
+ description: A clear & concise description of what you're experiencing.
+ validations:
+ required: false
+ - type: textarea
+ attributes:
+ label: Expected Behavior
+ description: A clear & concise description of what you expected to happen.
+ validations:
+ required: false
+ - type: textarea
+ attributes:
+ label: Steps To Reproduce
+ description: Steps to reproduce the behavior.
+ value: |
+ 1. In this environment...
+ 2. With this config...
+ 3. Run '...'
+ 4. See error...
+ validations:
+ required: false
+ - type: textarea
+ attributes:
+ label: Environment
+ description: |
+ examples:
+ - **npm**: 7.6.3
+ - **Node**: 13.14.0
+ - **OS**: Ubuntu 20.04
+ - **platform**: Macbook Pro
+ value: |
- npm:
- Node:
- OS:
- platform:
- validations:
- required: false
-
+ validations:
+ required: false
diff --git a/lib/content/ci-no-windows.yml b/lib/content/ci-no-windows.yml
deleted file mode 100644
index 438b7686..00000000
--- a/lib/content/ci-no-windows.yml
+++ /dev/null
@@ -1,48 +0,0 @@
-# This file is automatically added by @npmcli/template-oss. Do not edit.
-
-name: CI
-
-on:
- pull_request:
- push:
- branches:
- - main
- - latest
- schedule:
- # "At 02:00 on Monday" https://crontab.guru/#0_1_*_*_1
- - cron: "0 2 * * 1"
-
-jobs:
- lint:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v2
- - uses: actions/setup-node@v2
- with:
- node-version: '16'
- - run: npm i --prefer-online -g npm@latest
- - run: npm i
- - run: npm run lint
-
- test:
- strategy:
- fail-fast: false
- matrix:
- node-version: [12.13.0, 12.x, 14.15.0, 14.x, 16.13.0, 16.x]
- platform:
- - os: ubuntu-latest
- shell: bash
- - os: macos-latest
- shell: bash
- runs-on: ${{ matrix.platform.os }}
- defaults:
- run:
- shell: ${{ matrix.platform.shell }}
- steps:
- - uses: actions/checkout@v2
- - uses: actions/setup-node@v2
- with:
- node-version: ${{ matrix.node-version }}
- - run: npm i --prefer-online -g npm@latest
- - run: npm i
- - run: npm test --ignore-scripts
diff --git a/lib/content/ci-workspace.yml b/lib/content/ci-workspace.yml
deleted file mode 100644
index c6e93641..00000000
--- a/lib/content/ci-workspace.yml
+++ /dev/null
@@ -1,63 +0,0 @@
-name: Node Workspace CI %%pkgname%%
-
-on:
- pull_request:
- paths:
- - %%pkgpath%%/**
- push:
- paths:
- - %%pkgpath%%/**
- branches:
- - release-next
- - latest
- workflow_dispatch:
-
-jobs:
- lint:
- runs-on: ubuntu-latest
- steps:
- # Checkout the npm/cli repo
- - uses: actions/checkout@v2
- - uses: actions/setup-node@v2
- with:
- node-version: '16'
- - run: npm i --prefer-online -g npm@latest
- - run: npm i
- - run: npm run lint -w %%pkgpath%%
-
- test:
- strategy:
- fail-fast: false
- matrix:
- node-version: [12.13.0, 12.x, 14.15.0, 14.x, 16.13.0, 16.x]
- platform:
- - os: ubuntu-latest
- shell: bash
- - os: macos-latest
- shell: bash
- - os: windows-latest
- shell: cmd
- runs-on: ${{ matrix.platform.os }}
- defaults:
- run:
- shell: ${{ matrix.platform.shell }}
- steps:
- - uses: actions/checkout@v2
- - uses: actions/setup-node@v2
- with:
- node-version: ${{ matrix.node-version }}
- # node 12 and 14 ship with npm@6, which is known to fail when updating itself in windows
- - name: Update to workable npm (windows)
- if: matrix.platform.os == 'windows-latest' && (startsWith(matrix.node-version, '12') || startsWith(matrix.node-version, '14'))
- run: |
- curl -sO https://registry.npmjs.org/npm/-/npm-7.5.4.tgz
- tar xf npm-7.5.4.tgz
- cd package
- node lib/npm.js install --no-fund --no-audit -g ..\npm-7.5.4.tgz
- cd ..
- rmdir /s /q package
- - name: Update npm
- run: npm i --prefer-online --no-fund --no-audit -g npm@latest
- - run: npm -v
- - run: npm i
- - run: npm test --ignore-scripts -w %%pkgpath%%
diff --git a/lib/content/ci.yml b/lib/content/ci.yml
index 175457e2..d2f51d15 100644
--- a/lib/content/ci.yml
+++ b/lib/content/ci.yml
@@ -1,13 +1,23 @@
-# This file is automatically added by @npmcli/template-oss. Do not edit.
-
-name: CI
+name: CI {{~#if isWorkspace}} - {{pkgName}}{{/if}}
on:
+ workflow_dispatch:
pull_request:
+ branches:
+ - '*'
+ {{#if pkgRelPath}}
+ paths:
+ - {{pkgRelPath}}
+ {{/if}}
push:
branches:
- - main
- - latest
+ {{#each branches}}
+ - {{.}}
+ {{/each}}
+ {{#if pkgRelPath}}
+ paths:
+ - {{pkgRelPath}}
+ {{/if}}
schedule:
# "At 02:00 on Monday" https://crontab.guru/#0_2_*_*_1
- cron: "0 2 * * 1"
@@ -16,47 +26,34 @@ jobs:
lint:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
- - uses: actions/setup-node@v2
- with:
- node-version: '16'
- - run: npm i --prefer-online -g npm@latest
+ {{> setupGit}}
+ {{> setupNode}}
- run: npm i
- - run: npm run lint
+ - run: npm run lint {{~#if isWorkspace}} -w {{pkgName}}{{/if}}
test:
strategy:
fail-fast: false
matrix:
- node-version: [12.13.0, 12.x, 14.15.0, 14.x, 16.13.0, 16.x]
+ node-version:
+ {{#each ciVersions}}
+ - {{.}}
+ {{/each}}
platform:
- - os: ubuntu-latest
- shell: bash
- - os: macos-latest
- shell: bash
- - os: windows-latest
- shell: cmd
- runs-on: ${{ matrix.platform.os }}
+ - os: ubuntu-latest
+ shell: bash
+ - os: macos-latest
+ shell: bash
+ {{#if windowsCI}}
+ - os: windows-latest
+ shell: cmd
+ {{/if}}
+ runs-on: $\{{ matrix.platform.os }}
defaults:
run:
- shell: ${{ matrix.platform.shell }}
+ shell: $\{{ matrix.platform.shell }}
steps:
- - uses: actions/checkout@v2
- - uses: actions/setup-node@v2
- with:
- node-version: ${{ matrix.node-version }}
- # node 12 and 14 ship with npm@6, which is known to fail when updating itself in windows
- - name: Update to workable npm (windows)
- if: matrix.platform.os == 'windows-latest' && (startsWith(matrix.node-version, '12') || startsWith(matrix.node-version, '14'))
- run: |
- curl -sO https://registry.npmjs.org/npm/-/npm-7.5.4.tgz
- tar xf npm-7.5.4.tgz
- cd package
- node lib/npm.js install --no-fund --no-audit -g ..\npm-7.5.4.tgz
- cd ..
- rmdir /s /q package
- - name: Update npm
- run: npm i --prefer-online --no-fund --no-audit -g npm@latest
- - run: npm -v
+ {{> setupGit}}
+ {{> setupNode}}
- run: npm i
- - run: npm test --ignore-scripts
+ - run: npm test --ignore-scripts {{~#if isWorkspace}} -w {{pkgName}}{{/if}}
diff --git a/lib/content/codeql-analysis.yml b/lib/content/codeql-analysis.yml
index c7694c49..584316fe 100644
--- a/lib/content/codeql-analysis.yml
+++ b/lib/content/codeql-analysis.yml
@@ -1,13 +1,17 @@
-# This file is automatically added by @npmcli/template-oss. Do not edit.
-
name: "CodeQL"
on:
push:
- branches: [ main ]
+ branches:
+ {{#each branches}}
+ - {{.}}
+ {{/each}}
pull_request:
# The branches below must be a subset of the branches above
- branches: [ main ]
+ branches:
+ {{#each branches}}
+ - {{.}}
+ {{/each}}
schedule:
# "At 03:00 on Monday" https://crontab.guru/#0_3_*_*_1
- cron: "0 3 * * 1"
@@ -24,15 +28,13 @@ jobs:
strategy:
fail-fast: false
matrix:
- language: [ 'javascript' ]
+ language: [javascript]
steps:
- - name: Checkout repository
- uses: actions/checkout@v2
-
+ {{> setupGit}}
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
- languages: ${{ matrix.language }}
+ languages: $\{{ matrix.language }}
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
diff --git a/lib/content/commitlintrc.js b/lib/content/commitlintrc.js
index cf8f3d76..2a0b0cde 100644
--- a/lib/content/commitlintrc.js
+++ b/lib/content/commitlintrc.js
@@ -1,10 +1,7 @@
-// This file is automatically added by @npmcli/template-oss. Do not edit.
-
module.exports = {
extends: ['@commitlint/config-conventional'],
- // If you change rules be sure to also update release-please.yml
rules: {
- 'type-enum': [2, 'always', ['feat', 'fix', 'docs', 'chore', 'deps']],
+ 'type-enum': [2, 'always', [{{#each changelogTypes}}'{{type}}'{{#unless @last}}, {{/unless}}{{/each}}]],
'header-max-length': [2, 'always', 80],
'subject-case': [0, 'always', ['lower-case', 'sentence-case', 'start-case']],
},
diff --git a/lib/content/config.yml b/lib/content/config.yml
index d640909f..0086358d 100644
--- a/lib/content/config.yml
+++ b/lib/content/config.yml
@@ -1,3 +1 @@
-# This file is automatically added by @npmcli/template-oss. Do not edit.
-
blank_issues_enabled: true
diff --git a/lib/content/dependabot.yml b/lib/content/dependabot.yml
index 4c8e23f4..af6a0802 100644
--- a/lib/content/dependabot.yml
+++ b/lib/content/dependabot.yml
@@ -1,16 +1,15 @@
-# This file is automatically added by @npmcli/template-oss. Do not edit.
-
version: 2
+
updates:
-- package-ecosystem: npm
- directory: "/"
- schedule:
- interval: daily
- allow:
- - dependency-type: direct
- versioning-strategy: increase
- commit-message:
- prefix: deps
- prefix-development: chore
- labels:
- - "Dependencies"
+ - package-ecosystem: npm
+ directory: "/"
+ schedule:
+ interval: daily
+ allow:
+ - dependency-type: direct
+ versioning-strategy: increase
+ commit-message:
+ prefix: deps
+ prefix-development: chore
+ labels:
+ - "Dependencies"
diff --git a/lib/content/eslintrc.js b/lib/content/eslintrc.js
index 022767bc..7e95cd4f 100644
--- a/lib/content/eslintrc.js
+++ b/lib/content/eslintrc.js
@@ -1,5 +1,3 @@
-// This file is automatically added by @npmcli/template-oss. Do not edit.
-
const { readdirSync: readdir } = require('fs')
const localConfigs = readdir(__dirname)
diff --git a/lib/content/gitignore b/lib/content/gitignore
index 6ed44c72..f1cbf0fe 100644
--- a/lib/content/gitignore
+++ b/lib/content/gitignore
@@ -1,23 +1,17 @@
-# This file is automatically added by @npmcli/template-oss. Do not edit.
-
# ignore everything in the root
/*
# keep these
-!/.commitlintrc.js
-!/.npmrc
-!/.eslintrc*
-!/.github
+!/.eslintrc.local.*
!**/.gitignore
-!/package.json
-!/docs
-!/bin
-!/lib
+!/docs/
+!/tap-snapshots/
+!/test/
!/map.js
-!/tap-snapshots
-!/test
-!/scripts
+!/scripts/
!/README*
!/LICENSE*
-!/SECURITY*
!/CHANGELOG*
+{{#each ignorePaths}}
+{{.}}
+{{/each}}
diff --git a/lib/content/index.js b/lib/content/index.js
new file mode 100644
index 00000000..483eb51f
--- /dev/null
+++ b/lib/content/index.js
@@ -0,0 +1,90 @@
+// Changes applied to the root of the repo
+const rootRepo = {
+ add: {
+ '.commitlintrc.js': 'commitlintrc.js',
+ '.github/workflows/ci.yml': 'ci.yml',
+ '.github/ISSUE_TEMPLATE/bug.yml': 'bug.yml',
+ '.github/ISSUE_TEMPLATE/config.yml': 'config.yml',
+ '.github/CODEOWNERS': 'CODEOWNERS',
+ '.github/dependabot.yml': 'dependabot.yml',
+ '.github/workflows/audit.yml': 'audit.yml',
+ '.github/workflows/codeql-analysis.yml': 'codeql-analysis.yml',
+ '.github/workflows/post-dependabot.yml': 'post-dependabot.yml',
+ '.github/workflows/pull-request.yml': 'pull-request.yml',
+ '.github/workflows/release-please.yml': 'release-please.yml',
+ },
+}
+
+// These are also applied to the root of the repo
+// but can by controlled by the `rootModule` config
+// XXX: im not sure the distinction between repo
+// and module in the root. both are applied to the same
+// dir. so we might want to combine these
+const rootModule = {
+ add: {
+ '.eslintrc.js': 'eslintrc.js',
+ '.gitignore': 'gitignore',
+ '.npmrc': 'npmrc',
+ 'SECURITY.md': 'SECURITY.md',
+ 'package.json': 'package.json',
+ },
+ rm: [
+ '.eslintrc.!(js|local.*)',
+ ],
+}
+
+// Changes for each workspace but applied to the root of the repo
+const workspaceRepo = {
+ add: {
+ '.github/workflows/release-please-{{pkgNameFs}}.yml': 'release-please.yml',
+ '.github/workflows/ci-{{pkgNameFs}}.yml': 'ci.yml'
+ },
+}
+
+// Changes for each workspace but applied to the relative workspace dir
+const workspaceModule = {
+ add: {
+ '.eslintrc.js': 'eslintrc.js',
+ '.gitignore': 'gitignore',
+ 'package.json': 'package.json',
+ },
+ rm: [
+ '.npmrc',
+ '.eslintrc.!(js|local.*)',
+ ],
+}
+
+module.exports = {
+ rootRepo,
+ rootModule,
+ workspaceRepo,
+ workspaceModule,
+ windowsCI: true,
+ branches: ['main', 'latest'],
+ distPaths: ['bin/', 'lib/'],
+ ciVersions: ['12.13.0', '12.x', '14.15.0', '14.x', '16.0.0', '16.x'],
+ unwantedPackages: [
+ 'eslint',
+ 'eslint-plugin-node',
+ '@npmcli/lint',
+ 'eslint-plugin-promise',
+ 'eslint-plugin-standard',
+ 'eslint-plugin-import',
+ 'standard',
+ ],
+ requiredPackages: {
+ devDependencies: {
+ '@npmcli/template-oss': '*',
+ '@npmcli/eslint-config': '^3.0.0',
+ tap: '^15.0.0',
+ },
+ },
+ allowedPackages: [],
+ changelogTypes: [
+ { type: "feat", section: "Features", hidden: false },
+ { type: "fix", section: "Bug Fixes", hidden: false },
+ { type: "docs", section: "Documentation", hidden: false },
+ { type: "deps", section: "Dependencies", hidden: false },
+ { type: "chore", hidden: true },
+ ],
+}
diff --git a/lib/content/npmrc b/lib/content/npmrc
index 878b7dde..43c97e71 100644
--- a/lib/content/npmrc
+++ b/lib/content/npmrc
@@ -1,3 +1 @@
-;This file is automatically added by @npmcli/template-oss. Do not edit.
-
package-lock=false
diff --git a/lib/content/package.json b/lib/content/package.json
new file mode 100644
index 00000000..f3ba9626
--- /dev/null
+++ b/lib/content/package.json
@@ -0,0 +1,27 @@
+{
+ "author": "GitHub Inc.",
+ "files": {{{json distPaths}}},
+ "scripts": {
+ "lint": "eslint \"**/*.js\"",
+ "postlint": "template-oss-check",
+ "template-oss-apply": "template-oss-apply --force",
+ "lintfix": "npm run lint -- --fix",
+ "preversion": "npm test",
+ "postversion": "npm publish",
+ "prepublishOnly": "git push origin --follow-tags",
+ "snap": "tap",
+ "test": "tap",
+ "posttest": "npm run lint",
+ "template-copy": {{{del}}},
+ "lint:fix": {{{del}}}
+ },
+ "repository": {{#if repository}}{{{json repository}}}{{else}}{{{del}}}{{/if}},
+ "engines": {
+ "node": {{{json engines}}}
+ },
+ {{{json __CONFIG_KEY__}}}: {
+ "version": {{#if __DOGFOOD__}}{{{del}}}{{else}}{{{json __VERSION__}}}{{/if}}
+ },
+ "templateVersion": {{{del}}},
+ "standard": {{{del}}}
+}
diff --git a/lib/content/post-dependabot.yml b/lib/content/post-dependabot.yml
index d3c12f40..ffc94a6d 100644
--- a/lib/content/post-dependabot.yml
+++ b/lib/content/post-dependabot.yml
@@ -1,7 +1,7 @@
-# This file is automatically added by @npmcli/template-oss. Do not edit.
+name: Post Dependabot Actions
-name: "Post Dependabot Actions"
-on: pull_request
+on:
+ pull_request
# https://docs.github.com/en/rest/overview/permissions-required-for-github-apps
permissions:
@@ -10,27 +10,23 @@ permissions:
jobs:
Install:
runs-on: ubuntu-latest
- if: ${{ github.actor == 'dependabot[bot]' }}
+ if: github.actor == 'dependabot[bot]'
steps:
- - uses: actions/checkout@v2
- - uses: actions/setup-node@v2
- with:
- node-version: '16'
+ {{> setupGit}}
+ {{> setupNode}}
- name: Dependabot metadata
id: metadata
uses: dependabot/fetch-metadata@v1.1.1
with:
- github-token: "${{ secrets.GITHUB_TOKEN }}"
+ github-token: "$\{{ secrets.GITHUB_TOKEN }}"
- name: npm install and commit
- if: ${{contains(steps.metadata.outputs.dependency-names, '@npmcli/template-oss')}}
+ if: contains(steps.metadata.outputs.dependency-names, '@npmcli/template-oss')
env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ GITHUB_TOKEN: $\{{ secrets.GITHUB_TOKEN }}
run: |
- git config --local user.email "ops+npm-cli@npmjs.com"
- git config --local user.name "npm cli ops bot"
- gh pr checkout ${{ github.event.pull_request.number }}
+ gh pr checkout $\{{ github.event.pull_request.number }}
npm install --no-scripts
- npm run template-copy
+ npm run template-oss-apply
git add .
git commit -am "chore: postinstall for dependabot template-oss PR"
git push
diff --git a/lib/content/pull-request.yml b/lib/content/pull-request.yml
index 948960e7..4566128f 100644
--- a/lib/content/pull-request.yml
+++ b/lib/content/pull-request.yml
@@ -1,27 +1,28 @@
-# This file is automatically added by @npmcli/template-oss. Do not edit.
-
name: Pull Request Linting
on:
pull_request:
- types: [opened, reopened, edited, synchronize]
+ types:
+ - opened
+ - reopened
+ - edited
+ - synchronize
jobs:
check:
name: Check PR Title or Commits
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
- with:
- fetch-depth: 0
- - uses: actions/setup-node@v2
- with:
- node-version: '16'
+ {{> setupGit with=(obj fetch-depth=0)}}
+ {{> setupNode}}
- name: Install deps
run: |
npm i -D @commitlint/cli @commitlint/config-conventional
- name: Check commits OR PR title
env:
- PR_TITLE: ${{ github.event.pull_request.title }}
+ PR_TITLE: $\{{ github.event.pull_request.title }}
run: |
- 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
+ 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
diff --git a/lib/content/release-please-workspace.yml b/lib/content/release-please-workspace.yml
deleted file mode 100644
index acd2eda5..00000000
--- a/lib/content/release-please-workspace.yml
+++ /dev/null
@@ -1,29 +0,0 @@
-# This file is automatically added by @npmcli/template-oss. Do not edit.
-
-name: Node Workspace Release Please %%pkgname%%
-
-on:
- push:
- paths:
- - %%pkgpath%%/**
- branches:
- - release-next
- - latest
-
-jobs:
- release-please:
- runs-on: ubuntu-latest
- steps:
- - uses: google-github-actions/release-please-action@v2
- id: release
- with:
- release-type: node
- monorepo-tags: true
- path: %%pkgpath%%
- # If you change changelog-types be sure to also update commitlintrc.js
- changelog-types: >
- [{"type":"feat","section":"Features","hidden":false},
- {"type":"fix","section":"Bug Fixes","hidden":false},
- {"type":"docs","section":"Documentation","hidden":false},
- {"type":"deps","section":"Dependencies","hidden":false},
- {"type":"chore","hidden":true}]
diff --git a/lib/content/release-please.yml b/lib/content/release-please.yml
index c5a165f3..2a43aefb 100644
--- a/lib/content/release-please.yml
+++ b/lib/content/release-please.yml
@@ -1,24 +1,32 @@
-# This file is automatically added by @npmcli/template-oss. Do not edit.
-
-name: Release Please
+name: Release Please {{~#if isWorkspace}} - {{pkgName}}{{/if}}
on:
push:
+ {{#if pkgRelPath}}
+ paths:
+ - {{pkgRelPath}}/**
+ {{/if}}
branches:
- - main
+ {{#each branches}}
+ - {{.}}
+ {{/each}}
jobs:
release-please:
runs-on: ubuntu-latest
steps:
- - uses: google-github-actions/release-please-action@v2
+ - uses: google-github-actions/release-please-action@v3
id: release
with:
release-type: node
- # If you change changelog-types be sure to also update commitlintrc.js
+ changelog-type: github
+ {{#if pkgRelPath}}
+ monorepo-tags: true
+ paths: {{pkgRelPath}}
+ {{/if}}
changelog-types: >
- [{"type":"feat","section":"Features","hidden":false},
- {"type":"fix","section":"Bug Fixes","hidden":false},
- {"type":"docs","section":"Documentation","hidden":false},
- {"type":"deps","section":"Dependencies","hidden":false},
- {"type":"chore","hidden":true}]
+ [
+ {{#each changelogTypes}}
+ {{{json .}}}{{#unless @last}},{{/unless}}
+ {{/each}}
+ ]
diff --git a/lib/content/setup-git.yml b/lib/content/setup-git.yml
new file mode 100644
index 00000000..4ecd4697
--- /dev/null
+++ b/lib/content/setup-git.yml
@@ -0,0 +1,11 @@
+- uses: actions/checkout@v3
+{{#if with}}
+ with:
+ {{#each with}}
+ {{@key}}: {{this}}
+ {{/each}}
+{{/if}}
+- name: Setup git user
+ run: |
+ git config --global user.email "ops+npm-cli@npmjs.com"
+ git config --global user.name "npm cli ops bot"
diff --git a/lib/content/setup-node.yml b/lib/content/setup-node.yml
new file mode 100644
index 00000000..55a0eacd
--- /dev/null
+++ b/lib/content/setup-node.yml
@@ -0,0 +1,21 @@
+- uses: actions/setup-node@v3
+ with:
+ node-version: {{#each ciVersions}}{{#if @last}}{{.}}{{/if}}{{/each}}
+- name: Update to workable npm (windows)
+ # node 12 and 14 ship with npm@6, which is known to fail when updating itself in windows
+ if: matrix.platform.os == 'windows-latest' && (startsWith(matrix.node-version, '12') || startsWith(matrix.node-version, '14'))
+ run: |
+ curl -sO https://registry.npmjs.org/npm/-/npm-7.5.4.tgz
+ tar xf npm-7.5.4.tgz
+ cd package
+ node lib/npm.js install --no-fund --no-audit -g ..\npm-7.5.4.tgz
+ cd ..
+ rmdir /s /q package
+- name: Update npm to 7
+ # If we do test on npm 10 it needs npm7
+ if: matrix.node-version <= 10
+ run: npm i --prefer-online --no-fund --no-audit -g npm@7
+- name: Update npm to latest
+ if: matrix.node-version > 10
+ run: npm i --prefer-online --no-fund --no-audit -g npm@latest
+- run: npm -v
diff --git a/lib/index.js b/lib/index.js
new file mode 100644
index 00000000..a055823d
--- /dev/null
+++ b/lib/index.js
@@ -0,0 +1,100 @@
+const log = require('proc-log')
+const { defaults } = require('lodash')
+const getConfig = require('./config.js')
+const PackageJson = require('@npmcli/package-json')
+const mapWorkspaces = require('@npmcli/map-workspaces')
+
+const getPkg = async (path, baseConfig) => {
+ log.verbose('get-pkg', path)
+
+ const pkg = (await PackageJson.load(path)).content
+ const pkgConfig = getConfig.getPkgConfig(pkg)
+ log.verbose('get-pkg', pkgConfig)
+
+ return { pkg, path, config: { ...baseConfig, ...pkgConfig } }
+}
+
+const getWsPkgs = async (root, rootPkg) => {
+ const wsPkgs = []
+
+ // workspaces are only used to filter paths and control changes to workspaces
+ // so dont pass it along with the rest of the config
+ const { workspaces, ...baseConfig } = rootPkg.config
+
+ // Include all by default
+ const include = (name) => Array.isArray(workspaces) ? workspaces.includes(name) : true
+
+ // Look through all workspaces on the root pkg
+ const rootWorkspaces = await mapWorkspaces({ pkg: rootPkg.pkg, cwd: root })
+
+ for (const [wsName, wsPath] of rootWorkspaces.entries()) {
+ if (include(wsName)) {
+ // A workspace can control its own workspaceRepo and workspaceModule settings
+ // which are true by default on the root config
+ wsPkgs.push(await getPkg(wsPath, baseConfig))
+ }
+ }
+
+ return {
+ pkgs: wsPkgs,
+ paths: [...rootWorkspaces.values()],
+ }
+}
+
+const getPkgs = async (root) => {
+ log.verbose('get-pkgs', 'root', root)
+
+ const rootPkg = await getPkg(root)
+ const pkgs = [rootPkg]
+
+ defaults(rootPkg.config, {
+ rootRepo: true,
+ rootModule: true,
+ workspaceRepo: true,
+ workspaceModule: true,
+ workspaces: null,
+ })
+
+ const ws = await getWsPkgs(root, rootPkg)
+
+ return {
+ pkgs: pkgs.concat(ws.pkgs),
+ workspaces: ws.paths,
+ }
+}
+
+const runAll = async (root, content, checks) => {
+ const results = []
+ const { pkgs, workspaces } = await getPkgs(root)
+
+ for (const { pkg, path, config } of pkgs) {
+ // full config includes original config values
+ const fullConfig = await getConfig({
+ pkgs,
+ workspaces,
+ root,
+ pkg,
+ path,
+ config,
+ content,
+ })
+
+ const options = { root, pkg, path, config: fullConfig }
+ log.verbose('run-all', options)
+
+ // files can export multiple checks so flatten first
+ for (const { when, run, name } of checks.flat()) {
+ log.info('attempting to run', name)
+ if (await when(options)) {
+ log.info('running', name)
+ results.push(await run(options))
+ }
+ }
+ }
+
+ // checks can return multiple results or nothing
+ // so flatten first and remove nulls before returning
+ return results.flat().filter(Boolean)
+}
+
+module.exports = runAll
diff --git a/lib/postinstall/copy-content.js b/lib/postinstall/copy-content.js
deleted file mode 100644
index 084cc7e5..00000000
--- a/lib/postinstall/copy-content.js
+++ /dev/null
@@ -1,133 +0,0 @@
-const { dirname, join, resolve } = require('path')
-const fs = require('@npmcli/fs')
-const PackageJson = require('@npmcli/package-json')
-
-const contentDir = resolve(__dirname, '..', 'content')
-
-// keys are destination paths in the target project
-// values are paths to contents relative to '../content/'
-const moduleFiles = {
- '.eslintrc.js': './eslintrc.js',
- '.gitignore': './gitignore',
- '.npmrc': './npmrc',
- 'SECURITY.md': './SECURITY.md',
-}
-
-const repoFiles = {
- '.commitlintrc.js': './commitlintrc.js',
- '.github/workflows/ci.yml': './ci.yml',
- '.github/ISSUE_TEMPLATE/bug.yml': './bug.yml',
- '.github/ISSUE_TEMPLATE/config.yml': './config.yml',
- '.github/CODEOWNERS': './CODEOWNERS',
- '.github/dependabot.yml': './dependabot.yml',
- '.github/workflows/audit.yml': './audit.yml',
- '.github/workflows/codeql-analysis.yml': './codeql-analysis.yml',
- '.github/workflows/post-dependabot.yml': './post-dependabot.yml',
- '.github/workflows/pull-request.yml': './pull-request.yml',
- '.github/workflows/release-please.yml': './release-please.yml',
-}
-
-// currently no workspace module files
-// const workspaceModuleFiles = {}
-
-const workspaceRepoFiles = {
- '.github/workflows/release-please-%%pkgfsname%%.yml': './release-please-workspace.yml',
- '.github/workflows/ci-%%pkgfsname%%.yml': './ci-workspace.yml',
-}
-
-const filesToDelete = [
- // remove any eslint config files that aren't local to the project
- /^\.eslintrc\.(?!(local\.)).*/,
-]
-
-const defaultConfig = {
- applyRootRepoFiles: true,
- applyWorkspaceRepoFiles: true,
- applyRootModuleFiles: true,
- windowsCI: true,
-}
-
-const findReplace = (str, replace = {}) => {
- for (const [f, r] of Object.entries(replace)) {
- str = str.replace(new RegExp(f, 'g'), r)
- }
- return str
-}
-
-const copyFile = async (source, target, replacements) => {
- if (replacements) {
- const content = await fs.readFile(source, { encoding: 'utf-8' })
- return fs.writeFile(target, findReplace(content, replacements), { owner: 'inherit' })
- }
- return fs.copyFile(source, target, { owner: 'inherit' })
-}
-
-const copyFiles = async (targetDir, files, replacements) => {
- for (let [target, source] of Object.entries(files)) {
- target = findReplace(join(targetDir, target), replacements)
- source = join(contentDir, source)
- // if the target is a subdirectory of the path, mkdirp it first
- if (dirname(target) !== targetDir) {
- await fs.mkdir(dirname(target), {
- owner: 'inherit',
- recursive: true,
- force: true,
- })
- }
- await copyFile(source, target, replacements)
- }
-}
-
-// given a root directory, copy all files in the content map
-// after purging any files we need to delete
-const copyContent = async (path, rootPath, config) => {
- config = { ...defaultConfig, ...config }
- const isWorkspace = path !== rootPath
-
- const contents = await fs.readdir(path)
-
- if (isWorkspace || config.applyRootModuleFiles) {
- // delete files and copy moduleFiles if it's a workspace
- // or if we enabled doing so for the root
- for (const file of contents) {
- if (filesToDelete.some((p) => p.test(file))) {
- await fs.rm(join(path, file))
- }
- }
- await copyFiles(path, moduleFiles)
- }
-
- if (!isWorkspace) {
- if (config.applyRootRepoFiles) {
- await copyFiles(rootPath, repoFiles)
- if (!config.windowsCI) {
- // copyFiles already did the mkdir so we can just fs.copyFile now
- await fs.copyFile(
- join(contentDir, 'ci-no-windows.yml'),
- join(rootPath, '.github', 'workflows', 'ci.yml')
- )
- }
- }
- return
- }
-
- // only workspace now
-
- // TODO: await copyFiles(path, workspaceModuleFiles)
- // if we ever have workspace specific files
-
- // transform and copy all workspace repo files
- if (config.applyWorkspaceRepoFiles) {
- const workspacePkg = (await PackageJson.load(path)).content
- await copyFiles(rootPath, workspaceRepoFiles, {
- '%%pkgname%%': workspacePkg.name,
- '%%pkgfsname%%': workspacePkg.name.replace(/\//g, '-').replace(/@/g, ''),
- '%%pkgpath%%': path.substring(rootPath.length + 1),
- })
- }
-}
-
-copyContent.moduleFiles = moduleFiles
-copyContent.repoFiles = repoFiles
-
-module.exports = copyContent
diff --git a/lib/postinstall/update-package.js b/lib/postinstall/update-package.js
deleted file mode 100644
index d03efeea..00000000
--- a/lib/postinstall/update-package.js
+++ /dev/null
@@ -1,100 +0,0 @@
-const PackageJson = require('@npmcli/package-json')
-
-const {
- version: TEMPLATE_VERSION,
- name: TEMPLATE_NAME,
-} = require('../../package.json')
-
-const changes = {
- author: 'GitHub Inc.',
- files: ['bin', 'lib'],
- scripts: {
- lint: 'eslint "**/*.js"',
- postlint: 'npm-template-check',
- 'template-copy': 'npm-template-copy --force',
- lintfix: 'npm run lint -- --fix',
- preversion: 'npm test',
- postversion: 'npm publish',
- prepublishOnly: 'git push origin --follow-tags',
- snap: 'tap',
- test: 'tap',
- posttest: 'npm run lint',
- },
- engines: {
- node: '^12.13.0 || ^14.15.0 || >=16',
- },
-}
-
-const patchPackage = async (path, root, config) => {
- const pkg = await PackageJson.load(path)
- config = config || {}
-
- // If we are running this on itself, we always run the script.
- // We also don't set templateVersion in package.json because
- // its not relavent and would cause git churn after running
- // `npm version`.
- const isDogfood = pkg.content.name === TEMPLATE_NAME
- const currentVersion = (pkg.content.templateOSS === undefined) ?
- pkg.content.templateVersion : pkg.content.templateOSS.version
-
- // if the target package.json has a templateVersion field matching our own
- // current version, we return false here so the postinstall script knows to
- // exit early instead of running everything again
- if (!config.force
- && currentVersion === TEMPLATE_VERSION
- && !isDogfood) {
- return false
- }
-
- const templateConfig = {
- templateOSS: {
- ...pkg.content.templateOSS,
- ...{ version: TEMPLATE_VERSION },
- },
- }
-
- let update
-
- if (path === root && !config.applyRootModuleFiles) {
- // only update templateVersion if we're skipping root module files
- update = {
- ...templateConfig,
- }
- } else {
- // we build a new object here so our exported set of changes is not modified
- update = {
- ...changes,
- scripts: {
- ...pkg.content.scripts,
- ...changes.scripts,
- },
- ...templateConfig,
- }
- }
-
- if (isDogfood) {
- delete update.templateVersion
- delete update.templateOSS
- } else {
- if (pkg.content.templateVersion) {
- update.templateVersion = undefined
- }
- if (pkg.content.scripts && pkg.content.scripts['lint:fix']) {
- // some old packages using standard had a lint:fix script
- delete update.scripts['lint:fix']
- }
- if (pkg.content.standard) {
- // remove standard configuration if it exists
- update.standard = undefined
- }
- }
-
- pkg.update(update)
-
- await pkg.save()
- return true
-}
-
-patchPackage.changes = changes
-
-module.exports = patchPackage
diff --git a/lib/postlint/check-gitignore.js b/lib/postlint/check-gitignore.js
deleted file mode 100644
index ed4d0b60..00000000
--- a/lib/postlint/check-gitignore.js
+++ /dev/null
@@ -1,59 +0,0 @@
-const path = require('path')
-const fs = require('fs')
-const { sync: which } = require('which')
-const { spawnSync } = require('child_process')
-
-// The problem we are trying to solve is when a new .gitignore file
-// is copied into an existing repo, there could be files already checked in
-// to git that are now ignored by new gitignore rules. We want to warn
-// about these files.
-const check = async (root) => {
- const git = path.resolve(root, '.git')
- const gitignore = path.resolve(root, '.gitignore')
-
- if (!fs.existsSync(git)) {
- return []
- }
-
- if (!fs.existsSync(gitignore)) {
- throw new Error(`${gitignore} must exist to run npm-template-check`)
- }
-
- const res = spawnSync(which('git'), [
- 'ls-files',
- '--cached',
- '--ignored',
- // The .gitignore file has already been placed by now with the postinstall
- // script so when this script runs via postlint, it can use that file
- `--exclude-from=${gitignore}`,
- ], { encoding: 'utf-8', cwd: root })
-
- const files = res.stdout
- .trim()
- .split('\n')
- .filter(Boolean)
-
- if (!files.length) {
- return []
- }
-
- const relativeGitignore = path.relative(root, gitignore)
- const ignores = fs.readFileSync(gitignore)
- .toString()
- .split('\n')
- .filter((l) => l && !l.trim().startsWith('#'))
-
- const message = [
- `The following files are tracked by git but matching a pattern in ${relativeGitignore}:`,
- ...files.map((f) => ` ${f}`),
- ].join('\n')
-
- const solution = [
- 'Move files to not match one of these patterns:',
- ...ignores.map((i) => ` ${i}`),
- ].join('\n')
-
- return [{ message, solution }]
-}
-
-module.exports = check
diff --git a/lib/postlint/check-package.js b/lib/postlint/check-package.js
deleted file mode 100644
index 07ffe4eb..00000000
--- a/lib/postlint/check-package.js
+++ /dev/null
@@ -1,90 +0,0 @@
-const PackageJson = require('@npmcli/package-json')
-const patchPackage = require('../postinstall/update-package.js')
-
-const unwantedPackages = [
- '@npmcli/lint',
- 'eslint-plugin-promise',
- 'eslint-plugin-standard',
- 'eslint-plugin-import',
- 'standard',
-]
-
-const hasOwn = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key)
-
-const check = async (root) => {
- const pkg = (await PackageJson.load(root)).content
- const changes = Object.entries(patchPackage.changes)
- const problems = []
- const incorrectFields = []
- // 1. ensure package.json changes have been applied
- for (const [key, value] of changes) {
- if (!hasOwn(pkg, key)) {
- incorrectFields.push({
- name: key,
- found: pkg[key],
- expected: value,
- })
- } else if (value && typeof value === 'object') {
- for (const [subKey, subValue] of Object.entries(value)) {
- if (!hasOwn(pkg[key], subKey) ||
- pkg[key][subKey] !== subValue) {
- incorrectFields.push({
- name: `${key}.${subKey}`,
- found: pkg[key][subKey],
- expected: subValue,
- })
- }
- }
- } else {
- if (pkg[key] !== patchPackage.changes[key]) {
- incorrectFields.push({
- name: key,
- found: pkg[key],
- expected: value,
- })
- }
- }
- }
-
- if (incorrectFields.length) {
- problems.push({
- message: [
- `The following package.json fields are incorrect:`,
- ...incorrectFields.map((field) => {
- const message = [
- 'Field:',
- `${JSON.stringify(field.name)}`,
- 'Expected:',
- `${JSON.stringify(field.expected)}`,
- 'Found:',
- `${JSON.stringify(field.found)}`,
- ].join(' ')
- return ` ${message}`
- }),
- ].join('\n'),
- solution: 'npm rm @npmcli/template-oss && npm i -D @npmcli/template-oss',
- })
- }
-
- // 2. ensure packages that should not be present are removed
- const mustRemove = unwantedPackages.filter((name) => {
- return hasOwn(pkg.dependencies || {}, name) ||
- hasOwn(pkg.devDependencies || {}, name)
- })
-
- if (mustRemove.length) {
- problems.push({
- message: [
- 'The following unwanted packages were found:',
- ...mustRemove.map((p) => ` ${p}`),
- ].join('\n'),
- solution: `npm rm ${mustRemove.join(' ')}`,
- })
- }
-
- return problems
-}
-
-check.unwantedPackages = unwantedPackages
-
-module.exports = check
diff --git a/lib/util/files.js b/lib/util/files.js
new file mode 100644
index 00000000..8dd0698a
--- /dev/null
+++ b/lib/util/files.js
@@ -0,0 +1,43 @@
+const { join } = require('path')
+const { promisify } = require('util')
+const glob = promisify(require('glob'))
+const Parser = require('./parser.js')
+const template = require('./template.js')
+
+// target paths need to be joinsed with dir and templated
+const fullTarget = (dir, file, options) => join(dir, template(file, options))
+
+// given an obj of files, return the full target/source paths and associated parser
+const getParsers = (dir, files, options) => Object.entries(files).map(([t, s]) => {
+ let { file, parser: fileParser } = typeof s === 'string' ? { file: s } : s
+ const target = fullTarget(dir, t, options)
+ file = join(options.config.sourceDir, file)
+ if (fileParser) {
+ // allow files to extend base parsers or create new ones
+ return new (fileParser(Parser.Parsers))(target, file, options)
+ }
+ return new (Parser(file))(target, file, options)
+})
+
+const rmEach = async (dir, files, options, fn) => {
+ const res = []
+ for (const target of files.map((t) => fullTarget(dir, t, options))) {
+ for (const file of await glob(target, { cwd: dir })) {
+ res.push(await fn(file))
+ }
+ }
+ return res.filter(Boolean)
+}
+
+const parseEach = async (dir, files, options, fn) => {
+ const res = []
+ for (const parser of getParsers(dir, files, options)) {
+ res.push(await fn(parser))
+ }
+ return res.filter(Boolean)
+}
+
+module.exports = {
+ rmEach,
+ parseEach,
+}
diff --git a/lib/util/get-git-url.js b/lib/util/get-git-url.js
new file mode 100644
index 00000000..d2262be0
--- /dev/null
+++ b/lib/util/get-git-url.js
@@ -0,0 +1,24 @@
+const hgi = require('hosted-git-info')
+const git = require('@npmcli/git')
+
+// parse a repo from a git origin into a format
+// for a package.json#repository object
+const getRepo = async (path) => {
+ if (!await git.is({ cwd: path })) {
+ return
+ }
+
+ try {
+ const res = await git.spawn([
+ 'remote',
+ 'get-url',
+ 'origin',
+ ], { cwd: path })
+ const { domain, user, project } = hgi.fromUrl(res.stdout.trim())
+ const url = new URL(`https://${domain}`)
+ url.pathname = `/${user}/${project}.git`
+ return url.toString()
+ } catch {}
+}
+
+module.exports = getRepo
diff --git a/lib/util/has-package.js b/lib/util/has-package.js
new file mode 100644
index 00000000..11979a43
--- /dev/null
+++ b/lib/util/has-package.js
@@ -0,0 +1,30 @@
+const intersects = require('semver/ranges/intersects')
+const { has } = require('lodash')
+
+const installLocations = [
+ 'dependencies',
+ 'devDependencies',
+ 'peerDependencies',
+ 'bundledDependencies',
+ 'optionalDependencies',
+]
+
+const hasPackage = (
+ pkg,
+ name,
+ version = '*',
+ locations = installLocations
+) => locations
+ .map((l) => pkg[l])
+ .some((deps) =>
+ has(deps, name) &&
+ (version === '*' || intersects(deps[name], version))
+ )
+
+module.exports = hasPackage
+
+module.exports.flags = installLocations.reduce((acc, location) => {
+ const type = location.replace(/dependencies/i, '')
+ acc[location] = '--save' + (type ? `-${type}` : '')
+ return acc
+}, {})
diff --git a/lib/util/json-diff.js b/lib/util/json-diff.js
new file mode 100644
index 00000000..5b3b5da7
--- /dev/null
+++ b/lib/util/json-diff.js
@@ -0,0 +1,38 @@
+const { format } = require('util')
+const { get } = require('lodash')
+const { diff } = require('just-diff')
+
+const j = (obj, replacer = null) => JSON.stringify(obj, replacer, 2)
+
+const jsonDiff = (s1, s2, DELETE) => {
+ // DELETE is a special string that will be the value of updated if it exists
+ // but should be deleted
+
+ const ops = diff(s1, s2).map(({ op, path, value }) => {
+ // there could be cases where a whole object is reported
+ // as missing and the expected value does not need to show
+ // special DELETED values so filter those out here
+ const msgVal = j(value, (_, v) => v === DELETE ? undefined : v)
+ const prev = j(get(s1, path))
+ const key = j(path.reduce((acc, p) => acc + (typeof p === 'number' ? `[${p}]` : `.${p}`)))
+
+ const msg = (...args) => format('%s is %s, expected %s', ...args)
+ const AD = msg(key, 'missing', msgVal)
+ const RM = msg(key, prev, 'to be removed')
+ const UP = msg(key, prev, msgVal)
+
+ if (op === 'replace') {
+ return value === DELETE ? RM : UP
+ } else if (op === 'add' && value !== DELETE) {
+ return AD
+ }
+ }).filter(Boolean).sort((a, b) => a.localeCompare(b))
+
+ if (!ops.length) {
+ return true
+ }
+
+ return ops.join('\n')
+}
+
+module.exports = jsonDiff
diff --git a/lib/util/output.js b/lib/util/output.js
new file mode 100644
index 00000000..d96bea23
--- /dev/null
+++ b/lib/util/output.js
@@ -0,0 +1,35 @@
+const indent = (v, i = 2) => {
+ if (Array.isArray(v)) {
+ return v.map((a) => indent(a, i)).join('\n')
+ }
+ return v.toString().split('\n').map((l) => ' '.repeat(i) + l).join('\n')
+}
+
+const output = () => {
+ const res = []
+ const push = (...parts) => res.push(parts.join('\n'))
+ return {
+ toString: () => res.join('\n'),
+ sep: () => push('', '-'.repeat(67), ''),
+ push,
+ }
+}
+
+const outputProblems = (problems) => {
+ const o = output()
+ o.push('', 'Some problems were detected:')
+ o.sep()
+ for (const { title, body, solution } of problems) {
+ const [solutionTitle, ...solutionRest] = Array.isArray(solution)
+ ? solution : [solution]
+ o.push(title, '', indent(body), '', `To correct it: ${solutionTitle}`)
+ if (solutionRest.length) {
+ o.push('', indent(solutionRest))
+ }
+ o.sep()
+ }
+
+ return o.toString()
+}
+
+module.exports = outputProblems
diff --git a/lib/util/parse-ci-versions.js b/lib/util/parse-ci-versions.js
new file mode 100644
index 00000000..7a1bf0e3
--- /dev/null
+++ b/lib/util/parse-ci-versions.js
@@ -0,0 +1,78 @@
+const semver = require('semver')
+const { partition, uniq, groupBy } = require('lodash')
+
+// try to parse a version. if its invalid then
+// try to parse it as a range instead
+const versionOrRange = (v) => semver.parse(v) || new semver.Range(v)
+
+// get the version or the upper bound of the range
+// used for sorting to give the latest ci target
+const getMaxVersion = (v) => v.version || v.set[0][1].semver.version
+
+// given an array of versions, returns an object where
+// each key is a major and each value is a sorted list of versions
+const versionsByMajor = (versions) => {
+ const majors = groupBy(versions, (v) => semver.major(v))
+ for (const [k, vs] of Object.entries(majors)) {
+ majors[k] = semver.sort(vs)[0]
+ }
+ return majors
+}
+
+// given a list of semver ci targets like:
+// ['12.13.0', '12.x', '14.15.0', '14.x', '16.0.0', '16.x']
+// this will parse into a uniq list of lowest "supported"
+// versions. In our cases so that will return
+// '^12.13.0 || ^14.15.0 || >=16'. This is not super generic but fits
+// our use case for now where we want to test on a bunch of
+// specific versions/ranges and map them to somewhat loose
+// semver range for package.json#engines.node. This only supports
+// returning ^ ranges and makes the last version >= currently.
+//
+// Assumptions:
+// - ranges span a single major version
+// - specific versions are lower then the upper bound of
+// ranges within the same major version
+const parseCITargets = (targets = []) => {
+ const [versions, ranges] = partition(
+ targets.map((t) => versionOrRange(t)),
+ (t) => t.version
+ )
+
+ const sorted = [...versions, ...ranges]
+ .sort((a, b) => semver.compareBuild(getMaxVersion(a), getMaxVersion(b)))
+ .map((v) => v.version || v.raw)
+
+ // object of {major: lowestVersion } for all passed in versions
+ const minVersions = versionsByMajor(versions)
+
+ // object of {major: lowestVersionInRange } for all passed in ranges
+ const minRanges = versionsByMajor(ranges.map((r) => semver.minVersion(r)))
+
+ // Given all the uniq major versions in targets...
+ const parsedRanges = uniq([...Object.keys(minVersions), ...Object.keys(minRanges)])
+ // first sort by major to make it display nicer
+ .sort((a, b) => Number(a) - Number(b))
+ .map((major) => {
+ const minVersion = minVersions[major]
+ const minRange = minRanges[major]
+ // if we only have one then return that
+ if (!minVersion || !minRange) {
+ return minVersion || minRange
+ }
+ // otherwise return min version
+ // XXX: this assumes the versions are lower than the upper
+ // bound for any range for the same major. This is ok for
+ // now but will break with more complex/specific semver ranges
+ return minVersion
+ })
+ // make the last version allow all greater than
+ .map((v, index, list) => (index === list.length - 1 ? '>=' : '^') + v)
+
+ return {
+ targets: sorted,
+ engines: parsedRanges.join(' || '),
+ }
+}
+
+module.exports = parseCITargets
diff --git a/lib/util/parser.js b/lib/util/parser.js
new file mode 100644
index 00000000..8cb8ef1f
--- /dev/null
+++ b/lib/util/parser.js
@@ -0,0 +1,279 @@
+const fs = require('@npmcli/fs')
+const { EOL } = require('os')
+const { basename, extname, dirname } = require('path')
+const yaml = require('yaml')
+const NpmPackageJson = require('@npmcli/package-json')
+const jsonParse = require('json-parse-even-better-errors')
+const Diff = require('diff')
+const { unset, merge } = require('lodash')
+const template = require('./template.js')
+const jsonDiff = require('./json-diff')
+const setFirst = (first, rest) => ({ ...first, ...rest })
+const traverse = (value, visit, keys = []) => {
+ if (keys.length) {
+ const res = visit(keys, value)
+ if (res != null) {
+ return
+ }
+ }
+ if (typeof value === 'object' && value !== null) {
+ for (const [k, v] of Object.entries(value)) {
+ traverse(v, visit, keys.concat(k))
+ }
+ }
+}
+
+class Base {
+ static types = []
+ static header = 'This file is automatically added by {{__NAME__}}. Do not edit.'
+ comment = (v) => v
+ merge = false // supply a merge function which runs on prepare for certain types
+ DELETE = template.DELETE
+
+ constructor (target, source, options) {
+ this.target = target
+ this.source = source
+ this.options = options
+ }
+
+ header () {
+ if (typeof this.comment === 'function') {
+ return this.comment(this.template(this.constructor.header || ''))
+ }
+ }
+
+ read (s) {
+ return fs.readFile(s, { encoding: 'utf-8' })
+ }
+
+ template (s) {
+ return template(s, this.options)
+ }
+
+ parse (s) {
+ return s
+ }
+
+ prepare (s) {
+ const header = this.header()
+ return header ? header + EOL + EOL + s : s
+ }
+
+ prepareTarget (s) {
+ return s
+ }
+
+ toString (s) {
+ return s.toString()
+ }
+
+ async write (s) {
+ // XXX: find more efficient way to do this. we can build all possible dirs before get here
+ await fs.mkdir(dirname(this.target), { owner: 'inherit', recursive: true, force: true })
+ return fs.writeFile(this.target, this.toString(s), { owner: 'inherit' })
+ }
+
+ diffPatch (t, s) {
+ // create a patch and strip out the filename. if it ends up an empty string
+ // then return true since the files are equal
+ return Diff.createPatch('', t.replace(/\r\n/g, '\n'), s.replace(/\r\n/g, '\n'))
+ .split('\n').slice(4).join('\n').trim() || true
+ }
+
+ diff (t, s) {
+ return this.diffPatch(t, s)
+ }
+
+ // the apply methods are the only ones that should be called publically
+ // XXX: everything is allowed to be overridden in base classes but we could
+ // find a different solution than making everything public
+ applyWrite () {
+ return Promise.resolve(this.read(this.source))
+ // replace template vars first, this will throw for nonexistant vars
+ // because it must be parseable after this step
+ .then((s) => this.template(s))
+ // parse into whatever data structure is necessary for maniuplating
+ // diffing, merging, etc. by default its a string
+ .then((s) => this.parse(s))
+ // prepare the source for writing and diffing, pass in current
+ // target for merging. errors parsing or preparing targets are ok here
+ .then((s) => this.applyTarget().catch(() => null).then((t) => this.prepare(s, t)))
+ .then((s) => this.write(s))
+ }
+
+ applyTarget () {
+ return Promise.resolve(this.read(this.target))
+ .then((s) => this.parse(s))
+ // for only preparing the target for diffing
+ .then((s) => this.prepareTarget(s))
+ }
+
+ async applyDiff () {
+ const target = await this.applyTarget().catch((e) => {
+ // handle if old does not exist
+ if (e.code === 'ENOENT') {
+ return null
+ } else {
+ return { code: 'ETARGETERROR', error: e }
+ }
+ })
+
+ // no need to diff if current file does not exist
+ if (target === null) {
+ return null
+ }
+
+ const source = await Promise.resolve(this.read(this.source))
+ .then((s) => this.template(s))
+ .then((s) => this.parse(s))
+ // gets the target to diff against in case it needs to merge, etc
+ .then((s) => this.prepare(s, target))
+
+ // if there was a target error then there is no need to diff
+ // so we just show the source with an error message
+ if (target.code === 'ETARGETERROR') {
+ const msg = `[${this.options.config.__NAME__} ERROR]`
+ return [
+ `${msg} There was an erroring getting the target file`,
+ `${msg} ${target.error}`,
+ `${msg} It will be overwritten with the following source:`,
+ '-'.repeat(40),
+ this.toString(source),
+ ].join('\n')
+ }
+
+ // individual diff methods are responsible for formatting or returning
+ // true if the are equal
+ return this.diff(target, source)
+ }
+}
+
+class Gitignore extends Base {
+ static types = ['codeowners', 'gitignore']
+ comment = (c) => `# ${c}`
+}
+
+class Js extends Base {
+ static types = ['js']
+ comment = (c) => `/* ${c} */`
+}
+
+class Ini extends Base {
+ // XXX: add merge mode for updating ini files
+ static types = ['npmrc']
+ comment = (c) => `; ${c}`
+}
+
+class Markdown extends Base {
+ static types = ['md']
+ comment = (c) => ``
+}
+
+class Yml extends Base {
+ static types = ['yml']
+ comment = (c) => ` ${c}`
+
+ toString (s) {
+ return s.toString({ lineWidth: 0, indent: 2 })
+ }
+
+ parse (s) {
+ return yaml.parseDocument(s)
+ }
+
+ prepare (s) {
+ s.commentBefore = this.header()
+ return this.toString(s)
+ }
+
+ prepareTarget (s) {
+ return this.toString(s)
+ }
+}
+
+class Json extends Base {
+ static types = ['json']
+ // its a json comment! not really but we do add a special key
+ // to json objects
+ comment = (c) => ({ [`//${this.options.config.__NAME__}`]: c })
+
+ toString (s) {
+ return JSON.stringify(s, (_, v) => v === this.DELETE ? undefined : v, 2)
+ }
+
+ parse (s) {
+ return jsonParse(s)
+ }
+
+ prepare (s, t) {
+ let source = s
+ if (typeof this.merge === 'function' && t) {
+ source = this.merge(t, s)
+ }
+ return setFirst(this.header(), source)
+ }
+
+ diff (t, s) {
+ return jsonDiff(t, s, this.DELETE)
+ }
+}
+
+class JsonMerge extends Json {
+ static header = 'This file is partially managed by {{__NAME__}}. Edits may be overwritten.'
+ merge = (t, s) => merge({}, t, s)
+}
+
+class PackageJson extends JsonMerge {
+ static types = ['package.json']
+
+ async prepare (s, t) {
+ // merge new source with current pkg content
+ const update = super.prepare(s, t)
+
+ // move comment to config field
+ const configKey = this.options.config.__CONFIG_KEY__
+ const header = this.header()
+ const headerKey = Object.keys(header)[0]
+ update[configKey] = setFirst(header, update[configKey])
+ delete update[headerKey]
+
+ return update
+ }
+
+ async write (s) {
+ const pkg = await NpmPackageJson.load(dirname(this.target))
+ pkg.update(s)
+ traverse(pkg.content, (keys, value) => {
+ if (value === this.DELETE) {
+ return unset(pkg.content, keys)
+ }
+ })
+ pkg.save()
+ }
+}
+
+const Parsers = {
+ Base,
+ Gitignore,
+ Js,
+ Ini,
+ Markdown,
+ Yml,
+ Json,
+ JsonMerge,
+ PackageJson,
+}
+
+const parserLookup = Object.values(Parsers)
+
+const getParser = (file) => {
+ const base = basename(file).toLowerCase()
+ const ext = extname(file).slice(1).toLowerCase()
+
+ return parserLookup.find((p) => p.types.includes(base))
+ || parserLookup.find((p) => p.types.includes(ext))
+ || Parsers.Base
+}
+
+module.exports = getParser
+module.exports.Parsers = Parsers
diff --git a/lib/util/template.js b/lib/util/template.js
new file mode 100644
index 00000000..4c89cdd3
--- /dev/null
+++ b/lib/util/template.js
@@ -0,0 +1,41 @@
+const Handlebars = require('handlebars')
+const { basename, extname, join } = require('path')
+const fs = require('fs')
+const DELETE = '__DELETE__'
+
+const partialName = (s) =>
+ basename(s, extname(s)).replace(/-([a-z])/g, (_, g) => g.toUpperCase())
+
+const setupHandlebars = (partialsDir) => {
+ Handlebars.registerHelper('obj', ({ hash }) => hash)
+ Handlebars.registerHelper('json', (c) => JSON.stringify(c))
+ Handlebars.registerHelper('del', () => JSON.stringify(DELETE))
+
+ // Load all content files as camelcase partial names
+ for (const f of fs.readdirSync(join(partialsDir))) {
+ Handlebars.registerPartial(
+ partialName(f),
+ fs.readFileSync(join(partialsDir, f)).toString()
+ )
+ }
+}
+
+const cache = new Map()
+
+const template = (str, { config, ...options }) => {
+ if (cache.size === 0) {
+ setupHandlebars(config.sourceDir)
+ }
+
+ let t = cache.get(str)
+ if (t == null) {
+ t = Handlebars.compile(str, { strict: true })
+ cache.set(str, t)
+ }
+
+ // merge in config as top level data in templates
+ return t({ ...options, ...config })
+}
+
+module.exports = template
+module.exports.DELETE = DELETE
diff --git a/map.js b/map.js
deleted file mode 100644
index ab540b2e..00000000
--- a/map.js
+++ /dev/null
@@ -1,3 +0,0 @@
-module.exports = testFile => testFile
- .replace(/^test\/bin\//, 'bin/')
- .replace(/^test\//, 'lib/')
diff --git a/package.json b/package.json
index 3901cf5c..84303d72 100644
--- a/package.json
+++ b/package.json
@@ -2,27 +2,26 @@
"name": "@npmcli/template-oss",
"version": "2.9.2",
"description": "templated files used in npm CLI team oss projects",
- "main": "lib/index.js",
+ "main": "lib/content/index.js",
"bin": {
- "npm-template-check": "bin/npm-template-check.js",
- "npm-template-copy": "bin/postinstall.js"
+ "template-oss-apply": "bin/apply.js",
+ "template-oss-check": "bin/check.js"
},
"scripts": {
"lint": "eslint \"**/*.js\"",
"lintfix": "npm run lint -- --fix",
- "postinstall": "node bin/postinstall.js",
- "postlint": "npm-template-check",
"posttest": "npm run lint",
"postversion": "npm publish",
"prepublishOnly": "git push origin --follow-tags",
"preversion": "npm test",
"snap": "tap",
"test": "tap",
- "template-copy": "npm-template-copy --force"
+ "template-oss-apply": "template-oss-apply --force",
+ "postlint": "template-oss-check"
},
"repository": {
"type": "git",
- "url": "git+https://github.com/npm/template-oss.git"
+ "url": "https://github.com/npm/template-oss.git"
},
"keywords": [
"npm",
@@ -32,31 +31,35 @@
"license": "ISC",
"dependencies": {
"@npmcli/fs": "^2.0.1",
+ "@npmcli/git": "^3.0.0",
"@npmcli/map-workspaces": "^2.0.2",
"@npmcli/package-json": "^1.0.1",
+ "diff": "^5.0.0",
+ "handlebars": "^4.7.7",
+ "hosted-git-info": "^4.1.0",
"json-parse-even-better-errors": "^2.3.1",
- "which": "^2.0.2"
+ "just-diff": "^5.0.1",
+ "lodash": "^4.17.21",
+ "proc-log": "^2.0.0",
+ "semver": "^7.3.5",
+ "yaml": "^2.0.0-10"
},
"files": [
- "bin",
- "lib"
+ "bin/",
+ "lib/"
],
"devDependencies": {
- "@npmcli/eslint-config": "*",
- "@npmcli/promise-spawn": "^2.0.1",
+ "@npmcli/eslint-config": "^3.0.0",
"@npmcli/template-oss": "file:./",
- "eslint": "^8.11.0",
- "eslint-plugin-node": "^11.1.0",
- "tap": "*"
+ "tap": "^15.1.6"
},
- "peerDependencies": {
- "@npmcli/eslint-config": "^2.0.0",
- "tap": "^15.0.9"
- },
- "tap": {
- "coverage-map": "map.js"
+ "templateOSS": {
+ "//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten."
},
"engines": {
- "node": "^12.13.0 || ^14.15.0 || >=16"
- }
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ },
+ "eslintIgnore": [
+ "lib/content/"
+ ]
}
diff --git a/tap-snapshots/test/apply/full-content.js.test.cjs b/tap-snapshots/test/apply/full-content.js.test.cjs
new file mode 100644
index 00000000..2dd69a26
--- /dev/null
+++ b/tap-snapshots/test/apply/full-content.js.test.cjs
@@ -0,0 +1,1521 @@
+/* IMPORTANT
+ * This snapshot file is auto-generated, but designed for humans.
+ * It should be checked into source control and tracked carefully.
+ * Re-generate by setting TAP_SNAPSHOT=1 and running tests.
+ * Make sure to inspect the output below. Do not ignore changes!
+ */
+'use strict'
+exports[`test/apply/full-content.js TAP default > expect resolving Promise 1`] = `
+.commitlintrc.js
+========================================
+/* This file is automatically added by @npmcli/template-oss. Do not edit. */
+
+module.exports = {
+ extends: ['@commitlint/config-conventional'],
+ rules: {
+ 'type-enum': [2, 'always', ['feat', 'fix', 'docs', 'deps', 'chore']],
+ 'header-max-length': [2, 'always', 80],
+ 'subject-case': [0, 'always', ['lower-case', 'sentence-case', 'start-case']],
+ },
+}
+
+.eslintrc.js
+========================================
+/* This file is automatically added by @npmcli/template-oss. Do not edit. */
+
+const { readdirSync: readdir } = require('fs')
+
+const localConfigs = readdir(__dirname)
+ .filter((file) => file.startsWith('.eslintrc.local.'))
+ .map((file) => \`./\${file}\`)
+
+module.exports = {
+ extends: [
+ '@npmcli',
+ ...localConfigs,
+ ],
+}
+
+.github/CODEOWNERS
+========================================
+# This file is automatically added by @npmcli/template-oss. Do not edit.
+
+* @npm/cli-team
+
+.github/ISSUE_TEMPLATE/bug.yml
+========================================
+# This file is automatically added by @npmcli/template-oss. Do not edit.
+
+name: Bug
+description: File a bug/issue
+title: "[BUG] "
+labels: [ Bug, Needs Triage ]
+
+body:
+ - type: checkboxes
+ attributes:
+ label: Is there an existing issue for this?
+ description: Please [search here](./issues) to see if an issue already exists for your problem.
+ options:
+ - label: I have searched the existing issues
+ required: true
+ - type: textarea
+ attributes:
+ label: Current Behavior
+ description: A clear & concise description of what you're experiencing.
+ validations:
+ required: false
+ - type: textarea
+ attributes:
+ label: Expected Behavior
+ description: A clear & concise description of what you expected to happen.
+ validations:
+ required: false
+ - type: textarea
+ attributes:
+ label: Steps To Reproduce
+ description: Steps to reproduce the behavior.
+ value: |
+ 1. In this environment...
+ 2. With this config...
+ 3. Run '...'
+ 4. See error...
+ validations:
+ required: false
+ - type: textarea
+ attributes:
+ label: Environment
+ description: |
+ examples:
+ - **npm**: 7.6.3
+ - **Node**: 13.14.0
+ - **OS**: Ubuntu 20.04
+ - **platform**: Macbook Pro
+ value: |
+ - npm:
+ - Node:
+ - OS:
+ - platform:
+ validations:
+ required: false
+
+.github/ISSUE_TEMPLATE/config.yml
+========================================
+# This file is automatically added by @npmcli/template-oss. Do not edit.
+
+blank_issues_enabled: true
+
+.github/dependabot.yml
+========================================
+# This file is automatically added by @npmcli/template-oss. Do not edit.
+
+version: 2
+
+updates:
+ - package-ecosystem: npm
+ directory: "/"
+ schedule:
+ interval: daily
+ allow:
+ - dependency-type: direct
+ versioning-strategy: increase
+ commit-message:
+ prefix: deps
+ prefix-development: chore
+ labels:
+ - "Dependencies"
+
+.github/workflows/audit.yml
+========================================
+# This file is automatically added by @npmcli/template-oss. Do not edit.
+
+name: Audit
+
+on:
+ workflow_dispatch: null
+ schedule:
+ # "At 01:00 on Monday" https://crontab.guru/#0_1_*_*_1
+ - cron: "0 1 * * 1"
+
+jobs:
+ audit:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - name: Setup git user
+ run: |
+ git config --global user.email "ops+npm-cli@npmjs.com"
+ git config --global user.name "npm cli ops bot"
+ - uses: actions/setup-node@v3
+ with:
+ node-version: 16.x
+ - name: Update to workable npm (windows)
+ # node 12 and 14 ship with npm@6, which is known to fail when updating itself in windows
+ if: matrix.platform.os == 'windows-latest' && (startsWith(matrix.node-version, '12') || startsWith(matrix.node-version, '14'))
+ run: |
+ curl -sO https://registry.npmjs.org/npm/-/npm-7.5.4.tgz
+ tar xf npm-7.5.4.tgz
+ cd package
+ node lib/npm.js install --no-fund --no-audit -g ../npm-7.5.4.tgz
+ cd ..
+ rmdir /s /q package
+ - name: Update npm to 7
+ # If we do test on npm 10 it needs npm7
+ if: matrix.node-version <= 10
+ run: npm i --prefer-online --no-fund --no-audit -g npm@7
+ - name: Update npm to latest
+ if: matrix.node-version > 10
+ run: npm i --prefer-online --no-fund --no-audit -g npm@latest
+ - run: npm -v
+ - run: npm i --package-lock
+ - run: npm audit
+
+.github/workflows/ci.yml
+========================================
+# This file is automatically added by @npmcli/template-oss. Do not edit.
+
+name: CI
+
+on:
+ workflow_dispatch: null
+ pull_request:
+ branches:
+ - '*'
+ push:
+ branches:
+ - main
+ - latest
+ schedule:
+ # "At 02:00 on Monday" https://crontab.guru/#0_2_*_*_1
+ - cron: "0 2 * * 1"
+
+jobs:
+ lint:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - name: Setup git user
+ run: |
+ git config --global user.email "ops+npm-cli@npmjs.com"
+ git config --global user.name "npm cli ops bot"
+ - uses: actions/setup-node@v3
+ with:
+ node-version: 16.x
+ - name: Update to workable npm (windows)
+ # node 12 and 14 ship with npm@6, which is known to fail when updating itself in windows
+ if: matrix.platform.os == 'windows-latest' && (startsWith(matrix.node-version, '12') || startsWith(matrix.node-version, '14'))
+ run: |
+ curl -sO https://registry.npmjs.org/npm/-/npm-7.5.4.tgz
+ tar xf npm-7.5.4.tgz
+ cd package
+ node lib/npm.js install --no-fund --no-audit -g ../npm-7.5.4.tgz
+ cd ..
+ rmdir /s /q package
+ - name: Update npm to 7
+ # If we do test on npm 10 it needs npm7
+ if: matrix.node-version <= 10
+ run: npm i --prefer-online --no-fund --no-audit -g npm@7
+ - name: Update npm to latest
+ if: matrix.node-version > 10
+ run: npm i --prefer-online --no-fund --no-audit -g npm@latest
+ - run: npm -v
+ - run: npm i
+ - run: npm run lint
+
+ test:
+ strategy:
+ fail-fast: false
+ matrix:
+ node-version:
+ - 12.13.0
+ - 12.x
+ - 14.15.0
+ - 14.x
+ - 16.0.0
+ - 16.x
+ platform:
+ - os: ubuntu-latest
+ shell: bash
+ - os: macos-latest
+ shell: bash
+ - os: windows-latest
+ shell: cmd
+ runs-on: \${{ matrix.platform.os }}
+ defaults:
+ run:
+ shell: \${{ matrix.platform.shell }}
+ steps:
+ - uses: actions/checkout@v3
+ - name: Setup git user
+ run: |
+ git config --global user.email "ops+npm-cli@npmjs.com"
+ git config --global user.name "npm cli ops bot"
+ - uses: actions/setup-node@v3
+ with:
+ node-version: 16.x
+ - name: Update to workable npm (windows)
+ # node 12 and 14 ship with npm@6, which is known to fail when updating itself in windows
+ if: matrix.platform.os == 'windows-latest' && (startsWith(matrix.node-version, '12') || startsWith(matrix.node-version, '14'))
+ run: |
+ curl -sO https://registry.npmjs.org/npm/-/npm-7.5.4.tgz
+ tar xf npm-7.5.4.tgz
+ cd package
+ node lib/npm.js install --no-fund --no-audit -g ../npm-7.5.4.tgz
+ cd ..
+ rmdir /s /q package
+ - name: Update npm to 7
+ # If we do test on npm 10 it needs npm7
+ if: matrix.node-version <= 10
+ run: npm i --prefer-online --no-fund --no-audit -g npm@7
+ - name: Update npm to latest
+ if: matrix.node-version > 10
+ run: npm i --prefer-online --no-fund --no-audit -g npm@latest
+ - run: npm -v
+ - run: npm i
+ - run: npm test --ignore-scripts
+
+.github/workflows/codeql-analysis.yml
+========================================
+# This file is automatically added by @npmcli/template-oss. Do not edit.
+
+name: "CodeQL"
+
+on:
+ push:
+ branches:
+ - main
+ - latest
+ pull_request:
+ # The branches below must be a subset of the branches above
+ branches:
+ - main
+ - latest
+ schedule:
+ # "At 03:00 on Monday" https://crontab.guru/#0_3_*_*_1
+ - cron: "0 3 * * 1"
+
+jobs:
+ analyze:
+ name: Analyze
+ runs-on: ubuntu-latest
+ permissions:
+ actions: read
+ contents: read
+ security-events: write
+
+ strategy:
+ fail-fast: false
+ matrix:
+ language: [ javascript ]
+
+ steps:
+ - uses: actions/checkout@v3
+ - name: Setup git user
+ run: |
+ git config --global user.email "ops+npm-cli@npmjs.com"
+ git config --global user.name "npm cli ops bot"
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v1
+ with:
+ languages: \${{ matrix.language }}
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v1
+
+.github/workflows/post-dependabot.yml
+========================================
+# This file is automatically added by @npmcli/template-oss. Do not edit.
+
+name: Post Dependabot Actions
+
+on: pull_request
+
+# https://docs.github.com/en/rest/overview/permissions-required-for-github-apps
+permissions:
+ contents: write
+
+jobs:
+ Install:
+ runs-on: ubuntu-latest
+ if: github.actor == 'dependabot[bot]'
+ steps:
+ - uses: actions/checkout@v3
+ - name: Setup git user
+ run: |
+ git config --global user.email "ops+npm-cli@npmjs.com"
+ git config --global user.name "npm cli ops bot"
+ - uses: actions/setup-node@v3
+ with:
+ node-version: 16.x
+ - name: Update to workable npm (windows)
+ # node 12 and 14 ship with npm@6, which is known to fail when updating itself in windows
+ if: matrix.platform.os == 'windows-latest' && (startsWith(matrix.node-version, '12') || startsWith(matrix.node-version, '14'))
+ run: |
+ curl -sO https://registry.npmjs.org/npm/-/npm-7.5.4.tgz
+ tar xf npm-7.5.4.tgz
+ cd package
+ node lib/npm.js install --no-fund --no-audit -g ../npm-7.5.4.tgz
+ cd ..
+ rmdir /s /q package
+ - name: Update npm to 7
+ # If we do test on npm 10 it needs npm7
+ if: matrix.node-version <= 10
+ run: npm i --prefer-online --no-fund --no-audit -g npm@7
+ - name: Update npm to latest
+ if: matrix.node-version > 10
+ run: npm i --prefer-online --no-fund --no-audit -g npm@latest
+ - run: npm -v
+ - name: Dependabot metadata
+ id: metadata
+ uses: dependabot/fetch-metadata@v1.1.1
+ with:
+ github-token: "\${{ secrets.GITHUB_TOKEN }}"
+ - name: npm install and commit
+ if: contains(steps.metadata.outputs.dependency-names, '@npmcli/template-oss')
+ env:
+ GITHUB_TOKEN: \${{ secrets.GITHUB_TOKEN }}
+ run: |
+ gh pr checkout \${{ github.event.pull_request.number }}
+ npm install --no-scripts
+ npm run template-oss-apply
+ git add .
+ git commit -am "chore: postinstall for dependabot template-oss PR"
+ git push
+
+.github/workflows/pull-request.yml
+========================================
+# This file is automatically added by @npmcli/template-oss. Do not edit.
+
+name: Pull Request Linting
+
+on:
+ pull_request:
+ types:
+ - opened
+ - reopened
+ - edited
+ - synchronize
+
+jobs:
+ check:
+ name: Check PR Title or Commits
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+ - name: Setup git user
+ run: |
+ git config --global user.email "ops+npm-cli@npmjs.com"
+ git config --global user.name "npm cli ops bot"
+ - uses: actions/setup-node@v3
+ with:
+ node-version: 16.x
+ - name: Update to workable npm (windows)
+ # node 12 and 14 ship with npm@6, which is known to fail when updating itself in windows
+ if: matrix.platform.os == 'windows-latest' && (startsWith(matrix.node-version, '12') || startsWith(matrix.node-version, '14'))
+ run: |
+ curl -sO https://registry.npmjs.org/npm/-/npm-7.5.4.tgz
+ tar xf npm-7.5.4.tgz
+ cd package
+ node lib/npm.js install --no-fund --no-audit -g ../npm-7.5.4.tgz
+ cd ..
+ rmdir /s /q package
+ - name: Update npm to 7
+ # If we do test on npm 10 it needs npm7
+ if: matrix.node-version <= 10
+ run: npm i --prefer-online --no-fund --no-audit -g npm@7
+ - name: Update npm to latest
+ if: matrix.node-version > 10
+ run: npm i --prefer-online --no-fund --no-audit -g npm@latest
+ - run: npm -v
+ - name: Install deps
+ run: |
+ npm i -D @commitlint/cli @commitlint/config-conventional
+ - name: Check commits OR PR title
+ env:
+ PR_TITLE: \${{ github.event.pull_request.title }}
+ run: |
+ 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
+
+.github/workflows/release-please.yml
+========================================
+# This file is automatically added by @npmcli/template-oss. Do not edit.
+
+name: Release Please
+
+on:
+ push:
+ branches:
+ - main
+ - latest
+
+jobs:
+ release-please:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: google-github-actions/release-please-action@v3
+ id: release
+ with:
+ release-type: node
+ changelog-type: github
+ changelog-types: >
+ [
+ {"type":"feat","section":"Features","hidden":false},
+ {"type":"fix","section":"Bug Fixes","hidden":false},
+ {"type":"docs","section":"Documentation","hidden":false},
+ {"type":"deps","section":"Dependencies","hidden":false},
+ {"type":"chore","hidden":true}
+ ]
+
+.gitignore
+========================================
+# This file is automatically added by @npmcli/template-oss. Do not edit.
+
+# ignore everything in the root
+/*
+
+# keep these
+!/.eslintrc.local.*
+!**/.gitignore
+!/docs/
+!/tap-snapshots/
+!/test/
+!/map.js
+!/scripts/
+!/README*
+!/LICENSE*
+!/CHANGELOG*
+!/.commitlintrc.js
+!/.eslintrc.js
+!/.github/
+!/.gitignore
+!/.npmrc
+!/SECURITY.md
+!/bin/
+!/lib/
+!/package.json
+
+.npmrc
+========================================
+; This file is automatically added by @npmcli/template-oss. Do not edit.
+
+package-lock=false
+
+SECURITY.md
+========================================
+
+
+Please send vulnerability reports through [hackerone](https://hackerone.com/github).
+
+package.json
+========================================
+{
+ "name": "testpkg",
+ "scripts": {
+ "lint": "eslint /"**/*.js/"",
+ "postlint": "template-oss-check",
+ "template-oss-apply": "template-oss-apply --force",
+ "lintfix": "npm run lint -- --fix",
+ "preversion": "npm test",
+ "postversion": "npm publish",
+ "prepublishOnly": "git push origin --follow-tags",
+ "snap": "tap",
+ "test": "tap",
+ "posttest": "npm run lint"
+ },
+ "author": "GitHub Inc.",
+ "files": [
+ "bin/",
+ "lib/"
+ ],
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ },
+ "templateOSS": {
+ "//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.",
+ "version": "2.9.2"
+ }
+}
+`
+
+exports[`test/apply/full-content.js TAP workspaces + everything > expect resolving Promise 1`] = `
+.commitlintrc.js
+========================================
+/* This file is automatically added by @npmcli/template-oss. Do not edit. */
+
+module.exports = {
+ extends: ['@commitlint/config-conventional'],
+ rules: {
+ 'type-enum': [2, 'always', ['feat', 'fix', 'docs', 'deps', 'chore']],
+ 'header-max-length': [2, 'always', 80],
+ 'subject-case': [0, 'always', ['lower-case', 'sentence-case', 'start-case']],
+ },
+}
+
+.eslintrc.js
+========================================
+/* This file is automatically added by @npmcli/template-oss. Do not edit. */
+
+const { readdirSync: readdir } = require('fs')
+
+const localConfigs = readdir(__dirname)
+ .filter((file) => file.startsWith('.eslintrc.local.'))
+ .map((file) => \`./\${file}\`)
+
+module.exports = {
+ extends: [
+ '@npmcli',
+ ...localConfigs,
+ ],
+}
+
+.eslintrc.local.yml
+========================================
+KEEP
+
+.github/CODEOWNERS
+========================================
+# This file is automatically added by @npmcli/template-oss. Do not edit.
+
+* @npm/cli-team
+
+.github/ISSUE_TEMPLATE/bug.yml
+========================================
+# This file is automatically added by @npmcli/template-oss. Do not edit.
+
+name: Bug
+description: File a bug/issue
+title: "[BUG] "
+labels: [ Bug, Needs Triage ]
+
+body:
+ - type: checkboxes
+ attributes:
+ label: Is there an existing issue for this?
+ description: Please [search here](./issues) to see if an issue already exists for your problem.
+ options:
+ - label: I have searched the existing issues
+ required: true
+ - type: textarea
+ attributes:
+ label: Current Behavior
+ description: A clear & concise description of what you're experiencing.
+ validations:
+ required: false
+ - type: textarea
+ attributes:
+ label: Expected Behavior
+ description: A clear & concise description of what you expected to happen.
+ validations:
+ required: false
+ - type: textarea
+ attributes:
+ label: Steps To Reproduce
+ description: Steps to reproduce the behavior.
+ value: |
+ 1. In this environment...
+ 2. With this config...
+ 3. Run '...'
+ 4. See error...
+ validations:
+ required: false
+ - type: textarea
+ attributes:
+ label: Environment
+ description: |
+ examples:
+ - **npm**: 7.6.3
+ - **Node**: 13.14.0
+ - **OS**: Ubuntu 20.04
+ - **platform**: Macbook Pro
+ value: |
+ - npm:
+ - Node:
+ - OS:
+ - platform:
+ validations:
+ required: false
+
+.github/ISSUE_TEMPLATE/config.yml
+========================================
+# This file is automatically added by @npmcli/template-oss. Do not edit.
+
+blank_issues_enabled: true
+
+.github/dependabot.yml
+========================================
+# This file is automatically added by @npmcli/template-oss. Do not edit.
+
+version: 2
+
+updates:
+ - package-ecosystem: npm
+ directory: "/"
+ schedule:
+ interval: daily
+ allow:
+ - dependency-type: direct
+ versioning-strategy: increase
+ commit-message:
+ prefix: deps
+ prefix-development: chore
+ labels:
+ - "Dependencies"
+
+.github/workflows/audit.yml
+========================================
+# This file is automatically added by @npmcli/template-oss. Do not edit.
+
+name: Audit
+
+on:
+ workflow_dispatch: null
+ schedule:
+ # "At 01:00 on Monday" https://crontab.guru/#0_1_*_*_1
+ - cron: "0 1 * * 1"
+
+jobs:
+ audit:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - name: Setup git user
+ run: |
+ git config --global user.email "ops+npm-cli@npmjs.com"
+ git config --global user.name "npm cli ops bot"
+ - uses: actions/setup-node@v3
+ with:
+ node-version: 16.x
+ - name: Update to workable npm (windows)
+ # node 12 and 14 ship with npm@6, which is known to fail when updating itself in windows
+ if: matrix.platform.os == 'windows-latest' && (startsWith(matrix.node-version, '12') || startsWith(matrix.node-version, '14'))
+ run: |
+ curl -sO https://registry.npmjs.org/npm/-/npm-7.5.4.tgz
+ tar xf npm-7.5.4.tgz
+ cd package
+ node lib/npm.js install --no-fund --no-audit -g ../npm-7.5.4.tgz
+ cd ..
+ rmdir /s /q package
+ - name: Update npm to 7
+ # If we do test on npm 10 it needs npm7
+ if: matrix.node-version <= 10
+ run: npm i --prefer-online --no-fund --no-audit -g npm@7
+ - name: Update npm to latest
+ if: matrix.node-version > 10
+ run: npm i --prefer-online --no-fund --no-audit -g npm@latest
+ - run: npm -v
+ - run: npm i --package-lock
+ - run: npm audit
+
+.github/workflows/ci-bbb.yml
+========================================
+# This file is automatically added by @npmcli/template-oss. Do not edit.
+
+name: CI - bbb
+
+on:
+ workflow_dispatch: null
+ pull_request:
+ branches:
+ - '*'
+ paths:
+ - workspaces/b
+ push:
+ branches:
+ - main
+ - latest
+ paths:
+ - workspaces/b
+ schedule:
+ # "At 02:00 on Monday" https://crontab.guru/#0_2_*_*_1
+ - cron: "0 2 * * 1"
+
+jobs:
+ lint:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - name: Setup git user
+ run: |
+ git config --global user.email "ops+npm-cli@npmjs.com"
+ git config --global user.name "npm cli ops bot"
+ - uses: actions/setup-node@v3
+ with:
+ node-version: 16.x
+ - name: Update to workable npm (windows)
+ # node 12 and 14 ship with npm@6, which is known to fail when updating itself in windows
+ if: matrix.platform.os == 'windows-latest' && (startsWith(matrix.node-version, '12') || startsWith(matrix.node-version, '14'))
+ run: |
+ curl -sO https://registry.npmjs.org/npm/-/npm-7.5.4.tgz
+ tar xf npm-7.5.4.tgz
+ cd package
+ node lib/npm.js install --no-fund --no-audit -g ../npm-7.5.4.tgz
+ cd ..
+ rmdir /s /q package
+ - name: Update npm to 7
+ # If we do test on npm 10 it needs npm7
+ if: matrix.node-version <= 10
+ run: npm i --prefer-online --no-fund --no-audit -g npm@7
+ - name: Update npm to latest
+ if: matrix.node-version > 10
+ run: npm i --prefer-online --no-fund --no-audit -g npm@latest
+ - run: npm -v
+ - run: npm i
+ - run: npm run lint -w bbb
+
+ test:
+ strategy:
+ fail-fast: false
+ matrix:
+ node-version:
+ - 12.13.0
+ - 12.x
+ - 14.15.0
+ - 14.x
+ - 16.0.0
+ - 16.x
+ platform:
+ - os: ubuntu-latest
+ shell: bash
+ - os: macos-latest
+ shell: bash
+ - os: windows-latest
+ shell: cmd
+ runs-on: \${{ matrix.platform.os }}
+ defaults:
+ run:
+ shell: \${{ matrix.platform.shell }}
+ steps:
+ - uses: actions/checkout@v3
+ - name: Setup git user
+ run: |
+ git config --global user.email "ops+npm-cli@npmjs.com"
+ git config --global user.name "npm cli ops bot"
+ - uses: actions/setup-node@v3
+ with:
+ node-version: 16.x
+ - name: Update to workable npm (windows)
+ # node 12 and 14 ship with npm@6, which is known to fail when updating itself in windows
+ if: matrix.platform.os == 'windows-latest' && (startsWith(matrix.node-version, '12') || startsWith(matrix.node-version, '14'))
+ run: |
+ curl -sO https://registry.npmjs.org/npm/-/npm-7.5.4.tgz
+ tar xf npm-7.5.4.tgz
+ cd package
+ node lib/npm.js install --no-fund --no-audit -g ../npm-7.5.4.tgz
+ cd ..
+ rmdir /s /q package
+ - name: Update npm to 7
+ # If we do test on npm 10 it needs npm7
+ if: matrix.node-version <= 10
+ run: npm i --prefer-online --no-fund --no-audit -g npm@7
+ - name: Update npm to latest
+ if: matrix.node-version > 10
+ run: npm i --prefer-online --no-fund --no-audit -g npm@latest
+ - run: npm -v
+ - run: npm i
+ - run: npm test --ignore-scripts -w bbb
+
+.github/workflows/ci-name-aaaa.yml
+========================================
+# This file is automatically added by @npmcli/template-oss. Do not edit.
+
+name: CI - @name/aaaa
+
+on:
+ workflow_dispatch: null
+ pull_request:
+ branches:
+ - '*'
+ paths:
+ - workspaces/a
+ push:
+ branches:
+ - main
+ - latest
+ paths:
+ - workspaces/a
+ schedule:
+ # "At 02:00 on Monday" https://crontab.guru/#0_2_*_*_1
+ - cron: "0 2 * * 1"
+
+jobs:
+ lint:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - name: Setup git user
+ run: |
+ git config --global user.email "ops+npm-cli@npmjs.com"
+ git config --global user.name "npm cli ops bot"
+ - uses: actions/setup-node@v3
+ with:
+ node-version: 16.x
+ - name: Update to workable npm (windows)
+ # node 12 and 14 ship with npm@6, which is known to fail when updating itself in windows
+ if: matrix.platform.os == 'windows-latest' && (startsWith(matrix.node-version, '12') || startsWith(matrix.node-version, '14'))
+ run: |
+ curl -sO https://registry.npmjs.org/npm/-/npm-7.5.4.tgz
+ tar xf npm-7.5.4.tgz
+ cd package
+ node lib/npm.js install --no-fund --no-audit -g ../npm-7.5.4.tgz
+ cd ..
+ rmdir /s /q package
+ - name: Update npm to 7
+ # If we do test on npm 10 it needs npm7
+ if: matrix.node-version <= 10
+ run: npm i --prefer-online --no-fund --no-audit -g npm@7
+ - name: Update npm to latest
+ if: matrix.node-version > 10
+ run: npm i --prefer-online --no-fund --no-audit -g npm@latest
+ - run: npm -v
+ - run: npm i
+ - run: npm run lint -w @name/aaaa
+
+ test:
+ strategy:
+ fail-fast: false
+ matrix:
+ node-version:
+ - 12.13.0
+ - 12.x
+ - 14.15.0
+ - 14.x
+ - 16.0.0
+ - 16.x
+ platform:
+ - os: ubuntu-latest
+ shell: bash
+ - os: macos-latest
+ shell: bash
+ - os: windows-latest
+ shell: cmd
+ runs-on: \${{ matrix.platform.os }}
+ defaults:
+ run:
+ shell: \${{ matrix.platform.shell }}
+ steps:
+ - uses: actions/checkout@v3
+ - name: Setup git user
+ run: |
+ git config --global user.email "ops+npm-cli@npmjs.com"
+ git config --global user.name "npm cli ops bot"
+ - uses: actions/setup-node@v3
+ with:
+ node-version: 16.x
+ - name: Update to workable npm (windows)
+ # node 12 and 14 ship with npm@6, which is known to fail when updating itself in windows
+ if: matrix.platform.os == 'windows-latest' && (startsWith(matrix.node-version, '12') || startsWith(matrix.node-version, '14'))
+ run: |
+ curl -sO https://registry.npmjs.org/npm/-/npm-7.5.4.tgz
+ tar xf npm-7.5.4.tgz
+ cd package
+ node lib/npm.js install --no-fund --no-audit -g ../npm-7.5.4.tgz
+ cd ..
+ rmdir /s /q package
+ - name: Update npm to 7
+ # If we do test on npm 10 it needs npm7
+ if: matrix.node-version <= 10
+ run: npm i --prefer-online --no-fund --no-audit -g npm@7
+ - name: Update npm to latest
+ if: matrix.node-version > 10
+ run: npm i --prefer-online --no-fund --no-audit -g npm@latest
+ - run: npm -v
+ - run: npm i
+ - run: npm test --ignore-scripts -w @name/aaaa
+
+.github/workflows/ci.yml
+========================================
+# This file is automatically added by @npmcli/template-oss. Do not edit.
+
+name: CI
+
+on:
+ workflow_dispatch: null
+ pull_request:
+ branches:
+ - '*'
+ push:
+ branches:
+ - main
+ - latest
+ schedule:
+ # "At 02:00 on Monday" https://crontab.guru/#0_2_*_*_1
+ - cron: "0 2 * * 1"
+
+jobs:
+ lint:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - name: Setup git user
+ run: |
+ git config --global user.email "ops+npm-cli@npmjs.com"
+ git config --global user.name "npm cli ops bot"
+ - uses: actions/setup-node@v3
+ with:
+ node-version: 16.x
+ - name: Update to workable npm (windows)
+ # node 12 and 14 ship with npm@6, which is known to fail when updating itself in windows
+ if: matrix.platform.os == 'windows-latest' && (startsWith(matrix.node-version, '12') || startsWith(matrix.node-version, '14'))
+ run: |
+ curl -sO https://registry.npmjs.org/npm/-/npm-7.5.4.tgz
+ tar xf npm-7.5.4.tgz
+ cd package
+ node lib/npm.js install --no-fund --no-audit -g ../npm-7.5.4.tgz
+ cd ..
+ rmdir /s /q package
+ - name: Update npm to 7
+ # If we do test on npm 10 it needs npm7
+ if: matrix.node-version <= 10
+ run: npm i --prefer-online --no-fund --no-audit -g npm@7
+ - name: Update npm to latest
+ if: matrix.node-version > 10
+ run: npm i --prefer-online --no-fund --no-audit -g npm@latest
+ - run: npm -v
+ - run: npm i
+ - run: npm run lint
+
+ test:
+ strategy:
+ fail-fast: false
+ matrix:
+ node-version:
+ - 12.13.0
+ - 12.x
+ - 14.15.0
+ - 14.x
+ - 16.0.0
+ - 16.x
+ platform:
+ - os: ubuntu-latest
+ shell: bash
+ - os: macos-latest
+ shell: bash
+ - os: windows-latest
+ shell: cmd
+ runs-on: \${{ matrix.platform.os }}
+ defaults:
+ run:
+ shell: \${{ matrix.platform.shell }}
+ steps:
+ - uses: actions/checkout@v3
+ - name: Setup git user
+ run: |
+ git config --global user.email "ops+npm-cli@npmjs.com"
+ git config --global user.name "npm cli ops bot"
+ - uses: actions/setup-node@v3
+ with:
+ node-version: 16.x
+ - name: Update to workable npm (windows)
+ # node 12 and 14 ship with npm@6, which is known to fail when updating itself in windows
+ if: matrix.platform.os == 'windows-latest' && (startsWith(matrix.node-version, '12') || startsWith(matrix.node-version, '14'))
+ run: |
+ curl -sO https://registry.npmjs.org/npm/-/npm-7.5.4.tgz
+ tar xf npm-7.5.4.tgz
+ cd package
+ node lib/npm.js install --no-fund --no-audit -g ../npm-7.5.4.tgz
+ cd ..
+ rmdir /s /q package
+ - name: Update npm to 7
+ # If we do test on npm 10 it needs npm7
+ if: matrix.node-version <= 10
+ run: npm i --prefer-online --no-fund --no-audit -g npm@7
+ - name: Update npm to latest
+ if: matrix.node-version > 10
+ run: npm i --prefer-online --no-fund --no-audit -g npm@latest
+ - run: npm -v
+ - run: npm i
+ - run: npm test --ignore-scripts
+
+.github/workflows/codeql-analysis.yml
+========================================
+# This file is automatically added by @npmcli/template-oss. Do not edit.
+
+name: "CodeQL"
+
+on:
+ push:
+ branches:
+ - main
+ - latest
+ pull_request:
+ # The branches below must be a subset of the branches above
+ branches:
+ - main
+ - latest
+ schedule:
+ # "At 03:00 on Monday" https://crontab.guru/#0_3_*_*_1
+ - cron: "0 3 * * 1"
+
+jobs:
+ analyze:
+ name: Analyze
+ runs-on: ubuntu-latest
+ permissions:
+ actions: read
+ contents: read
+ security-events: write
+
+ strategy:
+ fail-fast: false
+ matrix:
+ language: [ javascript ]
+
+ steps:
+ - uses: actions/checkout@v3
+ - name: Setup git user
+ run: |
+ git config --global user.email "ops+npm-cli@npmjs.com"
+ git config --global user.name "npm cli ops bot"
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v1
+ with:
+ languages: \${{ matrix.language }}
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v1
+
+.github/workflows/post-dependabot.yml
+========================================
+# This file is automatically added by @npmcli/template-oss. Do not edit.
+
+name: Post Dependabot Actions
+
+on: pull_request
+
+# https://docs.github.com/en/rest/overview/permissions-required-for-github-apps
+permissions:
+ contents: write
+
+jobs:
+ Install:
+ runs-on: ubuntu-latest
+ if: github.actor == 'dependabot[bot]'
+ steps:
+ - uses: actions/checkout@v3
+ - name: Setup git user
+ run: |
+ git config --global user.email "ops+npm-cli@npmjs.com"
+ git config --global user.name "npm cli ops bot"
+ - uses: actions/setup-node@v3
+ with:
+ node-version: 16.x
+ - name: Update to workable npm (windows)
+ # node 12 and 14 ship with npm@6, which is known to fail when updating itself in windows
+ if: matrix.platform.os == 'windows-latest' && (startsWith(matrix.node-version, '12') || startsWith(matrix.node-version, '14'))
+ run: |
+ curl -sO https://registry.npmjs.org/npm/-/npm-7.5.4.tgz
+ tar xf npm-7.5.4.tgz
+ cd package
+ node lib/npm.js install --no-fund --no-audit -g ../npm-7.5.4.tgz
+ cd ..
+ rmdir /s /q package
+ - name: Update npm to 7
+ # If we do test on npm 10 it needs npm7
+ if: matrix.node-version <= 10
+ run: npm i --prefer-online --no-fund --no-audit -g npm@7
+ - name: Update npm to latest
+ if: matrix.node-version > 10
+ run: npm i --prefer-online --no-fund --no-audit -g npm@latest
+ - run: npm -v
+ - name: Dependabot metadata
+ id: metadata
+ uses: dependabot/fetch-metadata@v1.1.1
+ with:
+ github-token: "\${{ secrets.GITHUB_TOKEN }}"
+ - name: npm install and commit
+ if: contains(steps.metadata.outputs.dependency-names, '@npmcli/template-oss')
+ env:
+ GITHUB_TOKEN: \${{ secrets.GITHUB_TOKEN }}
+ run: |
+ gh pr checkout \${{ github.event.pull_request.number }}
+ npm install --no-scripts
+ npm run template-oss-apply
+ git add .
+ git commit -am "chore: postinstall for dependabot template-oss PR"
+ git push
+
+.github/workflows/pull-request.yml
+========================================
+# This file is automatically added by @npmcli/template-oss. Do not edit.
+
+name: Pull Request Linting
+
+on:
+ pull_request:
+ types:
+ - opened
+ - reopened
+ - edited
+ - synchronize
+
+jobs:
+ check:
+ name: Check PR Title or Commits
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+ - name: Setup git user
+ run: |
+ git config --global user.email "ops+npm-cli@npmjs.com"
+ git config --global user.name "npm cli ops bot"
+ - uses: actions/setup-node@v3
+ with:
+ node-version: 16.x
+ - name: Update to workable npm (windows)
+ # node 12 and 14 ship with npm@6, which is known to fail when updating itself in windows
+ if: matrix.platform.os == 'windows-latest' && (startsWith(matrix.node-version, '12') || startsWith(matrix.node-version, '14'))
+ run: |
+ curl -sO https://registry.npmjs.org/npm/-/npm-7.5.4.tgz
+ tar xf npm-7.5.4.tgz
+ cd package
+ node lib/npm.js install --no-fund --no-audit -g ../npm-7.5.4.tgz
+ cd ..
+ rmdir /s /q package
+ - name: Update npm to 7
+ # If we do test on npm 10 it needs npm7
+ if: matrix.node-version <= 10
+ run: npm i --prefer-online --no-fund --no-audit -g npm@7
+ - name: Update npm to latest
+ if: matrix.node-version > 10
+ run: npm i --prefer-online --no-fund --no-audit -g npm@latest
+ - run: npm -v
+ - name: Install deps
+ run: |
+ npm i -D @commitlint/cli @commitlint/config-conventional
+ - name: Check commits OR PR title
+ env:
+ PR_TITLE: \${{ github.event.pull_request.title }}
+ run: |
+ 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
+
+.github/workflows/release-please-bbb.yml
+========================================
+# This file is automatically added by @npmcli/template-oss. Do not edit.
+
+name: Release Please - bbb
+
+on:
+ push:
+ paths:
+ - workspaces/b/**
+ branches:
+ - main
+ - latest
+
+jobs:
+ release-please:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: google-github-actions/release-please-action@v3
+ id: release
+ with:
+ release-type: node
+ changelog-type: github
+ monorepo-tags: true
+ paths: workspaces/b
+ changelog-types: >
+ [
+ {"type":"feat","section":"Features","hidden":false},
+ {"type":"fix","section":"Bug Fixes","hidden":false},
+ {"type":"docs","section":"Documentation","hidden":false},
+ {"type":"deps","section":"Dependencies","hidden":false},
+ {"type":"chore","hidden":true}
+ ]
+
+.github/workflows/release-please-name-aaaa.yml
+========================================
+# This file is automatically added by @npmcli/template-oss. Do not edit.
+
+name: Release Please - @name/aaaa
+
+on:
+ push:
+ paths:
+ - workspaces/a/**
+ branches:
+ - main
+ - latest
+
+jobs:
+ release-please:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: google-github-actions/release-please-action@v3
+ id: release
+ with:
+ release-type: node
+ changelog-type: github
+ monorepo-tags: true
+ paths: workspaces/a
+ changelog-types: >
+ [
+ {"type":"feat","section":"Features","hidden":false},
+ {"type":"fix","section":"Bug Fixes","hidden":false},
+ {"type":"docs","section":"Documentation","hidden":false},
+ {"type":"deps","section":"Dependencies","hidden":false},
+ {"type":"chore","hidden":true}
+ ]
+
+.github/workflows/release-please.yml
+========================================
+# This file is automatically added by @npmcli/template-oss. Do not edit.
+
+name: Release Please
+
+on:
+ push:
+ branches:
+ - main
+ - latest
+
+jobs:
+ release-please:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: google-github-actions/release-please-action@v3
+ id: release
+ with:
+ release-type: node
+ changelog-type: github
+ changelog-types: >
+ [
+ {"type":"feat","section":"Features","hidden":false},
+ {"type":"fix","section":"Bug Fixes","hidden":false},
+ {"type":"docs","section":"Documentation","hidden":false},
+ {"type":"deps","section":"Dependencies","hidden":false},
+ {"type":"chore","hidden":true}
+ ]
+
+.gitignore
+========================================
+# This file is automatically added by @npmcli/template-oss. Do not edit.
+
+# ignore everything in the root
+/*
+
+# keep these
+!/.eslintrc.local.*
+!**/.gitignore
+!/docs/
+!/tap-snapshots/
+!/test/
+!/map.js
+!/scripts/
+!/README*
+!/LICENSE*
+!/CHANGELOG*
+!/.commitlintrc.js
+!/.eslintrc.js
+!/.github/
+!/.gitignore
+!/.npmrc
+!/SECURITY.md
+!/bin/
+!/lib/
+!/package.json
+!/workspaces/
+
+.npmrc
+========================================
+; This file is automatically added by @npmcli/template-oss. Do not edit.
+
+package-lock=false
+
+SECURITY.md
+========================================
+
+
+Please send vulnerability reports through [hackerone](https://hackerone.com/github).
+
+package.json
+========================================
+{
+ "name": "testpkg",
+ "workspaces": [
+ "workspaces/a",
+ "workspaces/b"
+ ],
+ "scripts": {
+ "lint": "eslint /"**/*.js/"",
+ "postlint": "template-oss-check",
+ "template-oss-apply": "template-oss-apply --force",
+ "lintfix": "npm run lint -- --fix",
+ "preversion": "npm test",
+ "postversion": "npm publish",
+ "prepublishOnly": "git push origin --follow-tags",
+ "snap": "tap",
+ "test": "tap",
+ "posttest": "npm run lint"
+ },
+ "author": "GitHub Inc.",
+ "files": [
+ "bin/",
+ "lib/"
+ ],
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ },
+ "templateOSS": {
+ "//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.",
+ "version": "2.9.2"
+ }
+}
+
+workspaces/a/.eslintrc.js
+========================================
+/* This file is automatically added by @npmcli/template-oss. Do not edit. */
+
+const { readdirSync: readdir } = require('fs')
+
+const localConfigs = readdir(__dirname)
+ .filter((file) => file.startsWith('.eslintrc.local.'))
+ .map((file) => \`./\${file}\`)
+
+module.exports = {
+ extends: [
+ '@npmcli',
+ ...localConfigs,
+ ],
+}
+
+workspaces/a/.eslintrc.local.yml
+========================================
+KEEP
+
+workspaces/a/.gitignore
+========================================
+# This file is automatically added by @npmcli/template-oss. Do not edit.
+
+# ignore everything in the root
+/*
+
+# keep these
+!/.eslintrc.local.*
+!**/.gitignore
+!/docs/
+!/tap-snapshots/
+!/test/
+!/map.js
+!/scripts/
+!/README*
+!/LICENSE*
+!/CHANGELOG*
+!/.eslintrc.js
+!/.gitignore
+!/bin/
+!/lib/
+!/package.json
+
+workspaces/a/package.json
+========================================
+{
+ "name": "@name/aaaa",
+ "scripts": {
+ "lint": "eslint /"**/*.js/"",
+ "postlint": "template-oss-check",
+ "template-oss-apply": "template-oss-apply --force",
+ "lintfix": "npm run lint -- --fix",
+ "preversion": "npm test",
+ "postversion": "npm publish",
+ "prepublishOnly": "git push origin --follow-tags",
+ "snap": "tap",
+ "test": "tap",
+ "posttest": "npm run lint"
+ },
+ "author": "GitHub Inc.",
+ "files": [
+ "bin/",
+ "lib/"
+ ],
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ },
+ "templateOSS": {
+ "//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.",
+ "version": "2.9.2"
+ }
+}
+
+workspaces/b/.eslintrc.js
+========================================
+/* This file is automatically added by @npmcli/template-oss. Do not edit. */
+
+const { readdirSync: readdir } = require('fs')
+
+const localConfigs = readdir(__dirname)
+ .filter((file) => file.startsWith('.eslintrc.local.'))
+ .map((file) => \`./\${file}\`)
+
+module.exports = {
+ extends: [
+ '@npmcli',
+ ...localConfigs,
+ ],
+}
+
+workspaces/b/.eslintrc.local.yml
+========================================
+KEEP
+
+workspaces/b/.gitignore
+========================================
+# This file is automatically added by @npmcli/template-oss. Do not edit.
+
+# ignore everything in the root
+/*
+
+# keep these
+!/.eslintrc.local.*
+!**/.gitignore
+!/docs/
+!/tap-snapshots/
+!/test/
+!/map.js
+!/scripts/
+!/README*
+!/LICENSE*
+!/CHANGELOG*
+!/.eslintrc.js
+!/.gitignore
+!/bin/
+!/lib/
+!/package.json
+
+workspaces/b/package.json
+========================================
+{
+ "name": "bbb",
+ "scripts": {
+ "lint": "eslint /"**/*.js/"",
+ "postlint": "template-oss-check",
+ "template-oss-apply": "template-oss-apply --force",
+ "lintfix": "npm run lint -- --fix",
+ "preversion": "npm test",
+ "postversion": "npm publish",
+ "prepublishOnly": "git push origin --follow-tags",
+ "snap": "tap",
+ "test": "tap",
+ "posttest": "npm run lint"
+ },
+ "author": "GitHub Inc.",
+ "files": [
+ "bin/",
+ "lib/"
+ ],
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ },
+ "templateOSS": {
+ "//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.",
+ "version": "2.9.2"
+ }
+}
+`
diff --git a/tap-snapshots/test/apply/index.js.test.cjs b/tap-snapshots/test/apply/index.js.test.cjs
new file mode 100644
index 00000000..3068dd7b
--- /dev/null
+++ b/tap-snapshots/test/apply/index.js.test.cjs
@@ -0,0 +1,49 @@
+/* IMPORTANT
+ * This snapshot file is auto-generated, but designed for humans.
+ * It should be checked into source control and tracked carefully.
+ * Re-generate by setting TAP_SNAPSHOT=1 and running tests.
+ * Make sure to inspect the output below. Do not ignore changes!
+ */
+'use strict'
+exports[`test/apply/index.js TAP turn off all > expect resolving Promise 1`] = `
+package.json
+`
+
+exports[`test/apply/index.js TAP turn off module > expect resolving Promise 1`] = `
+.commitlintrc.js
+.github/CODEOWNERS
+.github/ISSUE_TEMPLATE/bug.yml
+.github/ISSUE_TEMPLATE/config.yml
+.github/dependabot.yml
+.github/workflows/audit.yml
+.github/workflows/ci.yml
+.github/workflows/codeql-analysis.yml
+.github/workflows/post-dependabot.yml
+.github/workflows/pull-request.yml
+.github/workflows/release-please.yml
+package.json
+`
+
+exports[`test/apply/index.js TAP turn off repo > expect resolving Promise 1`] = `
+.eslintrc.js
+.gitignore
+.npmrc
+SECURITY.md
+package.json
+`
+
+exports[`test/apply/index.js TAP workspaces > expect resolving Promise 1`] = `
+.github/workflows/ci-d.yml
+.github/workflows/release-please-d.yml
+package.json
+workspaces/a/.eslintrc.js
+workspaces/a/.gitignore
+workspaces/a/package.json
+workspaces/b/.eslintrc.js
+workspaces/b/.gitignore
+workspaces/b/package.json
+workspaces/c/package.json
+workspaces/d/.eslintrc.js
+workspaces/d/.gitignore
+workspaces/d/package.json
+`
diff --git a/tap-snapshots/test/bin/check.js.test.cjs b/tap-snapshots/test/bin/check.js.test.cjs
new file mode 100644
index 00000000..4983251d
--- /dev/null
+++ b/tap-snapshots/test/bin/check.js.test.cjs
@@ -0,0 +1,31 @@
+/* IMPORTANT
+ * This snapshot file is auto-generated, but designed for humans.
+ * It should be checked into source control and tracked carefully.
+ * Re-generate by setting TAP_SNAPSHOT=1 and running tests.
+ * Make sure to inspect the output below. Do not ignore changes!
+ */
+'use strict'
+exports[`test/bin/check.js TAP problems > must match snapshot 1`] = `
+
+Some problems were detected:
+
+-------------------------------------------------------------------
+
+message1
+
+ a
+ b
+
+To correct it: solution1
+
+-------------------------------------------------------------------
+
+message2
+
+ c
+
+To correct it: solution2
+
+-------------------------------------------------------------------
+
+`
diff --git a/tap-snapshots/test/check/changelog.js.test.cjs b/tap-snapshots/test/check/changelog.js.test.cjs
new file mode 100644
index 00000000..a76ff451
--- /dev/null
+++ b/tap-snapshots/test/check/changelog.js.test.cjs
@@ -0,0 +1,23 @@
+/* IMPORTANT
+ * This snapshot file is auto-generated, but designed for humans.
+ * It should be checked into source control and tracked carefully.
+ * Re-generate by setting TAP_SNAPSHOT=1 and running tests.
+ * Make sure to inspect the output below. Do not ignore changes!
+ */
+'use strict'
+exports[`test/check/changelog.js TAP will report incorrect changelog > expect resolving Promise 1`] = `
+Some problems were detected:
+
+-------------------------------------------------------------------
+
+The CHANGELOG.md is incorrect:
+
+ The changelog should start with
+ "# Changelog
+
+ #"
+
+To correct it: reformat the changelog to have the correct heading
+
+-------------------------------------------------------------------
+`
diff --git a/tap-snapshots/test/check/diffs.js.test.cjs b/tap-snapshots/test/check/diffs.js.test.cjs
new file mode 100644
index 00000000..8d6739ba
--- /dev/null
+++ b/tap-snapshots/test/check/diffs.js.test.cjs
@@ -0,0 +1,444 @@
+/* IMPORTANT
+ * This snapshot file is auto-generated, but designed for humans.
+ * It should be checked into source control and tracked carefully.
+ * Re-generate by setting TAP_SNAPSHOT=1 and running tests.
+ * Make sure to inspect the output below. Do not ignore changes!
+ */
+'use strict'
+exports[`test/check/diffs.js TAP different headers > initial check 1`] = `
+Some problems were detected:
+
+-------------------------------------------------------------------
+
+The following repo files need to be added:
+
+ header.txt
+ noheader.txt
+ nocomment.txt
+
+To correct it: npx template-oss-apply --force
+
+-------------------------------------------------------------------
+`
+
+exports[`test/check/diffs.js TAP different headers > source after apply 1`] = `
+content/index.js
+========================================
+
+module.exports = {
+ rootRepo: {
+ add: {
+ 'header.txt': {
+ file: 'source.txt',
+ parser: (p) => class extends p.Base {
+ static header = 'Different header'
+ },
+ },
+ 'noheader.txt': {
+ file: 'source.txt',
+ parser: (p) => class extends p.Base {
+ static header = null
+ },
+ },
+ 'nocomment.txt': {
+ file: 'source.txt',
+ parser: (p) => class extends p.Base {
+ comment = null
+ },
+ },
+ },
+ },
+}
+
+content/source.txt
+========================================
+source
+
+header.txt
+========================================
+Different header
+
+source
+
+nocomment.txt
+========================================
+source
+
+noheader.txt
+========================================
+source
+
+package.json
+========================================
+{
+ "name": "testpkg"
+}
+`
+
+exports[`test/check/diffs.js TAP json delete > initial check 1`] = `
+Some problems were detected:
+
+-------------------------------------------------------------------
+
+The repo file target.json needs to be updated:
+
+ target.json
+ ========================================
+ "//@npmcli/template-oss" is missing, expected "This file is automatically added by @npmcli/template-oss. Do not edit."
+ "a" is 1, expected to be removed
+ "b" is missing, expected 2
+
+To correct it: npx template-oss-apply --force
+
+-------------------------------------------------------------------
+`
+
+exports[`test/check/diffs.js TAP json delete > source after apply 1`] = `
+content/index.js
+========================================
+module.exports = {
+ rootRepo: {
+ add: {
+ 'target.json': {
+ file: 'source.json',
+ parser: (p) => p.Json,
+ },
+ },
+ },
+}
+
+content/source.json
+========================================
+{"a":"__DELETE__","b":2}
+
+package.json
+========================================
+{
+ "name": "testpkg"
+}
+
+target.json
+========================================
+{
+ "//@npmcli/template-oss": "This file is automatically added by @npmcli/template-oss. Do not edit.",
+ "b": 2
+}
+`
+
+exports[`test/check/diffs.js TAP json merge > initial check 1`] = `
+Some problems were detected:
+
+-------------------------------------------------------------------
+
+The repo file target.json needs to be updated:
+
+ target.json
+ ========================================
+ "//@npmcli/template-oss" is missing, expected "This file is partially managed by @npmcli/template-oss. Edits may be overwritten."
+ "b" is missing, expected 1
+
+To correct it: npx template-oss-apply --force
+
+-------------------------------------------------------------------
+`
+
+exports[`test/check/diffs.js TAP json merge > source after apply 1`] = `
+content/index.js
+========================================
+module.exports = {
+ rootRepo: {
+ add: {
+ 'target.json': {
+ file: 'source.json',
+ parser: (p) => p.JsonMerge,
+ },
+ },
+ },
+}
+
+content/source.json
+========================================
+{"b":1}
+
+package.json
+========================================
+{
+ "name": "testpkg"
+}
+
+target.json
+========================================
+{
+ "//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.",
+ "a": 1,
+ "b": 1
+}
+`
+
+exports[`test/check/diffs.js TAP json overwrite > initial check 1`] = `
+Some problems were detected:
+
+-------------------------------------------------------------------
+
+The repo file target.json needs to be updated:
+
+ target.json
+ ========================================
+ "//@npmcli/template-oss" is missing, expected "This file is automatically added by @npmcli/template-oss. Do not edit."
+ "b" is missing, expected 1
+
+To correct it: npx template-oss-apply --force
+
+-------------------------------------------------------------------
+`
+
+exports[`test/check/diffs.js TAP json overwrite > source after apply 1`] = `
+content/index.js
+========================================
+module.exports={rootRepo:{add:{'target.json':'source.json'}}}
+
+content/source.json
+========================================
+{"b":1}
+
+package.json
+========================================
+{
+ "name": "testpkg"
+}
+
+target.json
+========================================
+{
+ "//@npmcli/template-oss": "This file is automatically added by @npmcli/template-oss. Do not edit.",
+ "b": 1
+}
+`
+
+exports[`test/check/diffs.js TAP node 10 > expect resolving Promise 1`] = `
+Some problems were detected:
+
+-------------------------------------------------------------------
+
+The repo file ci.yml needs to be updated:
+
+ .github/workflows/ci.yml
+ ========================================
+ @@ -52,8 +52,9 @@
+ strategy:
+ fail-fast: false
+ matrix:
+ node-version:
+ + - 10
+ - 12.13.0
+ - 12.x
+ - 14.15.0
+ - 14.x
+
+To correct it: npx template-oss-apply --force
+
+-------------------------------------------------------------------
+
+The module file package.json needs to be updated:
+
+ package.json
+ ========================================
+ "engines.node" is "^12.13.0 || ^14.15.0 || >=16.0.0", expected "^10.0.0 || ^12.13.0 || ^14.15.0 || >=16.0.0"
+
+To correct it: npx template-oss-apply --force
+
+-------------------------------------------------------------------
+`
+
+exports[`test/check/diffs.js TAP unknown file type > initial check 1`] = `
+Some problems were detected:
+
+-------------------------------------------------------------------
+
+The following repo files need to be added:
+
+ target.txt
+
+To correct it: npx template-oss-apply --force
+
+-------------------------------------------------------------------
+`
+
+exports[`test/check/diffs.js TAP unknown file type > source after apply 1`] = `
+content/index.js
+========================================
+module.exports={rootRepo:{add:{'target.txt':'source.txt'}}}
+
+content/source.txt
+========================================
+source
+
+package.json
+========================================
+{
+ "name": "testpkg"
+}
+
+target.txt
+========================================
+This file is automatically added by @npmcli/template-oss. Do not edit.
+
+source
+`
+
+exports[`test/check/diffs.js TAP update, remove, errors > expect resolving Promise 1`] = `
+Some problems were detected:
+
+-------------------------------------------------------------------
+
+The repo file ci.yml needs to be updated:
+
+ .github/workflows/ci.yml
+ ========================================
+ @@ -78,4 +78,24 @@
+ git config --global user.name "npm cli ops bot"
+ - uses: actions/setup-node@v3
+ with:
+ node-version: 16.x
+ + - name: Update to workable npm (windows)
+ + # node 12 and 14 ship with npm@6, which is known to fail when updating itself in windows
+ + if: matrix.platform.os == 'windows-latest' && (startsWith(matrix.node-version, '12') || startsWith(matrix.node-version, '14'))
+ + run: |
+ + curl -sO https://registry.npmjs.org/npm/-/npm-7.5.4.tgz
+ + tar xf npm-7.5.4.tgz
+ + cd package
+ + node lib/npm.js install --no-fund --no-audit -g ../npm-7.5.4.tgz
+ + cd ..
+ + rmdir /s /q package
+ + - name: Update npm to 7
+ + # If we do test on npm 10 it needs npm7
+ + if: matrix.node-version <= 10
+ + run: npm i --prefer-online --no-fund --no-audit -g npm@7
+ + - name: Update npm to latest
+ + if: matrix.node-version > 10
+ + run: npm i --prefer-online --no-fund --no-audit -g npm@latest
+ + - run: npm -v
+ + - run: npm i
+ + - run: npm test --ignore-scripts
+
+To correct it: npx template-oss-apply --force
+
+-------------------------------------------------------------------
+
+The repo file audit.yml needs to be updated:
+
+ .github/workflows/audit.yml
+ ========================================
+ [@npmcli/template-oss ERROR] There was an erroring getting the target file
+ [@npmcli/template-oss ERROR] Error: Document with errors cannot be stringified
+ [@npmcli/template-oss ERROR] It will be overwritten with the following source:
+ ----------------------------------------
+ # This file is automatically added by @npmcli/template-oss. Do not edit.
+
+ name: Audit
+
+ on:
+ workflow_dispatch: null
+ schedule:
+ # "At 01:00 on Monday" https://crontab.guru/#0_1_*_*_1
+ - cron: "0 1 * * 1"
+
+ jobs:
+ audit:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - name: Setup git user
+ run: |
+ git config --global user.email "ops+npm-cli@npmjs.com"
+ git config --global user.name "npm cli ops bot"
+ - uses: actions/setup-node@v3
+ with:
+ node-version: 16.x
+ - name: Update to workable npm (windows)
+ # node 12 and 14 ship with npm@6, which is known to fail when updating itself in windows
+ if: matrix.platform.os == 'windows-latest' && (startsWith(matrix.node-version, '12') || startsWith(matrix.node-version, '14'))
+ run: |
+ curl -sO https://registry.npmjs.org/npm/-/npm-7.5.4.tgz
+ tar xf npm-7.5.4.tgz
+ cd package
+ node lib/npm.js install --no-fund --no-audit -g ../npm-7.5.4.tgz
+ cd ..
+ rmdir /s /q package
+ - name: Update npm to 7
+ # If we do test on npm 10 it needs npm7
+ if: matrix.node-version <= 10
+ run: npm i --prefer-online --no-fund --no-audit -g npm@7
+ - name: Update npm to latest
+ if: matrix.node-version > 10
+ run: npm i --prefer-online --no-fund --no-audit -g npm@latest
+ - run: npm -v
+ - run: npm i --package-lock
+ - run: npm audit
+
+
+To correct it: npx template-oss-apply --force
+
+-------------------------------------------------------------------
+
+The following module files need to be deleted:
+
+ .eslintrc.json
+
+To correct it: npx template-oss-apply --force
+
+-------------------------------------------------------------------
+
+The following module files need to be added:
+
+ .npmrc
+
+To correct it: npx template-oss-apply --force
+
+-------------------------------------------------------------------
+`
+
+exports[`test/check/diffs.js TAP will diff json > expect resolving Promise 1`] = `
+Some problems were detected:
+
+-------------------------------------------------------------------
+
+The module file package.json needs to be updated:
+
+ package.json
+ ========================================
+ "author" is "heynow", expected "GitHub Inc."
+ "files[1]" is "x", expected "lib/"
+ "scripts.lint:fix" is "x", expected to be removed
+ "scripts.preversion" is "x", expected "npm test"
+ "standard" is {
+ "config": "x "
+ }, expected to be removed
+ "templateVersion" is "1", expected to be removed
+
+To correct it: npx template-oss-apply --force
+
+-------------------------------------------------------------------
+`
+
+exports[`test/check/diffs.js TAP workspaces > expect resolving Promise 1`] = `
+Some problems were detected:
+
+-------------------------------------------------------------------
+
+The following module files need to be deleted:
+
+ workspaces/a/.npmrc
+
+To correct it: npx template-oss-apply --force
+
+-------------------------------------------------------------------
+
+The following module files need to be deleted:
+
+ workspaces/b/.npmrc
+
+To correct it: npx template-oss-apply --force
+
+-------------------------------------------------------------------
+`
diff --git a/tap-snapshots/test/check/gitignore.js.test.cjs b/tap-snapshots/test/check/gitignore.js.test.cjs
new file mode 100644
index 00000000..89e415f5
--- /dev/null
+++ b/tap-snapshots/test/check/gitignore.js.test.cjs
@@ -0,0 +1,214 @@
+/* IMPORTANT
+ * This snapshot file is auto-generated, but designed for humans.
+ * It should be checked into source control and tracked carefully.
+ * Re-generate by setting TAP_SNAPSHOT=1 and running tests.
+ * Make sure to inspect the output below. Do not ignore changes!
+ */
+'use strict'
+exports[`test/check/gitignore.js TAP will report tracked files in gitignore > expect resolving Promise 1`] = `
+Some problems were detected:
+
+-------------------------------------------------------------------
+
+The following files are tracked by git but matching a pattern in .gitignore:
+
+ ignorethis
+
+To correct it: move files to not match one of the following patterns:
+
+ /*
+ !/.eslintrc.local.*
+ !**/.gitignore
+ !/docs/
+ !/tap-snapshots/
+ !/test/
+ !/map.js
+ !/scripts/
+ !/README*
+ !/LICENSE*
+ !/CHANGELOG*
+ !/.commitlintrc.js
+ !/.eslintrc.js
+ !/.github/
+ !/.gitignore
+ !/.npmrc
+ !/SECURITY.md
+ !/bin/
+ !/lib/
+ !/package.json
+
+-------------------------------------------------------------------
+`
+
+exports[`test/check/gitignore.js TAP will report tracked files in gitignore workspace > expect resolving Promise 1`] = `
+Some problems were detected:
+
+-------------------------------------------------------------------
+
+The following files are tracked by git but matching a pattern in .gitignore:
+
+ ignorethis
+
+To correct it: move files to not match one of the following patterns:
+
+ /*
+ !/.eslintrc.local.*
+ !**/.gitignore
+ !/docs/
+ !/tap-snapshots/
+ !/test/
+ !/map.js
+ !/scripts/
+ !/README*
+ !/LICENSE*
+ !/CHANGELOG*
+ !/.commitlintrc.js
+ !/.eslintrc.js
+ !/.github/
+ !/.gitignore
+ !/.npmrc
+ !/SECURITY.md
+ !/bin/
+ !/lib/
+ !/package.json
+ !/workspaces/
+
+-------------------------------------------------------------------
+
+The following files are tracked by git but matching a pattern in workspaces/a/.gitignore:
+
+ workspaces/a/wsafile
+
+To correct it: move files to not match one of the following patterns:
+
+ /*
+ !/.eslintrc.local.*
+ !**/.gitignore
+ !/docs/
+ !/tap-snapshots/
+ !/test/
+ !/map.js
+ !/scripts/
+ !/README*
+ !/LICENSE*
+ !/CHANGELOG*
+ !/.eslintrc.js
+ !/.gitignore
+ !/bin/
+ !/lib/
+ !/package.json
+
+-------------------------------------------------------------------
+
+The following files are tracked by git but matching a pattern in workspaces/b/.gitignore:
+
+ workspaces/b/wsbfile
+
+To correct it: move files to not match one of the following patterns:
+
+ /*
+ !/.eslintrc.local.*
+ !**/.gitignore
+ !/docs/
+ !/tap-snapshots/
+ !/test/
+ !/map.js
+ !/scripts/
+ !/README*
+ !/LICENSE*
+ !/CHANGELOG*
+ !/.eslintrc.js
+ !/.gitignore
+ !/bin/
+ !/lib/
+ !/package.json
+
+-------------------------------------------------------------------
+`
+
+exports[`test/check/gitignore.js TAP works with workspaces in separate dirs > expect resolving Promise 1`] = `
+Some problems were detected:
+
+-------------------------------------------------------------------
+
+The following files are tracked by git but matching a pattern in .gitignore:
+
+ ignorethis
+
+To correct it: move files to not match one of the following patterns:
+
+ /*
+ !/.eslintrc.local.*
+ !**/.gitignore
+ !/docs/
+ !/tap-snapshots/
+ !/test/
+ !/map.js
+ !/scripts/
+ !/README*
+ !/LICENSE*
+ !/CHANGELOG*
+ !/.commitlintrc.js
+ !/.eslintrc.js
+ !/.github/
+ !/.gitignore
+ !/.npmrc
+ !/SECURITY.md
+ !/bin/
+ !/lib/
+ !/package.json
+ !/workspace-a
+ !/workspace-b
+
+-------------------------------------------------------------------
+
+The following files are tracked by git but matching a pattern in workspace-a/.gitignore:
+
+ workspace-a/wsafile
+
+To correct it: move files to not match one of the following patterns:
+
+ /*
+ !/.eslintrc.local.*
+ !**/.gitignore
+ !/docs/
+ !/tap-snapshots/
+ !/test/
+ !/map.js
+ !/scripts/
+ !/README*
+ !/LICENSE*
+ !/CHANGELOG*
+ !/.eslintrc.js
+ !/.gitignore
+ !/bin/
+ !/lib/
+ !/package.json
+
+-------------------------------------------------------------------
+
+The following files are tracked by git but matching a pattern in workspace-b/.gitignore:
+
+ workspace-b/wsbfile
+
+To correct it: move files to not match one of the following patterns:
+
+ /*
+ !/.eslintrc.local.*
+ !**/.gitignore
+ !/docs/
+ !/tap-snapshots/
+ !/test/
+ !/map.js
+ !/scripts/
+ !/README*
+ !/LICENSE*
+ !/CHANGELOG*
+ !/.eslintrc.js
+ !/.gitignore
+ !/bin/
+ !/lib/
+ !/package.json
+
+-------------------------------------------------------------------
+`
diff --git a/tap-snapshots/test/check/index.js.test.cjs b/tap-snapshots/test/check/index.js.test.cjs
new file mode 100644
index 00000000..39e5e82c
--- /dev/null
+++ b/tap-snapshots/test/check/index.js.test.cjs
@@ -0,0 +1,284 @@
+/* IMPORTANT
+ * This snapshot file is auto-generated, but designed for humans.
+ * It should be checked into source control and tracked carefully.
+ * Re-generate by setting TAP_SNAPSHOT=1 and running tests.
+ * Make sure to inspect the output below. Do not ignore changes!
+ */
+'use strict'
+exports[`test/check/index.js TAP check empty dir > expect resolving Promise 1`] = `
+Some problems were detected:
+
+-------------------------------------------------------------------
+
+The following repo files need to be added:
+
+ .commitlintrc.js
+ .github/workflows/ci.yml
+ .github/ISSUE_TEMPLATE/bug.yml
+ .github/ISSUE_TEMPLATE/config.yml
+ .github/CODEOWNERS
+ .github/dependabot.yml
+ .github/workflows/audit.yml
+ .github/workflows/codeql-analysis.yml
+ .github/workflows/post-dependabot.yml
+ .github/workflows/pull-request.yml
+ .github/workflows/release-please.yml
+
+To correct it: npx template-oss-apply --force
+
+-------------------------------------------------------------------
+
+The following module files need to be added:
+
+ .eslintrc.js
+ .gitignore
+ .npmrc
+ SECURITY.md
+
+To correct it: npx template-oss-apply --force
+
+-------------------------------------------------------------------
+
+The module file package.json needs to be updated:
+
+ package.json
+ ========================================
+ "author" is missing, expected "GitHub Inc."
+ "engines" is missing, expected {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ "files" is missing, expected [
+ "bin/",
+ "lib/"
+ ]
+ "scripts" is missing, expected {
+ "lint": "eslint /"**/*.js/"",
+ "postlint": "template-oss-check",
+ "template-oss-apply": "template-oss-apply --force",
+ "lintfix": "npm run lint -- --fix",
+ "preversion": "npm test",
+ "postversion": "npm publish",
+ "prepublishOnly": "git push origin --follow-tags",
+ "snap": "tap",
+ "test": "tap",
+ "posttest": "npm run lint"
+ }
+ "templateOSS" is missing, expected {
+ "//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.",
+ "version": "2.9.2"
+ }
+
+To correct it: npx template-oss-apply --force
+
+-------------------------------------------------------------------
+
+The following required devDependencies were not found:
+
+ @npmcli/template-oss@*
+ @npmcli/eslint-config@^3.0.0
+ tap@^15.0.0
+
+To correct it: npm rm @npmcli/template-oss @npmcli/eslint-config tap && npm i @npmcli/template-oss@* @npmcli/eslint-config@^3.0.0 tap@^15.0.0 --save-dev
+
+-------------------------------------------------------------------
+`
+
+exports[`test/check/index.js TAP workspaces with empty dir > expect resolving Promise 1`] = `
+Some problems were detected:
+
+-------------------------------------------------------------------
+
+The following repo files need to be added:
+
+ .commitlintrc.js
+ .github/workflows/ci.yml
+ .github/ISSUE_TEMPLATE/bug.yml
+ .github/ISSUE_TEMPLATE/config.yml
+ .github/CODEOWNERS
+ .github/dependabot.yml
+ .github/workflows/audit.yml
+ .github/workflows/codeql-analysis.yml
+ .github/workflows/post-dependabot.yml
+ .github/workflows/pull-request.yml
+ .github/workflows/release-please.yml
+
+To correct it: npx template-oss-apply --force
+
+-------------------------------------------------------------------
+
+The following module files need to be added:
+
+ .eslintrc.js
+ .gitignore
+ .npmrc
+ SECURITY.md
+
+To correct it: npx template-oss-apply --force
+
+-------------------------------------------------------------------
+
+The module file package.json needs to be updated:
+
+ package.json
+ ========================================
+ "author" is missing, expected "GitHub Inc."
+ "engines" is missing, expected {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ "files" is missing, expected [
+ "bin/",
+ "lib/"
+ ]
+ "scripts" is missing, expected {
+ "lint": "eslint /"**/*.js/"",
+ "postlint": "template-oss-check",
+ "template-oss-apply": "template-oss-apply --force",
+ "lintfix": "npm run lint -- --fix",
+ "preversion": "npm test",
+ "postversion": "npm publish",
+ "prepublishOnly": "git push origin --follow-tags",
+ "snap": "tap",
+ "test": "tap",
+ "posttest": "npm run lint"
+ }
+ "templateOSS" is missing, expected {
+ "//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.",
+ "version": "2.9.2"
+ }
+
+To correct it: npx template-oss-apply --force
+
+-------------------------------------------------------------------
+
+The following required devDependencies were not found:
+
+ @npmcli/template-oss@*
+ @npmcli/eslint-config@^3.0.0
+ tap@^15.0.0
+
+To correct it: npm rm @npmcli/template-oss @npmcli/eslint-config tap && npm i @npmcli/template-oss@* @npmcli/eslint-config@^3.0.0 tap@^15.0.0 --save-dev
+
+-------------------------------------------------------------------
+
+The following repo files need to be added:
+
+ .github/workflows/release-please-name-aaaa.yml
+ .github/workflows/ci-name-aaaa.yml
+
+To correct it: npx template-oss-apply --force
+
+-------------------------------------------------------------------
+
+The following module files need to be added:
+
+ workspaces/a/.eslintrc.js
+ workspaces/a/.gitignore
+
+To correct it: npx template-oss-apply --force
+
+-------------------------------------------------------------------
+
+The module file package.json needs to be updated:
+
+ workspaces/a/package.json
+ ========================================
+ "author" is missing, expected "GitHub Inc."
+ "engines" is missing, expected {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ "files" is missing, expected [
+ "bin/",
+ "lib/"
+ ]
+ "scripts" is missing, expected {
+ "lint": "eslint /"**/*.js/"",
+ "postlint": "template-oss-check",
+ "template-oss-apply": "template-oss-apply --force",
+ "lintfix": "npm run lint -- --fix",
+ "preversion": "npm test",
+ "postversion": "npm publish",
+ "prepublishOnly": "git push origin --follow-tags",
+ "snap": "tap",
+ "test": "tap",
+ "posttest": "npm run lint"
+ }
+ "templateOSS" is missing, expected {
+ "//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.",
+ "version": "2.9.2"
+ }
+
+To correct it: npx template-oss-apply --force
+
+-------------------------------------------------------------------
+
+The following required devDependencies were not found:
+
+ @npmcli/template-oss@*
+ @npmcli/eslint-config@^3.0.0
+ tap@^15.0.0
+
+To correct it: npm rm @npmcli/template-oss @npmcli/eslint-config tap && npm i @npmcli/template-oss@* @npmcli/eslint-config@^3.0.0 tap@^15.0.0 --save-dev
+
+-------------------------------------------------------------------
+
+The following repo files need to be added:
+
+ .github/workflows/release-please-bbb.yml
+ .github/workflows/ci-bbb.yml
+
+To correct it: npx template-oss-apply --force
+
+-------------------------------------------------------------------
+
+The following module files need to be added:
+
+ workspaces/b/.eslintrc.js
+ workspaces/b/.gitignore
+
+To correct it: npx template-oss-apply --force
+
+-------------------------------------------------------------------
+
+The module file package.json needs to be updated:
+
+ workspaces/b/package.json
+ ========================================
+ "author" is missing, expected "GitHub Inc."
+ "engines" is missing, expected {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ "files" is missing, expected [
+ "bin/",
+ "lib/"
+ ]
+ "scripts" is missing, expected {
+ "lint": "eslint /"**/*.js/"",
+ "postlint": "template-oss-check",
+ "template-oss-apply": "template-oss-apply --force",
+ "lintfix": "npm run lint -- --fix",
+ "preversion": "npm test",
+ "postversion": "npm publish",
+ "prepublishOnly": "git push origin --follow-tags",
+ "snap": "tap",
+ "test": "tap",
+ "posttest": "npm run lint"
+ }
+ "templateOSS" is missing, expected {
+ "//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.",
+ "version": "2.9.2"
+ }
+
+To correct it: npx template-oss-apply --force
+
+-------------------------------------------------------------------
+
+The following required devDependencies were not found:
+
+ @npmcli/template-oss@*
+ @npmcli/eslint-config@^3.0.0
+ tap@^15.0.0
+
+To correct it: npm rm @npmcli/template-oss @npmcli/eslint-config tap && npm i @npmcli/template-oss@* @npmcli/eslint-config@^3.0.0 tap@^15.0.0 --save-dev
+
+-------------------------------------------------------------------
+`
diff --git a/tap-snapshots/test/check/unwanted.js.test.cjs b/tap-snapshots/test/check/unwanted.js.test.cjs
new file mode 100644
index 00000000..b4fb97e7
--- /dev/null
+++ b/tap-snapshots/test/check/unwanted.js.test.cjs
@@ -0,0 +1,20 @@
+/* IMPORTANT
+ * This snapshot file is auto-generated, but designed for humans.
+ * It should be checked into source control and tracked carefully.
+ * Re-generate by setting TAP_SNAPSHOT=1 and running tests.
+ * Make sure to inspect the output below. Do not ignore changes!
+ */
+'use strict'
+exports[`test/check/unwanted.js TAP unwanted > expect resolving Promise 1`] = `
+Some problems were detected:
+
+-------------------------------------------------------------------
+
+The following unwanted packages were found:
+
+ eslint
+
+To correct it: npm rm eslint
+
+-------------------------------------------------------------------
+`
diff --git a/tap-snapshots/test/postlint/check-gitignore.js.test.cjs b/tap-snapshots/test/postlint/check-gitignore.js.test.cjs
deleted file mode 100644
index 760270b2..00000000
--- a/tap-snapshots/test/postlint/check-gitignore.js.test.cjs
+++ /dev/null
@@ -1,27 +0,0 @@
-/* IMPORTANT
- * This snapshot file is auto-generated, but designed for humans.
- * It should be checked into source control and tracked carefully.
- * Re-generate by setting TAP_SNAPSHOT=1 and running tests.
- * Make sure to inspect the output below. Do not ignore changes!
- */
-'use strict'
-exports[`test/postlint/check-gitignore.js TAP no warnings > must match snapshot 1`] = `
-Array []
-`
-
-exports[`test/postlint/check-gitignore.js TAP will report tracked files in gitignore > must match snapshot 1`] = `
-Array [
- Object {
- "message": String(
- The following files are tracked by git but matching a pattern in .gitignore:
- willIgnore1
- willIgnore2
- ),
- "solution": String(
- Move files to not match one of these patterns:
- ignored
- willIgnore*
- ),
- },
-]
-`
diff --git a/tap-snapshots/test/postlint/check-package.js.test.cjs b/tap-snapshots/test/postlint/check-package.js.test.cjs
deleted file mode 100644
index 1fab8d8e..00000000
--- a/tap-snapshots/test/postlint/check-package.js.test.cjs
+++ /dev/null
@@ -1,90 +0,0 @@
-/* IMPORTANT
- * This snapshot file is auto-generated, but designed for humans.
- * It should be checked into source control and tracked carefully.
- * Re-generate by setting TAP_SNAPSHOT=1 and running tests.
- * Make sure to inspect the output below. Do not ignore changes!
- */
-'use strict'
-exports[`test/postlint/check-package.js TAP checks a package.json incorrect fields > problems 1`] = `
-Array [
- Object {
- "message": String(
- The following package.json fields are incorrect:
- Field: "author" Expected: "GitHub Inc." Found: "Bob"
- Field: "files" Expected: ["bin","lib"] Found: undefined
- Field: "scripts" Expected: {"lint":"eslint \\\\"**/*.js\\\\"","postlint":"npm-template-check","template-copy":"npm-template-copy --force","lintfix":"npm run lint -- --fix","preversion":"npm test","postversion":"npm publish","prepublishOnly":"git push origin --follow-tags","snap":"tap","test":"tap","posttest":"npm run lint"} Found: undefined
- Field: "engines" Expected: {"node":"^12.13.0 || ^14.15.0 || >=16"} Found: undefined
- ),
- "solution": "npm rm @npmcli/template-oss && npm i -D @npmcli/template-oss",
- },
-]
-`
-
-exports[`test/postlint/check-package.js TAP checks a package.json incorrect object fields > problems 1`] = `
-Array [
- Object {
- "message": String(
- The following package.json fields are incorrect:
- Field: "author" Expected: "GitHub Inc." Found: undefined
- Field: "files" Expected: ["bin","lib"] Found: undefined
- Field: "scripts.lint" Expected: "eslint \\\\"**/*.js\\\\"" Found: undefined
- Field: "scripts.postlint" Expected: "npm-template-check" Found: undefined
- Field: "scripts.template-copy" Expected: "npm-template-copy --force" Found: undefined
- Field: "scripts.lintfix" Expected: "npm run lint -- --fix" Found: undefined
- Field: "scripts.preversion" Expected: "npm test" Found: undefined
- Field: "scripts.postversion" Expected: "npm publish" Found: undefined
- Field: "scripts.prepublishOnly" Expected: "git push origin --follow-tags" Found: undefined
- Field: "scripts.snap" Expected: "tap" Found: undefined
- Field: "scripts.test" Expected: "tap" Found: undefined
- Field: "scripts.posttest" Expected: "npm run lint" Found: undefined
- Field: "engines" Expected: {"node":"^12.13.0 || ^14.15.0 || >=16"} Found: undefined
- ),
- "solution": "npm rm @npmcli/template-oss && npm i -D @npmcli/template-oss",
- },
-]
-`
-
-exports[`test/postlint/check-package.js TAP checks a package.json missing fields > problems 1`] = `
-Array [
- Object {
- "message": String(
- The following package.json fields are incorrect:
- Field: "author" Expected: "GitHub Inc." Found: undefined
- Field: "files" Expected: ["bin","lib"] Found: undefined
- Field: "scripts" Expected: {"lint":"eslint \\\\"**/*.js\\\\"","postlint":"npm-template-check","template-copy":"npm-template-copy --force","lintfix":"npm run lint -- --fix","preversion":"npm test","postversion":"npm publish","prepublishOnly":"git push origin --follow-tags","snap":"tap","test":"tap","posttest":"npm run lint"} Found: undefined
- Field: "engines" Expected: {"node":"^12.13.0 || ^14.15.0 || >=16"} Found: undefined
- ),
- "solution": "npm rm @npmcli/template-oss && npm i -D @npmcli/template-oss",
- },
-]
-`
-
-exports[`test/postlint/check-package.js TAP checks a package.json unwanted deps > problems 1`] = `
-Array [
- Object {
- "message": String(
- The following package.json fields are incorrect:
- Field: "author" Expected: "GitHub Inc." Found: undefined
- Field: "files" Expected: ["bin","lib"] Found: undefined
- Field: "scripts" Expected: {"lint":"eslint \\\\"**/*.js\\\\"","postlint":"npm-template-check","template-copy":"npm-template-copy --force","lintfix":"npm run lint -- --fix","preversion":"npm test","postversion":"npm publish","prepublishOnly":"git push origin --follow-tags","snap":"tap","test":"tap","posttest":"npm run lint"} Found: undefined
- Field: "engines" Expected: {"node":"^12.13.0 || ^14.15.0 || >=16"} Found: undefined
- ),
- "solution": "npm rm @npmcli/template-oss && npm i -D @npmcli/template-oss",
- },
- Object {
- "message": String(
- The following unwanted packages were found:
- @npmcli/lint
- eslint-plugin-promise
- eslint-plugin-standard
- eslint-plugin-import
- standard
- ),
- "solution": "npm rm @npmcli/lint eslint-plugin-promise eslint-plugin-standard eslint-plugin-import standard",
- },
-]
-`
-
-exports[`test/postlint/check-package.js TAP checks a package.json unwanted deps > problems 2`] = `
-Array []
-`
diff --git a/test/apply/full-content.js b/test/apply/full-content.js
new file mode 100644
index 00000000..944ea187
--- /dev/null
+++ b/test/apply/full-content.js
@@ -0,0 +1,43 @@
+const t = require('tap')
+const setup = require('../setup.js')
+
+t.cleanSnapshot = setup.clean
+t.formatSnapshot = setup.format.readdirSource
+
+t.test('default', async (t) => {
+ const s = await setup(t)
+ await s.apply()
+ await t.resolveMatchSnapshot(s.readdirSource())
+})
+
+t.test('workspaces + everything', async (t) => {
+ const s = await setup(t, {
+ workspaces: { a: '@name/aaaa', b: 'bbb' },
+ testdir: {
+ '.eslintrc.json': 'DELETE',
+ '.eslintrc.local.yml': 'KEEP',
+ workspaces: {
+ a: {
+ '.npmrc': 'DELETE',
+ '.eslintrc.json': 'DELETE',
+ '.eslintrc.local.yml': 'KEEP',
+ },
+ b: {
+ '.npmrc': 'DELETE',
+ '.eslintrc.json': 'DELETE',
+ '.eslintrc.local.yml': 'KEEP',
+ },
+ },
+ },
+ })
+ await s.apply()
+ await t.resolveMatchSnapshot(s.readdirSource())
+})
+
+t.test('with empty content', async (t) => {
+ const s = await setup(t, { content: {} })
+ await s.apply()
+ t.strictSame(await s.readdirSource(), {
+ 'package.json': JSON.stringify({ name: 'testpkg' }, null, 2),
+ })
+})
diff --git a/test/apply/index.js b/test/apply/index.js
new file mode 100644
index 00000000..9e37407b
--- /dev/null
+++ b/test/apply/index.js
@@ -0,0 +1,73 @@
+const t = require('tap')
+const setup = require('../setup.js')
+
+t.cleanSnapshot = setup.clean
+t.formatSnapshot = setup.format.readdir
+
+t.test('turn off repo', async (t) => {
+ const s = await setup(t, {
+ package: {
+ templateOSS: {
+ rootRepo: false,
+ },
+ },
+ })
+ await s.apply()
+ await t.resolveMatchSnapshot(s.readdir())
+})
+
+t.test('turn off module', async (t) => {
+ const s = await setup(t, {
+ package: {
+ templateOSS: {
+ rootModule: false,
+ },
+ },
+ })
+ await s.apply()
+ await t.resolveMatchSnapshot(s.readdir())
+})
+
+t.test('turn off all', async (t) => {
+ const s = await setup(t, {
+ package: {
+ templateOSS: {
+ rootRepo: false,
+ rootModule: false,
+ },
+ },
+ })
+ await s.apply()
+ await t.resolveMatchSnapshot(s.readdir())
+})
+
+t.test('workspaces', async (t) => {
+ const s = await setup(t, {
+ package: {
+ templateOSS: {
+ rootRepo: false,
+ rootModule: false,
+ workspaceRepo: false,
+ workspaces: ['@aaa/aaa', '@bbb/bbb', 'd'],
+ },
+ },
+ workspaces: {
+ a: '@aaa/aaa',
+ b: '@bbb/bbb',
+ c: {
+ templateOSS: {
+ // this has no effect since its filtered out at root
+ workspaceRepo: true,
+ },
+ },
+ d: {
+ templateOSS: {
+ // turn on repo to override root config
+ workspaceRepo: true,
+ },
+ },
+ },
+ })
+ await s.apply()
+ await t.resolveMatchSnapshot(s.readdir())
+})
diff --git a/test/bin/apply.js b/test/bin/apply.js
new file mode 100644
index 00000000..40f958fe
--- /dev/null
+++ b/test/bin/apply.js
@@ -0,0 +1,54 @@
+const t = require('tap')
+
+const templateApply = (mocks) => t.mock('../../bin/apply.js', mocks && {
+ '../../lib/apply/index.js': async () => mocks(),
+})
+
+const _console = console
+const _global = process.env.npm_config_global
+const _prefix = process.env.npm_config_local_prefix
+const errors = []
+
+t.beforeEach(() => {
+ delete process.env.npm_config_global
+ delete process.env.npm_config_local_prefix
+ console.error = (...args) => errors.push(...args)
+ console.log = () => {
+ throw 'nolog'
+ }
+})
+
+t.afterEach(() => {
+ process.env.npm_config_global = _global
+ process.env.npm_config_local_prefix = _prefix
+ global.console = _console
+ delete process.exitCode
+})
+
+t.test('when npm_config_local_prefix is unset, does nothing', async (t) => {
+ await templateApply()
+ t.equal(process.exitCode, undefined, 'exitCode is unset')
+})
+
+t.test('when npm_config_global is true, does nothing', async (t) => {
+ process.env.npm_config_global = 'true'
+
+ await templateApply()
+ t.equal(process.exitCode, undefined, 'exitCode is unset')
+})
+
+t.test('with mocks', async (t) => {
+ process.env.npm_config_local_prefix = 'heynow'
+
+ await templateApply(() => {})
+ t.equal(process.exitCode, undefined, 'exitCode is unset')
+})
+
+t.test('error', async (t) => {
+ process.env.npm_config_local_prefix = 'heynow'
+
+ await templateApply(() => {
+ throw new Error('apply')
+ })
+ t.equal(process.exitCode, 1, 'exitCode is unset')
+})
diff --git a/test/bin/check.js b/test/bin/check.js
new file mode 100644
index 00000000..6841ceea
--- /dev/null
+++ b/test/bin/check.js
@@ -0,0 +1,58 @@
+const t = require('tap')
+
+const templateCheck = (mocks) => t.mock('../../bin/check.js', mocks && {
+ '../../lib/check/index.js': async () => mocks(),
+})
+
+const _console = console
+const _prefix = process.env.npm_config_local_prefix
+const errors = []
+
+t.beforeEach(() => {
+ delete process.env.npm_config_local_prefix
+ console.error = (...args) => errors.push(...args)
+ console.log = () => {
+ throw 'nolog'
+ }
+})
+
+t.afterEach(() => {
+ process.env.npm_config_local_prefix = _prefix
+ errors.length = 0
+ global.console = _console
+ delete process.exitCode
+})
+
+t.test('no local prefix', async (t) => {
+ await templateCheck()
+
+ t.equal(process.exitCode, 1, 'exit code')
+ t.match(errors, ['Error: This package requires npm'], 'errors')
+ t.equal(errors.length, 1)
+})
+
+t.test('problems', async (t) => {
+ process.env.npm_config_local_prefix = t.testdir()
+
+ await templateCheck(() => [{
+ title: 'message1',
+ body: ['a', 'b'],
+ solution: 'solution1',
+ }, {
+ title: 'message2',
+ body: ['c'],
+ solution: 'solution2',
+ }])
+
+ t.equal(process.exitCode, 1, 'exit code')
+ t.matchSnapshot(errors.join('\n'))
+})
+
+t.test('no problems', async (t) => {
+ process.env.npm_config_local_prefix = t.testdir()
+
+ await templateCheck(() => [])
+
+ t.equal(process.exitCode, undefined, 'exit code')
+ t.strictSame(errors, [], 'errors')
+})
diff --git a/test/bin/npm-template-check.js b/test/bin/npm-template-check.js
deleted file mode 100644
index 94d5bcd4..00000000
--- a/test/bin/npm-template-check.js
+++ /dev/null
@@ -1,121 +0,0 @@
-const t = require('tap')
-
-// t.mock instead of require so the cache doesn't interfere
-const check = (mocks) => t.mock('../../bin/npm-template-check.js', mocks && {
- '../../lib/postlint/check-package.js': async () => mocks.package(),
- '../../lib/postlint/check-gitignore.js': async () => mocks.gitignore(),
-})
-
-const _console = console
-const _prefix = process.env.npm_config_local_prefix
-let errors = []
-let logs = []
-
-t.beforeEach(() => {
- delete process.env.npm_config_local_prefix
- // eslint-disable-next-line no-global-assign
- console = {
- ..._console,
- error: (...args) => errors.push(...args),
- log: (...args) => logs.push(...args),
- }
-})
-
-t.afterEach(() => {
- process.env.npm_config_local_prefix = _prefix
- errors = []
- logs = []
- global.console = _console
- delete process.exitCode
-})
-
-t.test('no local prefix', async (t) => {
- await check()
-
- t.equal(process.exitCode, 1, 'exit code')
- t.strictSame(logs, [], 'logs')
- t.match(errors, ['Error: This package requires npm'], 'errors')
- t.equal(errors.length, 1)
-})
-
-// We have 100% coverage via the coverage map
-// so this is only for how the bin script formats
-// error logs
-t.test('with mocks', (t) => {
- t.plan(2)
-
- t.test('problems', async (t) => {
- process.env.npm_config_local_prefix = t.testdir()
-
- await check({
- package: () => [{
- message: 'message1',
- solution: 'solution1',
- }],
- gitignore: () => [{
- message: 'message2',
- solution: 'solution2',
- }],
- })
-
- t.equal(process.exitCode, 1, 'exit code')
- t.strictSame(logs, [], 'logs')
- t.strictSame(errors, [
- 'Some problems were detected:',
- 'message1\nmessage2',
- 'To correct them:',
- 'solution1\nsolution2',
- ], 'errors')
- })
-
- t.test('no problems', async (t) => {
- process.env.npm_config_local_prefix = t.testdir()
-
- await check({
- package: () => [],
- gitignore: () => [],
- })
-
- t.equal(process.exitCode, undefined, 'exit code')
- t.strictSame(logs, [], 'logs')
- t.strictSame(errors, [], 'errors')
- })
-})
-
-t.test('workspace without root module files', async (t) => {
- const pkgWithWorkspaces = {
- 'package.json': JSON.stringify({
- name: 'testpkg',
- templateOSS: {
- applyRootRepoFiles: false,
- applyWorkspaceRepoFiles: true,
- applyRootModuleFiles: false,
-
- workspaces: ['amazinga'],
- },
- }),
- workspace: {
- a: {
- 'package.json': JSON.stringify({
- name: 'amazinga',
- }),
- },
- },
- }
- const localPrefix = t.testdir(pkgWithWorkspaces)
- process.env.npm_config_local_prefix = localPrefix
-
- await check({
- package: (path, root, config) => [{
- message: 'package',
- solution: `${path} ${root} ${config.applyRootModuleFiles}`,
- }],
- gitignore: (path, root, config) => [{
- message: 'gitignore',
- solution: `${path} ${root} ${config.applyRootRepoFiles}`,
- }],
- })
-
- t.strictSame(logs, [], 'logs')
- t.strictSame(errors, [], 'errors')
-})
diff --git a/test/bin/postinstall.js b/test/bin/postinstall.js
deleted file mode 100644
index c8bda298..00000000
--- a/test/bin/postinstall.js
+++ /dev/null
@@ -1,124 +0,0 @@
-const t = require('tap')
-
-// t.mock instead of require so the cache doesn't interfere
-const postinstall = (mocks) => t.mock('../../bin/postinstall.js', mocks && {
- '../../lib/postinstall/update-package.js': async () => mocks.package(),
- '../../lib/postinstall/copy-content.js': async () => mocks.content(),
-})
-
-const _global = process.env.npm_config_global
-const _prefix = process.env.npm_config_local_prefix
-const _console = console
-let errors = []
-let logs = []
-
-t.beforeEach(() => {
- delete process.env.npm_config_global
- delete process.env.npm_config_local_prefix
- // eslint-disable-next-line no-global-assign
- console = {
- ..._console,
- error: (...args) => errors.push(...args),
- logs: (...args) => logs.push(...args),
- }
-})
-
-t.afterEach(() => {
- process.env.npm_config_global = _global
- process.env.npm_config_local_prefix = _prefix
- errors = []
- logs = []
- global.console = _console
- delete process.exitCode
-})
-
-t.test('when npm_config_local_prefix is unset, does nothing', async (t) => {
- await postinstall()
- t.equal(process.exitCode, undefined, 'exitCode is unset')
-})
-
-t.test('when npm_config_global is true, does nothing', async (t) => {
- process.env.npm_config_global = 'true'
-
- await postinstall()
- t.equal(process.exitCode, undefined, 'exitCode is unset')
-})
-
-// We have 100% coverage via the coverage map
-// so this is only for how the bin script control flow
-t.test('with mocks', (t) => {
- t.plan(4)
-
- t.test('when patchPackage returns false no further action is taken', async (t) => {
- t.plan(2)
-
- process.env.npm_config_local_prefix = 'heynow'
-
- await postinstall({
- package: () => {
- t.pass('package is called')
- return false
- },
- content: () => {
- t.fail('not called')
- },
- })
-
- t.equal(process.exitCode, undefined, 'exitCode is unset')
- })
-
- t.test('when patchPackage returns true content is called', async (t) => {
- t.plan(3)
-
- process.env.npm_config_local_prefix = 'heynow'
-
- await postinstall({
- package: () => {
- t.pass('package is called')
- return true
- },
- content: () => {
- t.pass('content is called')
- },
- })
-
- t.equal(process.exitCode, undefined, 'exitCode is unset')
- })
-
- t.test('sets code and errors when throwing in content', async (t) => {
- process.env.npm_config_local_prefix = 'heynow'
-
- let stack
-
- await postinstall({
- package: () => true,
- content: () => {
- const e = new Error('whoops')
- stack = e.stack
- throw e
- },
- })
-
- t.strictSame(logs, [], 'logs')
- t.match(errors, [stack], 'errors')
- t.equal(process.exitCode, 1, 'exitCode is 1')
- })
-
- t.test('sets code and errors when throwing in package', async (t) => {
- process.env.npm_config_local_prefix = 'heynow'
-
- let stack
-
- await postinstall({
- package: () => {
- const e = new Error('whoops')
- stack = e.stack
- throw e
- },
- })
-
- t.strictSame(logs, [], 'logs')
- t.match(errors, [stack], 'errors')
- t.equal(process.exitCode, 1, 'exitCode is 1')
- })
-})
diff --git a/test/check/changelog.js b/test/check/changelog.js
new file mode 100644
index 00000000..317c11c0
--- /dev/null
+++ b/test/check/changelog.js
@@ -0,0 +1,17 @@
+const t = require('tap')
+const setup = require('../setup.js')
+
+t.cleanSnapshot = setup.clean
+t.formatSnapshot = setup.format.checks
+
+t.test('will report incorrect changelog', async (t) => {
+ const s = await setup(t, {
+ ok: true,
+ testdir: {
+ 'CHANGELOG.md': '# changelorg\n\n#',
+ },
+ })
+
+ await s.apply()
+ await t.resolveMatchSnapshot(s.check())
+})
diff --git a/test/check/diffs.js b/test/check/diffs.js
new file mode 100644
index 00000000..478b6dd6
--- /dev/null
+++ b/test/check/diffs.js
@@ -0,0 +1,163 @@
+const t = require('tap')
+const { join } = require('path')
+const setup = require('../setup.js')
+
+t.cleanSnapshot = setup.clean
+t.formatSnapshot = (a) => Array.isArray(a)
+ ? setup.format.checks(a)
+ : setup.format.readdirSource(a)
+
+t.test('update, remove, errors', async (t) => {
+ const s = await setup(t, { ok: true })
+
+ await s.apply()
+
+ await s.unlink('.npmrc')
+
+ const ciPath = join('.github', 'workflows', 'ci.yml')
+ const ci = await s.readFile(ciPath)
+ await s.writeFile(ciPath, ci.split('\n').slice(0, -21).join('\n'))
+
+ await s.appendFile(
+ join('.github', 'workflows', 'audit.yml'),
+ '>>>>I HOPE THIS IS NOT VALID YAML<<<<<<<<<<<'
+ )
+
+ await s.writeFile('.eslintrc.json', 'this has to be deleted')
+
+ await t.resolveMatchSnapshot(s.check())
+})
+
+t.test('workspaces', async (t) => {
+ const s = await setup(t, {
+ ok: true,
+ workspaces: { a: 'a', b: 'b' },
+ })
+
+ await s.apply()
+
+ await s.writeFile(join(s.workspaces.a, '.npmrc'), 'no workspace npmrc')
+ await s.writeFile(join(s.workspaces.b, '.npmrc'), 'no workspace npmrc')
+
+ await t.resolveMatchSnapshot(s.check())
+})
+
+t.test('will diff json', async (t) => {
+ const s = await setup(t, { ok: true })
+ await s.apply()
+
+ const pkg = await s.readJson('package.json')
+ pkg.author = 'heynow'
+ pkg.files.pop()
+ pkg.files.push('x')
+ pkg.scripts.preversion = 'x'
+ pkg.scripts['lint:fix'] = 'x'
+ pkg.scripts.engines = { node: '15' }
+ pkg.templateVersion = '1'
+ pkg.standard = { config: 'x ' }
+ await s.writeJson('package.json', pkg)
+
+ await t.resolveMatchSnapshot(s.check())
+})
+
+t.test('json overwrite', async (t) => {
+ const s = await setup(t, {
+ testdir: {
+ 'target.json': JSON.stringify({ a: 1 }),
+ content: {
+ 'source.json': JSON.stringify({ b: 1 }),
+ 'index.js': `module.exports={rootRepo:{add:{'target.json':'source.json'}}}`,
+ },
+ },
+ content: 'content',
+ })
+
+ await t.resolveMatchSnapshot(s.check(), 'initial check')
+ await s.apply()
+ t.strictSame(await s.check(), [])
+ await t.resolveMatchSnapshot(s.readdirSource(), 'source after apply')
+})
+
+t.test('json merge', async (t) => {
+ const s = await setup(t, {
+ testdir: {
+ 'target.json': JSON.stringify({ a: 1 }),
+ content: {
+ 'source.json': JSON.stringify({ b: 1 }),
+ 'index.js': await setup.fixture('json-merge.js'),
+ },
+ },
+ content: 'content',
+ })
+
+ await t.resolveMatchSnapshot(s.check(), 'initial check')
+ await s.apply()
+ t.strictSame(await s.check(), [])
+ await t.resolveMatchSnapshot(s.readdirSource(), 'source after apply')
+})
+
+t.test('json delete', async (t) => {
+ const s = await setup(t, {
+ testdir: {
+ 'target.json': JSON.stringify({ a: 1 }),
+ content: {
+ 'source.json': JSON.stringify({ a: '__DELETE__', b: 2 }),
+ 'index.js': await setup.fixture('json-delete.js'),
+ },
+ },
+ content: 'content',
+ })
+
+ await t.resolveMatchSnapshot(s.check(), 'initial check')
+ await s.apply()
+ t.strictSame(await s.check(), [])
+ await t.resolveMatchSnapshot(s.readdirSource(), 'source after apply')
+})
+
+t.test('node 10', async (t) => {
+ const s = await setup(t, { ok: true })
+ await s.apply()
+
+ t.strictSame(await s.check(), [])
+
+ const pkg = await s.readJson('package.json')
+ pkg.templateOSS.ciVersions = [...setup.content.ciVersions, '10']
+ await s.writeJson('package.json', pkg)
+
+ await s.apply()
+ await t.resolveMatchSnapshot(s.check())
+})
+
+t.test('different headers', async (t) => {
+ const s = await setup(t, {
+ testdir: {
+ content: {
+ 'source.txt': 'source',
+ 'index.js': await setup.fixture('header.js'),
+ },
+ },
+ content: 'content',
+ })
+
+ await t.resolveMatchSnapshot(s.check(), 'initial check')
+ await s.apply()
+ t.strictSame(await s.check(), [])
+ await t.resolveMatchSnapshot(s.readdirSource(), 'source after apply')
+})
+
+t.test('unknown file type', async (t) => {
+ const s = await setup(t, {
+ testdir: {
+ content: {
+ 'source.txt': 'source',
+ 'index.js': `module.exports={rootRepo:{add:{'target.txt':'source.txt'}}}`,
+ },
+ },
+ content: 'content',
+ })
+
+ await t.resolveMatchSnapshot(s.check(), 'initial check')
+ await s.apply()
+ t.strictSame(await s.check(), [])
+ await t.resolveMatchSnapshot(s.readdirSource(), 'source after apply')
+})
diff --git a/test/check/dogfood.js b/test/check/dogfood.js
new file mode 100644
index 00000000..51ff024b
--- /dev/null
+++ b/test/check/dogfood.js
@@ -0,0 +1,9 @@
+const t = require('tap')
+const { resolve } = require('path')
+const check = require('../../lib/check/index.js')
+
+t.test('this repo passes all checks', async (t) => {
+ const root = resolve(__dirname, '..', '..')
+ const res = await check(root, resolve(root, 'lib', 'content'))
+ t.equal(res.length, 0)
+})
diff --git a/test/check/gitignore.js b/test/check/gitignore.js
new file mode 100644
index 00000000..bd5f249d
--- /dev/null
+++ b/test/check/gitignore.js
@@ -0,0 +1,65 @@
+const { join } = require('path')
+const t = require('tap')
+const setup = require('../setup.js')
+
+t.cleanSnapshot = setup.clean
+t.formatSnapshot = setup.format.checks
+
+t.test('will report tracked files in gitignore', async (t) => {
+ const s = await setup.git(t, { ok: true })
+
+ await s.writeFile('ignorethis', 'empty')
+
+ await s.gca()
+ await s.apply()
+ await t.resolveMatchSnapshot(s.check())
+})
+
+t.test('will report tracked files in gitignore workspace', async (t) => {
+ const s = await setup.git(t, {
+ ok: true,
+ workspaces: {
+ a: '@aaa/a',
+ b: 'b',
+ },
+ })
+
+ await s.writeFile('ignorethis', 'empty')
+ await s.writeFile(join(s.workspaces.a, 'wsafile'), 'empty')
+ await s.writeFile(join(s.workspaces.b, 'wsbfile'), 'empty')
+
+ await s.gca()
+ await s.apply()
+ await t.resolveMatchSnapshot(s.check())
+})
+
+t.test('works with workspaces in separate dirs', async (t) => {
+ const s = await setup.git(t, {
+ ok: true,
+ package: {
+ workspaces: ['workspace-a', 'workspace-b'],
+ },
+ testdir: {
+ 'workspace-a': {
+ 'package.json': JSON.stringify({
+ name: 'a',
+ ...setup.okPackage(),
+ }),
+ },
+ 'workspace-b': {
+ 'package.json': JSON.stringify({
+ name: 'b',
+ ...setup.okPackage(),
+ }),
+ },
+ },
+ })
+
+ await s.writeFile('ignorethis', 'empty')
+ await s.writeFile(join('workspace-a', 'wsafile'), 'empty')
+ await s.writeFile(join('workspace-b', 'wsbfile'), 'empty')
+
+ await s.gca()
+ await s.apply()
+ await t.resolveMatchSnapshot(s.check())
+})
diff --git a/test/check/index.js b/test/check/index.js
new file mode 100644
index 00000000..369b5511
--- /dev/null
+++ b/test/check/index.js
@@ -0,0 +1,22 @@
+const t = require('tap')
+const setup = require('../setup.js')
+
+t.cleanSnapshot = setup.clean
+t.formatSnapshot = setup.format.checks
+
+t.test('check empty dir', async (t) => {
+ const s = await setup(t)
+ await t.resolveMatchSnapshot(s.check())
+})
+
+t.test('workspaces with empty dir', async (t) => {
+ const s = await setup(t, {
+ workspaces: { a: '@name/aaaa', b: 'bbb' },
+ })
+ await t.resolveMatchSnapshot(s.check())
+})
+
+t.test('with empty content', async (t) => {
+ const s = await setup(t, { content: {} })
+ t.same(await s.check(), [])
+})
diff --git a/test/check/unwanted.js b/test/check/unwanted.js
new file mode 100644
index 00000000..c145f550
--- /dev/null
+++ b/test/check/unwanted.js
@@ -0,0 +1,38 @@
+const t = require('tap')
+const setup = require('../setup.js')
+
+t.cleanSnapshot = setup.clean
+t.formatSnapshot = setup.format.checks
+
+t.test('unwanted', async (t) => {
+ const s = await setup(t, {
+ ok: true,
+ package: {
+ dependencies: {
+ eslint: '^8.0.0',
+ },
+ },
+ })
+
+ await s.apply()
+ await t.resolveMatchSnapshot(s.check())
+})
+
+t.test('unwanted can be overriden with allow', async (t) => {
+ const s = await setup(t, {
+ ok: true,
+ package: {
+ dependencies: {
+ eslint: '^8.0.0',
+ },
+ templateOSS: {
+ allowedPackages: [
+ 'eslint',
+ ],
+ },
+ },
+ })
+
+ await s.apply()
+ t.strictSame(await s.check(), [])
+})
diff --git a/test/fixtures/header.js b/test/fixtures/header.js
new file mode 100644
index 00000000..335c0bbe
--- /dev/null
+++ b/test/fixtures/header.js
@@ -0,0 +1,25 @@
+
+module.exports = {
+ rootRepo: {
+ add: {
+ 'header.txt': {
+ file: 'source.txt',
+ parser: (p) => class extends p.Base {
+ static header = 'Different header'
+ },
+ },
+ 'noheader.txt': {
+ file: 'source.txt',
+ parser: (p) => class extends p.Base {
+ static header = null
+ },
+ },
+ 'nocomment.txt': {
+ file: 'source.txt',
+ parser: (p) => class extends p.Base {
+ comment = null
+ },
+ },
+ },
+ },
+}
diff --git a/test/fixtures/json-delete.js b/test/fixtures/json-delete.js
new file mode 100644
index 00000000..d35a2408
--- /dev/null
+++ b/test/fixtures/json-delete.js
@@ -0,0 +1,10 @@
+module.exports = {
+ rootRepo: {
+ add: {
+ 'target.json': {
+ file: 'source.json',
+ parser: (p) => p.Json,
+ },
+ },
+ },
+}
diff --git a/test/fixtures/json-merge.js b/test/fixtures/json-merge.js
new file mode 100644
index 00000000..69d78c27
--- /dev/null
+++ b/test/fixtures/json-merge.js
@@ -0,0 +1,10 @@
+module.exports = {
+ rootRepo: {
+ add: {
+ 'target.json': {
+ file: 'source.json',
+ parser: (p) => p.JsonMerge,
+ },
+ },
+ },
+}
diff --git a/test/index.js b/test/index.js
new file mode 100644
index 00000000..0783d3f1
--- /dev/null
+++ b/test/index.js
@@ -0,0 +1,32 @@
+const t = require('tap')
+const setup = require('./setup.js')
+
+t.test('apply and check is ok', async (t) => {
+ const s = await setup(t, { ok: true })
+ t.same(await s.runAll(), [])
+})
+
+t.test('apply and check multiple is ok', async (t) => {
+ const s = await setup(t, { ok: true })
+ t.same(await s.runAll(), [])
+ t.same(await s.runAll(), [])
+})
+
+t.test('empty content is ok', async (t) => {
+ const s = await setup(t, { content: {} })
+ t.same(await s.runAll(), [])
+})
+
+t.test('empty file objects', async (t) => {
+ const s = await setup.git(t, {
+ ok: true,
+ workspaces: { a: 'a', b: 'b' },
+ content: {
+ rootRepo: {},
+ rootModule: {},
+ workspaceRepo: {},
+ workspaceModule: {},
+ },
+ })
+ t.same(await s.runAll(), [])
+})
diff --git a/test/postinstall/copy-content.js b/test/postinstall/copy-content.js
deleted file mode 100644
index d80e537b..00000000
--- a/test/postinstall/copy-content.js
+++ /dev/null
@@ -1,250 +0,0 @@
-const { join } = require('path')
-const fs = require('@npmcli/fs')
-const t = require('tap')
-const getConfig = require('../../lib/config.js')
-
-const copyContent = require('../../lib/postinstall/copy-content.js')
-
-t.test('copies content', async (t) => {
- const root = t.testdir()
-
- await copyContent(root, root)
- for (let target of Object.keys(copyContent.moduleFiles)) {
- target = join(root, target)
- await t.resolves(fs.stat(target))
- }
- for (let target of Object.keys(copyContent.repoFiles)) {
- target = join(root, target)
- await t.resolves(fs.stat(target))
- }
-})
-
-t.test('removes files', async (t) => {
- const content = {
- '.eslintrc.json': '{}',
- '.eslintrc.yml': '',
- '.eslintrc.local.json': '{}',
- 'something.txt': '',
- }
- const keepContent = [
- '.eslintrc.local.json',
- 'something.txt',
- ]
- const root = t.testdir(content, {
- applyRootRepoFiles: true,
- applyWorkspaceRepoFiles: true,
- applyRootModuleFiles: true,
- })
-
- await copyContent(root, root)
- for (const target of Object.keys(copyContent.moduleFiles)) {
- const fullTarget = join(root, target)
- await t.resolves(fs.stat(fullTarget), `copied ${target}`)
- }
- for (const target of Object.keys(copyContent.repoFiles)) {
- const fullTarget = join(root, target)
- await t.resolves(fs.stat(fullTarget), `copied ${target}`)
- }
-
- for (const target in content) {
- const fullTarget = join(root, target)
- if (keepContent.find((f) => f === target)) {
- await t.resolves(fs.stat(fullTarget), `left existing ${target}`)
- } else {
- await t.rejects(fs.stat(fullTarget), { code: 'ENOENT' }, `removed ${target}`)
- }
- }
-})
-
-t.test('handles workspaces', async (t) => {
- const pkgWithWorkspaces = {
- 'package.json': JSON.stringify({
- name: 'testpkg',
- templateOSS: {
- applyRootRepoFiles: true,
- applyWorkspaceRepoFiles: true,
- applyRootModuleFiles: true,
- workspaces: ['workspace/a', 'workspace/b'],
- },
- }),
- workspace: {
- a: {
- 'package.json': JSON.stringify({
- name: 'amazinga',
- }),
- },
- b: {
- 'package.json': JSON.stringify({
- name: '@somenamespace/amazingb',
- }),
- },
- },
- }
- const root = t.testdir(pkgWithWorkspaces)
- const workspacea = join(root, 'workspace', 'a')
- const config = await getConfig(root)
- await copyContent(workspacea, root, config)
-
- // change should have applied to the workspace, not the root
- await t.resolves(fs.stat(join(root, 'workspace', 'a', '.eslintrc.js')))
- await t.rejects(fs.stat(join(root, 'workspace', 'b', '.eslintrc.js')))
-
- await t.rejects(fs.stat(join(root, '.eslintrc.js')))
- await copyContent(root, root, config)
- await t.resolves(fs.stat(join(root, '.eslintrc.js')))
- // should have made the workspace action in the root
- await t.resolves(fs.stat(join(root, '.github', 'workflows', 'ci-amazinga.yml')))
- await t.resolves(fs.stat(join(root, '.github', 'workflows', 'release-please-amazinga.yml')))
- await t.resolves(fs.stat(join(root, '.github', 'ISSUE_TEMPLATE', 'bug.yml')))
-
- const workspaceb = join(root, 'workspace', 'b')
- await copyContent(workspaceb, root, config)
-
- const workspacebCi = join(root, '.github', 'workflows', 'ci-somenamespace-amazingb.yml')
- await t.resolves(fs.stat(workspacebCi))
- const workspacebCiContent = await fs.readFile(workspacebCi, { encoding: 'utf-8' })
- t.match(workspacebCiContent, '@somenamespace/amazingb')
- t.match(workspacebCiContent, join('workspace', 'b'))
- t.notMatch(workspacebCiContent, '%%')
-})
-
-t.test('handles workspaces with no root repo files', async (t) => {
- const pkgWithWorkspaces = {
- 'package.json': JSON.stringify({
- name: 'testpkg',
- templateOSS: {
- applyRootRepoFiles: false,
- applyWorkspaceRepoFiles: true,
- applyRootModuleFiles: true,
-
- workspaces: ['workspace/a', 'workspace/b'],
- },
- }),
- workspace: {
- a: {
- 'package.json': JSON.stringify({
- name: 'amazinga',
- }),
- },
- b: {
- 'package.json': JSON.stringify({
- name: 'amazingb',
- }),
- },
- },
- }
-
- const root = t.testdir(pkgWithWorkspaces)
- const workspacea = join(root, 'workspace', 'a')
- const config = await getConfig(root)
- await copyContent(workspacea, root, config)
- await copyContent(root, root, config)
-
- await t.resolves(fs.stat(join(root, '.github', 'workflows', 'ci-amazinga.yml')))
- await t.rejects(fs.stat(join(root, '.github', 'ISSUE_TEMPLATE', 'bug.yml')))
- await t.resolves(fs.stat(join(root, 'workspace', 'a', '.eslintrc.js')))
- await t.rejects(fs.stat(join(root, 'workspace', 'b', '.eslintrc.js')))
- await t.resolves(fs.stat(join(root, '.eslintrc.js')))
-})
-
-t.test('handles workspaces with no root repo and repo files', async (t) => {
- const pkgWithWorkspaces = {
- 'package.json': JSON.stringify({
- name: 'testpkg',
- templateOSS: {
- applyRootRepoFiles: false,
- applyWorkspaceRepoFiles: false,
- applyRootModuleFiles: true,
-
- workspaces: ['workspace/a', 'workspace/b'],
- },
- }),
- workspace: {
- a: {
- 'package.json': JSON.stringify({
- name: 'amazinga',
- }),
- },
- b: {
- 'package.json': JSON.stringify({
- name: 'amazingb',
- }),
- },
- },
- }
-
- const root = t.testdir(pkgWithWorkspaces)
- const workspacea = join(root, 'workspace', 'a')
- const config = await getConfig(root)
- await copyContent(workspacea, root, config)
-
- await t.rejects(fs.stat(join(root, '.github', 'workflows', 'ci-amazinga.yml')))
- await t.rejects(fs.stat(join(root, '.github', 'ISSUE_TEMPLATE', 'bug.yml')))
- await t.resolves(fs.stat(join(root, 'workspace', 'a', '.eslintrc.js')))
- await t.rejects(fs.stat(join(root, 'workspace', 'b', '.eslintrc.js')))
-
- await t.rejects(fs.stat(join(root, '.eslintrc.js')))
- await copyContent(root, root, config)
- await t.resolves(fs.stat(join(root, '.eslintrc.js')))
- await t.rejects(fs.stat(join(root, '.github', 'ISSUE_TEMPLATE', 'bug.yml')))
-})
-
-t.test('handles workspaces with no root files', async (t) => {
- const pkgWithWorkspaces = {
- 'package.json': JSON.stringify({
- name: 'testpkg',
- templateOSS: {
- applyRootRepoFiles: false,
- applyWorkspaceRepoFiles: false,
- applyRootModuleFiles: false,
-
- workspaces: ['workspace/a', 'workspace/b'],
- },
- }),
- workspace: {
- a: {
- 'package.json': JSON.stringify({
- name: 'amazinga',
- }),
- },
- b: {
- 'package.json': JSON.stringify({
- name: 'amazingb',
- }),
- },
- },
- }
-
- const root = t.testdir(pkgWithWorkspaces)
- const workspacea = join(root, 'workspace', 'a')
- const config = await getConfig(root)
- await copyContent(workspacea, root, config)
-
- await t.rejects(fs.stat(join(root, '.github', 'workflows', 'ci-amazinga.yml')))
- await t.rejects(fs.stat(join(root, '.github', 'ISSUE_TEMPLATE', 'bug.yml')))
- await t.resolves(fs.stat(join(root, 'workspace', 'a', '.eslintrc.js')))
- await t.rejects(fs.stat(join(root, 'workspace', 'b', '.eslintrc.js')))
-
- await t.rejects(fs.stat(join(root, '.eslintrc.js')))
- await copyContent(root, root, config)
- await t.rejects(fs.stat(join(root, '.eslintrc.js')))
- await t.rejects(fs.stat(join(root, '.github', 'workflows', 'ci-amazinga.yml')))
- await t.rejects(fs.stat(join(root, '.github', 'ISSUE_TEMPLATE', 'bug.yml')))
-})
-
-t.test('no windows ci', async (t) => {
- const pkgWithNoWindowsCI = {
- 'package.json': JSON.stringify({
- name: 'testpkg',
- templateOSS: {
- windowsCI: false,
- },
- }),
- }
- const root = t.testdir(pkgWithNoWindowsCI)
- const config = await getConfig(root)
- await copyContent(root, root, config)
- const target = join(root, '.github', 'workflows', 'ci.yml')
- const contents = await fs.readFile(target, 'utf8')
- await t.notMatch(/windows/, contents, 'no windows ci')
-})
diff --git a/test/postinstall/update-package.js b/test/postinstall/update-package.js
deleted file mode 100644
index 5608f9b3..00000000
--- a/test/postinstall/update-package.js
+++ /dev/null
@@ -1,189 +0,0 @@
-const { join } = require('path')
-const fs = require('@npmcli/fs')
-const t = require('tap')
-
-const {
- version: TEMPLATE_VERSION,
- name: TEMPLATE_NAME,
-} = require('../../package.json')
-
-const patchPackage = require('../../lib/postinstall/update-package.js')
-
-t.test('can patch a package.json', async (t) => {
- const pkg = {
- name: '@npmcli/foo',
- version: '1.0.0',
- author: 'someone else',
- files: [],
- license: 'MIT',
- scripts: {
- foo: 'echo bar',
- },
- }
-
- const project = t.testdir({
- 'package.json': JSON.stringify(pkg, null, 2),
- })
-
- const needsAction = await patchPackage(project)
- t.equal(needsAction, true, 'returned true')
-
- const contents = await fs.readFile(join(project, 'package.json'), {
- encoding: 'utf8',
- })
- const parsed = JSON.parse(contents)
- t.match(parsed, patchPackage.changes, 'all changes were applied')
- t.equal(parsed.scripts.foo, 'echo bar', 'did not remove existing script')
-})
-
-t.test('returns false when templateVersion matches own version', async (t) => {
- const pkg = {
- name: '@npmcli/foo',
- templateOSS: {
- version: TEMPLATE_VERSION,
- },
- version: '1.0.0',
- author: 'someone else',
- files: [],
- license: 'MIT',
- }
-
- const project = t.testdir({
- 'package.json': JSON.stringify(pkg, null, 2),
- })
-
- const needsAction = await patchPackage(project)
- t.equal(needsAction, false, 'returned false')
-
- const contents = await fs.readFile(join(project, 'package.json'), {
- encoding: 'utf8',
- })
- t.notMatch(JSON.parse(contents), patchPackage.changes, 'changes were NOT applied')
-})
-
-t.test('returns true when templateVersion matches own version when forced', async (t) => {
- const pkg = {
- name: '@npmcli/foo',
- templateOSS: {
- version: TEMPLATE_VERSION,
- },
- version: '1.0.0',
- author: 'someone else',
- files: [],
- license: 'MIT',
- }
-
- const project = t.testdir({
- 'package.json': JSON.stringify(pkg, null, 2),
- })
-
- const needsAction = await patchPackage(project, undefined, { force: true })
- t.equal(needsAction, true, 'returned true')
-
- const contents = await fs.readFile(join(project, 'package.json'), {
- encoding: 'utf8',
- })
- const parsed = JSON.parse(contents)
- t.match(parsed, patchPackage.changes, 'all changes were applied')
-})
-
-t.test('doesnt set templateVersion on own repo', async (t) => {
- const pkg = {
- name: TEMPLATE_NAME,
- }
-
- const project = t.testdir({
- 'package.json': JSON.stringify(pkg, null, 2),
- })
-
- const needsAction = await patchPackage(project)
- t.equal(needsAction, true, 'needs action')
-
- const contents = JSON.parse(await fs.readFile(join(project, 'package.json'), {
- encoding: 'utf8',
- }))
- const version = (contents.templateOSS) ?
- contents.templateOSS.version : contents.templateVersion
- t.equal(version, undefined, 'did not get template version')
-})
-
-t.test('only sets templateVersion on root pkg when configured', async (t) => {
- const pkgWithWorkspaces = {
- 'package.json': JSON.stringify({
- name: 'testpkg',
- templateOSS: {
- applyRootRepoFiles: false,
- applyWorkspaceRepoFiles: true,
- applyRootModuleFiles: false,
-
- workspaces: ['amazinga'],
- },
- }),
- workspace: {
- a: {
- 'package.json': JSON.stringify({
- name: 'amazinga',
- }),
- },
- },
- }
- const root = t.testdir(pkgWithWorkspaces)
- await patchPackage(root, root, {
- applyRootRepoFiles: false,
- applyWorkspaceRepoFiles: true,
- applyRootModuleFiles: false,
- })
-
- const contents = JSON.parse(await fs.readFile(join(root, 'package.json'), {
- encoding: 'utf8',
- }))
-
- t.not(contents.templateOSS.version, undefined, 'should set templateVersion')
- t.equal(contents.author, undefined, 'should not set other fields')
-})
-
-t.test('converts template Version', async (t) => {
- const pkg = {
- name: 'testpkg',
- templateVersion: '2.0.0',
- }
-
- const project = t.testdir({
- 'package.json': JSON.stringify(pkg, null, 2),
- })
-
- const needsAction = await patchPackage(project)
- t.equal(needsAction, true, 'needs action')
-
- const contents = JSON.parse(await fs.readFile(join(project, 'package.json'), {
- encoding: 'utf8',
- }))
- t.equal(contents.templateVersion, undefined, 'did not get template version')
- t.equal(contents.templateOSS.version, TEMPLATE_VERSION, 'did not get template version')
-})
-
-t.test('removes standard', async (t) => {
- const pkg = {
- name: 'testpkg',
- scripts: {
- test: 'test',
- 'lint:fix': 'something',
- },
- standard: {
- ignore: [],
- },
- }
-
- const project = t.testdir({
- 'package.json': JSON.stringify(pkg, null, 2),
- })
-
- const needsAction = await patchPackage(project)
- t.equal(needsAction, true, 'needs action')
-
- const contents = JSON.parse(await fs.readFile(join(project, 'package.json'), {
- encoding: 'utf8',
- }))
- t.equal(contents.standard, undefined, 'removed standard')
- t.equal(contents.scripts['lint:fix'], undefined, 'removes lint:fix script')
-})
diff --git a/test/postlint/check-gitignore.js b/test/postlint/check-gitignore.js
deleted file mode 100644
index 3372583a..00000000
--- a/test/postlint/check-gitignore.js
+++ /dev/null
@@ -1,76 +0,0 @@
-const t = require('tap')
-const { sync: which } = require('which')
-const { spawnSync } = require('child_process')
-const fs = require('fs')
-const { resolve } = require('path')
-
-const check = require('../../lib/postlint/check-gitignore.js')
-
-const gitCwd = (cwd) => (args) =>
- spawnSync(which('git'), args.split(' '), { encoding: 'utf-8', cwd }).stdout
-
-const gitTestdir = (t, files) => {
- const path = t.testdir(files)
- const git = gitCwd(path)
-
- git('init')
- git('add -A .')
- git('commit -m "init"')
-
- const appendGitignore = (append) => {
- fs.appendFileSync(resolve(path, '.gitignore'), append)
- git('add -A .')
- git('commit -m "update gitignore"')
- }
-
- return { path, appendGitignore }
-}
-
-t.test('will report tracked files in gitignore', async (t) => {
- const { path, appendGitignore } = gitTestdir(t, {
- '.gitignore': '# comment\n\n\n\nignored\n',
- ignored: '',
- tracked: '',
- willIgnore1: '',
- willIgnore2: '',
- })
-
- appendGitignore('willIgnore*\n')
-
- const problems = await check(path)
-
- t.equal(problems.length, 1)
- t.strictSame(problems[0].message.match(/\bwillIgnore1\b/), ['willIgnore1'])
- t.strictSame(problems[0].message.match(/\bwillIgnore2\b/), ['willIgnore2'])
- t.matchSnapshot(problems)
-})
-
-t.test('no warnings', async (t) => {
- const { path, appendGitignore } = gitTestdir(t, {
- '.gitignore': 'ignored\n',
- ignored: '',
- tracked: '',
- willIgnore1: '',
- willIgnore2: '',
- })
-
- appendGitignore('wontIgnore*\n')
-
- const problems = await check(path)
-
- t.equal(problems.length, 0)
- t.strictSame(problems, [])
- t.matchSnapshot(problems)
-})
-
-t.test('noop with no .git', async (t) => {
- const path = t.testdir()
- t.strictSame(await check(path), [])
-})
-
-t.test('error with .git but no .gitignore', async (t) => {
- const path = t.testdir({
- '.git': '',
- })
- t.rejects(check(path), /\.gitignore must exist/)
-})
diff --git a/test/postlint/check-package.js b/test/postlint/check-package.js
deleted file mode 100644
index 1e3befec..00000000
--- a/test/postlint/check-package.js
+++ /dev/null
@@ -1,96 +0,0 @@
-const t = require('tap')
-
-const check = require('../../lib/postlint/check-package.js')
-const { changes } = require('../../lib/postinstall/update-package.js')
-const { name } = require('../../package.json')
-
-t.cleanSnapshot = (snapshot) => {
- return snapshot.replace(
- /("version" Expected: ").*(" Found)/g,
- '$1$TEMPLATE_VERSION$2'
- )
-}
-
-t.test('checks a package.json', (t) => {
- t.plan(5)
-
- t.test('missing fields', async (t) => {
- const project = t.testdir({
- 'package.json': '{}',
- })
-
- const problems = await check(project)
- t.matchSnapshot(problems, 'problems')
- t.equal(problems.length, 1)
- })
-
- t.test('incorrect fields', async (t) => {
- const project = t.testdir({
- 'package.json': JSON.stringify({
- author: 'Bob',
- }),
- })
-
- const problems = await check(project)
- t.matchSnapshot(problems, 'problems')
- t.equal(problems.length, 1)
- })
-
- t.test('incorrect object fields', async (t) => {
- const project = t.testdir({
- 'package.json': JSON.stringify({
- scripts: {},
- }),
- })
-
- const problems = await check(project)
- t.matchSnapshot(problems, 'problems')
- t.equal(problems.length, 1)
- })
-
- t.test('unwanted deps', async (t) => {
- const project = t.testdir({
- 'package.json': JSON.stringify({
- dependencies: {
- ...check.unwantedPackages.reduce((acc, k) => {
- acc[k] = '1'
- return acc
- }, {}),
- },
- }),
- })
-
- const problems = await check(project)
- t.matchSnapshot(problems, 'problems')
- t.equal(problems.length, 2)
- })
-
- t.test('unwanted deps', async (t) => {
- const project = t.testdir({
- 'package.json': JSON.stringify(changes),
- })
-
- const problems = await check(project)
- t.matchSnapshot(problems, 'problems')
- t.equal(problems.length, 0)
- })
-
- t.end()
-})
-
-t.test('this repo doesnt get version', async (t) => {
- const pkg = {
- ...changes,
- name,
- }
-
- delete pkg.templateVersion
- delete pkg.templateOSS
-
- const project = t.testdir({
- 'package.json': JSON.stringify(pkg),
- })
-
- const needsAction = await check(project)
- t.same(needsAction, [])
-})
diff --git a/test/sequential/no-local-changes.js b/test/sequential/no-local-changes.js
deleted file mode 100644
index 65c5fc6a..00000000
--- a/test/sequential/no-local-changes.js
+++ /dev/null
@@ -1,42 +0,0 @@
-const promiseSpawn = require('@npmcli/promise-spawn')
-const t = require('tap')
-
-const spawn = (name, args) => promiseSpawn(name, args, {
- stdioString: true,
- shell: true,
-})
-
-// Check if the repo is clean, ignoring the tests
-const isClean = () => spawn('git',
- ['status', '--porcelain', '.', '--', ':!test/'])
-
-// When the tests are run via `npm run preversion` it should fail
-// if any of the changes haven't already been applied to this repo.
-// Also note that this test has to be sequential so that there are
-// no test/tap-testdir-* being created.
-t.test('commands dont change repo', async (t) => {
- const startClean = await isClean()
-
- if (startClean.stdout) {
- t.equal(startClean.stdout, '', 'git status must be clean')
- const diff = await spawn('git',
- ['--no-pager', 'diff', '--word-diff=porcelain'])
- t.comment(`git diff: ${diff}`)
- t.end()
- return
- }
-
- const postinstall = await spawn('npm', ['run', 'postinstall', '-s'])
- const postlint = await spawn('npm', ['run', 'postlint', '-s'])
- const clean = await isClean()
-
- t.equal(postinstall.stdout, '', 'postinstall stdout')
- t.equal(postinstall.stderr, '', 'postinstall stderr')
- t.equal(postinstall.code, 0, 'postinstall code')
-
- t.equal(postlint.stdout, '', 'postlint stdout')
- t.equal(postlint.stderr, '', 'postlint stderr')
- t.equal(postlint.code, 0, 'postlint code')
-
- t.equal(clean.stdout, '', 'git status is clean')
-})
diff --git a/test/sequential/tap-parallel-not-ok b/test/sequential/tap-parallel-not-ok
deleted file mode 100644
index e69de29b..00000000
diff --git a/test/setup.js b/test/setup.js
new file mode 100644
index 00000000..9331dca4
--- /dev/null
+++ b/test/setup.js
@@ -0,0 +1,178 @@
+const t = require('tap')
+const { join, isAbsolute, resolve } = require('path')
+const { merge, defaults } = require('lodash')
+const fs = require('@npmcli/fs')
+const Git = require('@npmcli/git')
+const output = require('../lib/util/output.js')
+const apply = require('../lib/apply/index.js')
+const check = require('../lib/check/index.js')
+const CONTENT = require('..')
+
+const createPackageJson = (pkg) => ({
+ 'package.json': JSON.stringify(pkg, null, 2),
+})
+
+const pkgWithName = (name, defName) => {
+ const pkg = typeof name === 'string' ? { name } : name
+ if (!pkg.name) {
+ pkg.name = defName
+ }
+ return pkg
+}
+
+// minimum package.json for no errors
+const okPackage = () => ({ ...CONTENT.requiredPackages })
+
+const setupRoot = async (root, content) => {
+ const rootPath = (...p) => join(root, ...p)
+
+ // fs methods for reading from the root
+ const rootFs = Object.fromEntries(Object.entries({
+ readdir: fs.readdir,
+ readFile: (p) => fs.readFile(p, { encoding: 'utf-8' }),
+ writeFile: (p, d) => fs.writeFile(p, d, { encoding: 'utf-8' }),
+ appendFile: fs.appendFile,
+ stat: fs.stat,
+ unlink: fs.unlink,
+ }).map(([k, fn]) => [k, (p, ...rest) => fn(rootPath(p), ...rest)]))
+
+ // Returns a recurisve list of relative file
+ // paths in the testdir root
+ const readdir = async (p = '') => {
+ const paths = []
+ const rootRes = await rootFs.readdir(p)
+ for (const source of rootRes) {
+ const nextPath = join(p, source)
+ if (nextPath === '.git') {
+ continue
+ }
+ if ((await rootFs.stat(nextPath)).isDirectory()) {
+ paths.push(...await readdir(nextPath))
+ } else {
+ paths.push(nextPath)
+ }
+ }
+ return paths.sort()
+ }
+
+ // returns an object where the keys are relative file
+ // paths and the values are the full contents
+ const readdirSource = async (p = '') => {
+ const files = await readdir(p)
+ const contents = await Promise.all(files.map((f) => rootFs.readFile(f)))
+ return Object.fromEntries(files.map((f, i) => [f, contents[i]]))
+ }
+
+ return {
+ root,
+ ...rootFs,
+ readdirSource,
+ readdir,
+ readJson: async (f) => JSON.parse(await rootFs.readFile(f)),
+ writeJson: (p, d) => rootFs.writeFile(p, JSON.stringify(d, null, 2)),
+ join: rootPath,
+ // use passed in content path or allow overriding per method call for tests
+ apply: () => apply(root, content),
+ check: () => check(root, content),
+ runAll: () => apply(root, content).then(() => check(root, content)),
+ }
+}
+
+const setup = async (t, {
+ package = {},
+ workspaces = {},
+ testdir = {},
+ content,
+ ok,
+} = {}) => {
+ const wsLookup = {}
+ const pkg = merge(
+ pkgWithName(package, 'testpkg'),
+ ok ? okPackage() : {}
+ )
+
+ // convenience for passing in workspaces as an object
+ // and getting those converted to a proper workspaces array
+ // and adding the workspaces to the testdir
+ if (Object.keys(workspaces).length) {
+ const wsEntries = Object.entries(workspaces)
+ const wsDir = 'workspaces'
+ defaults(pkg, { workspaces: [] })
+ merge(testdir, { [wsDir]: {} })
+
+ for (const [wsBase, wsPkgName] of wsEntries) {
+ const wsPkg = merge(
+ pkgWithName(wsPkgName, wsBase),
+ ok ? okPackage() : {}
+ )
+ const wsPath = join(wsDir, wsBase)
+ // obj to lookup workspaces by path in tests
+ wsLookup[wsBase] = wsPath
+ merge(testdir[wsDir], { [wsBase]: createPackageJson(wsPkg) })
+ pkg.workspaces.push(wsPath)
+ }
+ }
+
+ // creates dir with a root package.json and
+ // package.json files for each workspace
+ const root = t.testdir(merge(
+ createPackageJson(pkg),
+ testdir
+ ))
+
+ if (typeof content === 'string') {
+ // default content path is absolute but tests can either setup their
+ // own relative path or pass in objects. But if it is a path it has
+ // to be absolute to be passed in
+ content = isAbsolute(content) ? content : join(root, content)
+ }
+
+ return {
+ ...(await setupRoot(root, content)),
+ workspaces: wsLookup,
+ }
+}
+
+const setupGit = async (...args) => {
+ const s = await setup(...args)
+ const git = (arg) => Git.spawn(arg.split(' '), { cwd: s.root })
+
+ const gca = async () => {
+ await git('add -A .')
+ await git('commit -m "init"')
+ }
+
+ await git('init')
+ await git('remote add origin git@github.com:testuser/myrepo.git')
+ await gca()
+
+ return {
+ ...s,
+ git,
+ gca,
+ }
+}
+
+const cleanSnapshot = (str) => str.replace(/\\+/g, '/').replace(/\r\n/g, '\n')
+const formatSnapshots = {
+ readdir: (arr) => arr.join('\n').trim(),
+ readdirSource: (obj) => Object.entries(obj).map(([file, content]) =>
+ [file, '='.repeat(40), content].join('\n').trim()).join('\n\n').trim(),
+ checks: (arr) => output(arr).trim(),
+}
+
+module.exports = setup
+module.exports.git = setupGit
+module.exports.content = CONTENT
+module.exports.clean = cleanSnapshot
+module.exports.format = formatSnapshots
+module.exports.okPackage = okPackage
+module.exports.fixture = (f) => fs.readFile(resolve(__dirname, 'fixtures', f), 'utf-8')
+module.exports.log = (t, f = () => true) => {
+ const cb = (...args) => f(...args) && console.error(...args)
+ process.on('log', cb)
+ t.teardown(() => process.off('log', cb))
+}
+
+// make tap not report this as skipping tests
+t.ok(1)
diff --git a/test/util/parse-ci-versions.js b/test/util/parse-ci-versions.js
new file mode 100644
index 00000000..85226e76
--- /dev/null
+++ b/test/util/parse-ci-versions.js
@@ -0,0 +1,30 @@
+const t = require('tap')
+const parse = require('../../lib/util/parse-ci-versions.js')
+
+const targets = [
+ [[], ''],
+ [undefined, ''],
+ [['12.1.0'], '>=12.1.0'],
+ [['12.1.0', '12.2.0', '12'], '>=12.1.0'],
+ [['12'], '>=12.0.0'],
+ [['~12.5'], '>=12.5.0'],
+ [['12.99.0', '12.1.1'], '>=12.1.1'],
+ [['12.1.0', '12.x', '18.5.0'], '^12.1.0 || >=18.5.0'],
+ [['12.1.0', '12.x', '^18'], '^12.1.0 || >=18.0.0', '^18'],
+ [['12.1.0', '12.2.0', '~12.5'], '>=12.1.0', '~12.5'],
+ [['12.13.0', '12.x', '14.15.0', '14.x', '16.0.0', '16.x'],
+ '^12.13.0 || ^14.15.0 || >=16.0.0', '16.x'],
+ // this is to test current behavior but these go against
+ // the assumption that provided versions will always be lower
+ // then the upper bound of the range
+ [['12.99.0', '12.1.x'], '>=12.99.0'],
+ [['12.99.0', '12.100.0', '12.50.0', '12.2.x', '12.5.x'], '>=12.50.0'],
+]
+
+targets.forEach(([target, engine, max]) => {
+ const found = parse(target)
+ t.equal(found.engines, engine, `${target} => ${engine}`)
+ if (max) {
+ t.equal(found.targets[found.targets.length - 1], max, 'sorted max version')
+ }
+})