From 0c18e4f774562fa054fedf323bea25805ebf39b3 Mon Sep 17 00:00:00 2001 From: Ruy Adorno Date: Thu, 13 May 2021 12:24:39 -0400 Subject: [PATCH 1/7] @npmcli/arborist@2.5.0 --- .../@npmcli/arborist/lib/arborist/index.js | 23 +++++++++++++ .../arborist/lib/get-workspace-nodes.js | 33 +++++++++++++++++++ node_modules/@npmcli/arborist/package.json | 2 +- package-lock.json | 14 ++++---- package.json | 2 +- 5 files changed, 65 insertions(+), 9 deletions(-) create mode 100644 node_modules/@npmcli/arborist/lib/get-workspace-nodes.js diff --git a/node_modules/@npmcli/arborist/lib/arborist/index.js b/node_modules/@npmcli/arborist/lib/arborist/index.js index 3578d50389ea6..cd39df01af6c6 100644 --- a/node_modules/@npmcli/arborist/lib/arborist/index.js +++ b/node_modules/@npmcli/arborist/lib/arborist/index.js @@ -45,6 +45,7 @@ const mixins = [ ] const Base = mixins.reduce((a, b) => b(a), require('events')) +const getWorkspaceNodes = require('../get-workspace-nodes.js') class Arborist extends Base { constructor (options = {}) { @@ -64,6 +65,28 @@ class Arborist extends Base { this.path = resolve(this.options.path) process.emit('timeEnd', 'arborist:ctor') } + + // returns an array of the actual nodes for all the workspaces + workspaceNodes (tree, workspaces) { + return getWorkspaceNodes(tree, workspaces, this.log) + } + + // returns a set of workspace nodes and all their deps + workspaceDependencySet (tree, workspaces) { + const wsNodes = this.workspaceNodes(tree, workspaces) + const set = new Set(wsNodes) + for (const node of set) { + for (const edge of node.edgesOut.values()) { + const dep = edge.to + if (dep) { + set.add(dep) + if (dep.target) + set.add(dep.target) + } + } + } + return set + } } module.exports = Arborist diff --git a/node_modules/@npmcli/arborist/lib/get-workspace-nodes.js b/node_modules/@npmcli/arborist/lib/get-workspace-nodes.js new file mode 100644 index 0000000000000..6db489f69c518 --- /dev/null +++ b/node_modules/@npmcli/arborist/lib/get-workspace-nodes.js @@ -0,0 +1,33 @@ +// Get the actual nodes corresponding to a root node's child workspaces, +// given a list of workspace names. +const relpath = require('./relpath.js') +const getWorkspaceNodes = (tree, workspaces, log) => { + const wsMap = tree.workspaces + if (!wsMap) { + log.warn('workspaces', 'filter set, but no workspaces present') + return [] + } + + const nodes = [] + for (const name of workspaces) { + const path = wsMap.get(name) + if (!path) { + log.warn('workspaces', `${name} in filter set, but not in workspaces`) + continue + } + + const loc = relpath(tree.realpath, path) + const node = tree.inventory.get(loc) + + if (!node) { + log.warn('workspaces', `${name} in filter set, but no workspace folder present`) + continue + } + + nodes.push(node) + } + + return nodes +} + +module.exports = getWorkspaceNodes diff --git a/node_modules/@npmcli/arborist/package.json b/node_modules/@npmcli/arborist/package.json index bbe87d8bf97ad..fd579cbf27fdc 100644 --- a/node_modules/@npmcli/arborist/package.json +++ b/node_modules/@npmcli/arborist/package.json @@ -1,6 +1,6 @@ { "name": "@npmcli/arborist", - "version": "2.4.4", + "version": "2.5.0", "description": "Manage node_modules trees", "dependencies": { "@npmcli/installed-package-contents": "^1.0.7", diff --git a/package-lock.json b/package-lock.json index 26d2536dcf081..8b03fb6e2cf71 100644 --- a/package-lock.json +++ b/package-lock.json @@ -78,7 +78,7 @@ ], "license": "Artistic-2.0", "dependencies": { - "@npmcli/arborist": "^2.4.4", + "@npmcli/arborist": "^2.5.0", "@npmcli/ci-detect": "^1.2.0", "@npmcli/config": "^2.2.0", "@npmcli/run-script": "^1.8.5", @@ -712,9 +712,9 @@ } }, "node_modules/@npmcli/arborist": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/@npmcli/arborist/-/arborist-2.4.4.tgz", - "integrity": "sha512-mqZvcPCWT6gSSYxs08aKvXmECXh9fP85q1pUIY/jDkaQ58QTRy6F7XrUQr7F77jXpYfpYKPUi6RhpuSpOXCITA==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@npmcli/arborist/-/arborist-2.5.0.tgz", + "integrity": "sha512-YPSkV/8vofpbAJyeu52J12YnC5VTkYIcfcNkRoSW6qjfQG+QybgbJtCbcdx+M0YxfdzDKS6iDTjpNMoETZ8HOA==", "inBundle": true, "dependencies": { "@npmcli/installed-package-contents": "^1.0.7", @@ -10823,9 +10823,9 @@ "dev": true }, "@npmcli/arborist": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/@npmcli/arborist/-/arborist-2.4.4.tgz", - "integrity": "sha512-mqZvcPCWT6gSSYxs08aKvXmECXh9fP85q1pUIY/jDkaQ58QTRy6F7XrUQr7F77jXpYfpYKPUi6RhpuSpOXCITA==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@npmcli/arborist/-/arborist-2.5.0.tgz", + "integrity": "sha512-YPSkV/8vofpbAJyeu52J12YnC5VTkYIcfcNkRoSW6qjfQG+QybgbJtCbcdx+M0YxfdzDKS6iDTjpNMoETZ8HOA==", "requires": { "@npmcli/installed-package-contents": "^1.0.7", "@npmcli/map-workspaces": "^1.0.2", diff --git a/package.json b/package.json index 70f94838882b7..9ac34d43b9bca 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "./package.json": "./package.json" }, "dependencies": { - "@npmcli/arborist": "^2.4.4", + "@npmcli/arborist": "^2.5.0", "@npmcli/ci-detect": "^1.2.0", "@npmcli/config": "^2.2.0", "@npmcli/run-script": "^1.8.5", From b551c6811251dbc901f47fea3c137f93e205a9e4 Mon Sep 17 00:00:00 2001 From: Ruy Adorno Date: Thu, 13 May 2021 12:26:04 -0400 Subject: [PATCH 2/7] libnpmfund@1.1.0 --- node_modules/libnpmfund/CHANGELOG.md | 6 --- node_modules/libnpmfund/README.md | 5 ++- node_modules/libnpmfund/index.js | 63 +++++++++++++++++----------- node_modules/libnpmfund/package.json | 19 +++++---- package-lock.json | 18 ++++---- package.json | 2 +- 6 files changed, 64 insertions(+), 49 deletions(-) delete mode 100644 node_modules/libnpmfund/CHANGELOG.md diff --git a/node_modules/libnpmfund/CHANGELOG.md b/node_modules/libnpmfund/CHANGELOG.md deleted file mode 100644 index b890b58e1405a..0000000000000 --- a/node_modules/libnpmfund/CHANGELOG.md +++ /dev/null @@ -1,6 +0,0 @@ -# Changelog - -## 0.0.0-pre.0 - -- Initial pre-release. - diff --git a/node_modules/libnpmfund/README.md b/node_modules/libnpmfund/README.md index c373a9ceb7dd5..8ab663f634d6f 100644 --- a/node_modules/libnpmfund/README.md +++ b/node_modules/libnpmfund/README.md @@ -73,7 +73,10 @@ Options: - `countOnly`: Uses the tree-traversal logic from **npm fund** but skips over any obj definition and just returns an obj containing `{ length }` - useful for things such as printing a `6 packages are looking for funding` msg. -- `path`: Location to current working directory +- `workspaces`: `Array` List of workspaces names to filter for, +the result will only include a subset of the resulting tree that includes +only the nodes that are children of the listed workspaces names. +- `path`, `registry` and more [Arborist](https://github.com/npm/arborist/) options. ##### `> fund.readTree(tree, [opts]) -> Promise` diff --git a/node_modules/libnpmfund/index.js b/node_modules/libnpmfund/index.js index 58aba028e3622..37bc1dd0b7916 100644 --- a/node_modules/libnpmfund/index.js +++ b/node_modules/libnpmfund/index.js @@ -15,11 +15,11 @@ function normalizeFunding (funding) { // Is the value of a `funding` property of a `package.json` // a valid type+url for `npm fund` to display? function isValidFunding (funding) { - if (!funding) return false + if (!funding) + return false - if (Array.isArray(funding)) { + if (Array.isArray(funding)) return funding.every(f => !Array.isArray(f) && isValidFunding(f)) - } try { var parsed = new URL(funding.url || funding) @@ -30,7 +30,8 @@ function isValidFunding (funding) { if ( parsed.protocol !== 'https:' && parsed.protocol !== 'http:' - ) return false + ) + return false return Boolean(parsed.host) } @@ -43,11 +44,18 @@ function readTree (tree, opts) { const { countOnly } = opts || {} const _trailingDependencies = Symbol('trailingDependencies') + let filterSet + + if (opts && opts.workspaces && opts.workspaces.length) { + const arb = new Arborist(opts) + filterSet = arb.workspaceDependencySet(tree, opts.workspaces) + } + function tracked (name, version) { const key = String(name) + String(version) - if (seen.has(key)) { + if (seen.has(key)) return true - } + seen.add(key) } @@ -81,30 +89,36 @@ function readTree (tree, opts) { function getFundingDependencies (tree) { const edges = tree && tree.edgesOut && tree.edgesOut.values() - if (!edges) return empty() + if (!edges) + return empty() const directDepsWithFunding = Array.from(edges).map(edge => { - if (!edge || !edge.to) return empty() + if (!edge || !edge.to) + return empty() const node = edge.to.target || edge.to - if (!node.package) return empty() + if (!node.package) + return empty() + + if (filterSet && filterSet.size > 0 && !filterSet.has(node)) + return empty() const { name, funding, version } = node.package // avoids duplicated items within the funding tree - if (tracked(name, version)) return empty() + if (tracked(name, version)) + return empty() const fundingItem = {} - if (version) { + if (version) fundingItem.version = version - } attachFundingInfo(fundingItem, funding) return { node, - fundingItem + fundingItem, } }) @@ -112,7 +126,8 @@ function readTree (tree, opts) { (res, { node, fundingItem }, i) => { if (!fundingItem || fundingItem.length === 0 || - !node) return res + !node) + return res // recurse const transitiveDependencies = node.edgesOut && @@ -121,16 +136,17 @@ function readTree (tree, opts) { // if we're only counting items there's no need // to add all the data to the resulting object - if (countOnly) return null + if (countOnly) + return null if (hasDependencies(transitiveDependencies)) { fundingItem.dependencies = retrieveDependencies(transitiveDependencies) } - if (isValidFunding(fundingItem.funding)) { + if (isValidFunding(fundingItem.funding)) res[node.package.name] = fundingItem - } else if (hasDependencies(fundingItem.dependencies)) { + else if (hasDependencies(fundingItem.dependencies)) { res[_trailingDependencies] = Object.assign( empty(), @@ -145,7 +161,7 @@ function readTree (tree, opts) { const treeDependencies = getFundingDependencies(tree) const result = { - length: packageWithFundingCount + length: packageWithFundingCount, } if (!countOnly) { @@ -154,13 +170,11 @@ function readTree (tree, opts) { (tree && tree.name) result.name = name || (tree && tree.path) - if (tree && tree.package && tree.package.version) { + if (tree && tree.package && tree.package.version) result.version = tree.package.version - } - if (tree && tree.package && tree.package.funding) { + if (tree && tree.package && tree.package.funding) result.funding = normalizeFunding(tree.package.funding) - } result.dependencies = retrieveDependencies(treeDependencies) } @@ -170,8 +184,7 @@ function readTree (tree, opts) { async function read (opts) { const arb = new Arborist(opts) - const tree = await arb.loadActual() - + const tree = await arb.loadActual(opts) return readTree(tree, opts) } @@ -179,5 +192,5 @@ module.exports = { read, readTree, normalizeFunding, - isValidFunding + isValidFunding, } diff --git a/node_modules/libnpmfund/package.json b/node_modules/libnpmfund/package.json index b25d3aa6b520e..7f4acad383bb8 100644 --- a/node_modules/libnpmfund/package.json +++ b/node_modules/libnpmfund/package.json @@ -1,6 +1,6 @@ { "name": "libnpmfund", - "version": "1.0.2", + "version": "1.1.0", "files": [ "index.js" ], @@ -25,8 +25,10 @@ ], "license": "ISC", "scripts": { - "lint": "standard", - "pretest": "npm run lint", + "eslint": "eslint", + "lint": "npm run eslint -- index.js test.js", + "lintfix": "npm run lint -- --fix", + "posttest": "npm run lint", "test": "tap", "snap": "tap", "preversion": "npm test", @@ -42,11 +44,14 @@ ] }, "devDependencies": { - "require-inject": "^1.4.4", - "standard": "^14.3.4", - "tap": "^14.10.7" + "eslint": "^7.26.0", + "eslint-plugin-import": "^2.22.1", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-promise": "^5.1.0", + "eslint-plugin-standard": "^5.0.0", + "tap": "^15.0.9" }, "dependencies": { - "@npmcli/arborist": "^2.0.0" + "@npmcli/arborist": "^2.5.0" } } diff --git a/package-lock.json b/package-lock.json index 8b03fb6e2cf71..7ddad043e0835 100644 --- a/package-lock.json +++ b/package-lock.json @@ -104,7 +104,7 @@ "libnpmaccess": "^4.0.2", "libnpmdiff": "^2.0.4", "libnpmexec": "^1.1.1", - "libnpmfund": "^1.0.2", + "libnpmfund": "^1.1.0", "libnpmhook": "^6.0.2", "libnpmorg": "^2.0.2", "libnpmpack": "^2.0.1", @@ -4648,12 +4648,12 @@ } }, "node_modules/libnpmfund": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/libnpmfund/-/libnpmfund-1.0.2.tgz", - "integrity": "sha512-Scw2JiLxfT7wqW/VbxIXV8u3FaFT/ZlR8YLFgTdCPsL1Hhli0554ZXyP8JTu1sLeDpHsoqtgLb4mgYVQnqigjA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/libnpmfund/-/libnpmfund-1.1.0.tgz", + "integrity": "sha512-Kfmh3pLS5/RGKG5WXEig8mjahPVOxkik6lsbH4iX0si1xxNi6eeUh/+nF1MD+2cgalsQif3O5qyr6mNz2ryJrQ==", "inBundle": true, "dependencies": { - "@npmcli/arborist": "^2.0.0" + "@npmcli/arborist": "^2.5.0" } }, "node_modules/libnpmhook": { @@ -13705,11 +13705,11 @@ } }, "libnpmfund": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/libnpmfund/-/libnpmfund-1.0.2.tgz", - "integrity": "sha512-Scw2JiLxfT7wqW/VbxIXV8u3FaFT/ZlR8YLFgTdCPsL1Hhli0554ZXyP8JTu1sLeDpHsoqtgLb4mgYVQnqigjA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/libnpmfund/-/libnpmfund-1.1.0.tgz", + "integrity": "sha512-Kfmh3pLS5/RGKG5WXEig8mjahPVOxkik6lsbH4iX0si1xxNi6eeUh/+nF1MD+2cgalsQif3O5qyr6mNz2ryJrQ==", "requires": { - "@npmcli/arborist": "^2.0.0" + "@npmcli/arborist": "^2.5.0" } }, "libnpmhook": { diff --git a/package.json b/package.json index 9ac34d43b9bca..03ca369e4c899 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,7 @@ "libnpmaccess": "^4.0.2", "libnpmdiff": "^2.0.4", "libnpmexec": "^1.1.1", - "libnpmfund": "^1.0.2", + "libnpmfund": "^1.1.0", "libnpmhook": "^6.0.2", "libnpmorg": "^2.0.2", "libnpmpack": "^2.0.1", From 9c46a0aabeee5468f3530139d4c8527966814ec7 Mon Sep 17 00:00:00 2001 From: Ruy Adorno Date: Wed, 12 May 2021 17:28:18 -0400 Subject: [PATCH 3/7] fix: lint all files in lib/ folder PR-URL: https://github.com/npm/cli/pull/3237 Credit: @ruyadorno Close: #3237 Reviewed-by: @wraithgar --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 03ca369e4c899..9fe4338efce75 100644 --- a/package.json +++ b/package.json @@ -205,7 +205,7 @@ "sudotest:nocleanup": "sudo NO_TEST_CLEANUP=1 npm run test --", "posttest": "npm run lint", "eslint": "eslint", - "lint": "npm run eslint -- test/lib test/bin \"lib/**/*.js\"", + "lint": "npm run eslint -- test/lib test/bin lib", "lintfix": "npm run lint -- --fix", "prelint": "rimraf test/npm_cache*", "resetdeps": "bash scripts/resetdeps.sh", From 076420c149d097056f687e44e21744b743b86e4e Mon Sep 17 00:00:00 2001 From: Gar Date: Tue, 11 May 2021 12:48:28 -0700 Subject: [PATCH 4/7] feat(publish): add workspace support Errors will make things stop altogether, dunno if we want to bikeshed that here or not PR-URL: https://github.com/npm/cli/pull/3231 Credit: @wraithgar Close: #3231 Reviewed-by: @ruyadorno --- docs/content/commands/npm-publish.md | 7 + lib/publish.js | 76 ++++++---- tap-snapshots/test/lib/publish.js.test.cjs | 103 ++++++++++++++ .../test/lib/utils/npm-usage.js.test.cjs | 2 + test/lib/publish.js | 132 +++++++++++++++--- 5 files changed, 274 insertions(+), 46 deletions(-) diff --git a/docs/content/commands/npm-publish.md b/docs/content/commands/npm-publish.md index fc13e67222358..10e65f895ec5b 100644 --- a/docs/content/commands/npm-publish.md +++ b/docs/content/commands/npm-publish.md @@ -47,6 +47,13 @@ by specifying a different default registry or using a actually publishing to the registry. Reports the details of what would have been published. +* `[--workspaces]`: Enables workspace context while publishing. All + workspace packages will be published. + +* `[--workspace]`: Enables workspaces context and limits results to only + those specified by this config item. Only the packages in the + workspaces given will be published. + The publish will fail if the package name and version combination already exists in the specified registry. diff --git a/lib/publish.js b/lib/publish.js index b121cb3d36773..b33839903a266 100644 --- a/lib/publish.js +++ b/lib/publish.js @@ -8,13 +8,19 @@ const pacote = require('pacote') const npa = require('npm-package-arg') const npmFetch = require('npm-registry-fetch') -const flatten = require('./utils/config/flatten.js') const otplease = require('./utils/otplease.js') const { getContents, logTar } = require('./utils/tar.js') +const getWorkspaces = require('./workspaces/get-workspaces.js') + +// for historical reasons, publishConfig in package.json can contain ANY config +// keys that npm supports in .npmrc files and elsewhere. We *may* want to +// revisit this at some point, and have a minimal set that's a SemVer-major +// change that ought to get a RFC written on it. +const flatten = require('./utils/config/flatten.js') -// this is the only case in the CLI where we use the old full slow -// 'read-package-json' module, because we want to pull in all the -// defaults and metadata, like git sha's and default scripts and all that. +// this is the only case in the CLI where we want to use the old full slow +// 'read-package-json' module, because we want to pull in all the defaults and +// metadata, like git sha's and default scripts and all that. const readJson = util.promisify(require('read-package-json')) const BaseCommand = require('./base-command.js') @@ -30,7 +36,7 @@ class Publish extends BaseCommand { /* istanbul ignore next - see test/lib/load-all-commands.js */ static get params () { - return ['tag', 'access', 'dry-run'] + return ['tag', 'access', 'dry-run', 'workspace', 'workspaces'] } /* istanbul ignore next - see test/lib/load-all-commands.js */ @@ -44,6 +50,10 @@ class Publish extends BaseCommand { this.publish(args).then(() => cb()).catch(cb) } + execWorkspaces (args, filters, cb) { + this.publishWorkspaces(args, filters).then(() => cb()).catch(cb) + } + async publish (args) { if (args.length === 0) args = ['.'] @@ -56,6 +66,7 @@ class Publish extends BaseCommand { const dryRun = this.npm.config.get('dry-run') const json = this.npm.config.get('json') const defaultTag = this.npm.config.get('tag') + const silent = log.level === 'silent' if (semver.validRange(defaultTag)) throw new Error('Tag name must not be a valid SemVer range: ' + defaultTag.trim()) @@ -68,7 +79,7 @@ class Publish extends BaseCommand { let manifest = await this.getManifest(spec, opts) if (manifest.publishConfig) - Object.assign(opts, this.publishConfigToOpts(manifest.publishConfig)) + flatten(manifest.publishConfig, opts) // only run scripts for directory type publishes if (spec.type === 'directory') { @@ -77,7 +88,7 @@ class Publish extends BaseCommand { path: spec.fetchSpec, stdio: 'inherit', pkg: manifest, - banner: log.level !== 'silent', + banner: !silent, }) } @@ -89,7 +100,7 @@ class Publish extends BaseCommand { // note that publishConfig might have changed as well! manifest = await this.getManifest(spec, opts) if (manifest.publishConfig) - Object.assign(opts, this.publishConfigToOpts(manifest.publishConfig)) + flatten(manifest.publishConfig, opts) // note that logTar calls npmlog.notice(), so if we ARE in silent mode, // this will do nothing, but we still want it in the debuglog if it fails. @@ -114,7 +125,7 @@ class Publish extends BaseCommand { path: spec.fetchSpec, stdio: 'inherit', pkg: manifest, - banner: log.level !== 'silent', + banner: !silent, }) await runScript({ @@ -122,19 +133,43 @@ class Publish extends BaseCommand { path: spec.fetchSpec, stdio: 'inherit', pkg: manifest, - banner: log.level !== 'silent', + banner: !silent, }) } - const silent = log.level === 'silent' - if (!silent && json) - this.npm.output(JSON.stringify(pkgContents, null, 2)) - else if (!silent) - this.npm.output(`+ ${pkgContents.id}`) + if (!this.workspaces) { + if (!silent && json) + this.npm.output(JSON.stringify(pkgContents, null, 2)) + else if (!silent) + this.npm.output(`+ ${pkgContents.id}`) + } return pkgContents } + async publishWorkspaces (args, filters) { + // Suppresses JSON output in publish() so we can handle it here + this.workspaces = true + + const results = {} + const json = this.npm.config.get('json') + const silent = log.level === 'silent' + const workspaces = + await getWorkspaces(filters, { path: this.npm.localPrefix }) + for (const [name, workspace] of workspaces.entries()) { + const pkgContents = await this.publish([workspace]) + // This needs to be in-line w/ the rest of the output that non-JSON + // publish generates + if (!silent && !json) + this.npm.output(`+ ${pkgContents.id}`) + else + results[name] = pkgContents + } + + if (!silent && json) + this.npm.output(JSON.stringify(results, null, 2)) + } + // if it's a directory, read it from the file system // otherwise, get the full metadata from whatever it is getManifest (spec, opts) { @@ -142,16 +177,5 @@ class Publish extends BaseCommand { return readJson(`${spec.fetchSpec}/package.json`) return pacote.manifest(spec, { ...opts, fullMetadata: true }) } - - // for historical reasons, publishConfig in package.json can contain - // ANY config keys that npm supports in .npmrc files and elsewhere. - // We *may* want to revisit this at some point, and have a minimal set - // that's a SemVer-major change that ought to get a RFC written on it. - publishConfigToOpts (publishConfig) { - // create a new object that inherits from the config stack - // then squash the css-case into camelCase opts, like we do - // this is Object.assign()'ed onto the base npm.flatOptions - return flatten(publishConfig, {}) - } } module.exports = Publish diff --git a/tap-snapshots/test/lib/publish.js.test.cjs b/tap-snapshots/test/lib/publish.js.test.cjs index 172ed5b29f478..0d652e289a822 100644 --- a/tap-snapshots/test/lib/publish.js.test.cjs +++ b/tap-snapshots/test/lib/publish.js.test.cjs @@ -15,6 +15,109 @@ npm publish [] Options: [--tag ] [--access ] [--dry-run] +[-w|--workspace [-w|--workspace ...]] +[-ws|--workspaces] Run "npm help publish" for more info ` + +exports[`test/lib/publish.js TAP workspaces all workspaces > should output all publishes 1`] = ` +Array [ + "+ workspace-a@1.2.3-a", + "+ workspace-b@1.2.3-n", +] +` + +exports[`test/lib/publish.js TAP workspaces all workspaces > should publish all workspaces 1`] = ` +Array [ + Object { + "_id": "workspace-a@1.2.3-a", + "name": "workspace-a", + "readme": "ERROR: No README data found!", + "repository": Object { + "type": "git", + "url": "http://repo.workspace-a/", + }, + "version": "1.2.3-a", + }, + Object { + "_id": "workspace-b@1.2.3-n", + "bugs": Object { + "url": "https://github.com/npm/workspace-b/issues", + }, + "homepage": "https://github.com/npm/workspace-b#readme", + "name": "workspace-b", + "readme": "ERROR: No README data found!", + "repository": Object { + "type": "git", + "url": "git+https://github.com/npm/workspace-b.git", + }, + "version": "1.2.3-n", + }, +] +` + +exports[`test/lib/publish.js TAP workspaces json > should output all publishes as json 1`] = ` +Array [ + String( + { + "workspace-a": { + "id": "workspace-a@1.2.3-a" + }, + "workspace-b": { + "id": "workspace-b@1.2.3-n" + } + } + ), +] +` + +exports[`test/lib/publish.js TAP workspaces json > should publish all workspaces 1`] = ` +Array [ + Object { + "_id": "workspace-a@1.2.3-a", + "name": "workspace-a", + "readme": "ERROR: No README data found!", + "repository": Object { + "type": "git", + "url": "http://repo.workspace-a/", + }, + "version": "1.2.3-a", + }, + Object { + "_id": "workspace-b@1.2.3-n", + "bugs": Object { + "url": "https://github.com/npm/workspace-b/issues", + }, + "homepage": "https://github.com/npm/workspace-b#readme", + "name": "workspace-b", + "readme": "ERROR: No README data found!", + "repository": Object { + "type": "git", + "url": "git+https://github.com/npm/workspace-b.git", + }, + "version": "1.2.3-n", + }, +] +` + +exports[`test/lib/publish.js TAP workspaces one workspace > should output one publish 1`] = ` +Array [ + "+ workspace-a@1.2.3-a", +] +` + +exports[`test/lib/publish.js TAP workspaces one workspace > should publish given workspace 1`] = ` +Array [ + Object { + "_id": "workspace-a@1.2.3-a", + "name": "workspace-a", + "readme": "ERROR: No README data found!", + "repository": Object { + "type": "git", + "url": "http://repo.workspace-a/", + }, + "version": "1.2.3-a", + }, +] +` diff --git a/tap-snapshots/test/lib/utils/npm-usage.js.test.cjs b/tap-snapshots/test/lib/utils/npm-usage.js.test.cjs index ad61dc7969ac4..9bcba775fd85b 100644 --- a/tap-snapshots/test/lib/utils/npm-usage.js.test.cjs +++ b/tap-snapshots/test/lib/utils/npm-usage.js.test.cjs @@ -697,6 +697,8 @@ All commands: Options: [--tag ] [--access ] [--dry-run] + [-w|--workspace [-w|--workspace ...]] + [-ws|--workspaces] Run "npm help publish" for more info diff --git a/test/lib/publish.js b/test/lib/publish.js index 57574b22a56ea..7aff7b08c1971 100644 --- a/test/lib/publish.js +++ b/test/lib/publish.js @@ -9,15 +9,12 @@ const fs = require('fs') const log = require('npmlog') log.level = 'silent' -// mock config const {definitions} = require('../../lib/utils/config') const defaults = Object.entries(definitions).reduce((defaults, [key, def]) => { defaults[key] = def.default return defaults }, {}) -const config = defaults - t.afterEach(() => log.level = 'silent') t.test('should publish with libnpmpublish, passing through flatOptions and respecting publishConfig.registry', (t) => { @@ -54,7 +51,6 @@ t.test('should publish with libnpmpublish, passing through flatOptions and respe }, }) const npm = mockNpm({ - config, flatOptions: { customValue: true, }, @@ -102,7 +98,7 @@ t.test('re-loads publishConfig.registry if added during script process', (t) => }, }, }) - const npm = mockNpm({ config }) + const npm = mockNpm() npm.config.getCredentialsByURI = (uri) => { t.same(uri, registry, 'gets credentials for expected registry') return { token: 'some.registry.token' } @@ -144,13 +140,13 @@ t.test('if loglevel=info and json, should not output package contents', (t) => { }, }) const npm = mockNpm({ - config: { ...config, json: true }, + config: { json: true }, output: () => { t.pass('output is called') }, }) npm.config.getCredentialsByURI = (uri) => { - t.same(uri, defaults.registry, 'gets credentials for expected registry') + t.same(uri, npm.config.get('registry'), 'gets credentials for expected registry') return { token: 'some.registry.token' } } const publish = new Publish(npm) @@ -190,7 +186,7 @@ t.test('if loglevel=silent and dry-run, should not output package contents or pu }, }) const npm = mockNpm({ - config: { ...config, 'dry-run': true }, + config: { 'dry-run': true }, output: () => { throw new Error('should not output in dry run mode') }, @@ -236,7 +232,7 @@ t.test('if loglevel=info and dry-run, should not publish, should log package con }, }) const npm = mockNpm({ - config: { ...config, 'dry-run': true }, + config: { 'dry-run': true }, output: () => { t.pass('output fn is called') }, @@ -270,7 +266,7 @@ t.test('throws when invalid tag', (t) => { const Publish = t.mock('../../lib/publish.js') const npm = mockNpm({ - config: { ...config, tag: '0.0.13' }, + config: { tag: '0.0.13' }, }) const publish = new Publish(npm) @@ -313,9 +309,9 @@ t.test('can publish a tarball', t => { }, }, }) - const npm = mockNpm({ config }) + const npm = mockNpm() npm.config.getCredentialsByURI = (uri) => { - t.same(uri, defaults.registry, 'gets credentials for expected registry') + t.same(uri, npm.config.get('registry'), 'gets credentials for expected registry') return { token: 'some.registry.token' } } const publish = new Publish(npm) @@ -331,9 +327,9 @@ t.test('can publish a tarball', t => { t.test('should check auth for default registry', t => { t.plan(2) const Publish = t.mock('../../lib/publish.js') - const npm = mockNpm({ config }) + const npm = mockNpm() npm.config.getCredentialsByURI = (uri) => { - t.same(uri, defaults.registry, 'gets credentials for expected registry') + t.same(uri, npm.config.get('registry'), 'gets credentials for expected registry') return {} } const publish = new Publish(npm) @@ -352,7 +348,6 @@ t.test('should check auth for configured registry', t => { const registry = 'https://some.registry' const Publish = t.mock('../../lib/publish.js') const npm = mockNpm({ - config, flatOptions: { registry }, }) npm.config.getCredentialsByURI = (uri) => { @@ -382,7 +377,6 @@ t.test('should check auth for scope specific registry', t => { const Publish = t.mock('../../lib/publish.js') const npm = mockNpm({ - config, flatOptions: { '@npm:registry': registry }, }) npm.config.getCredentialsByURI = (uri) => { @@ -419,7 +413,6 @@ t.test('should use auth for scope specific registry', t => { }, }) const npm = mockNpm({ - config, flatOptions: { '@npm:registry': registry }, }) npm.config.getCredentialsByURI = (uri) => { @@ -457,9 +450,7 @@ t.test('read registry only from publishConfig', t => { }, }, }) - const npm = mockNpm({ - config, - }) + const npm = mockNpm() npm.config.getCredentialsByURI = (uri) => { t.same(uri, registry, 'gets credentials for expected registry') return { token: 'some.registry.token' } @@ -525,3 +516,104 @@ t.test('able to publish after if encountered multiple configs', t => { t.end() }) }) + +t.test('workspaces', (t) => { + const testDir = t.testdir({ + 'package.json': JSON.stringify({ + name: 'my-cool-pkg', + version: '1.0.0', + workspaces: ['workspace-a', 'workspace-b', 'workspace-c'], + }, null, 2), + 'workspace-a': { + 'package.json': JSON.stringify({ + name: 'workspace-a', + version: '1.2.3-a', + repository: 'http://repo.workspace-a/', + }), + }, + 'workspace-b': { + 'package.json': JSON.stringify({ + name: 'workspace-b', + version: '1.2.3-n', + repository: 'https://github.com/npm/workspace-b', + }), + }, + 'workspace-c': JSON.stringify({ + 'package.json': { + name: 'workspace-n', + version: '1.2.3-n', + }, + }), + }) + + const publishes = [] + const outputs = [] + t.beforeEach(() => { + npm.config.set('json', false) + outputs.length = 0 + publishes.length = 0 + }) + const Publish = t.mock('../../lib/publish.js', { + '../../lib/utils/tar.js': { + getContents: (manifest) => ({ + id: manifest._id, + }), + logTar: () => {}, + }, + libnpmpublish: { + publish: (manifest, tarballData, opts) => { + publishes.push(manifest) + }, + }, + }) + const npm = mockNpm({ + output: (o) => { + outputs.push(o) + }, + }) + npm.localPrefix = testDir + npm.config.getCredentialsByURI = (uri) => { + return { token: 'some.registry.token' } + } + const publish = new Publish(npm) + + t.test('all workspaces', (t) => { + log.level = 'info' + publish.execWorkspaces([], [], (err) => { + t.notOk(err) + t.matchSnapshot(publishes, 'should publish all workspaces') + t.matchSnapshot(outputs, 'should output all publishes') + t.end() + }) + }) + + t.test('one workspace', t => { + log.level = 'info' + publish.execWorkspaces([], ['workspace-a'], (err) => { + t.notOk(err) + t.matchSnapshot(publishes, 'should publish given workspace') + t.matchSnapshot(outputs, 'should output one publish') + t.end() + }) + }) + + t.test('invalid workspace', t => { + publish.execWorkspaces([], ['workspace-x'], (err) => { + t.match(err, /No workspaces found/) + t.match(err, /workspace-x/) + t.end() + }) + }) + + t.test('json', t => { + log.level = 'info' + npm.config.set('json', true) + publish.execWorkspaces([], [], (err) => { + t.notOk(err) + t.matchSnapshot(publishes, 'should publish all workspaces') + t.matchSnapshot(outputs, 'should output all publishes as json') + t.end() + }) + }) + t.end() +}) From 370b36a36ca226840761e4214cbccaf2a1a90e3c Mon Sep 17 00:00:00 2001 From: Ruy Adorno Date: Wed, 12 May 2021 18:21:14 -0400 Subject: [PATCH 5/7] feat: add fund workspaces Add workspaces support to `npm fund` - Add lib/workspaces/arborist-cmd.js base class - Add ability to filter fund results to a specific set of workspaces - Added tests and docs Fixes: https://github.com/npm/statusboard/issues/301 PR-URL: https://github.com/npm/cli/pull/3241 Credit: @ruyadorno Close: #3241 Reviewed-by: @isaacs --- docs/content/commands/npm-fund.md | 56 +++++++++ lib/fund.js | 16 ++- lib/workspaces/arborist-cmd.js | 24 ++++ tap-snapshots/test/lib/fund.js.test.cjs | 20 ++++ .../test/lib/utils/npm-usage.js.test.cjs | 1 + test/lib/fund.js | 86 ++++++++++++++ test/lib/workspaces/arborist-cmd.js | 109 ++++++++++++++++++ 7 files changed, 307 insertions(+), 5 deletions(-) create mode 100644 lib/workspaces/arborist-cmd.js create mode 100644 test/lib/workspaces/arborist-cmd.js diff --git a/docs/content/commands/npm-fund.md b/docs/content/commands/npm-fund.md index aa1b26b9a8971..45c5dfaac2afc 100644 --- a/docs/content/commands/npm-fund.md +++ b/docs/content/commands/npm-fund.md @@ -8,6 +8,7 @@ description: Retrieve funding information ```bash npm fund [] +npm fund [-w ] ``` ### Description @@ -24,6 +25,43 @@ The list will avoid duplicated entries and will stack all packages that share the same url as a single entry. Thus, the list does not have the same shape of the output from `npm ls`. +#### Example + +### Workspaces support + +It's possible to filter the results to only include a single workspace and its +dependencies using the `workspace` config option. + +#### Example: + +Here's an example running `npm fund` in a project with a configured +workspace `a`: + +```bash +$ npm fund +test-workspaces-fund@1.0.0 ++-- https://example.com/a +| | `-- a@1.0.0 +| `-- https://example.com/maintainer +| `-- foo@1.0.0 ++-- https://example.com/npmcli-funding +| `-- @npmcli/test-funding +`-- https://example.com/org + `-- bar@2.0.0 +``` + +And here is an example of the expected result when filtering only by +a specific workspace `a` in the same project: + +```bash +$ npm fund -w a +test-workspaces-fund@1.0.0 +`-- https://example.com/a + | `-- a@1.0.0 + `-- https://example.com/maintainer + `-- foo@2.0.0 +``` + ### Configuration #### browser @@ -48,6 +86,23 @@ Show information in JSON format. Whether to represent the tree structure using unicode characters. Set it to `false` in order to use all-ansi output. +#### `workspace` + +* Default: +* Type: String (can be set multiple times) + +Enable running a command in the context of the configured workspaces of the +current project while filtering by running only the workspaces defined by +this configuration option. + +Valid values for the `workspace` config are either: +* Workspace names +* Path to a workspace directory +* Path to a parent workspace directory (will result to selecting all of the +nested workspaces) + +This value is not exported to the environment for child processes. + #### which * Type: Number @@ -61,3 +116,4 @@ If there are multiple funding sources, which 1-indexed source URL to open. * [npm docs](/commands/npm-docs) * [npm ls](/commands/npm-ls) * [npm config](/commands/npm-config) +* [npm workspaces](/using-npm/workspaces) diff --git a/lib/fund.js b/lib/fund.js index 25d3462f63869..55d2f65dc4b55 100644 --- a/lib/fund.js +++ b/lib/fund.js @@ -13,15 +13,14 @@ const { const completion = require('./utils/completion/installed-deep.js') const openUrl = require('./utils/open-url.js') +const ArboristWorkspaceCmd = require('./workspaces/arborist-cmd.js') const getPrintableName = ({ name, version }) => { const printableVersion = version ? `@${version}` : '' return `${name}${printableVersion}` } -const BaseCommand = require('./base-command.js') - -class Fund extends BaseCommand { +class Fund extends ArboristWorkspaceCmd { /* istanbul ignore next - see test/lib/load-all-commands.js */ static get description () { return 'Retrieve funding information' @@ -38,6 +37,7 @@ class Fund extends BaseCommand { 'json', 'browser', 'unicode', + 'workspace', 'which', ] } @@ -92,10 +92,16 @@ class Fund extends BaseCommand { return } + const fundingInfo = getFundingInfo(tree, { + ...this.flatOptions, + log: this.npm.log, + workspaces: this.workspaces, + }) + if (this.npm.config.get('json')) - this.npm.output(this.printJSON(getFundingInfo(tree))) + this.npm.output(this.printJSON(fundingInfo)) else - this.npm.output(this.printHuman(getFundingInfo(tree))) + this.npm.output(this.printHuman(fundingInfo)) } printJSON (fundingInfo) { diff --git a/lib/workspaces/arborist-cmd.js b/lib/workspaces/arborist-cmd.js new file mode 100644 index 0000000000000..f08843bd9ea5a --- /dev/null +++ b/lib/workspaces/arborist-cmd.js @@ -0,0 +1,24 @@ +// This is the base for all commands whose execWorkspaces just gets +// a list of workspace names and passes it on to new Arborist() to +// be able to run a filtered Arborist.reify() at some point. + +const BaseCommand = require('../base-command.js') +const getWorkspaces = require('../workspaces/get-workspaces.js') +class ArboristCmd extends BaseCommand { + /* istanbul ignore next - see test/lib/load-all-commands.js */ + static get params () { + return [ + 'workspace', + ] + } + + execWorkspaces (args, filters, cb) { + getWorkspaces(filters, { path: this.npm.localPrefix }) + .then(workspaces => { + this.workspaces = [...workspaces.keys()] + this.exec(args, cb) + }) + } +} + +module.exports = ArboristCmd diff --git a/tap-snapshots/test/lib/fund.js.test.cjs b/tap-snapshots/test/lib/fund.js.test.cjs index 7ad86ebeea7e9..c078beb7d9866 100644 --- a/tap-snapshots/test/lib/fund.js.test.cjs +++ b/tap-snapshots/test/lib/fund.js.test.cjs @@ -92,3 +92,23 @@ test-multiple-funding-sources@1.0.0 ` + +exports[`test/lib/fund.js TAP workspaces filter funding info by a specific workspace > should display only filtered workspace name and its deps 1`] = ` +workspaces-support@1.0.0 +\`-- https://example.com/a + | \`-- a@1.0.0 + \`-- http://example.com/c + \`-- c@1.0.0 + + +` + +exports[`test/lib/fund.js TAP workspaces filter funding info by a specific workspace > should display only filtered workspace path and its deps 1`] = ` +workspaces-support@1.0.0 +\`-- https://example.com/a + | \`-- a@1.0.0 + \`-- http://example.com/c + \`-- c@1.0.0 + + +` diff --git a/tap-snapshots/test/lib/utils/npm-usage.js.test.cjs b/tap-snapshots/test/lib/utils/npm-usage.js.test.cjs index 9bcba775fd85b..84ffc44e33300 100644 --- a/tap-snapshots/test/lib/utils/npm-usage.js.test.cjs +++ b/tap-snapshots/test/lib/utils/npm-usage.js.test.cjs @@ -422,6 +422,7 @@ All commands: Options: [--json] [--browser|--browser ] [--unicode] + [-w|--workspace [-w|--workspace ...]] [--which ] Run "npm help fund" for more info diff --git a/test/lib/fund.js b/test/lib/fund.js index 41754d51f3589..65778fca50bd7 100644 --- a/test/lib/fund.js +++ b/test/lib/fund.js @@ -839,3 +839,89 @@ t.test('sub dep with fund info and a parent with no funding info', t => { t.end() }) }) + +t.test('workspaces', t => { + t.test('filter funding info by a specific workspace', async t => { + npm.localPrefix = npm.prefix = t.testdir({ + 'package.json': JSON.stringify({ + name: 'workspaces-support', + version: '1.0.0', + workspaces: ['packages/*'], + dependencies: { + d: '^1.0.0', + }, + }), + node_modules: { + a: t.fixture('symlink', '../packages/a'), + b: t.fixture('symlink', '../packages/b'), + c: { + 'package.json': JSON.stringify({ + name: 'c', + version: '1.0.0', + funding: [ + 'http://example.com/c', + 'http://example.com/c-other', + ], + }), + }, + d: { + 'package.json': JSON.stringify({ + name: 'd', + version: '1.0.0', + funding: 'http://example.com/d', + }), + }, + }, + packages: { + a: { + 'package.json': JSON.stringify({ + name: 'a', + version: '1.0.0', + funding: 'https://example.com/a', + dependencies: { + c: '^1.0.0', + }, + }), + }, + b: { + 'package.json': JSON.stringify({ + name: 'b', + version: '1.0.0', + funding: 'http://example.com/b', + dependencies: { + d: '^1.0.0', + }, + }), + }, + }, + }) + + await new Promise((res, rej) => { + fund.execWorkspaces([], ['a'], (err) => { + if (err) + rej(err) + + t.matchSnapshot(result, + 'should display only filtered workspace name and its deps') + + result = '' + res() + }) + }) + + await new Promise((res, rej) => { + fund.execWorkspaces([], ['./packages/a'], (err) => { + if (err) + rej(err) + + t.matchSnapshot(result, + 'should display only filtered workspace path and its deps') + + result = '' + res() + }) + }) + }) + + t.end() +}) diff --git a/test/lib/workspaces/arborist-cmd.js b/test/lib/workspaces/arborist-cmd.js new file mode 100644 index 0000000000000..cceeb68dbd42a --- /dev/null +++ b/test/lib/workspaces/arborist-cmd.js @@ -0,0 +1,109 @@ +const { resolve } = require('path') +const t = require('tap') +const ArboristCmd = require('../../../lib/workspaces/arborist-cmd.js') + +t.test('arborist-cmd', async t => { + const path = t.testdir({ + 'package.json': JSON.stringify({ + name: 'simple-workspaces-list', + version: '1.1.1', + workspaces: [ + 'a', + 'b', + 'group/*', + ], + }), + node_modules: { + abbrev: { + 'package.json': JSON.stringify({ name: 'abbrev', version: '1.1.1' }), + }, + a: t.fixture('symlink', '../a'), + b: t.fixture('symlink', '../b'), + }, + a: { + 'package.json': JSON.stringify({ name: 'a', version: '1.0.0' }), + }, + b: { + 'package.json': JSON.stringify({ name: 'b', version: '1.0.0' }), + }, + group: { + c: { + 'package.json': JSON.stringify({ + name: 'c', + version: '1.0.0', + dependencies: { + abbrev: '^1.1.1', + }, + }), + }, + d: { + 'package.json': JSON.stringify({ name: 'd', version: '1.0.0' }), + }, + }, + }) + + class TestCmd extends ArboristCmd {} + + const cmd = new TestCmd() + cmd.npm = { localPrefix: path } + + // check filtering for a single workspace name + cmd.exec = function (args, cb) { + t.same(this.workspaces, ['a'], 'should set array with single ws name') + t.same(args, ['foo'], 'should get received args') + cb() + } + await new Promise(res => { + cmd.execWorkspaces(['foo'], ['a'], res) + }) + + // check filtering single workspace by path + cmd.exec = function (args, cb) { + t.same(this.workspaces, ['a'], + 'should set array with single ws name from path') + cb() + } + await new Promise(res => { + cmd.execWorkspaces([], ['./a'], res) + }) + + // check filtering single workspace by full path + cmd.exec = function (args, cb) { + t.same(this.workspaces, ['a'], + 'should set array with single ws name from full path') + cb() + } + await new Promise(res => { + cmd.execWorkspaces([], [resolve(path, './a')], res) + }) + + // filtering multiple workspaces by name + cmd.exec = function (args, cb) { + t.same(this.workspaces, ['a', 'c'], + 'should set array with multiple listed ws names') + cb() + } + await new Promise(res => { + cmd.execWorkspaces([], ['a', 'c'], res) + }) + + // filtering multiple workspaces by path names + cmd.exec = function (args, cb) { + t.same(this.workspaces, ['a', 'c'], + 'should set array with multiple ws names from paths') + cb() + } + await new Promise(res => { + cmd.execWorkspaces([], ['./a', 'group/c'], res) + }) + + // filtering multiple workspaces by parent path name + cmd.exec = function (args, cb) { + t.same(this.workspaces, ['c', 'd'], + 'should set array with multiple ws names from a parent folder name') + cb() + } + await new Promise(res => { + cmd.execWorkspaces([], ['./group'], res) + }) +}) From 00ffe8a9e879ae7f52c066e4f4e1eed48cdbc0b4 Mon Sep 17 00:00:00 2001 From: Ruy Adorno Date: Thu, 13 May 2021 15:49:51 -0400 Subject: [PATCH 6/7] docs: changelog for v7.13.0 --- CHANGELOG.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bed0f87c2b80c..1c36c70875f72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,21 @@ +## v7.13.0 (2021-05-13) + +* [`076420c14`](https://github.com/npm/cli/commit/076420c149d097056f687e44e21744b743b86e4e) + [#3231](https://github.com/npm/cli/issues/3231) + feat(publish): add workspace support + ([@wraithgar](https://github.com/wraithgar)) +* [`370b36a36`](https://github.com/npm/cli/commit/370b36a36ca226840761e4214cbccaf2a1a90e3c) + [#3241](https://github.com/npm/cli/issues/3241) + feat(fund): add workspaces support + ([@ruyadorno](https://github.com/ruyadorno)) + +### DEPENDENCIES + +* [`0c18e4f77`](https://github.com/npm/cli/commit/0c18e4f774562fa054fedf323bea25805ebf39b3) + `@npmcli/arborist@2.5.0` +* [`b551c6811`](https://github.com/npm/cli/commit/b551c6811251dbc901f47fea3c137f93e205a9e4) + `libnpmfund@1.1.0` + ## v7.12.1 (2021-05-10) ### BUG FIXES From 3bd758387f0f4668ae1eb2bfe420051da2a74c8e Mon Sep 17 00:00:00 2001 From: Ruy Adorno Date: Thu, 13 May 2021 15:50:25 -0400 Subject: [PATCH 7/7] 7.13.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7ddad043e0835..a29f2471aacc8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "npm", - "version": "7.12.1", + "version": "7.13.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "npm", - "version": "7.12.1", + "version": "7.13.0", "bundleDependencies": [ "@npmcli/arborist", "@npmcli/ci-detect", diff --git a/package.json b/package.json index 9fe4338efce75..f939dacadf23f 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "version": "7.12.1", + "version": "7.13.0", "name": "npm", "description": "a package manager for JavaScript", "keywords": [