From d2e4ad7299f1e15ba57b23dd1ded133e08146851 Mon Sep 17 00:00:00 2001 From: nlf Date: Wed, 19 Jan 2022 16:17:59 -0800 Subject: [PATCH] feat: automatically detect workspace roots --- lib/index.js | 68 +++++++++++--- package-lock.json | 152 +++++++++++++++++------------- package.json | 2 + test/fixtures/definitions.js | 13 +++ test/index.js | 177 +++++++++++++++++++++++++++++++++++ 5 files changed, 337 insertions(+), 75 deletions(-) diff --git a/lib/index.js b/lib/index.js index 293fad2..c9c24e5 100644 --- a/lib/index.js +++ b/lib/index.js @@ -3,6 +3,8 @@ const walkUp = require('walk-up-path') const ini = require('ini') const nopt = require('nopt') const mkdirp = require('mkdirp-infer-owner') +const mapWorkspaces = require('@npmcli/map-workspaces') +const rpj = require('read-package-json-fast') /* istanbul ignore next */ const myUid = process.getuid && process.getuid() @@ -543,23 +545,65 @@ class Config { return } + const cliWorkspaces = this[_get]('workspaces', 'cli') + for (const p of walkUp(this.cwd)) { - // walk up until we have a nm dir or a pj file - const hasAny = (await Promise.all([ - stat(resolve(p, 'node_modules')) - .then(st => st.isDirectory()) - .catch(() => false), - stat(resolve(p, 'package.json')) - .then(st => st.isFile()) - .catch(() => false), - ])).some(is => is) - if (hasAny) { + const hasNodeModules = await stat(resolve(p, 'node_modules')) + .then((st) => st.isDirectory()) + .catch(() => false) + + const hasPackageJson = await stat(resolve(p, 'package.json')) + .then((st) => st.isFile()) + .catch(() => false) + + if (!this.localPrefix && (hasNodeModules || hasPackageJson)) { this.localPrefix = p - return + + // if workspaces are disabled, return now + if (cliWorkspaces === false) { + return + } + + // otherwise, continue the loop + continue + } + + if (this.localPrefix && hasPackageJson) { + // if we already set localPrefix but this dir has a package.json + // then we need to see if `p` is a workspace root by reading its package.json + // however, if reading it fails then we should just move on + const pkg = await rpj(resolve(p, 'package.json')).catch(() => false) + if (!pkg) { + continue + } + + const workspaces = await mapWorkspaces({ cwd: p, pkg }) + for (const w of workspaces.values()) { + if (w === this.localPrefix) { + // see if there's a .npmrc file in the workspace, if so log a warning + const hasNpmrc = await stat(resolve(this.localPrefix, '.npmrc')) + .then((st) => st.isFile()) + .catch(() => false) + + if (hasNpmrc) { + this.log.warn(`ignoring workspace config at ${this.localPrefix}/.npmrc`) + } + + // set the workspace in the default layer, which allows it to be overridden easily + const { data } = this.data.get('default') + data.workspace = [this.localPrefix] + this.localPrefix = p + this.log.info(`found workspace root at ${this.localPrefix}`) + // we found a root, so we return now + return + } + } } } - this.localPrefix = this.cwd + if (!this.localPrefix) { + this.localPrefix = this.cwd + } } loadUserConfig () { diff --git a/package-lock.json b/package-lock.json index a23b90d..883e81f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,9 +9,11 @@ "version": "2.4.0", "license": "ISC", "dependencies": { + "@npmcli/map-workspaces": "^2.0.0", "ini": "^2.0.0", "mkdirp-infer-owner": "^2.0.0", "nopt": "^5.0.0", + "read-package-json-fast": "^2.0.3", "semver": "^7.3.4", "walk-up-path": "^1.0.0" }, @@ -321,6 +323,25 @@ "node": ">=8" } }, + "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/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -467,8 +488,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", @@ -501,7 +521,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" @@ -707,8 +726,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.7.0", @@ -1031,8 +1049,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", @@ -1094,7 +1111,6 @@ "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -1233,7 +1249,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" @@ -1242,8 +1257,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/ini": { "version": "2.0.0", @@ -1523,6 +1537,11 @@ "node": ">=4" } }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + }, "node_modules/json-schema": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", @@ -1707,7 +1726,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" }, @@ -1804,6 +1822,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", @@ -1885,7 +1908,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, "dependencies": { "wrappy": "1" } @@ -1990,7 +2012,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" } @@ -2101,6 +2122,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.5.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", @@ -2898,14 +2931,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/tap/node_modules/ansi-regex": { - "version": "3.0.0", - "extraneous": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/tap/node_modules/ansi-styles": { "version": "3.2.1", "dev": true, @@ -4077,17 +4102,6 @@ "node": ">=8" } }, - "node_modules/tap/node_modules/strip-ansi": { - "version": "4.0.0", - "extraneous": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/tap/node_modules/supports-color": { "version": "5.5.0", "dev": true, @@ -4631,8 +4645,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", @@ -5085,6 +5098,22 @@ "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", "dev": true }, + "@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==" + }, "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -5203,8 +5232,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", @@ -5231,7 +5259,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" @@ -5395,8 +5422,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.7.0", @@ -5636,8 +5662,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", @@ -5683,7 +5708,6 @@ "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -5784,7 +5808,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" @@ -5793,8 +5816,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==" }, "ini": { "version": "2.0.0", @@ -6011,6 +6033,11 @@ "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", "dev": true }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + }, "json-schema": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", @@ -6152,7 +6179,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" } @@ -6222,6 +6248,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", @@ -6287,7 +6318,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, "requires": { "wrappy": "1" } @@ -6367,8 +6397,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", @@ -6452,6 +6481,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.5.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", @@ -7025,10 +7063,6 @@ "type-fest": "^0.21.3" } }, - "ansi-regex": { - "version": "3.0.0", - "extraneous": true - }, "ansi-styles": { "version": "3.2.1", "bundled": true, @@ -7809,13 +7843,6 @@ } } }, - "strip-ansi": { - "version": "4.0.0", - "extraneous": true, - "requires": { - "ansi-regex": "^3.0.0" - } - }, "supports-color": { "version": "5.5.0", "bundled": true, @@ -8263,8 +8290,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 83d8a34..43717b0 100644 --- a/package.json +++ b/package.json @@ -27,9 +27,11 @@ "tap": "^15.0.4" }, "dependencies": { + "@npmcli/map-workspaces": "^2.0.0", "ini": "^2.0.0", "mkdirp-infer-owner": "^2.0.0", "nopt": "^5.0.0", + "read-package-json-fast": "^2.0.3", "semver": "^7.3.4", "walk-up-path": "^1.0.0" }, diff --git a/test/fixtures/definitions.js b/test/fixtures/definitions.js index 7c756f0..12c4a1e 100644 --- a/test/fixtures/definitions.js +++ b/test/fixtures/definitions.js @@ -1820,6 +1820,19 @@ const definitions = module.exports = { description: '\n The program to use to view help content.\n\n Set to `"browser"` to view html help content in the default web browser.\n ', typeDescription: 'String', }, + workspace: { + key: 'workspace', + default: [], + type: [String, Array], + short: 'w', + envExport: false, + description: '\n Enable running a command in the context of the configured workspaces of the\n current project while filtering by running only the workspaces defined by\n this configuration option.\n\n Valid values for the `workspace` config are either:\n\n * Workspace names\n * Path to a workspace directory\n * Path to a parent workspace directory (will result in selecting all\n workspaces within that folder)\n\n When set for the `npm init` command, this may be set to the folder of\n a workspace which does not yet exist, to create the folder and set it\n up as a brand new workspace within the project.\n', + defaultDescription: '', + typeDescription: 'String (can be set multiple times)', + flatten: (key, obj, flatOptions) => { + definitions['user-agent'].flatten('user-agent', obj, flatOptions) + }, + }, yes: { key: 'yes', default: false, diff --git a/test/index.js b/test/index.js index 2e15caf..1ca5869 100644 --- a/test/index.js +++ b/test/index.js @@ -139,6 +139,7 @@ loglevel = yolo const logs = [] const log = { + info: (...msg) => logs.push(['info', ...msg]), warn: (...msg) => logs.push(['warn', ...msg]), verbose: (...msg) => logs.push(['verbose', ...msg]), } @@ -990,3 +991,179 @@ t.test('nerfdart auths set at the top level into the registry', async t => { }) } }) + +t.test('workspaces', async (t) => { + const path = resolve(t.testdir({ + 'package.json': JSON.stringify({ + name: 'root', + version: '1.0.0', + workspaces: ['./workspaces/*'], + }), + workspaces: { + one: { + 'package.json': JSON.stringify({ + name: 'one', + version: '1.0.0', + }), + }, + two: { + 'package.json': JSON.stringify({ + name: 'two', + version: '1.0.0', + }), + }, + three: { + 'package.json': JSON.stringify({ + name: 'three', + version: '1.0.0', + }), + '.npmrc': 'package-lock=false', + }, + }, + })) + + const logs = [] + const log = { + info: (...msg) => logs.push(['info', ...msg]), + warn: (...msg) => logs.push(['warn', ...msg]), + verbose: (...msg) => logs.push(['verbose', ...msg]), + } + t.afterEach(() => logs.length = 0) + + t.test('finds own parent', async (t) => { + const cwd = process.cwd() + t.teardown(() => process.chdir(cwd)) + process.chdir(`${path}/workspaces/one`) + + const config = new Config({ + npmPath: cwd, + env: {}, + argv: [process.execPath, __filename], + cwd: `${path}/workspaces/one`, + shorthands, + definitions, + log, + }) + + await config.load() + t.equal(config.localPrefix, path, 'localPrefix is the root') + t.same(config.get('workspace'), [join(path, 'workspaces', 'one')], 'set the workspace') + t.equal(logs.length, 1, 'got one log message') + t.match(logs[0], ['info', /^found workspace root at/], 'logged info about workspace root') + }) + + t.test('finds other workspace parent', async (t) => { + const cwd = process.cwd() + t.teardown(() => process.chdir(cwd)) + process.chdir(`${path}/workspaces/one`) + + const config = new Config({ + npmPath: process.cwd(), + env: {}, + argv: [process.execPath, __filename, '--workspace', '../two'], + cwd: `${path}/workspaces/one`, + shorthands, + definitions, + log, + }) + + await config.load() + t.equal(config.localPrefix, path, 'localPrefix is the root') + t.same(config.get('workspace'), ['../two'], 'kept the specified workspace') + t.equal(logs.length, 1, 'got one log message') + t.match(logs[0], ['info', /^found workspace root at/], 'logged info about workspace root') + }) + + t.test('warns when workspace has .npmrc', async (t) => { + const cwd = process.cwd() + t.teardown(() => process.chdir(cwd)) + process.chdir(`${path}/workspaces/three`) + + const config = new Config({ + npmPath: process.cwd(), + env: {}, + argv: [process.execPath, __filename], + cwd: `${path}/workspaces/three`, + shorthands, + definitions, + log, + }) + + await config.load() + t.equal(config.localPrefix, path, 'localPrefix is the root') + t.same(config.get('workspace'), [join(path, 'workspaces', 'three')], 'kept the specified workspace') + t.equal(logs.length, 2, 'got two log messages') + t.match(logs[0], ['warn', /^ignoring workspace config/], 'warned about ignored workspace config') + t.match(logs[1], ['info', /^found workspace root at/], 'logged info about workspace root') + }) + + t.test('prefix skips auto detect', async (t) => { + const cwd = process.cwd() + t.teardown(() => process.chdir(cwd)) + process.chdir(`${path}/workspaces/one`) + + const config = new Config({ + npmPath: process.cwd(), + env: {}, + argv: [process.execPath, __filename, '--prefix', './'], + cwd: `${path}/workspaces/one`, + shorthands, + definitions, + log, + }) + + await config.load() + t.equal(config.localPrefix, join(path, 'workspaces', 'one'), 'localPrefix is the root') + t.same(config.get('workspace'), [], 'did not set workspace') + t.equal(logs.length, 0, 'got no log messages') + }) + + t.test('no-workspaces skips auto detect', async (t) => { + const cwd = process.cwd() + t.teardown(() => process.chdir(cwd)) + process.chdir(`${path}/workspaces/one`) + + const config = new Config({ + npmPath: process.cwd(), + env: {}, + argv: [process.execPath, __filename, '--no-workspaces'], + cwd: `${path}/workspaces/one`, + shorthands, + definitions, + log, + }) + + await config.load() + t.equal(config.localPrefix, join(path, 'workspaces', 'one'), 'localPrefix is the root') + t.same(config.get('workspace'), [], 'did not set workspace') + t.equal(logs.length, 0, 'got no log messages') + }) + + t.test('does not error for invalid package.json', async (t) => { + const invalidPkg = join(path, 'workspaces', 'package.json') + const cwd = process.cwd() + t.teardown(() => { + fs.unlinkSync(invalidPkg) + process.chdir(cwd) + }) + process.chdir(`${path}/workspaces/one`) + + // write some garbage to the file so read-package-json-fast will throw + fs.writeFileSync(invalidPkg, 'not-json') + const config = new Config({ + npmPath: cwd, + env: {}, + argv: [process.execPath, __filename], + cwd: `${path}/workspaces/one`, + shorthands, + definitions, + log, + }) + + await config.load() + t.equal(config.localPrefix, path, 'localPrefix is the root') + t.same(config.get('workspace'), [join(path, 'workspaces', 'one')], 'set the workspace') + t.equal(logs.length, 1, 'got one log message') + t.match(logs[0], ['info', /^found workspace root at/], 'logged info about workspace root') + }) +})