diff --git a/README.md b/README.md index 94cf0116..c05ea287 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,28 @@ single devDependency. **CAUTION: THESE CHANGES WILL OVERWRITE ANY LOCAL FILES AND SETTINGS** +### Configuration + +Configure the use of `template-oss` in the root `package.json`. + +```js +{ + name: 'my-package', + // ... + templateOSS: { + // copy repo specific files for the root pkg + applyRootRepoFiles: 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 + workspaces: ['workspace-package-name'], + version: '2.3.1' + } +} + ### `package.json` patches These fields will be set in the project's `package.json`: @@ -58,13 +80,18 @@ These files will be copied, overwriting any existing files: - `LICENSE.md` - `SECURITY.md` +### Dynamic Files + +Currently, the only dynamic file generated is a github workflow for a given workspace. +`.github/workflows/ci-$$package-name$$.yml` + #### Extending 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`). -Modify the `content` object at the top of `lib/postinstall/copy-content.js` to include +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. ### `package.json` checks @@ -76,4 +103,4 @@ is not configured properly, with steps to run to correct any problems. 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. \ No newline at end of file +in this repo. diff --git a/bin/npm-template-check.js b/bin/npm-template-check.js index d76d499c..0f9b2599 100755 --- a/bin/npm-template-check.js +++ b/bin/npm-template-check.js @@ -2,6 +2,7 @@ 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 { @@ -12,10 +13,19 @@ const main = async () => { throw new Error('This package requires npm >7.21.1') } - const problems = [ - ...(await checkPackage(root)), - ...(await checkGitIgnore(root)), - ] + 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:') diff --git a/bin/postinstall.js b/bin/postinstall.js index 6e18c5e6..0022257d 100755 --- a/bin/postinstall.js +++ b/bin/postinstall.js @@ -2,6 +2,7 @@ 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 { @@ -14,12 +15,14 @@ const main = async () => { return } - const needsAction = await patchPackage(root) - if (!needsAction) { - return - } + const config = await getConfig(root) + for (const path of config.paths) { + if (!await patchPackage(path, root, config)) { + continue + } - await copyContent(root) + await copyContent(path, root, config) + } } module.exports = main().catch((err) => { diff --git a/lib/config.js b/lib/config.js new file mode 100644 index 00000000..cf6e8478 --- /dev/null +++ b/lib/config.js @@ -0,0 +1,48 @@ +const PackageJson = require('@npmcli/package-json') +const mapWorkspaces = require('@npmcli/map-workspaces') + +const defaultConfig = { + applyRootRepoFiles: true, + applyWorkspaceRepoFiles: true, + applyRootModuleFiles: true, + workspaces: [], + paths: [], +} + +module.exports = async (root) => { + let pkg + let pkgError = false + try { + pkg = (await PackageJson.load(root)).content + } catch (e) { + pkgError = true + } + if (pkgError || !pkg.templateOSS) { + return { + ...defaultConfig, + paths: [root], + } + } + const config = { + ...defaultConfig, + ...pkg.templateOSS, + } + 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) + } + } + config.workspacePaths = wsPaths + + config.paths = config.paths.concat(config.workspacePaths) + + config.paths.push(root) + + return config +} diff --git a/lib/content/ci-workspace.yml b/lib/content/ci-workspace.yml new file mode 100644 index 00000000..15560121 --- /dev/null +++ b/lib/content/ci-workspace.yml @@ -0,0 +1,76 @@ +name: Node Workspace CI %%pkgname%% + +on: + pull_request: + paths: + - %%pkgpath%%/** + branches: + - '*' + 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 + - name: Use Node.js 16.x + uses: actions/setup-node@v2 + with: + node-version: 16.x + cache: npm + - name: Install dependencies + run: | + node ./bin/npm-cli.js install --ignore-scripts --no-audit + node ./bin/npm-cli.js rebuild + - name: Run linting + run: node ./bin/npm-cli.js run posttest -w libnpmdiff + env: + DEPLOY_VERSION: testing + + 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: bash + - os: windows-latest + shell: powershell + + runs-on: ${{ matrix.platform.os }} + defaults: + run: + shell: ${{ matrix.platform.shell }} + + steps: + # Checkout the npm/cli repo + - uses: actions/checkout@v2 + + # Installs the specific version of Node.js + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v2 + with: + node-version: ${{ matrix.node-version }} + cache: npm + + # Run the installer script + - name: Install dependencies + run: | + node ./bin/npm-cli.js install --ignore-scripts --no-audit + node ./bin/npm-cli.js rebuild + + # Run the tests, but not if we're just gonna do coveralls later anyway + - name: Run Tap tests + run: node ./bin/npm-cli.js run -w libnpmdiff --ignore-scripts test -- -t600 -Rbase -c diff --git a/lib/postinstall/copy-content.js b/lib/postinstall/copy-content.js index 31cf813d..0c520061 100644 --- a/lib/postinstall/copy-content.js +++ b/lib/postinstall/copy-content.js @@ -1,44 +1,45 @@ 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 content = { +const moduleFiles = { '.eslintrc.js': './eslintrc.js', + '.gitignore': './gitignore', + 'SECURITY.md': './SECURITY.md', +} + +const repoFiles = { '.github/workflows/ci.yml': './ci.yml', '.github/ISSUE_TEMPLATE/bug.yml': './bug.yml', '.github/ISSUE_TEMPLATE/config.yml': './config.yml', '.github/CODEOWNERS': './CODEOWNERS', - '.gitignore': './gitignore', - 'LICENSE.md': './LICENSE.md', - 'SECURITY.md': './SECURITY.md', } +// currently no workspace moduleFiles +// const workspaceContent = {} +// const workspaceRootContent = {} + const filesToDelete = [ - // remove any other license files - /^LICENSE*/, // remove any eslint config files that aren't local to the project /^\.eslintrc\.(?!(local\.)).*/, ] -// given a root directory, copy all files in the content map -// after purging any files we need to delete -const copyContent = async (root) => { - const contents = await fs.readdir(root) - - for (const file of contents) { - if (filesToDelete.some((p) => p.test(file))) { - await fs.rm(join(root, file)) - } - } +const defaultConfig = { + applyRootRepoFiles: true, + applyWorkspaceRepoFiles: true, + applyRootModuleFiles: true, +} - for (let [target, source] of Object.entries(content)) { +const copyFiles = async (targetDir, files) => { + for (let [target, source] of Object.entries(files)) { source = join(contentDir, source) - target = join(root, target) - // if the target is a subdirectory of the root, mkdirp it first - if (dirname(target) !== root) { + target = join(targetDir, target) + // 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, @@ -48,6 +49,60 @@ const copyContent = async (root) => { await fs.copyFile(source, target, { owner: 'inherit' }) } } -copyContent.content = content + +// 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) + } + return + } // only workspace now + + // TODO: await copyFiles(path, workspaceFiles) + // if we ever have workspace specific files + + if (config.applyWorkspaceRepoFiles) { + // copy and edit workspace repo file (ci github action) + const workspacePkg = (await PackageJson.load(path)).content + const workspaceName = workspacePkg.name + const workflowPath = join(rootPath, '.github', 'workflows') + await fs.mkdir(workflowPath, { + owner: 'inherit', + recursive: true, + force: true, + }) + + let workflowData = await fs.readFile( + join(contentDir, './ci-workspace.yml'), + { encoding: 'utf-8' } + ) + + const relPath = path.substring(rootPath.length + 1) + workflowData = workflowData.replace(/%%pkgpath%%/g, relPath) + workflowData = workflowData.replace(/%%pkgname%%/g, workspaceName) + + await fs.writeFile(join(workflowPath, `ci-${workspaceName}.yml`), workflowData) + } +} +copyContent.moduleFiles = moduleFiles +copyContent.repoFiles = repoFiles module.exports = copyContent diff --git a/lib/postinstall/update-package.js b/lib/postinstall/update-package.js index 2c0834d0..cf04eff4 100644 --- a/lib/postinstall/update-package.js +++ b/lib/postinstall/update-package.js @@ -8,8 +8,6 @@ const { const changes = { author: 'GitHub Inc.', files: ['bin', 'lib'], - license: 'ISC', - templateVersion: TEMPLATE_VERSION, scripts: { lint: `eslint '**/*.js'`, postlint: 'npm-template-check', @@ -26,33 +24,57 @@ const changes = { }, } -const patchPackage = async (root) => { - const pkg = await PackageJson.load(root) +const patchPackage = async (path, root, config) => { + const pkg = await PackageJson.load(path) // 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 (pkg.content.templateVersion === TEMPLATE_VERSION && !isDogfood) { + if (currentVersion === TEMPLATE_VERSION && !isDogfood) { return false } - // we build a new object here so our exported set of changes is not modified - const update = { - ...changes, - scripts: { - ...pkg.content.scripts, - ...changes.scripts, + 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 + } } pkg.update(update) diff --git a/lib/postlint/check-package.js b/lib/postlint/check-package.js index b0a80c9e..869430eb 100644 --- a/lib/postlint/check-package.js +++ b/lib/postlint/check-package.js @@ -1,6 +1,4 @@ const PackageJson = require('@npmcli/package-json') - -const { name: TEMPLATE_NAME } = require('../../package.json') const patchPackage = require('../postinstall/update-package.js') const unwantedPackages = [ @@ -14,18 +12,8 @@ const hasOwn = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key) const check = async (root) => { const pkg = (await PackageJson.load(root)).content - - // templateVersion doesn't apply if we're on this repo - // since we always run the scripts here - const changes = Object.entries(patchPackage.changes).filter(([key]) => { - if (pkg.name === TEMPLATE_NAME && key === 'templateVersion') { - return false - } - return true - }) - + const changes = Object.entries(patchPackage.changes) const problems = [] - const incorrectFields = [] // 1. ensure package.json changes have been applied for (const [key, value] of changes) { diff --git a/package-lock.json b/package-lock.json index 408e1b6a..3e8ef521 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,9 @@ "license": "ISC", "dependencies": { "@npmcli/fs": "^1.0.0", + "@npmcli/map-workspaces": "^2.0.0", "@npmcli/package-json": "^1.0.1", + "json-parse-even-better-errors": "^2.3.1", "which": "^2.0.2" }, "bin": { @@ -573,6 +575,25 @@ "semver": "^7.3.5" } }, + "node_modules/@npmcli/map-workspaces": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/map-workspaces/-/map-workspaces-2.0.0.tgz", + "integrity": "sha512-QBJfpCY1NOAkkW3lFfru9VTdqvMB2TN0/vrevl5xBCv5Fi0XDVcA6rqqSau4Ysi4Iw3fBzyXV7hzyTBDfadf7g==", + "dependencies": { + "@npmcli/name-from-folder": "^1.0.1", + "glob": "^7.1.6", + "minimatch": "^3.0.4", + "read-package-json-fast": "^2.0.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16" + } + }, + "node_modules/@npmcli/name-from-folder": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/name-from-folder/-/name-from-folder-1.0.1.tgz", + "integrity": "sha512-qq3oEfcLFwNfEYOQ8HLimRGKlD8WSeGEdtUa7hmzpR8Sa7haL1KVQrvgO6wqMjhWFFVjgtrh1gIxDz+P8sjUaA==" + }, "node_modules/@npmcli/package-json": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-1.0.1.tgz", @@ -765,8 +786,7 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "node_modules/bcrypt-pbkdf": { "version": "1.0.2", @@ -799,7 +819,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1058,8 +1077,7 @@ "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "node_modules/convert-source-map": { "version": "1.8.0", @@ -1723,8 +1741,7 @@ "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "node_modules/fsevents": { "version": "2.3.2", @@ -1798,7 +1815,6 @@ "version": "7.1.7", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -1984,7 +2000,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -1993,8 +2008,7 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/is-binary-path": { "version": "2.1.0", @@ -2473,7 +2487,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -2550,6 +2563,11 @@ "node": ">=0.10.0" } }, + "node_modules/npm-normalize-package-bin": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", + "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==" + }, "node_modules/number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", @@ -2631,7 +2649,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, "dependencies": { "wrappy": "1" } @@ -2765,7 +2782,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -2900,6 +2916,18 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "dev": true }, + "node_modules/read-package-json-fast": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-2.0.3.tgz", + "integrity": "sha512-W/BKtbL+dUjTuRL2vziuYhp76s5HZ9qQhd/dKfWIZveD0O40453QNyZhC0e63lqZrAQ4jiOapVoeJ7JrszenQQ==", + "dependencies": { + "json-parse-even-better-errors": "^2.3.0", + "npm-normalize-package-bin": "^1.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -5580,8 +5608,7 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "node_modules/write-file-atomic": { "version": "3.0.3", @@ -6108,6 +6135,22 @@ "semver": "^7.3.5" } }, + "@npmcli/map-workspaces": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/map-workspaces/-/map-workspaces-2.0.0.tgz", + "integrity": "sha512-QBJfpCY1NOAkkW3lFfru9VTdqvMB2TN0/vrevl5xBCv5Fi0XDVcA6rqqSau4Ysi4Iw3fBzyXV7hzyTBDfadf7g==", + "requires": { + "@npmcli/name-from-folder": "^1.0.1", + "glob": "^7.1.6", + "minimatch": "^3.0.4", + "read-package-json-fast": "^2.0.1" + } + }, + "@npmcli/name-from-folder": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/name-from-folder/-/name-from-folder-1.0.1.tgz", + "integrity": "sha512-qq3oEfcLFwNfEYOQ8HLimRGKlD8WSeGEdtUa7hmzpR8Sa7haL1KVQrvgO6wqMjhWFFVjgtrh1gIxDz+P8sjUaA==" + }, "@npmcli/package-json": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-1.0.1.tgz", @@ -6127,11 +6170,13 @@ "requires": { "@npmcli/eslint-config": "*", "@npmcli/fs": "^1.0.0", + "@npmcli/map-workspaces": "^2.0.0", "@npmcli/package-json": "^1.0.1", "@npmcli/promise-spawn": "^2.0.0", "@npmcli/template-oss": "file:", - "eslint": "8.1.0", + "eslint": "^8.1.0", "eslint-plugin-node": "^11.1.0", + "json-parse-even-better-errors": "^2.3.1", "tap": "*", "which": "^2.0.2" }, @@ -6561,6 +6606,22 @@ "semver": "^7.3.5" } }, + "@npmcli/map-workspaces": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/map-workspaces/-/map-workspaces-2.0.0.tgz", + "integrity": "sha512-QBJfpCY1NOAkkW3lFfru9VTdqvMB2TN0/vrevl5xBCv5Fi0XDVcA6rqqSau4Ysi4Iw3fBzyXV7hzyTBDfadf7g==", + "requires": { + "@npmcli/name-from-folder": "^1.0.1", + "glob": "^7.1.6", + "minimatch": "^3.0.4", + "read-package-json-fast": "^2.0.1" + } + }, + "@npmcli/name-from-folder": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/name-from-folder/-/name-from-folder-1.0.1.tgz", + "integrity": "sha512-qq3oEfcLFwNfEYOQ8HLimRGKlD8WSeGEdtUa7hmzpR8Sa7haL1KVQrvgO6wqMjhWFFVjgtrh1gIxDz+P8sjUaA==" + }, "@npmcli/package-json": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-1.0.1.tgz", @@ -6707,8 +6768,7 @@ "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "bcrypt-pbkdf": { "version": "1.0.2", @@ -6735,7 +6795,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -6932,8 +6991,7 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "convert-source-map": { "version": "1.8.0", @@ -7429,8 +7487,7 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "fsevents": { "version": "2.3.2", @@ -7488,7 +7545,6 @@ "version": "7.1.7", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -7620,7 +7676,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -7629,8 +7684,7 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "is-binary-path": { "version": "2.1.0", @@ -8001,7 +8055,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, "requires": { "brace-expansion": "^1.1.7" } @@ -8060,6 +8113,11 @@ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true }, + "npm-normalize-package-bin": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", + "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==" + }, "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", @@ -8125,7 +8183,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, "requires": { "wrappy": "1" } @@ -8228,8 +8285,7 @@ "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "path-key": { "version": "3.1.1", @@ -8331,6 +8387,15 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "dev": true }, + "read-package-json-fast": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-2.0.3.tgz", + "integrity": "sha512-W/BKtbL+dUjTuRL2vziuYhp76s5HZ9qQhd/dKfWIZveD0O40453QNyZhC0e63lqZrAQ4jiOapVoeJ7JrszenQQ==", + "requires": { + "json-parse-even-better-errors": "^2.3.0", + "npm-normalize-package-bin": "^1.0.1" + } + }, "readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -10184,8 +10249,7 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "write-file-atomic": { "version": "3.0.3", @@ -10409,8 +10473,7 @@ "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "bcrypt-pbkdf": { "version": "1.0.2", @@ -10437,7 +10500,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -10634,8 +10696,7 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "convert-source-map": { "version": "1.8.0", @@ -11131,8 +11192,7 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "fsevents": { "version": "2.3.2", @@ -11190,7 +11250,6 @@ "version": "7.1.7", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -11322,7 +11381,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -11331,8 +11389,7 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "is-binary-path": { "version": "2.1.0", @@ -11703,7 +11760,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, "requires": { "brace-expansion": "^1.1.7" } @@ -11762,6 +11818,11 @@ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true }, + "npm-normalize-package-bin": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", + "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==" + }, "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", @@ -11827,7 +11888,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, "requires": { "wrappy": "1" } @@ -11930,8 +11990,7 @@ "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "path-key": { "version": "3.1.1", @@ -12033,6 +12092,15 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "dev": true }, + "read-package-json-fast": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-2.0.3.tgz", + "integrity": "sha512-W/BKtbL+dUjTuRL2vziuYhp76s5HZ9qQhd/dKfWIZveD0O40453QNyZhC0e63lqZrAQ4jiOapVoeJ7JrszenQQ==", + "requires": { + "json-parse-even-better-errors": "^2.3.0", + "npm-normalize-package-bin": "^1.0.1" + } + }, "readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -13886,8 +13954,7 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "write-file-atomic": { "version": "3.0.3", diff --git a/package.json b/package.json index 2f111c80..27c78b9a 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,9 @@ "license": "ISC", "dependencies": { "@npmcli/fs": "^1.0.0", + "@npmcli/map-workspaces": "^2.0.0", "@npmcli/package-json": "^1.0.1", + "json-parse-even-better-errors": "^2.3.1", "which": "^2.0.2" }, "files": [ diff --git a/tap-snapshots/test/postlint/check-package.js.test.cjs b/tap-snapshots/test/postlint/check-package.js.test.cjs index 74835441..4243e0b5 100644 --- a/tap-snapshots/test/postlint/check-package.js.test.cjs +++ b/tap-snapshots/test/postlint/check-package.js.test.cjs @@ -10,10 +10,8 @@ Array [ Object { "message": String( The following package.json fields are incorrect: - Field: "author" Expected: "GitHub Inc." Found: undefined + Field: "author" Expected: "GitHub Inc." Found: "Bob" Field: "files" Expected: ["bin","lib"] Found: undefined - Field: "license" Expected: "ISC" Found: "MIT" - Field: "templateVersion" Expected: "$TEMPLATE_VERSION" Found: undefined Field: "scripts" Expected: {"lint":"eslint '**/*.js'","postlint":"npm-template-check","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 ), @@ -29,8 +27,6 @@ Array [ The following package.json fields are incorrect: Field: "author" Expected: "GitHub Inc." Found: undefined Field: "files" Expected: ["bin","lib"] Found: undefined - Field: "license" Expected: "ISC" Found: undefined - Field: "templateVersion" Expected: "$TEMPLATE_VERSION" Found: undefined Field: "scripts.lint" Expected: "eslint '**/*.js'" Found: undefined Field: "scripts.postlint" Expected: "npm-template-check" Found: undefined Field: "scripts.lintfix" Expected: "npm run lint -- --fix" Found: undefined @@ -54,8 +50,6 @@ Array [ The following package.json fields are incorrect: Field: "author" Expected: "GitHub Inc." Found: undefined Field: "files" Expected: ["bin","lib"] Found: undefined - Field: "license" Expected: "ISC" Found: undefined - Field: "templateVersion" Expected: "$TEMPLATE_VERSION" Found: undefined Field: "scripts" Expected: {"lint":"eslint '**/*.js'","postlint":"npm-template-check","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 ), @@ -71,8 +65,6 @@ Array [ The following package.json fields are incorrect: Field: "author" Expected: "GitHub Inc." Found: undefined Field: "files" Expected: ["bin","lib"] Found: undefined - Field: "license" Expected: "ISC" Found: undefined - Field: "templateVersion" Expected: "$TEMPLATE_VERSION" Found: undefined Field: "scripts" Expected: {"lint":"eslint '**/*.js'","postlint":"npm-template-check","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 ), diff --git a/test/bin/npm-template-check.js b/test/bin/npm-template-check.js index 93851683..7c845b9f 100644 --- a/test/bin/npm-template-check.js +++ b/test/bin/npm-template-check.js @@ -81,3 +81,41 @@ t.test('with mocks', (t) => { 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 root = t.testdir(pkgWithWorkspaces) + process.env.npm_config_local_prefix = root + + 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/postinstall/copy-content.js b/test/postinstall/copy-content.js index 254859ed..fe6c425f 100644 --- a/test/postinstall/copy-content.js +++ b/test/postinstall/copy-content.js @@ -1,14 +1,19 @@ 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) - for (let target of Object.keys(copyContent.content)) { + 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)) } @@ -20,17 +25,23 @@ t.test('removes files', async (t) => { '.eslintrc.yml': '', '.eslintrc.local.json': '{}', 'something.txt': '', - LICENSE: '', - 'LICENSE.txt': '', } const keepContent = [ '.eslintrc.local.json', 'something.txt', ] - const root = t.testdir(content) + const root = t.testdir(content, { + applyRootRepoFiles: true, + applyWorkspaceRepoFiles: true, + applyRootModuleFiles: true, + }) - await copyContent(root) - for (const target of Object.keys(copyContent.content)) { + 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}`) } @@ -44,3 +55,168 @@ t.test('removes files', async (t) => { } } }) + +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: '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', 'ISSUE_TEMPLATE', 'bug.yml'))) +}) + +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'))) +}) diff --git a/test/postinstall/update-package.js b/test/postinstall/update-package.js index 2821fcbb..1268cb0b 100644 --- a/test/postinstall/update-package.js +++ b/test/postinstall/update-package.js @@ -39,7 +39,9 @@ t.test('can patch a package.json', async (t) => { t.test('returns false when templateVersion matches own version', async (t) => { const pkg = { name: '@npmcli/foo', - templateVersion: TEMPLATE_VERSION, + templateOSS: { + version: TEMPLATE_VERSION, + }, version: '1.0.0', author: 'someone else', files: [], @@ -71,8 +73,65 @@ t.test('doesnt set templateVersion on own repo', async (t) => { const needsAction = await patchPackage(project) t.equal(needsAction, true, 'needs action') - const contents = await fs.readFile(join(project, 'package.json'), { + 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, }) - t.equal(JSON.parse(contents).templateVersion, undefined, 'did not get template version') + + 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') }) diff --git a/test/postlint/check-package.js b/test/postlint/check-package.js index 8fa9c276..1e3befec 100644 --- a/test/postlint/check-package.js +++ b/test/postlint/check-package.js @@ -6,7 +6,7 @@ const { name } = require('../../package.json') t.cleanSnapshot = (snapshot) => { return snapshot.replace( - /("templateVersion" Expected: ").*(" Found)/g, + /("version" Expected: ").*(" Found)/g, '$1$TEMPLATE_VERSION$2' ) } @@ -27,7 +27,7 @@ t.test('checks a package.json', (t) => { t.test('incorrect fields', async (t) => { const project = t.testdir({ 'package.json': JSON.stringify({ - license: 'MIT', + author: 'Bob', }), }) @@ -85,6 +85,7 @@ t.test('this repo doesnt get version', async (t) => { } delete pkg.templateVersion + delete pkg.templateOSS const project = t.testdir({ 'package.json': JSON.stringify(pkg),