diff --git a/AUTHORS b/AUTHORS index 0c15af1db20cc..c204352764831 100644 --- a/AUTHORS +++ b/AUTHORS @@ -793,3 +793,4 @@ AkiJoey austincho Nathan Fritz tripu <1016538+tripu@users.noreply.github.com> +Matsuuu diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d9de01e553d7..c591cd2ac559b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,30 @@ +## v7.23.0 (2021-09-09) + +### FEATURES + +* [`6c12500ae`](https://github.com/npm/cli/commit/6c12500ae14a6f8b78e3ab091ee6cc8e2ea9fd23) + [#3731](https://github.com/npm/cli/issues/3731) + feat(install): very strict global npm engines + ([@wraithgar](https://github.com/wraithgar)) + +### BUG FIXES + +* [`1ad093824`](https://github.com/npm/cli/commit/1ad0938243110d983284e8763da41a57b561563d) + [#3732](https://github.com/npm/cli/issues/3732) + fix(error-message): clean urls from 404 error + ([@wraithgar](https://github.com/wraithgar)) + +### DOCUMENTATION + +* [`64f7d1a55`](https://github.com/npm/cli/commit/64f7d1a55db99b1aaf8fb59557b3dedcdcd954a0) + [#3727](https://github.com/npm/cli/issues/3727) + docs(contributing): add note on changes to tooling + ([@darcyclarke](https://github.com/darcyclarke)) +* [`eda9162f2`](https://github.com/npm/cli/commit/eda9162f2db19b512d3af6b0d43201d54045c13a) + [#3715](https://github.com/npm/cli/issues/3715) + Add --if-present flag documentation to workspaces + ([@Matsuuu](https://github.com/Matsuuu)) + ## v7.22.0 (2021-09-02) ### BUG FIXES diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ec1c513864c1b..5ee9b45608eab 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,6 +4,10 @@ All interactions in the **npm** organization on GitHub are considered to be covered by our standard [Code of Conduct](https://docs.npmjs.com/policies/conduct). +## Reporting Bugs + +When submitting a new bug report, please first [search](https://github.com/npm/cli/issues) for an existing or similar report & then use one of our existing [issue templates](https://github.com/npm/cli/issues/new/choose) if you believe you've come across a unique problem. Duplicate issues, or issues that don't use one of our templates may get closed without a response. + ## Development **1. Clone this repository...** @@ -33,7 +37,7 @@ $ npm run test ## Test Coverage -We expect that every new feature or bug fix comes with corresponding tests that validate the solutions. We strive to have as close to, if not exactly, 100% code coverage. +We use [`tap`](https://node-tap.org/) for testing & expect that every new feature or bug fix comes with corresponding tests that validate the solutions. We strive to have as close to, if not exactly, 100% code coverage. **You can find out what the current test coverage percentage is by running...** @@ -51,10 +55,12 @@ We've set up an automated [benchmark](https://github.com/npm/benchmarks) integra You can learn more about this tool, including how to run & configure it manually, [here](https://github.com/npm/benchmarks) -## Dependency Updates +## What _not_ to contribute? + +### Dependencies It should be noted that our team does not accept third-party dependency updates/PRs. We have a [release process](https://github.com/npm/cli/wiki/Release-Process) that includes checks to ensure dependencies are staying up-to-date & will ship security patches for CVEs as they occur. If you submit a PR trying to update our dependencies we will close it with or without a reference to these contribution guidelines. -## Reporting Bugs +### Tools/Automation -When submitting a new bug report, please first [search](https://github.com/npm/cli/issues) for an existing or similar report & then use one of our existing [issue templates](https://github.com/npm/cli/issues/new/choose) if you believe you've come across a unique problem. Duplicate issues, or issues that don't use one of our templates may get closed without a response. +Our core team is responsible for the maintaince of the tooling/automation in this project & we ask collaborators to kindle not make changes to these when contributing (ex. `.github/*`, `.eslintrc.json`, `.licensee.json` etc.) diff --git a/docs/content/using-npm/workspaces.md b/docs/content/using-npm/workspaces.md index 7cc125b3c7a7c..ae834c0cc7e22 100644 --- a/docs/content/using-npm/workspaces.md +++ b/docs/content/using-npm/workspaces.md @@ -176,6 +176,16 @@ npm run test --workspaces Will run the `test` script in both `./packages/a` and `./packages/b`. +### Ignoring missing scripts + +It is not required for all of the workspaces to implement scripts run with the `npm run` command. + +By running the command with the `--if-present` flag, npm will ignore workspaces missing target script. + +``` +npm run test --workspaces --if-present +``` + ### See also * [npm install](/commands/npm-install) diff --git a/lib/install.js b/lib/install.js index 6611763978e61..1589ff589c38e 100644 --- a/lib/install.js +++ b/lib/install.js @@ -8,6 +8,8 @@ const log = require('npmlog') const { resolve, join } = require('path') const Arborist = require('@npmcli/arborist') const runScript = require('@npmcli/run-script') +const pacote = require('pacote') +const checks = require('npm-install-checks') const ArboristWorkspaceCmd = require('./workspaces/arborist-cmd.js') class Install extends ArboristWorkspaceCmd { @@ -126,6 +128,23 @@ class Install extends ArboristWorkspaceCmd { const ignoreScripts = this.npm.config.get('ignore-scripts') const isGlobalInstall = this.npm.config.get('global') const where = isGlobalInstall ? globalTop : this.npm.prefix + const forced = this.npm.config.get('force') + const isDev = this.npm.config.get('dev') + const scriptShell = this.npm.config.get('script-shell') || undefined + + // be very strict about engines when trying to update npm itself + const npmInstall = args.find(arg => arg.startsWith('npm@') || arg === 'npm') + if (isGlobalInstall && npmInstall) { + const npmManifest = await pacote.manifest(npmInstall) + try { + checks.checkEngine(npmManifest, npmManifest.version, process.version) + } catch (e) { + if (forced) + this.npm.log.warn('install', `Forcing global npm install with incompatible version ${npmManifest.version} into node ${process.version}`) + else + throw e + } + } // don't try to install the prefix into itself args = args.filter(a => resolve(a) !== this.npm.prefix) @@ -135,7 +154,7 @@ class Install extends ArboristWorkspaceCmd { args = ['.'] // TODO: Add warnings for other deprecated flags? or remove this one? - if (this.npm.config.get('dev')) + if (isDev) log.warn('install', 'Usage of the `--dev` option is deprecated. Use `--include=dev` instead.') const opts = { @@ -150,7 +169,6 @@ class Install extends ArboristWorkspaceCmd { await arb.reify(opts) if (!args.length && !isGlobalInstall && !ignoreScripts) { - const scriptShell = this.npm.config.get('script-shell') || undefined const scripts = [ 'preinstall', 'install', diff --git a/lib/utils/error-message.js b/lib/utils/error-message.js index da97195dd04f0..6e12bcb918eef 100644 --- a/lib/utils/error-message.js +++ b/lib/utils/error-message.js @@ -181,7 +181,7 @@ module.exports = (er, npm) => { const pkg = er.pkgid.replace(/(?!^)@.*$/, '') detail.push(['404', '']) - detail.push(['404', '', "'" + er.pkgid + "' is not in the npm registry."]) + detail.push(['404', '', `'${replaceInfo(er.pkgid)}' is not in this registry.`]) const valResult = nameValidator(pkg) diff --git a/package-lock.json b/package-lock.json index 9abe13a85d668..ed4ce37cde425 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "npm", - "version": "7.22.0", + "version": "7.23.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "npm", - "version": "7.22.0", + "version": "7.23.0", "bundleDependencies": [ "@npmcli/arborist", "@npmcli/ci-detect", @@ -127,6 +127,7 @@ "node-gyp": "^7.1.2", "nopt": "^5.0.0", "npm-audit-report": "^2.1.5", + "npm-install-checks": "^4.0.0", "npm-package-arg": "^8.1.5", "npm-pick-manifest": "^6.1.1", "npm-profile": "^5.0.3", diff --git a/package.json b/package.json index e0363a49bf52e..3459c81ccf190 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "version": "7.22.0", + "version": "7.23.0", "name": "npm", "description": "a package manager for JavaScript", "workspaces": [ @@ -97,6 +97,7 @@ "node-gyp": "^7.1.2", "nopt": "^5.0.0", "npm-audit-report": "^2.1.5", + "npm-install-checks": "^4.0.0", "npm-package-arg": "^8.1.5", "npm-pick-manifest": "^6.1.1", "npm-profile": "^5.0.3", diff --git a/tap-snapshots/test/lib/utils/error-message.js.test.cjs b/tap-snapshots/test/lib/utils/error-message.js.test.cjs index e8f817cd15f00..1f73361c48589 100644 --- a/tap-snapshots/test/lib/utils/error-message.js.test.cjs +++ b/tap-snapshots/test/lib/utils/error-message.js.test.cjs @@ -5,6 +5,48 @@ * Make sure to inspect the output below. Do not ignore changes! */ 'use strict' +exports[`test/lib/utils/error-message.js TAP 404 cleans sensitive info from package id > must match snapshot 1`] = ` +Object { + "detail": Array [ + Array [ + "404", + "", + ], + Array [ + "404", + "", + "'http://evil:***@npmjs.org/not-found' is not in this registry.", + ], + Array [ + "404", + "This package name is not valid, because", + "", + ], + Array [ + "404", + " 1. name can only contain URL-friendly characters", + ], + Array [ + "404", + String( + + Note that you can also install from a + ), + ], + Array [ + "404", + "tarball, folder, http url, or git url.", + ], + ], + "summary": Array [ + Array [ + "404", + "not found", + ], + ], +} +` + exports[`test/lib/utils/error-message.js TAP 404 name with error > must match snapshot 1`] = ` Object { "detail": Array [ @@ -15,7 +57,7 @@ Object { Array [ "404", "", - "'node_modules' is not in the npm registry.", + "'node_modules' is not in this registry.", ], Array [ "404", @@ -57,7 +99,7 @@ Object { Array [ "404", "", - "'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' is not in the npm registry.", + "'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' is not in this registry.", ], Array [ "404", @@ -111,7 +153,7 @@ Object { Array [ "404", "", - "'yolo' is not in the npm registry.", + "'yolo' is not in this registry.", ], Array [ "404", diff --git a/test/lib/install.js b/test/lib/install.js index 6412b34c16f25..2cbee02e67b28 100644 --- a/test/lib/install.js +++ b/test/lib/install.js @@ -126,6 +126,146 @@ t.test('should install globally using Arborist', (t) => { }) }) +t.test('npm i -g npm engines check success', (t) => { + const Install = t.mock('../../lib/install.js', { + '../../lib/utils/reify-finish.js': async () => {}, + '@npmcli/arborist': function () { + this.reify = () => {} + }, + pacote: { + manifest: () => { + return { + version: '100.100.100', + engines: { + node: '>1', + }, + } + }, + }, + }) + const npm = mockNpm({ + globalDir: 'path/to/node_modules/', + config: { + global: true, + }, + }) + const install = new Install(npm) + install.exec(['npm'], er => { + if (er) + throw er + t.end() + }) +}) + +t.test('npm i -g npm engines check failure', (t) => { + const Install = t.mock('../../lib/install.js', { + pacote: { + manifest: () => { + return { + _id: 'npm@1.2.3', + version: '100.100.100', + engines: { + node: '>1000', + }, + } + }, + }, + }) + const npm = mockNpm({ + globalDir: 'path/to/node_modules/', + config: { + global: true, + }, + }) + const install = new Install(npm) + install.exec(['npm'], er => { + t.match(er, { + message: 'Unsupported engine', + pkgid: 'npm@1.2.3', + current: { + node: process.version, + npm: '100.100.100', + }, + required: { + node: '>1000', + }, + code: 'EBADENGINE', + }) + t.end() + }) +}) + +t.test('npm i -g npm engines check failure forced override', (t) => { + const Install = t.mock('../../lib/install.js', { + '../../lib/utils/reify-finish.js': async () => {}, + '@npmcli/arborist': function () { + this.reify = () => {} + }, + pacote: { + manifest: () => { + return { + _id: 'npm@1.2.3', + version: '100.100.100', + engines: { + node: '>1000', + }, + } + }, + }, + }) + const npm = mockNpm({ + globalDir: 'path/to/node_modules/', + config: { + force: true, + global: true, + }, + }) + const install = new Install(npm) + install.exec(['npm'], er => { + if (er) + throw er + t.end() + }) +}) + +t.test('npm i -g npm@version engines check failure', (t) => { + const Install = t.mock('../../lib/install.js', { + pacote: { + manifest: () => { + return { + _id: 'npm@1.2.3', + version: '100.100.100', + engines: { + node: '>1000', + }, + } + }, + }, + }) + const npm = mockNpm({ + globalDir: 'path/to/node_modules/', + config: { + global: true, + }, + }) + const install = new Install(npm) + install.exec(['npm@100'], er => { + t.match(er, { + message: 'Unsupported engine', + pkgid: 'npm@1.2.3', + current: { + node: process.version, + npm: '100.100.100', + }, + required: { + node: '>1000', + }, + code: 'EBADENGINE', + }) + t.end() + }) +}) + t.test('completion to folder', async t => { const Install = t.mock('../../lib/install.js', { '../../lib/utils/reify-finish.js': async () => {}, diff --git a/test/lib/utils/error-message.js b/test/lib/utils/error-message.js index 07328d588759b..d1c67a95137c4 100644 --- a/test/lib/utils/error-message.js +++ b/test/lib/utils/error-message.js @@ -423,6 +423,14 @@ t.test('404', t => { t.matchSnapshot(errorMessage(er, npm)) t.end() }) + t.test('cleans sensitive info from package id', t => { + const er = Object.assign(new Error('404 not found'), { + pkgid: 'http://evil:password@npmjs.org/not-found', + code: 'E404', + }) + t.matchSnapshot(errorMessage(er, npm)) + t.end() + }) t.end() })