From a507437bb286a6924c4b26cf2141c7848afefe5e Mon Sep 17 00:00:00 2001 From: Nathan Fritz Date: Mon, 6 Dec 2021 15:47:15 -0800 Subject: [PATCH 1/8] feat: add workspace support * optional configuration to include root and workspaces * github action for changes in workspace --- bin/npm-template-check.js | 14 ++- bin/postinstall.js | 13 ++- lib/config.js | 47 +++++++++ lib/content/ci-workspace.yml | 76 ++++++++++++++ lib/postinstall/copy-content.js | 80 +++++++++++---- package-lock.json | 169 +++++++++++++++++++++---------- package.json | 2 + test/postinstall/copy-content.js | 41 +++++++- 8 files changed, 362 insertions(+), 80 deletions(-) create mode 100644 lib/config.js create mode 100644 lib/content/ci-workspace.yml diff --git a/bin/npm-template-check.js b/bin/npm-template-check.js index d76d499c..1df9e058 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,15 @@ 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) { + problemSets.push(await checkPackage(path)) + 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..f2054cc4 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)) { + continue + } - await copyContent(root) + await copyContent(path, root) + } } module.exports = main().catch((err) => { diff --git a/lib/config.js b/lib/config.js new file mode 100644 index 00000000..f116a5df --- /dev/null +++ b/lib/config.js @@ -0,0 +1,47 @@ +const PackageJson = require('@npmcli/package-json') +const mapWorkspaces = require('@npmcli/map-workspaces') + +const defaultConfig = { + includeRoot: 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 + + if (config.includeRoot) { + config.paths.push(root) + } + config.paths = config.paths.concat(config.workspacePaths) + + return config +} diff --git a/lib/content/ci-workspace.yml b/lib/content/ci-workspace.yml new file mode 100644 index 00000000..24f43db2 --- /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 14.x + uses: actions/setup-node@v2 + with: + node-version: 14.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..7b7e8db3 100644 --- a/lib/postinstall/copy-content.js +++ b/lib/postinstall/copy-content.js @@ -1,5 +1,7 @@ const { dirname, join, resolve } = require('path') const fs = require('@npmcli/fs') +const { readFile, writeFile } = require('fs/promises') +const PackageJson = require('@npmcli/package-json') const contentDir = resolve(__dirname, '..', 'content') @@ -7,15 +9,22 @@ const contentDir = resolve(__dirname, '..', 'content') // values are paths to contents relative to '../content/' const content = { '.eslintrc.js': './eslintrc.js', + '.gitignore': './gitignore', + 'LICENSE.md': './LICENSE.md', + 'SECURITY.md': './SECURITY.md', +} + +const rootContent = { '.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 content +// const workspaceContent = {} +// const workspaceRootContent = {} + const filesToDelete = [ // remove any other license files /^LICENSE*/, @@ -23,22 +32,12 @@ const filesToDelete = [ /^\.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)) - } - } - - 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 +47,51 @@ const copyContent = async (root) => { await fs.copyFile(source, target, { owner: 'inherit' }) } } + +// given a root directory, copy all files in the content map +// after purging any files we need to delete +const copyContent = async (path, rootPath) => { + const contents = await fs.readdir(path) + + const isWorkspace = path !== rootPath + + for (const file of contents) { + if (filesToDelete.some((p) => p.test(file))) { + await fs.rm(join(path, file)) + } + } + + await copyFiles(path, content) + if (!isWorkspace) { + await copyFiles(rootPath, rootContent) + return + } + + // isWorkspace === true + // if we had workspace specific content... + // await copyFiles(path, workspaceContent) + // await copyFiles(rootPath, workspaceRootContent) + + 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 readFile( + join(contentDir, './ci-workspace.yml'), + { encoding: 'utf-8' } + ) + + workflowData = workflowData.replace(/%%pkgpath%%/g, path) + workflowData = workflowData.replace(/%%pkgname%%/g, workspaceName) + + await writeFile(join(workflowPath, `ci-${workspaceName}.yml`), workflowData) +} copyContent.content = content +copyContent.rootContent = rootContent module.exports = copyContent 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/test/postinstall/copy-content.js b/test/postinstall/copy-content.js index 254859ed..d40927cc 100644 --- a/test/postinstall/copy-content.js +++ b/test/postinstall/copy-content.js @@ -7,7 +7,7 @@ const copyContent = require('../../lib/postinstall/copy-content.js') t.test('copies content', async (t) => { const root = t.testdir() - await copyContent(root) + await copyContent(root, root) for (let target of Object.keys(copyContent.content)) { target = join(root, target) await t.resolves(fs.stat(target)) @@ -29,11 +29,15 @@ t.test('removes files', async (t) => { ] const root = t.testdir(content) - await copyContent(root) + await copyContent(root, root) for (const target of Object.keys(copyContent.content)) { const fullTarget = join(root, target) await t.resolves(fs.stat(fullTarget), `copied ${target}`) } + for (const target of Object.keys(copyContent.rootContent)) { + const fullTarget = join(root, target) + await t.resolves(fs.stat(fullTarget), `copied ${target}`) + } for (const target in content) { const fullTarget = join(root, target) @@ -44,3 +48,36 @@ t.test('removes files', async (t) => { } } }) + +t.test('handles workspaces', async (t) => { + const content = { + 'package.json': JSON.stringify({ + name: 'testpkg', + templateOSS: { + includeRoot: 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(content) + const workspacea = join(root, 'workspace', 'a') + await copyContent(workspacea, root) + + // should have made the workspace action in the root + await t.resolves(fs.stat(join(root, '.github', 'workflows', 'ci-amazinga.yml'))) + // change should have applied to the workspace, not the root + await t.rejects(fs.stat(join(root, '.eslintrc.js'))) + await t.resolves(fs.stat(join(root, 'workspace', 'a', '.eslintrc.js'))) +}) From 055e18b1736e065c01f40dadd0d100fc4ea715e3 Mon Sep 17 00:00:00 2001 From: Nathan Fritz Date: Tue, 7 Dec 2021 09:12:25 -0800 Subject: [PATCH 2/8] use @npmcli/fs instead of fs/promise --- lib/postinstall/copy-content.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/postinstall/copy-content.js b/lib/postinstall/copy-content.js index 7b7e8db3..23715dff 100644 --- a/lib/postinstall/copy-content.js +++ b/lib/postinstall/copy-content.js @@ -1,6 +1,5 @@ const { dirname, join, resolve } = require('path') const fs = require('@npmcli/fs') -const { readFile, writeFile } = require('fs/promises') const PackageJson = require('@npmcli/package-json') const contentDir = resolve(__dirname, '..', 'content') @@ -81,7 +80,7 @@ const copyContent = async (path, rootPath) => { force: true, }) - let workflowData = await readFile( + let workflowData = await fs.readFile( join(contentDir, './ci-workspace.yml'), { encoding: 'utf-8' } ) @@ -89,7 +88,7 @@ const copyContent = async (path, rootPath) => { workflowData = workflowData.replace(/%%pkgpath%%/g, path) workflowData = workflowData.replace(/%%pkgname%%/g, workspaceName) - await writeFile(join(workflowPath, `ci-${workspaceName}.yml`), workflowData) + await fs.writeFile(join(workflowPath, `ci-${workspaceName}.yml`), workflowData) } copyContent.content = content copyContent.rootContent = rootContent From 712db0b816b7608ff98543144a57c92a243fa2ad Mon Sep 17 00:00:00 2001 From: Nathan Fritz Date: Tue, 7 Dec 2021 09:12:25 -0800 Subject: [PATCH 3/8] cleanup based on feedback --- lib/content/ci-workspace.yml | 4 ++-- lib/postinstall/copy-content.js | 19 +++++++++---------- test/postinstall/copy-content.js | 14 +++++++++----- 3 files changed, 20 insertions(+), 17 deletions(-) diff --git a/lib/content/ci-workspace.yml b/lib/content/ci-workspace.yml index 24f43db2..15560121 100644 --- a/lib/content/ci-workspace.yml +++ b/lib/content/ci-workspace.yml @@ -20,10 +20,10 @@ jobs: steps: # Checkout the npm/cli repo - uses: actions/checkout@v2 - - name: Use Node.js 14.x + - name: Use Node.js 16.x uses: actions/setup-node@v2 with: - node-version: 14.x + node-version: 16.x cache: npm - name: Install dependencies run: | diff --git a/lib/postinstall/copy-content.js b/lib/postinstall/copy-content.js index 7b7e8db3..13da1f50 100644 --- a/lib/postinstall/copy-content.js +++ b/lib/postinstall/copy-content.js @@ -1,27 +1,26 @@ const { dirname, join, resolve } = require('path') const fs = require('@npmcli/fs') -const { readFile, writeFile } = require('fs/promises') 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', 'LICENSE.md': './LICENSE.md', 'SECURITY.md': './SECURITY.md', } -const rootContent = { +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', } -// currently no workspace content +// currently no workspace moduleFiles // const workspaceContent = {} // const workspaceRootContent = {} @@ -61,9 +60,9 @@ const copyContent = async (path, rootPath) => { } } - await copyFiles(path, content) + await copyFiles(path, moduleFiles) if (!isWorkspace) { - await copyFiles(rootPath, rootContent) + await copyFiles(rootPath, repoFiles) return } @@ -81,7 +80,7 @@ const copyContent = async (path, rootPath) => { force: true, }) - let workflowData = await readFile( + let workflowData = await fs.readFile( join(contentDir, './ci-workspace.yml'), { encoding: 'utf-8' } ) @@ -89,9 +88,9 @@ const copyContent = async (path, rootPath) => { workflowData = workflowData.replace(/%%pkgpath%%/g, path) workflowData = workflowData.replace(/%%pkgname%%/g, workspaceName) - await writeFile(join(workflowPath, `ci-${workspaceName}.yml`), workflowData) + await fs.writeFile(join(workflowPath, `ci-${workspaceName}.yml`), workflowData) } -copyContent.content = content -copyContent.rootContent = rootContent +copyContent.moduleFiles = moduleFiles +copyContent.repoFiles = repoFiles module.exports = copyContent diff --git a/test/postinstall/copy-content.js b/test/postinstall/copy-content.js index d40927cc..255cb04b 100644 --- a/test/postinstall/copy-content.js +++ b/test/postinstall/copy-content.js @@ -8,7 +8,11 @@ t.test('copies content', async (t) => { const root = t.testdir() await copyContent(root, root) - for (let target of Object.keys(copyContent.content)) { + 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)) } @@ -30,11 +34,11 @@ t.test('removes files', async (t) => { const root = t.testdir(content) await copyContent(root, root) - for (const target of Object.keys(copyContent.content)) { + 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.rootContent)) { + for (const target of Object.keys(copyContent.repoFiles)) { const fullTarget = join(root, target) await t.resolves(fs.stat(fullTarget), `copied ${target}`) } @@ -50,7 +54,7 @@ t.test('removes files', async (t) => { }) t.test('handles workspaces', async (t) => { - const content = { + const pkgWithWorkspaces = { 'package.json': JSON.stringify({ name: 'testpkg', templateOSS: { @@ -71,7 +75,7 @@ t.test('handles workspaces', async (t) => { }, }, } - const root = t.testdir(content) + const root = t.testdir(pkgWithWorkspaces) const workspacea = join(root, 'workspace', 'a') await copyContent(workspacea, root) From 7d16cfedcbb7c2d0803bcfea0dc2a6ad3713e5d8 Mon Sep 17 00:00:00 2001 From: Nathan Fritz Date: Tue, 7 Dec 2021 10:45:03 -0800 Subject: [PATCH 4/8] updated config options to be more granular --- bin/postinstall.js | 2 +- lib/config.js | 9 +- lib/postinstall/copy-content.js | 77 +++++++++------- test/postinstall/copy-content.js | 145 ++++++++++++++++++++++++++++++- 4 files changed, 193 insertions(+), 40 deletions(-) diff --git a/bin/postinstall.js b/bin/postinstall.js index f2054cc4..0e50a301 100755 --- a/bin/postinstall.js +++ b/bin/postinstall.js @@ -21,7 +21,7 @@ const main = async () => { continue } - await copyContent(path, root) + await copyContent(path, root, config) } } diff --git a/lib/config.js b/lib/config.js index f116a5df..cf6e8478 100644 --- a/lib/config.js +++ b/lib/config.js @@ -2,7 +2,9 @@ const PackageJson = require('@npmcli/package-json') const mapWorkspaces = require('@npmcli/map-workspaces') const defaultConfig = { - includeRoot: true, + applyRootRepoFiles: true, + applyWorkspaceRepoFiles: true, + applyRootModuleFiles: true, workspaces: [], paths: [], } @@ -38,10 +40,9 @@ module.exports = async (root) => { } config.workspacePaths = wsPaths - if (config.includeRoot) { - config.paths.push(root) - } config.paths = config.paths.concat(config.workspacePaths) + config.paths.push(root) + return config } diff --git a/lib/postinstall/copy-content.js b/lib/postinstall/copy-content.js index 13da1f50..23365bca 100644 --- a/lib/postinstall/copy-content.js +++ b/lib/postinstall/copy-content.js @@ -31,6 +31,12 @@ const filesToDelete = [ /^\.eslintrc\.(?!(local\.)).*/, ] +const defaultConfig = { + applyRootRepoFiles: true, + applyWorkspaceRepoFiles: true, + applyRootModuleFiles: true, +} + const copyFiles = async (targetDir, files) => { for (let [target, source] of Object.entries(files)) { source = join(contentDir, source) @@ -49,46 +55,55 @@ const copyFiles = async (targetDir, files) => { // given a root directory, copy all files in the content map // after purging any files we need to delete -const copyContent = async (path, rootPath) => { - const contents = await fs.readdir(path) +const copyContent = async (path, rootPath, config) => { + config = {...defaultConfig, ...config} const isWorkspace = path !== rootPath - for (const file of contents) { - if (filesToDelete.some((p) => p.test(file))) { - await fs.rm(join(path, file)) + 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) } - await copyFiles(path, moduleFiles) if (!isWorkspace) { - await copyFiles(rootPath, repoFiles) + 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' } + ) + + workflowData = workflowData.replace(/%%pkgpath%%/g, path) + workflowData = workflowData.replace(/%%pkgname%%/g, workspaceName) + + await fs.writeFile(join(workflowPath, `ci-${workspaceName}.yml`), workflowData) } - - // isWorkspace === true - // if we had workspace specific content... - // await copyFiles(path, workspaceContent) - // await copyFiles(rootPath, workspaceRootContent) - - 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' } - ) - - workflowData = workflowData.replace(/%%pkgpath%%/g, path) - workflowData = workflowData.replace(/%%pkgname%%/g, workspaceName) - - await fs.writeFile(join(workflowPath, `ci-${workspaceName}.yml`), workflowData) } copyContent.moduleFiles = moduleFiles copyContent.repoFiles = repoFiles diff --git a/test/postinstall/copy-content.js b/test/postinstall/copy-content.js index 255cb04b..28db5b9d 100644 --- a/test/postinstall/copy-content.js +++ b/test/postinstall/copy-content.js @@ -1,6 +1,7 @@ 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') @@ -31,7 +32,11 @@ t.test('removes files', async (t) => { '.eslintrc.local.json', 'something.txt', ] - const root = t.testdir(content) + const root = t.testdir(content, { + applyRootRepoFiles: true, + applyWorkspaceRepoFiles: true, + applyRootModuleFiles: true, + }) await copyContent(root, root) for (const target of Object.keys(copyContent.moduleFiles)) { @@ -58,7 +63,9 @@ t.test('handles workspaces', async (t) => { 'package.json': JSON.stringify({ name: 'testpkg', templateOSS: { - includeRoot: false, + applyRootRepoFiles: true, + applyWorkspaceRepoFiles: true, + applyRootModuleFiles: true, workspaces: ['workspace/a', 'workspace/b'], }, }), @@ -77,11 +84,141 @@ t.test('handles workspaces', async (t) => { } const root = t.testdir(pkgWithWorkspaces) const workspacea = join(root, 'workspace', 'a') - await copyContent(workspacea, root) + 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'))) - // change should have applied to the workspace, not the root + 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'))) }) From 7c89c83789d33b0b36027b4abfbf752596766d80 Mon Sep 17 00:00:00 2001 From: Nathan Fritz Date: Tue, 7 Dec 2021 10:45:03 -0800 Subject: [PATCH 5/8] updated config options to be more granular --- bin/postinstall.js | 2 +- lib/config.js | 9 +- lib/postinstall/copy-content.js | 78 ++++++++++------- test/postinstall/copy-content.js | 145 ++++++++++++++++++++++++++++++- 4 files changed, 193 insertions(+), 41 deletions(-) diff --git a/bin/postinstall.js b/bin/postinstall.js index f2054cc4..0e50a301 100755 --- a/bin/postinstall.js +++ b/bin/postinstall.js @@ -21,7 +21,7 @@ const main = async () => { continue } - await copyContent(path, root) + await copyContent(path, root, config) } } diff --git a/lib/config.js b/lib/config.js index f116a5df..cf6e8478 100644 --- a/lib/config.js +++ b/lib/config.js @@ -2,7 +2,9 @@ const PackageJson = require('@npmcli/package-json') const mapWorkspaces = require('@npmcli/map-workspaces') const defaultConfig = { - includeRoot: true, + applyRootRepoFiles: true, + applyWorkspaceRepoFiles: true, + applyRootModuleFiles: true, workspaces: [], paths: [], } @@ -38,10 +40,9 @@ module.exports = async (root) => { } config.workspacePaths = wsPaths - if (config.includeRoot) { - config.paths.push(root) - } config.paths = config.paths.concat(config.workspacePaths) + config.paths.push(root) + return config } diff --git a/lib/postinstall/copy-content.js b/lib/postinstall/copy-content.js index 13da1f50..96302d8c 100644 --- a/lib/postinstall/copy-content.js +++ b/lib/postinstall/copy-content.js @@ -31,6 +31,12 @@ const filesToDelete = [ /^\.eslintrc\.(?!(local\.)).*/, ] +const defaultConfig = { + applyRootRepoFiles: true, + applyWorkspaceRepoFiles: true, + applyRootModuleFiles: true, +} + const copyFiles = async (targetDir, files) => { for (let [target, source] of Object.entries(files)) { source = join(contentDir, source) @@ -49,46 +55,54 @@ const copyFiles = async (targetDir, files) => { // given a root directory, copy all files in the content map // after purging any files we need to delete -const copyContent = async (path, rootPath) => { - const contents = await fs.readdir(path) - +const copyContent = async (path, rootPath, config) => { + config = { ...defaultConfig, ...config } const isWorkspace = path !== rootPath - for (const file of contents) { - if (filesToDelete.some((p) => p.test(file))) { - await fs.rm(join(path, file)) + 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) } - await copyFiles(path, moduleFiles) if (!isWorkspace) { - await copyFiles(rootPath, repoFiles) + 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' } + ) + + workflowData = workflowData.replace(/%%pkgpath%%/g, path) + workflowData = workflowData.replace(/%%pkgname%%/g, workspaceName) + + await fs.writeFile(join(workflowPath, `ci-${workspaceName}.yml`), workflowData) } - - // isWorkspace === true - // if we had workspace specific content... - // await copyFiles(path, workspaceContent) - // await copyFiles(rootPath, workspaceRootContent) - - 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' } - ) - - workflowData = workflowData.replace(/%%pkgpath%%/g, path) - workflowData = workflowData.replace(/%%pkgname%%/g, workspaceName) - - await fs.writeFile(join(workflowPath, `ci-${workspaceName}.yml`), workflowData) } copyContent.moduleFiles = moduleFiles copyContent.repoFiles = repoFiles diff --git a/test/postinstall/copy-content.js b/test/postinstall/copy-content.js index 255cb04b..28db5b9d 100644 --- a/test/postinstall/copy-content.js +++ b/test/postinstall/copy-content.js @@ -1,6 +1,7 @@ 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') @@ -31,7 +32,11 @@ t.test('removes files', async (t) => { '.eslintrc.local.json', 'something.txt', ] - const root = t.testdir(content) + const root = t.testdir(content, { + applyRootRepoFiles: true, + applyWorkspaceRepoFiles: true, + applyRootModuleFiles: true, + }) await copyContent(root, root) for (const target of Object.keys(copyContent.moduleFiles)) { @@ -58,7 +63,9 @@ t.test('handles workspaces', async (t) => { 'package.json': JSON.stringify({ name: 'testpkg', templateOSS: { - includeRoot: false, + applyRootRepoFiles: true, + applyWorkspaceRepoFiles: true, + applyRootModuleFiles: true, workspaces: ['workspace/a', 'workspace/b'], }, }), @@ -77,11 +84,141 @@ t.test('handles workspaces', async (t) => { } const root = t.testdir(pkgWithWorkspaces) const workspacea = join(root, 'workspace', 'a') - await copyContent(workspacea, root) + 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'))) - // change should have applied to the workspace, not the root + 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'))) }) From 1aafd7788987b36eaacad88cd66228ea414cc326 Mon Sep 17 00:00:00 2001 From: Nathan Fritz Date: Wed, 8 Dec 2021 15:30:21 -0800 Subject: [PATCH 6/8] fixes after applying to the npm cli --- bin/npm-template-check.js | 8 +++++-- bin/postinstall.js | 2 +- lib/postinstall/copy-content.js | 3 ++- lib/postinstall/update-package.js | 27 ++++++++++++++------- test/bin/npm-template-check.js | 38 ++++++++++++++++++++++++++++++ test/postinstall/update-package.js | 35 +++++++++++++++++++++++++++ 6 files changed, 100 insertions(+), 13 deletions(-) diff --git a/bin/npm-template-check.js b/bin/npm-template-check.js index 1df9e058..0f9b2599 100755 --- a/bin/npm-template-check.js +++ b/bin/npm-template-check.js @@ -17,8 +17,12 @@ const main = async () => { const problemSets = [] for (const path of config.paths) { - problemSets.push(await checkPackage(path)) - problemSets.push(await checkGitIgnore(path)) + if (path !== root || config.applyRootModuleFiles) { + problemSets.push(await checkPackage(path)) + } + if (path !== root || config.applyRootRepoFiles) { + problemSets.push(await checkGitIgnore(path)) + } } const problems = problemSets.flat() diff --git a/bin/postinstall.js b/bin/postinstall.js index 0e50a301..0022257d 100755 --- a/bin/postinstall.js +++ b/bin/postinstall.js @@ -17,7 +17,7 @@ const main = async () => { const config = await getConfig(root) for (const path of config.paths) { - if (!await patchPackage(path)) { + if (!await patchPackage(path, root, config)) { continue } diff --git a/lib/postinstall/copy-content.js b/lib/postinstall/copy-content.js index 96302d8c..ba468d37 100644 --- a/lib/postinstall/copy-content.js +++ b/lib/postinstall/copy-content.js @@ -98,7 +98,8 @@ const copyContent = async (path, rootPath, config) => { { encoding: 'utf-8' } ) - workflowData = workflowData.replace(/%%pkgpath%%/g, path) + 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) diff --git a/lib/postinstall/update-package.js b/lib/postinstall/update-package.js index 2c0834d0..694fc3e7 100644 --- a/lib/postinstall/update-package.js +++ b/lib/postinstall/update-package.js @@ -26,8 +26,8 @@ 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 @@ -42,13 +42,22 @@ const patchPackage = async (root) => { 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, - }, + let update + + if (path === root && !config.applyRootModuleFiles) { + // only update templateVersion if we're skipping root module files + update = { + templateVersion: TEMPLATE_VERSION, + } + } else { + // we build a new object here so our exported set of changes is not modified + update = { + ...changes, + scripts: { + ...pkg.content.scripts, + ...changes.scripts, + }, + } } if (isDogfood) { 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/update-package.js b/test/postinstall/update-package.js index 2821fcbb..4f730743 100644 --- a/test/postinstall/update-package.js +++ b/test/postinstall/update-package.js @@ -76,3 +76,38 @@ t.test('doesnt set templateVersion on own repo', async (t) => { }) t.equal(JSON.parse(contents).templateVersion, 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.templateVersion, undefined, 'should set templateVersion') + t.equal(contents.author, undefined, 'should not set other fields') +}) From c24ae4cf60aa97a876ad4e72b8c541150b94e2fb Mon Sep 17 00:00:00 2001 From: Nathan Fritz Date: Thu, 9 Dec 2021 11:05:23 -0800 Subject: [PATCH 7/8] pkg.templateVersion to pkg.templateOSS.version --- README.md | 31 +++++++++++++++-- lib/postinstall/update-package.js | 20 +++++++++-- lib/postlint/check-package.js | 14 +------- .../test/postlint/check-package.js.test.cjs | 4 --- test/postinstall/update-package.js | 34 ++++++++++++++++--- test/postlint/check-package.js | 3 +- 6 files changed, 78 insertions(+), 28 deletions(-) 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/lib/postinstall/update-package.js b/lib/postinstall/update-package.js index 694fc3e7..6bd59fee 100644 --- a/lib/postinstall/update-package.js +++ b/lib/postinstall/update-package.js @@ -9,7 +9,6 @@ const changes = { author: 'GitHub Inc.', files: ['bin', 'lib'], license: 'ISC', - templateVersion: TEMPLATE_VERSION, scripts: { lint: `eslint '**/*.js'`, postlint: 'npm-template-check', @@ -34,20 +33,29 @@ const patchPackage = async (path, root, config) => { // 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 } + 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 = { - templateVersion: TEMPLATE_VERSION, + ...templateConfig, } } else { // we build a new object here so our exported set of changes is not modified @@ -57,11 +65,17 @@ const patchPackage = async (path, root, config) => { ...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/tap-snapshots/test/postlint/check-package.js.test.cjs b/tap-snapshots/test/postlint/check-package.js.test.cjs index 74835441..a5f0b1e1 100644 --- a/tap-snapshots/test/postlint/check-package.js.test.cjs +++ b/tap-snapshots/test/postlint/check-package.js.test.cjs @@ -13,7 +13,6 @@ Array [ Field: "author" Expected: "GitHub Inc." Found: undefined 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 ), @@ -30,7 +29,6 @@ Array [ 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 @@ -55,7 +53,6 @@ Array [ 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 ), @@ -72,7 +69,6 @@ Array [ 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/postinstall/update-package.js b/test/postinstall/update-package.js index 4f730743..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,10 +73,12 @@ 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', - }) - t.equal(JSON.parse(contents).templateVersion, undefined, 'did not get template version') + })) + 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) => { @@ -108,6 +112,26 @@ t.test('only sets templateVersion on root pkg when configured', async (t) => { encoding: 'utf8', })) - t.not(contents.templateVersion, undefined, 'should set templateVersion') + 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..61e67704 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' ) } @@ -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), From 30880505b344af5ee31b517d49ac87f763789fb8 Mon Sep 17 00:00:00 2001 From: Nathan Fritz Date: Thu, 9 Dec 2021 14:09:34 -0800 Subject: [PATCH 8/8] removed LICENSE.md content --- lib/postinstall/copy-content.js | 3 --- lib/postinstall/update-package.js | 1 - tap-snapshots/test/postlint/check-package.js.test.cjs | 6 +----- test/postinstall/copy-content.js | 2 -- test/postlint/check-package.js | 2 +- 5 files changed, 2 insertions(+), 12 deletions(-) diff --git a/lib/postinstall/copy-content.js b/lib/postinstall/copy-content.js index ba468d37..0c520061 100644 --- a/lib/postinstall/copy-content.js +++ b/lib/postinstall/copy-content.js @@ -9,7 +9,6 @@ const contentDir = resolve(__dirname, '..', 'content') const moduleFiles = { '.eslintrc.js': './eslintrc.js', '.gitignore': './gitignore', - 'LICENSE.md': './LICENSE.md', 'SECURITY.md': './SECURITY.md', } @@ -25,8 +24,6 @@ const repoFiles = { // const workspaceRootContent = {} const filesToDelete = [ - // remove any other license files - /^LICENSE*/, // remove any eslint config files that aren't local to the project /^\.eslintrc\.(?!(local\.)).*/, ] diff --git a/lib/postinstall/update-package.js b/lib/postinstall/update-package.js index 6bd59fee..cf04eff4 100644 --- a/lib/postinstall/update-package.js +++ b/lib/postinstall/update-package.js @@ -8,7 +8,6 @@ const { const changes = { author: 'GitHub Inc.', files: ['bin', 'lib'], - license: 'ISC', scripts: { lint: `eslint '**/*.js'`, postlint: 'npm-template-check', diff --git a/tap-snapshots/test/postlint/check-package.js.test.cjs b/tap-snapshots/test/postlint/check-package.js.test.cjs index a5f0b1e1..4243e0b5 100644 --- a/tap-snapshots/test/postlint/check-package.js.test.cjs +++ b/tap-snapshots/test/postlint/check-package.js.test.cjs @@ -10,9 +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: "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 ), @@ -28,7 +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: "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 @@ -52,7 +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: "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 ), @@ -68,7 +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: "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/postinstall/copy-content.js b/test/postinstall/copy-content.js index 28db5b9d..fe6c425f 100644 --- a/test/postinstall/copy-content.js +++ b/test/postinstall/copy-content.js @@ -25,8 +25,6 @@ t.test('removes files', async (t) => { '.eslintrc.yml': '', '.eslintrc.local.json': '{}', 'something.txt': '', - LICENSE: '', - 'LICENSE.txt': '', } const keepContent = [ '.eslintrc.local.json', diff --git a/test/postlint/check-package.js b/test/postlint/check-package.js index 61e67704..1e3befec 100644 --- a/test/postlint/check-package.js +++ b/test/postlint/check-package.js @@ -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', }), })