Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/npm/cli.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRuy Adorno <ruyadorno@hotmail.com>2021-04-07 18:03:41 +0300
committerRuy Adorno <ruyadorno@hotmail.com>2021-04-22 22:06:29 +0300
commit4c1f16d2c29a7a56c19b97f2820e6305a6075083 (patch)
tree460fd0dfa65b168d3f35bac5e0ad363b14684a1d
parent2aecec591df6866e27d0b17dc49cef8f7d738d77 (diff)
feat: add init workspaces
Add workspaces support to `npm init` - Fixes `npm exec` respecting `script-shell` option value - Refactored `lib/exec.js` into `libnpmexec` - Updates init-package-json@2.0.3 - Added ability to create a new workspace using the -w config PR-URL: https://github.com/npm/cli/pull/3095 Credit: @ruyadorno Close: #3095 Reviewed-by: @wraithgar
-rw-r--r--docs/content/commands/npm-exec.md1
-rw-r--r--docs/content/commands/npm-init.md120
-rw-r--r--docs/content/commands/npm-run-script.md1
-rw-r--r--docs/content/using-npm/config.md3
-rw-r--r--lib/exec.js302
-rw-r--r--lib/exec/get-workspace-location-msg.js25
-rw-r--r--lib/init.js190
-rw-r--r--lib/utils/config/definitions.js3
-rw-r--r--node_modules/init-package-json/default-input.js6
-rw-r--r--node_modules/init-package-json/package.json12
-rw-r--r--node_modules/libnpmexec/CHANGELOG.md8
-rw-r--r--node_modules/libnpmexec/LICENSE15
-rw-r--r--node_modules/libnpmexec/README.md48
-rw-r--r--node_modules/libnpmexec/lib/cache-install-dir.js19
-rw-r--r--node_modules/libnpmexec/lib/get-bin-from-manifest.js20
-rw-r--r--node_modules/libnpmexec/lib/index.js185
-rw-r--r--node_modules/libnpmexec/lib/manifest-missing.js17
-rw-r--r--node_modules/libnpmexec/lib/no-tty.js1
-rw-r--r--node_modules/libnpmexec/lib/run-script.js86
-rw-r--r--node_modules/libnpmexec/package.json63
-rw-r--r--node_modules/proc-log/LICENSE15
-rw-r--r--node_modules/proc-log/README.md33
-rw-r--r--node_modules/proc-log/index.js22
-rw-r--r--node_modules/proc-log/package.json28
-rw-r--r--package-lock.json80
-rw-r--r--package.json5
-rw-r--r--tap-snapshots/test/lib/init.js.test.cjs15
-rw-r--r--tap-snapshots/test/lib/utils/config/describe-all.js.test.cjs3
-rw-r--r--tap-snapshots/test/lib/utils/npm-usage.js.test.cjs4
-rw-r--r--test/lib/exec.js40
-rw-r--r--test/lib/init.js481
31 files changed, 1405 insertions, 446 deletions
diff --git a/docs/content/commands/npm-exec.md b/docs/content/commands/npm-exec.md
index 6666d30e8..2364da32c 100644
--- a/docs/content/commands/npm-exec.md
+++ b/docs/content/commands/npm-exec.md
@@ -291,3 +291,4 @@ project.
* [npm restart](/commands/npm-restart)
* [npm stop](/commands/npm-stop)
* [npm config](/commands/npm-config)
+* [npm workspaces](/using-npm/workspaces)
diff --git a/docs/content/commands/npm-init.md b/docs/content/commands/npm-init.md
index 4b0b8c4c4..8288034a3 100644
--- a/docs/content/commands/npm-init.md
+++ b/docs/content/commands/npm-init.md
@@ -8,8 +8,9 @@ description: Create a package.json file
```bash
npm init [--force|-f|--yes|-y|--scope]
-npm init <@scope> (same as `npx <@scope>/create`)
-npm init [<@scope>/]<name> (same as `npx [<@scope>/]create-<name>`)
+npm init <@scope> (same as `npm exec <@scope>/create`)
+npm init [<@scope>/]<name> (same as `npm exec [<@scope>/]create-<name>`)
+npm init [-w <dir>] [args...]
```
### Description
@@ -18,19 +19,16 @@ npm init [<@scope>/]<name> (same as `npx [<@scope>/]create-<name>`)
package.
`initializer` in this case is an npm package named `create-<initializer>`,
-which will be installed by [`npx`](https://npm.im/npx), and then have its
+which will be installed by [`npm-exec`](/commands/npm-exec), and then have its
main bin executed -- presumably creating or updating `package.json` and
running any other initialization-related operations.
-The init command is transformed to a corresponding `npx` operation as
+The init command is transformed to a corresponding `npm exec` operation as
follows:
-* `npm init foo` -> `npx create-foo`
-* `npm init @usr/foo` -> `npx @usr/create-foo`
-* `npm init @usr` -> `npx @usr/create`
-
-Any additional options will be passed directly to the command, so `npm init
-foo -- --hello` will map to `npx create-foo --hello`.
+* `npm init foo` -> `npm exec create-foo`
+* `npm init @usr/foo` -> `npm exec @usr/create-foo`
+* `npm init @usr` -> `npm exec @usr/create`
If the initializer is omitted (by just calling `npm init`), init will fall
back to legacy init behavior. It will ask you a bunch of questions, and
@@ -40,6 +38,18 @@ strictly additive, so it will keep any fields and values that were already
set. You can also use `-y`/`--yes` to skip the questionnaire altogether. If
you pass `--scope`, it will create a scoped package.
+#### Forwarding additional options
+
+Any additional options will be passed directly to the command, so `npm init
+foo -- --hello` will map to `npm exec -- create-foo --hello`.
+
+To better illustrate how options are forwarded, here's a more evolved
+example showing options passed to both the **npm cli** and a create package,
+both following commands are equivalent:
+
+- `npm init foo -y --registry=<url> -- --hello -a`
+- `npm exec -y --registry=<url> -- create-foo --hello -a`
+
### Examples
Create a new React-based project using
@@ -71,6 +81,68 @@ Generate it without having it ask any questions:
$ npm init -y
```
+### Workspaces support
+
+It's possible to create a new workspace within your project by using the
+`workspace` config option. When using `npm init -w <dir>` the cli will
+create the folders and boilerplate expected while also adding a reference
+to your project `package.json` `"workspaces": []` property in order to make
+sure that new generated **workspace** is properly set up as such.
+
+Given a project with no workspaces, e.g:
+
+```
+.
++-- package.json
+```
+
+You may generate a new workspace using the legacy init:
+
+```bash
+$ npm init -w packages/a
+```
+
+That will generate a new folder and `package.json` file, while also updating
+your top-level `package.json` to add the reference to this new workspace:
+
+```
+.
++-- package.json
+`-- packages
+ `-- a
+ `-- package.json
+```
+
+The workspaces init also supports the `npm init <initializer> -w <dir>`
+syntax, following the same set of rules explained earlier in the initial
+**Description** section of this page. Similar to the previous example of
+creating a new React-based project using
+[`create-react-app`](https://npm.im/create-react-app), the following syntax
+will make sure to create the new react app as a nested **workspace** within your
+project and configure your `package.json` to recognize it as such:
+
+```bash
+npm init -w packages/my-react-app react-app .
+```
+
+This will make sure to generate your react app as expected, one important
+consideration to have in mind is that `npm exec` is going to be run in the
+context of the newly created folder for that workspace, and that's the reason
+why in this example the initializer uses the initializer name followed with a
+dot to represent the current directory in that context, e.g: `react-app .`:
+
+```
+.
++-- package.json
+`-- packages
+ +-- a
+ | `-- package.json
+ `-- my-react-app
+ +-- README
+ +-- package.json
+ `-- ...
+```
+
### A note on caching
The npm cli utilizes its internal package cache when using the package
@@ -93,6 +165,33 @@ requested from the server. To force full offline mode, use `offline`.
Forces full offline mode. Any packages not locally cached will result in
an error.
+#### workspace
+
+* Alias: `-w`
+* Type: Array
+* Default: `[]`
+
+Enable running `npm init` in the context of workspaces, creating any missing
+folders, generating files and adding/updating the `"workspaces"` property of
+the project `package.json`.
+
+the provided names or paths provided.
+
+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
+children workspaces)
+
+#### workspaces
+
+* Alias: `-ws`
+* Type: Boolean
+* Default: `false`
+
+Run `npm init` in the context of all configured workspaces for the
+current project.
+
### See Also
* [init-package-json module](http://npm.im/init-package-json)
@@ -100,3 +199,4 @@ an error.
* [npm version](/commands/npm-version)
* [npm scope](/using-npm/scope)
* [npm exec](/commands/npm-exec)
+* [npm workspaces](/using-npm/workspaces)
diff --git a/docs/content/commands/npm-run-script.md b/docs/content/commands/npm-run-script.md
index 028f8eb15..6786312e0 100644
--- a/docs/content/commands/npm-run-script.md
+++ b/docs/content/commands/npm-run-script.md
@@ -204,3 +204,4 @@ project.
* [npm restart](/commands/npm-restart)
* [npm stop](/commands/npm-stop)
* [npm config](/commands/npm-config)
+* [npm workspaces](/using-npm/workspaces)
diff --git a/docs/content/using-npm/config.md b/docs/content/using-npm/config.md
index b2e8baf01..1a70c29f9 100644
--- a/docs/content/using-npm/config.md
+++ b/docs/content/using-npm/config.md
@@ -1087,7 +1087,8 @@ installation of packages specified according to the pattern
* Default: '/bin/sh' on POSIX systems, 'cmd.exe' on Windows
* Type: null or String
-The shell to use for scripts run with the `npm run` command.
+The shell to use for scripts run with the `npm exec`, `npm run` and
+`npm init <pkg>` commands.
#### `searchexclude`
diff --git a/lib/exec.js b/lib/exec.js
index f8c76eeed..3da672f99 100644
--- a/lib/exec.js
+++ b/lib/exec.js
@@ -1,18 +1,6 @@
-const { promisify } = require('util')
-const read = promisify(require('read'))
-const chalk = require('chalk')
-const mkdirp = require('mkdirp-infer-owner')
-const readPackageJson = require('read-package-json-fast')
-const Arborist = require('@npmcli/arborist')
-const runScript = require('@npmcli/run-script')
-const { resolve, delimiter } = require('path')
-const ciDetect = require('@npmcli/ci-detect')
-const crypto = require('crypto')
-const pacote = require('pacote')
-const npa = require('npm-package-arg')
-const fileExists = require('./utils/file-exists.js')
-const PATH = require('./utils/path.js')
+const libexec = require('libnpmexec')
const BaseCommand = require('./base-command.js')
+const getLocationMsg = require('./exec/get-workspace-location-msg.js')
const getWorkspaces = require('./workspaces/get-workspaces.js')
// it's like this:
@@ -40,13 +28,6 @@ const getWorkspaces = require('./workspaces/get-workspaces.js')
// runScript({ pkg, event: 'npx', ... })
// process.env.npm_lifecycle_event = 'npx'
-const nocolor = {
- reset: s => s,
- bold: s => s,
- dim: s => s,
- green: s => s,
-}
-
class Exec extends BaseCommand {
/* istanbul ignore next - see test/lib/load-all-commands.js */
static get description () {
@@ -86,276 +67,50 @@ class Exec extends BaseCommand {
// When commands go async and we can dump the boilerplate exec methods this
// can be named correctly
async _exec (_args, { locationMsg, path, runPath }) {
+ const args = [..._args]
+ const cache = this.npm.config.get('cache')
const call = this.npm.config.get('call')
- const shell = this.npm.config.get('shell')
- // dereferenced because we manipulate it later
- const packages = [...this.npm.config.get('package')]
+ const color = this.npm.config.get('color')
+ const {
+ flatOptions,
+ localBin,
+ log,
+ globalBin,
+ output,
+ } = this.npm
+ const scriptShell = this.npm.config.get('script-shell') || undefined
+ const packages = this.npm.config.get('package')
+ const yes = this.npm.config.get('yes')
if (call && _args.length)
throw this.usage
- const args = [..._args]
- const pathArr = [...PATH]
-
- // nothing to maybe install, skip the arborist dance
- if (!call && !args.length && !packages.length) {
- return await this.run({
- args,
- call,
- locationMsg,
- shell,
- path,
- pathArr,
- runPath,
- })
- }
-
- const needPackageCommandSwap = args.length && !packages.length
- // if there's an argument and no package has been explicitly asked for
- // check the local and global bin paths for a binary named the same as
- // the argument and run it if it exists, otherwise fall through to
- // the behavior of treating the single argument as a package name
- if (needPackageCommandSwap) {
- let binExists = false
- if (await fileExists(`${this.npm.localBin}/${args[0]}`)) {
- pathArr.unshift(this.npm.localBin)
- binExists = true
- } else if (await fileExists(`${this.npm.globalBin}/${args[0]}`)) {
- pathArr.unshift(this.npm.globalBin)
- binExists = true
- }
-
- if (binExists) {
- return await this.run({
- args,
- call,
- locationMsg,
- path,
- pathArr,
- runPath,
- shell,
- })
- }
-
- packages.push(args[0])
- }
-
- // If we do `npm exec foo`, and have a `foo` locally, then we'll
- // always use that, so we don't really need to fetch the manifest.
- // So: run npa on each packages entry, and if it is a name with a
- // rawSpec==='', then try to readPackageJson at
- // node_modules/${name}/package.json, and only pacote fetch if
- // that fails.
- const manis = await Promise.all(packages.map(async p => {
- const spec = npa(p, path)
- if (spec.type === 'tag' && spec.rawSpec === '') {
- // fall through to the pacote.manifest() approach
- try {
- const pj = resolve(path, 'node_modules', spec.name)
- return await readPackageJson(pj)
- } catch (er) {}
- }
- // Force preferOnline to true so we are making sure to pull in the latest
- // This is especially useful if the user didn't give us a version, and
- // they expect to be running @latest
- return await pacote.manifest(p, {
- ...this.npm.flatOptions,
- preferOnline: true,
- })
- }))
-
- if (needPackageCommandSwap)
- args[0] = this.getBinFromManifest(manis[0])
-
- // figure out whether we need to install stuff, or if local is fine
- const localArb = new Arborist({
- ...this.npm.flatOptions,
- path,
- })
- const tree = await localArb.loadActual()
-
- // do we have all the packages in manifest list?
- const needInstall = manis.some(mani => this.manifestMissing(tree, mani))
-
- if (needInstall) {
- const installDir = this.cacheInstallDir(packages)
- await mkdirp(installDir)
- const arb = new Arborist({
- ...this.npm.flatOptions,
- log: this.npm.log,
- path: installDir,
- })
- const tree = await arb.loadActual()
-
- // at this point, we have to ensure that we get the exact same
- // version, because it's something that has only ever been installed
- // by npm exec in the cache install directory
- const add = manis.filter(mani => this.manifestMissing(tree, {
- ...mani,
- _from: `${mani.name}@${mani.version}`,
- }))
- .map(mani => mani._from)
- .sort((a, b) => a.localeCompare(b))
-
- // no need to install if already present
- if (add.length) {
- if (!this.npm.config.get('yes')) {
- // set -n to always say no
- if (this.npm.config.get('yes') === false)
- throw new Error('canceled')
-
- if (!process.stdin.isTTY || ciDetect()) {
- this.npm.log.warn('exec', `The following package${
- add.length === 1 ? ' was' : 's were'
- } not found and will be installed: ${
- add.map((pkg) => pkg.replace(/@$/, '')).join(', ')
- }`)
- } else {
- const addList = add.map(a => ` ${a.replace(/@$/, '')}`)
- .join('\n') + '\n'
- const prompt = `Need to install the following packages:\n${
- addList
- }Ok to proceed? `
- const confirm = await read({ prompt, default: 'y' })
- if (confirm.trim().toLowerCase().charAt(0) !== 'y')
- throw new Error('canceled')
- }
- }
- await arb.reify({
- ...this.npm.flatOptions,
- log: this.npm.log,
- add,
- })
- }
- pathArr.unshift(resolve(installDir, 'node_modules/.bin'))
- }
-
- return await this.run({
+ return libexec({
+ ...flatOptions,
args,
call,
+ cache,
+ color,
+ localBin,
locationMsg,
+ log,
+ globalBin,
+ output,
+ packages,
path,
- pathArr,
runPath,
- shell,
+ scriptShell,
+ yes,
})
}
- async run ({ args, call, locationMsg, path, pathArr, runPath, shell }) {
- // turn list of args into command string
- const script = call || args.shift() || shell
-
- // do the fakey runScript dance
- // still should work if no package.json in cwd
- const realPkg = await readPackageJson(`${path}/package.json`)
- .catch(() => ({}))
- const pkg = {
- ...realPkg,
- scripts: {
- ...(realPkg.scripts || {}),
- npx: script,
- },
- }
-
- this.npm.log.disableProgress()
- try {
- if (script === shell) {
- if (process.stdin.isTTY) {
- if (ciDetect())
- return this.npm.log.warn('exec', 'Interactive mode disabled in CI environment')
-
- const color = this.npm.config.get('color')
- const colorize = color ? chalk : nocolor
-
- locationMsg = locationMsg || ` at location:\n${colorize.dim(runPath)}`
-
- this.npm.output(`${
- colorize.reset('\nEntering npm script environment')
- }${
- colorize.reset(locationMsg)
- }${
- colorize.bold('\nType \'exit\' or ^D when finished\n')
- }`)
- }
- }
- return await runScript({
- ...this.npm.flatOptions,
- pkg,
- banner: false,
- // we always run in cwd, not --prefix
- path: runPath,
- stdioString: true,
- event: 'npx',
- args,
- env: {
- PATH: pathArr.join(delimiter),
- },
- stdio: 'inherit',
- })
- } finally {
- this.npm.log.enableProgress()
- }
- }
-
- manifestMissing (tree, mani) {
- // if the tree doesn't have a child by that name/version, return true
- // true means we need to install it
- const child = tree.children.get(mani.name)
- // if no child, we have to load it
- if (!child)
- return true
-
- // if no version/tag specified, allow whatever's there
- if (mani._from === `${mani.name}@`)
- return false
-
- // otherwise the version has to match what we WOULD get
- return child.version !== mani.version
- }
-
- getBinFromManifest (mani) {
- // if we have a bin matching (unscoped portion of) packagename, use that
- // otherwise if there's 1 bin or all bin value is the same (alias), use
- // that, otherwise fail
- const bin = mani.bin || {}
- if (new Set(Object.values(bin)).size === 1)
- return Object.keys(bin)[0]
-
- // XXX probably a util to parse this better?
- const name = mani.name.replace(/^@[^/]+\//, '')
- if (bin[name])
- return name
-
- // XXX need better error message
- throw Object.assign(new Error('could not determine executable to run'), {
- pkgid: mani._id,
- })
- }
-
- cacheInstallDir (packages) {
- // only packages not found in ${prefix}/node_modules
- return resolve(this.npm.config.get('cache'), '_npx', this.getHash(packages))
- }
-
- getHash (packages) {
- return crypto.createHash('sha512')
- .update(packages.sort((a, b) => a.localeCompare(b)).join('\n'))
- .digest('hex')
- .slice(0, 16)
- }
-
async _execWorkspaces (args, filters) {
const workspaces =
await getWorkspaces(filters, { path: this.npm.localPrefix })
- const getLocationMsg = async path => {
- const color = this.npm.config.get('color')
- const colorize = color ? chalk : nocolor
- const { _id } = await readPackageJson(`${path}/package.json`)
- return ` in workspace ${colorize.green(_id)} at location:\n${colorize.dim(path)}`
- }
+ const color = this.npm.config.get('color')
for (const workspacePath of workspaces.values()) {
- const locationMsg = await getLocationMsg(workspacePath)
+ const locationMsg = await getLocationMsg({ color, path: workspacePath })
await this._exec(args, {
locationMsg,
path: workspacePath,
@@ -364,4 +119,5 @@ class Exec extends BaseCommand {
}
}
}
+
module.exports = Exec
diff --git a/lib/exec/get-workspace-location-msg.js b/lib/exec/get-workspace-location-msg.js
new file mode 100644
index 000000000..813b11e78
--- /dev/null
+++ b/lib/exec/get-workspace-location-msg.js
@@ -0,0 +1,25 @@
+const chalk = require('chalk')
+const readPackageJson = require('read-package-json-fast')
+
+const nocolor = {
+ dim: s => s,
+ green: s => s,
+}
+
+const getLocationMsg = async ({ color, path }) => {
+ const colorize = color ? chalk : nocolor
+ const { _id } =
+ await readPackageJson(`${path}/package.json`)
+ .catch(() => ({}))
+
+ const workspaceMsg = _id
+ ? ` in workspace ${colorize.green(_id)}`
+ : ` in a ${colorize.green('new')} workspace`
+ const locationMsg = ` at location:\n${
+ colorize.dim(path)
+ }`
+
+ return `${workspaceMsg}${locationMsg}`
+}
+
+module.exports = getLocationMsg
diff --git a/lib/init.js b/lib/init.js
index 81c673388..7d7f6bab3 100644
--- a/lib/init.js
+++ b/lib/init.js
@@ -1,6 +1,14 @@
+const fs = require('fs')
+const { relative, resolve } = require('path')
+const mkdirp = require('mkdirp-infer-owner')
const initJson = require('init-package-json')
const npa = require('npm-package-arg')
+const rpj = require('read-package-json-fast')
+const libexec = require('libnpmexec')
+const parseJSON = require('json-parse-even-better-errors')
+const mapWorkspaces = require('@npmcli/map-workspaces')
+const getLocationMsg = require('./exec/get-workspace-location-msg.js')
const BaseCommand = require('./base-command.js')
class Init extends BaseCommand {
@@ -10,6 +18,11 @@ class Init extends BaseCommand {
}
/* istanbul ignore next - see test/lib/load-all-commands.js */
+ static get params () {
+ return ['workspace', 'workspaces']
+ }
+
+ /* istanbul ignore next - see test/lib/load-all-commands.js */
static get name () {
return 'init'
}
@@ -27,42 +40,107 @@ class Init extends BaseCommand {
this.init(args).then(() => cb()).catch(cb)
}
+ execWorkspaces (args, filters, cb) {
+ this.initWorkspaces(args, filters).then(() => cb()).catch(cb)
+ }
+
async init (args) {
- // the new npx style way
+ // npm exec style
+ if (args.length)
+ return (await this.execCreate({ args, path: process.cwd() }))
+
+ // no args, uses classic init-package-json boilerplate
+ await this.template()
+ }
+
+ async initWorkspaces (args, filters) {
+ // reads package.json for the top-level folder first, by doing this we
+ // ensure the command throw if no package.json is found before trying
+ // to create a workspace package.json file or its folders
+ const pkg = await rpj(resolve(this.npm.localPrefix, 'package.json'))
+ const wPath = filterArg => resolve(this.npm.localPrefix, filterArg)
+
+ // npm-exec style, runs in the context of each workspace filter
if (args.length) {
- const initerName = args[0]
- let packageName = initerName
- if (/^@[^/]+$/.test(initerName))
- packageName = initerName + '/create'
- else {
- const req = npa(initerName)
- if (req.type === 'git' && req.hosted) {
- const { user, project } = req.hosted
- packageName = initerName
- .replace(user + '/' + project, user + '/create-' + project)
- } else if (req.registry) {
- packageName = req.name.replace(/^(@[^/]+\/)?/, '$1create-')
- if (req.rawSpec)
- packageName += '@' + req.rawSpec
- } else {
- throw Object.assign(new Error(
- 'Unrecognized initializer: ' + initerName +
- '\nFor more package binary executing power check out `npx`:' +
- '\nhttps://www.npmjs.com/package/npx'
- ), { code: 'EUNSUPPORTED' })
- }
+ for (const filterArg of filters) {
+ const path = wPath(filterArg)
+ await mkdirp(path)
+ await this.execCreate({ args, path })
+ await this.setWorkspace({ pkg, workspacePath: path })
+ }
+ return
+ }
+
+ // no args, uses classic init-package-json boilerplate
+ for (const filterArg of filters) {
+ const path = wPath(filterArg)
+ await mkdirp(path)
+ await this.template(path)
+ await this.setWorkspace({ pkg, workspacePath: path })
+ }
+ }
+
+ async execCreate ({ args, path }) {
+ const [initerName, ...otherArgs] = args
+ let packageName = initerName
+
+ if (/^@[^/]+$/.test(initerName))
+ packageName = initerName + '/create'
+ else {
+ const req = npa(initerName)
+ if (req.type === 'git' && req.hosted) {
+ const { user, project } = req.hosted
+ packageName = initerName
+ .replace(user + '/' + project, user + '/create-' + project)
+ } else if (req.registry) {
+ packageName = req.name.replace(/^(@[^/]+\/)?/, '$1create-')
+ if (req.rawSpec)
+ packageName += '@' + req.rawSpec
+ } else {
+ throw Object.assign(new Error(
+ 'Unrecognized initializer: ' + initerName +
+ '\nFor more package binary executing power check out `npx`:' +
+ '\nhttps://www.npmjs.com/package/npx'
+ ), { code: 'EUNSUPPORTED' })
}
- this.npm.config.set('package', [])
- const newArgs = [packageName, ...args.slice(1)]
- return new Promise((res, rej) => {
- this.npm.commands.exec(newArgs, er => er ? rej(er) : res())
- })
}
- // the old way
- const dir = process.cwd()
+ const newArgs = [packageName, ...otherArgs]
+ const cache = this.npm.config.get('cache')
+ const { color } = this.npm.flatOptions
+ const {
+ flatOptions,
+ localBin,
+ log,
+ globalBin,
+ output,
+ } = this.npm
+ const locationMsg = await getLocationMsg({ color, path })
+ const runPath = path
+ const scriptShell = this.npm.config.get('script-shell') || undefined
+ const yes = this.npm.config.get('yes')
+
+ await libexec({
+ ...flatOptions,
+ args: newArgs,
+ cache,
+ color,
+ localBin,
+ locationMsg,
+ log,
+ globalBin,
+ output,
+ path,
+ runPath,
+ scriptShell,
+ yes,
+ })
+ }
+
+ async template (path = process.cwd()) {
this.npm.log.pause()
this.npm.log.disableProgress()
+
const initFile = this.npm.config.get('init-module')
if (!this.npm.config.get('yes') && !this.npm.config.get('force')) {
this.npm.output([
@@ -78,9 +156,10 @@ class Init extends BaseCommand {
'Press ^C at any time to quit.',
].join('\n'))
}
+
// XXX promisify init-package-json
await new Promise((res, rej) => {
- initJson(dir, initFile, this.npm.config, (er, data) => {
+ initJson(path, initFile, this.npm.config, (er, data) => {
this.npm.log.resume()
this.npm.log.enableProgress()
this.npm.log.silly('package data', data)
@@ -97,5 +176,56 @@ class Init extends BaseCommand {
})
})
}
+
+ async setWorkspace ({ pkg, workspacePath }) {
+ const workspaces = await mapWorkspaces({ cwd: this.npm.localPrefix, pkg })
+
+ // skip setting workspace if current package.json glob already satisfies it
+ for (const wPath of workspaces.values()) {
+ if (wPath === workspacePath)
+ return
+ }
+
+ // if a create-pkg didn't generate a package.json at the workspace
+ // folder level, it might not be recognized as a workspace by
+ // mapWorkspaces, so we're just going to avoid touching the
+ // top-level package.json
+ try {
+ fs.statSync(resolve(workspacePath, 'package.json'))
+ } catch (err) {
+ return
+ }
+
+ let manifest
+ try {
+ manifest =
+ fs.readFileSync(resolve(this.npm.localPrefix, 'package.json'), 'utf-8')
+ } catch (error) {
+ throw new Error('package.json not found')
+ }
+
+ try {
+ manifest = parseJSON(manifest)
+ } catch (error) {
+ throw new Error(`Invalid package.json: ${error}`)
+ }
+
+ if (!manifest.workspaces)
+ manifest.workspaces = []
+
+ manifest.workspaces.push(relative(this.npm.localPrefix, workspacePath))
+
+ // format content
+ const {
+ [Symbol.for('indent')]: indent,
+ [Symbol.for('newline')]: newline,
+ } = manifest
+
+ const content = (JSON.stringify(manifest, null, indent) + '\n')
+ .replace(/\n/g, newline)
+
+ fs.writeFileSync(resolve(this.npm.localPrefix, 'package.json'), content)
+ }
}
+
module.exports = Init
diff --git a/lib/utils/config/definitions.js b/lib/utils/config/definitions.js
index db1f25e95..f8c6b41f3 100644
--- a/lib/utils/config/definitions.js
+++ b/lib/utils/config/definitions.js
@@ -1660,7 +1660,8 @@ define('script-shell', {
`,
type: [null, String],
description: `
- The shell to use for scripts run with the \`npm run\` command.
+ The shell to use for scripts run with the \`npm exec\`,
+ \`npm run\` and \`npm init <pkg>\` commands.
`,
flatten (key, obj, flatOptions) {
flatOptions.scriptShell = obj[key] || undefined
diff --git a/node_modules/init-package-json/default-input.js b/node_modules/init-package-json/default-input.js
index 8e9fe0b57..d1f65841d 100644
--- a/node_modules/init-package-json/default-input.js
+++ b/node_modules/init-package-json/default-input.js
@@ -12,7 +12,7 @@ function isTestPkg (p) {
}
function niceName (n) {
- return n.replace(/^node-|[.-]js$/g, '').replace(' ', '-').toLowerCase()
+ return n.replace(/^node-|[.-]js$/g, '').replace(/\s+/g, ' ').replace(/ /g, '-').toLowerCase()
}
function readDeps (test, excluded) { return function (cb) {
@@ -45,7 +45,7 @@ function readDeps (test, excluded) { return function (cb) {
})
}}
-var name = package.name || basename
+var name = niceName(package.name || basename)
var spec
try {
spec = npa(name)
@@ -61,7 +61,7 @@ if (scope) {
name = scope + '/' + name
}
}
-exports.name = yes ? name : prompt('package name', niceName(name), function (data) {
+exports.name = yes ? name : prompt('package name', name, function (data) {
var its = validateName(data)
if (its.validForNewPackages) return data
var errors = (its.errors || []).concat(its.warnings || [])
diff --git a/node_modules/init-package-json/package.json b/node_modules/init-package-json/package.json
index 91c6bfba8..584e313b4 100644
--- a/node_modules/init-package-json/package.json
+++ b/node_modules/init-package-json/package.json
@@ -1,6 +1,6 @@
{
"name": "init-package-json",
- "version": "2.0.2",
+ "version": "2.0.3",
"main": "init-package-json.js",
"scripts": {
"test": "tap",
@@ -17,19 +17,19 @@
"description": "A node module to get your node module started",
"dependencies": {
"glob": "^7.1.1",
- "npm-package-arg": "^8.1.0",
+ "npm-package-arg": "^8.1.2",
"promzard": "^0.3.0",
"read": "~1.0.1",
- "read-package-json": "^3.0.0",
- "semver": "^7.3.2",
+ "read-package-json": "^3.0.1",
+ "semver": "^7.3.5",
"validate-npm-package-license": "^3.0.4",
"validate-npm-package-name": "^3.0.0"
},
"devDependencies": {
- "@npmcli/config": "^1.2.1",
+ "@npmcli/config": "^2.1.0",
"mkdirp": "^1.0.4",
"rimraf": "^3.0.2",
- "tap": "^14.10.8"
+ "tap": "^14.11.0"
},
"engines": {
"node": ">=10"
diff --git a/node_modules/libnpmexec/CHANGELOG.md b/node_modules/libnpmexec/CHANGELOG.md
new file mode 100644
index 000000000..fe3ac0def
--- /dev/null
+++ b/node_modules/libnpmexec/CHANGELOG.md
@@ -0,0 +1,8 @@
+# Changelog
+
+## v1.0.0
+
+- Initial implementation, moves the code that used to live in the **npm cli**,
+ref: https://github.com/npm/cli/blob/release/v7.10.0/lib/exec.js into this
+separate module, providing a programmatic API to the **npm exec** functionality.
+
diff --git a/node_modules/libnpmexec/LICENSE b/node_modules/libnpmexec/LICENSE
new file mode 100644
index 000000000..d3a1cdfd2
--- /dev/null
+++ b/node_modules/libnpmexec/LICENSE
@@ -0,0 +1,15 @@
+The ISC License
+
+Copyright (c) GitHub Inc.
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/node_modules/libnpmexec/README.md b/node_modules/libnpmexec/README.md
new file mode 100644
index 000000000..a436c9a5a
--- /dev/null
+++ b/node_modules/libnpmexec/README.md
@@ -0,0 +1,48 @@
+# libnpmexec
+
+[![npm version](https://img.shields.io/npm/v/libnpmexec.svg)](https://npm.im/libnpmexec)
+[![license](https://img.shields.io/npm/l/libnpmexec.svg)](https://npm.im/libnpmexec)
+[![GitHub Actions](https://github.com/npm/libnpmexec/workflows/node-ci/badge.svg)](https://github.com/npm/libnpmexec/actions?query=workflow%3Anode-ci)
+[![Coverage Status](https://coveralls.io/repos/github/npm/libnpmexec/badge.svg?branch=main)](https://coveralls.io/github/npm/libnpmexec?branch=main)
+
+The `npm exec` (`npx`) Programmatic API
+
+## Install
+
+`npm install libnpmexec`
+
+## Usage:
+
+```js
+const libexec = require('libnpmexec')
+await libexec({
+ args: ['yosay', 'Bom dia!'],
+ cache: '~/.npm',
+ yes: true,
+})
+```
+
+## API:
+
+### `libexec(opts)`
+
+- `opts`:
+ - `args`: List of pkgs to execute **Array<String>**, defaults to `[]`
+ - `call`: An alternative command to run when using `packages` option **String**, defaults to empty string.
+ - `cache`: The path location to where the npm cache folder is placed **String**
+ - `color`: Output should use color? **Boolean**, defaults to `false`
+ - `localBin`: Location to the `node_modules/.bin` folder of the local project **String**, defaults to empty string.
+ - `locationMsg`: Overrides "at location" message when entering interactive mode **String**
+ - `log`: Sets an optional logger **Object**, defaults to `proc-log` module usage.
+ - `globalBin`: Location to the global space bin folder, same as: `$(npm bin -g)` **String**, defaults to empty string.
+ - `output`: A function to print output to **Function**
+ - `packages`: A list of packages to be used (possibly fetch from the registry) **Array<String>**, defaults to `[]`
+ - `path`: Location to where to read local project info (`package.json`) **String**, defaults to `.`
+ - `runPath`: Location to where to execute the script **String**, defaults to `.`
+ - `scriptShell`: Default shell to be used **String**
+ - `yes`: Should skip download confirmation prompt when fetching missing packages from the registry? **Boolean**
+ - `registry`, `cache`, and more options that are forwarded to [@npmcli/arborist](https://github.com/npm/arborist/) and [pacote](https://github.com/npm/pacote/#options) **Object**
+
+## LICENSE
+
+[ISC](./LICENSE)
diff --git a/node_modules/libnpmexec/lib/cache-install-dir.js b/node_modules/libnpmexec/lib/cache-install-dir.js
new file mode 100644
index 000000000..1bee28989
--- /dev/null
+++ b/node_modules/libnpmexec/lib/cache-install-dir.js
@@ -0,0 +1,19 @@
+const crypto = require('crypto')
+
+const { resolve } = require('path')
+
+const cacheInstallDir = ({ cache, packages }) => {
+ if (!cache)
+ throw new Error('Must provide a valid cache path')
+
+ // only packages not found in ${prefix}/node_modules
+ return resolve(cache, '_npx', getHash(packages))
+}
+
+const getHash = (packages) =>
+ crypto.createHash('sha512')
+ .update(packages.sort((a, b) => a.localeCompare(b)).join('\n'))
+ .digest('hex')
+ .slice(0, 16)
+
+module.exports = cacheInstallDir
diff --git a/node_modules/libnpmexec/lib/get-bin-from-manifest.js b/node_modules/libnpmexec/lib/get-bin-from-manifest.js
new file mode 100644
index 000000000..038095b50
--- /dev/null
+++ b/node_modules/libnpmexec/lib/get-bin-from-manifest.js
@@ -0,0 +1,20 @@
+const getBinFromManifest = (mani) => {
+ // if we have a bin matching (unscoped portion of) packagename, use that
+ // otherwise if there's 1 bin or all bin value is the same (alias), use
+ // that, otherwise fail
+ const bin = mani.bin || {}
+ if (new Set(Object.values(bin)).size === 1)
+ return Object.keys(bin)[0]
+
+ // XXX probably a util to parse this better?
+ const name = mani.name.replace(/^@[^/]+\//, '')
+ if (bin[name])
+ return name
+
+ // XXX need better error message
+ throw Object.assign(new Error('could not determine executable to run'), {
+ pkgid: mani._id,
+ })
+}
+
+module.exports = getBinFromManifest
diff --git a/node_modules/libnpmexec/lib/index.js b/node_modules/libnpmexec/lib/index.js
new file mode 100644
index 000000000..906a0b540
--- /dev/null
+++ b/node_modules/libnpmexec/lib/index.js
@@ -0,0 +1,185 @@
+const { delimiter, resolve } = require('path')
+const { promisify } = require('util')
+const read = promisify(require('read'))
+const stat = promisify(require('fs').stat)
+
+const Arborist = require('@npmcli/arborist')
+const ciDetect = require('@npmcli/ci-detect')
+const logger = require('proc-log')
+const mkdirp = require('mkdirp-infer-owner')
+const npa = require('npm-package-arg')
+const pacote = require('pacote')
+const readPackageJson = require('read-package-json-fast')
+
+const cacheInstallDir = require('./cache-install-dir.js')
+const getBinFromManifest = require('./get-bin-from-manifest.js')
+const manifestMissing = require('./manifest-missing.js')
+const noTTY = require('./no-tty.js')
+const runScript = require('./run-script.js')
+
+const fileExists = (file) => stat(file)
+ .then((stat) => stat.isFile())
+ .catch(() => false)
+
+/* istanbul ignore next */
+const PATH = (
+ process.env.PATH || process.env.Path || process.env.path
+).split(delimiter)
+
+const exec = async (opts) => {
+ const {
+ args = [],
+ call = '',
+ color = false,
+ localBin = '',
+ locationMsg = undefined,
+ globalBin = '',
+ output,
+ packages: _packages = [],
+ path = '.',
+ runPath = '.',
+ scriptShell = undefined,
+ yes = undefined,
+ ...flatOptions
+ } = opts
+ const log = flatOptions.log || logger
+
+ // dereferences values because we manipulate it later
+ const packages = [..._packages]
+ const pathArr = [...PATH]
+ const _run = () => runScript({
+ args,
+ call,
+ color,
+ flatOptions,
+ locationMsg,
+ log,
+ output,
+ path,
+ pathArr,
+ runPath,
+ scriptShell,
+ })
+
+ // nothing to maybe install, skip the arborist dance
+ if (!call && !args.length && !packages.length)
+ return await _run()
+
+ const needPackageCommandSwap = args.length && !packages.length
+ // if there's an argument and no package has been explicitly asked for
+ // check the local and global bin paths for a binary named the same as
+ // the argument and run it if it exists, otherwise fall through to
+ // the behavior of treating the single argument as a package name
+ if (needPackageCommandSwap) {
+ let binExists = false
+ if (await fileExists(`${localBin}/${args[0]}`)) {
+ pathArr.unshift(localBin)
+ binExists = true
+ } else if (await fileExists(`${globalBin}/${args[0]}`)) {
+ pathArr.unshift(globalBin)
+ binExists = true
+ }
+
+ if (binExists)
+ return await _run()
+
+ packages.push(args[0])
+ }
+
+ // If we do `npm exec foo`, and have a `foo` locally, then we'll
+ // always use that, so we don't really need to fetch the manifest.
+ // So: run npa on each packages entry, and if it is a name with a
+ // rawSpec==='', then try to readPackageJson at
+ // node_modules/${name}/package.json, and only pacote fetch if
+ // that fails.
+ const manis = await Promise.all(packages.map(async p => {
+ const spec = npa(p, path)
+ if (spec.type === 'tag' && spec.rawSpec === '') {
+ // fall through to the pacote.manifest() approach
+ try {
+ const pj = resolve(path, 'node_modules', spec.name, 'package.json')
+ return await readPackageJson(pj)
+ } catch (er) {}
+ }
+ // Force preferOnline to true so we are making sure to pull in the latest
+ // This is especially useful if the user didn't give us a version, and
+ // they expect to be running @latest
+ return await pacote.manifest(p, {
+ ...flatOptions,
+ preferOnline: true,
+ })
+ }))
+
+ if (needPackageCommandSwap)
+ args[0] = getBinFromManifest(manis[0])
+
+ // figure out whether we need to install stuff, or if local is fine
+ const localArb = new Arborist({
+ ...flatOptions,
+ path,
+ })
+ const tree = await localArb.loadActual()
+
+ // do we have all the packages in manifest list?
+ const needInstall =
+ manis.some(manifest => manifestMissing({ tree, manifest }))
+
+ if (needInstall) {
+ const { cache } = flatOptions
+ const installDir = cacheInstallDir({ cache, packages })
+ await mkdirp(installDir)
+ const arb = new Arborist({
+ ...flatOptions,
+ path: installDir,
+ })
+ const tree = await arb.loadActual()
+
+ // at this point, we have to ensure that we get the exact same
+ // version, because it's something that has only ever been installed
+ // by npm exec in the cache install directory
+ const add = manis.filter(mani => manifestMissing({
+ tree,
+ manifest: {
+ ...mani,
+ _from: `${mani.name}@${mani.version}`,
+ },
+ }))
+ .map(mani => mani._from)
+ .sort((a, b) => a.localeCompare(b))
+
+ // no need to install if already present
+ if (add.length) {
+ if (!yes) {
+ // set -n to always say no
+ if (yes === false)
+ throw new Error('canceled')
+
+ if (noTTY() || ciDetect()) {
+ log.warn('exec', `The following package${
+ add.length === 1 ? ' was' : 's were'
+ } not found and will be installed: ${
+ add.map((pkg) => pkg.replace(/@$/, '')).join(', ')
+ }`)
+ } else {
+ const addList = add.map(a => ` ${a.replace(/@$/, '')}`)
+ .join('\n') + '\n'
+ const prompt = `Need to install the following packages:\n${
+ addList
+ }Ok to proceed? `
+ const confirm = await read({ prompt, default: 'y' })
+ if (confirm.trim().toLowerCase().charAt(0) !== 'y')
+ throw new Error('canceled')
+ }
+ }
+ await arb.reify({
+ ...flatOptions,
+ add,
+ })
+ }
+ pathArr.unshift(resolve(installDir, 'node_modules/.bin'))
+ }
+
+ return await _run()
+}
+
+module.exports = exec
diff --git a/node_modules/libnpmexec/lib/manifest-missing.js b/node_modules/libnpmexec/lib/manifest-missing.js
new file mode 100644
index 000000000..471468096
--- /dev/null
+++ b/node_modules/libnpmexec/lib/manifest-missing.js
@@ -0,0 +1,17 @@
+const manifestMissing = ({ tree, manifest }) => {
+ // if the tree doesn't have a child by that name/version, return true
+ // true means we need to install it
+ const child = tree.children.get(manifest.name)
+ // if no child, we have to load it
+ if (!child)
+ return true
+
+ // if no version/tag specified, allow whatever's there
+ if (manifest._from === `${manifest.name}@`)
+ return false
+
+ // otherwise the version has to match what we WOULD get
+ return child.version !== manifest.version
+}
+
+module.exports = manifestMissing
diff --git a/node_modules/libnpmexec/lib/no-tty.js b/node_modules/libnpmexec/lib/no-tty.js
new file mode 100644
index 000000000..601798d25
--- /dev/null
+++ b/node_modules/libnpmexec/lib/no-tty.js
@@ -0,0 +1 @@
+module.exports = () => !process.stdin.isTTY
diff --git a/node_modules/libnpmexec/lib/run-script.js b/node_modules/libnpmexec/lib/run-script.js
new file mode 100644
index 000000000..819dacb8b
--- /dev/null
+++ b/node_modules/libnpmexec/lib/run-script.js
@@ -0,0 +1,86 @@
+const { delimiter } = require('path')
+
+const chalk = require('chalk')
+const ciDetect = require('@npmcli/ci-detect')
+const runScript = require('@npmcli/run-script')
+const readPackageJson = require('read-package-json-fast')
+const noTTY = require('./no-tty.js')
+
+const nocolor = {
+ reset: s => s,
+ bold: s => s,
+ dim: s => s,
+}
+
+const run = async ({
+ args,
+ call,
+ color,
+ flatOptions,
+ locationMsg,
+ log,
+ output = () => {},
+ path,
+ pathArr,
+ runPath,
+ scriptShell,
+}) => {
+ // turn list of args into command string
+ const script = call || args.shift() || scriptShell
+ const colorize = color ? chalk : nocolor
+
+ // do the fakey runScript dance
+ // still should work if no package.json in cwd
+ const realPkg = await readPackageJson(`${path}/package.json`)
+ .catch(() => ({}))
+ const pkg = {
+ ...realPkg,
+ scripts: {
+ ...(realPkg.scripts || {}),
+ npx: script,
+ },
+ }
+
+ if (log && log.disableProgress)
+ log.disableProgress()
+
+ try {
+ if (script === scriptShell) {
+ const isTTY = !noTTY()
+
+ if (isTTY) {
+ if (ciDetect())
+ return log.warn('exec', 'Interactive mode disabled in CI environment')
+
+ locationMsg = locationMsg || ` at location:\n${colorize.dim(runPath)}`
+
+ output(`${
+ colorize.reset('\nEntering npm script environment')
+ }${
+ colorize.reset(locationMsg)
+ }${
+ colorize.bold('\nType \'exit\' or ^D when finished\n')
+ }`)
+ }
+ }
+ return await runScript({
+ ...flatOptions,
+ pkg,
+ banner: false,
+ // we always run in cwd, not --prefix
+ path: runPath,
+ stdioString: true,
+ event: 'npx',
+ args,
+ env: {
+ PATH: pathArr.join(delimiter),
+ },
+ stdio: 'inherit',
+ })
+ } finally {
+ if (log && log.enableProgress)
+ log.enableProgress()
+ }
+}
+
+module.exports = run
diff --git a/node_modules/libnpmexec/package.json b/node_modules/libnpmexec/package.json
new file mode 100644
index 000000000..1b7d24103
--- /dev/null
+++ b/node_modules/libnpmexec/package.json
@@ -0,0 +1,63 @@
+{
+ "name": "libnpmexec",
+ "version": "1.0.1",
+ "files": [
+ "lib"
+ ],
+ "main": "lib/index.js",
+ "engines": {
+ "node": ">=10"
+ },
+ "description": "npm exec (npx) programmatic API",
+ "repository": "https://github.com/npm/libnpmexec",
+ "keywords": [
+ "npm",
+ "npmcli",
+ "libnpm",
+ "cli",
+ "workspaces",
+ "libnpmexec"
+ ],
+ "author": "GitHub Inc.",
+ "contributors": [
+ {
+ "name": "Ruy Adorno",
+ "url": "https://ruyadorno.com",
+ "twitter": "ruyadorno"
+ }
+ ],
+ "license": "ISC",
+ "scripts": {
+ "lint": "eslint lib/*.js",
+ "pretest": "npm run lint",
+ "test": "tap test/*.js",
+ "snap": "tap test/*.js",
+ "preversion": "npm test",
+ "postversion": "npm publish",
+ "prepublishOnly": "git push origin --follow-tags"
+ },
+ "tap": {
+ "check-coverage": true
+ },
+ "devDependencies": {
+ "bin-links": "^2.2.1",
+ "eslint": "^7.24.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.2"
+ },
+ "dependencies": {
+ "@npmcli/arborist": "^2.3.0",
+ "@npmcli/ci-detect": "^1.3.0",
+ "@npmcli/run-script": "^1.8.4",
+ "chalk": "^4.1.0",
+ "mkdirp-infer-owner": "^2.0.0",
+ "npm-package-arg": "^8.1.2",
+ "pacote": "^11.3.1",
+ "proc-log": "^1.0.0",
+ "read": "^1.0.7",
+ "read-package-json-fast": "^2.0.2"
+ }
+}
diff --git a/node_modules/proc-log/LICENSE b/node_modules/proc-log/LICENSE
new file mode 100644
index 000000000..838377972
--- /dev/null
+++ b/node_modules/proc-log/LICENSE
@@ -0,0 +1,15 @@
+The ISC License
+
+Copyright (c) GitHub, Inc.
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/node_modules/proc-log/README.md b/node_modules/proc-log/README.md
new file mode 100644
index 000000000..1adc2a658
--- /dev/null
+++ b/node_modules/proc-log/README.md
@@ -0,0 +1,33 @@
+# proc-log
+
+Emits 'log' events on the process object which a log output listener can
+consume and print to the terminal.
+
+This is used by various modules within the npm CLI stack in order to send
+log events that [`npmlog`](http://npm.im/npmlog) can consume and print.
+
+## API
+
+* `log.error(...args)` calls `process.emit('log', 'error', ...args)`
+ The highest log level. For printing extremely serious errors that
+ indicate something went wrong.
+* `log.warn(...args)` calls `process.emit('log', 'warn', ...args)`
+ A fairly high log level. Things that the user needs to be aware of, but
+ which won't necessarily cause improper functioning of the system.
+* `log.notice(...args)` calls `process.emit('log', 'notice', ...args)`
+ Notices which are important, but not necessarily dangerous or a cause for
+ excess concern.
+* `log.info(...args)` calls `process.emit('log', 'info', ...args)`
+ Informative messages that may benefit the user, but aren't particularly
+ important.
+* `log.verbose(...args)` calls `process.emit('log', 'verbose', ...args)`
+ Noisy output that is more detail that most users will care about.
+* `log.silly(...args)` calls `process.emit('log', 'silly', ...args)`
+ Extremely noisy excessive logging messages that are typically only useful
+ for debugging.
+* `log.http(...args)` calls `process.emit('log', 'http', ...args)`
+ Information about HTTP requests made and/or completed.
+* `log.pause(...args)` calls `process.emit('log', 'pause')` Used to tell
+ the consumer to stop printing messages.
+* `log.resume(...args)` calls `process.emit('log', 'resume', ...args)`
+ Used to tell the consumer that it is ok to print messages again.
diff --git a/node_modules/proc-log/index.js b/node_modules/proc-log/index.js
new file mode 100644
index 000000000..9b58713ff
--- /dev/null
+++ b/node_modules/proc-log/index.js
@@ -0,0 +1,22 @@
+// emits 'log' events on the process
+const LEVELS = [
+ 'notice',
+ 'error',
+ 'warn',
+ 'info',
+ 'verbose',
+ 'http',
+ 'silly',
+ 'pause',
+ 'resume',
+]
+
+const log = level => (...args) => process.emit('log', level, ...args)
+
+const logger = {}
+for (const level of LEVELS)
+ logger[level] = log(level)
+
+logger.LEVELS = LEVELS
+
+module.exports = logger
diff --git a/node_modules/proc-log/package.json b/node_modules/proc-log/package.json
new file mode 100644
index 000000000..178009f61
--- /dev/null
+++ b/node_modules/proc-log/package.json
@@ -0,0 +1,28 @@
+{
+ "name": "proc-log",
+ "version": "1.0.0",
+ "files": [
+ "index.js"
+ ],
+ "description": "just emit 'log' events on the process object",
+ "repository": "https://github.com/npm/proc-log",
+ "author": "Isaac Z. Schlueter <i@izs.me> (https://izs.me)",
+ "license": "ISC",
+ "scripts": {
+ "test": "tap",
+ "snap": "tap",
+ "posttest": "eslint index.js test/*.js",
+ "postsnap": "eslint index.js test/*.js --fix",
+ "preversion": "npm test",
+ "postversion": "npm publish",
+ "prepublishOnly": "git push origin --follow-tags"
+ },
+ "devDependencies": {
+ "eslint": "^7.9.0",
+ "eslint-plugin-import": "^2.22.0",
+ "eslint-plugin-node": "^11.1.0",
+ "eslint-plugin-promise": "^4.2.1",
+ "eslint-plugin-standard": "^4.0.1",
+ "tap": "^15.0.2"
+ }
+}
diff --git a/package-lock.json b/package-lock.json
index d2e64cb48..704fd1571 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,6 +11,7 @@
"@npmcli/arborist",
"@npmcli/ci-detect",
"@npmcli/config",
+ "@npmcli/name-from-folder",
"@npmcli/run-script",
"abbrev",
"ansicolors",
@@ -33,6 +34,7 @@
"leven",
"libnpmaccess",
"libnpmdiff",
+ "libnpmexec",
"libnpmfund",
"libnpmhook",
"libnpmorg",
@@ -80,7 +82,6 @@
"@npmcli/map-workspaces",
"@npmcli/metavuln-calculator",
"@npmcli/move-file",
- "@npmcli/name-from-folder",
"@npmcli/node-gyp",
"@npmcli/promise-spawn",
"@tootallnate/once",
@@ -202,6 +203,7 @@
"path-is-absolute",
"path-parse",
"performance-now",
+ "proc-log",
"process-nextick-args",
"promise-all-reject-late",
"promise-call-limit",
@@ -270,12 +272,13 @@
"graceful-fs": "^4.2.6",
"hosted-git-info": "^4.0.2",
"ini": "^2.0.0",
- "init-package-json": "^2.0.2",
+ "init-package-json": "^2.0.3",
"is-cidr": "^4.0.2",
"json-parse-even-better-errors": "^2.3.1",
"leven": "^3.1.0",
"libnpmaccess": "^4.0.1",
"libnpmdiff": "^2.0.4",
+ "libnpmexec": "^1.0.1",
"libnpmfund": "^1.0.2",
"libnpmhook": "^6.0.1",
"libnpmorg": "^2.0.1",
@@ -4018,17 +4021,17 @@
}
},
"node_modules/init-package-json": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/init-package-json/-/init-package-json-2.0.2.tgz",
- "integrity": "sha512-PO64kVeArePvhX7Ff0jVWkpnE1DfGRvaWcStYrPugcJz9twQGYibagKJuIMHCX7ENcp0M6LJlcjLBuLD5KeJMg==",
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/init-package-json/-/init-package-json-2.0.3.tgz",
+ "integrity": "sha512-tk/gAgbMMxR6fn1MgMaM1HpU1ryAmBWWitnxG5OhuNXeX0cbpbgV5jA4AIpQJVNoyOfOevTtO6WX+rPs+EFqaQ==",
"inBundle": true,
"dependencies": {
"glob": "^7.1.1",
- "npm-package-arg": "^8.1.0",
+ "npm-package-arg": "^8.1.2",
"promzard": "^0.3.0",
"read": "~1.0.1",
- "read-package-json": "^3.0.0",
- "semver": "^7.3.2",
+ "read-package-json": "^3.0.1",
+ "semver": "^7.3.5",
"validate-npm-package-license": "^3.0.4",
"validate-npm-package-name": "^3.0.0"
},
@@ -4795,6 +4798,27 @@
"node": ">=10"
}
},
+ "node_modules/libnpmexec": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/libnpmexec/-/libnpmexec-1.0.1.tgz",
+ "integrity": "sha512-YK2kEhZNCcaDEqOIUWjadhJl9MgS69YHgMmGD5P3yntF0UXNOQfEqABoMTZv9ngPOJTJQGlU4Dfp2xb3bpUKzw==",
+ "inBundle": true,
+ "dependencies": {
+ "@npmcli/arborist": "^2.3.0",
+ "@npmcli/ci-detect": "^1.3.0",
+ "@npmcli/run-script": "^1.8.4",
+ "chalk": "^4.1.0",
+ "mkdirp-infer-owner": "^2.0.0",
+ "npm-package-arg": "^8.1.2",
+ "pacote": "^11.3.1",
+ "proc-log": "^1.0.0",
+ "read": "^1.0.7",
+ "read-package-json-fast": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/libnpmfund": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/libnpmfund/-/libnpmfund-1.0.2.tgz",
@@ -6225,6 +6249,12 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/proc-log": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-1.0.0.tgz",
+ "integrity": "sha512-aCk8AO51s+4JyuYGg3Q/a6gnrlDO09NpVWePtjp7xwphcoQ04x5WAfCyugcsbLooWcMJ87CLkD4+604IckEdhg==",
+ "inBundle": true
+ },
"node_modules/process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
@@ -13419,16 +13449,16 @@
"integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA=="
},
"init-package-json": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/init-package-json/-/init-package-json-2.0.2.tgz",
- "integrity": "sha512-PO64kVeArePvhX7Ff0jVWkpnE1DfGRvaWcStYrPugcJz9twQGYibagKJuIMHCX7ENcp0M6LJlcjLBuLD5KeJMg==",
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/init-package-json/-/init-package-json-2.0.3.tgz",
+ "integrity": "sha512-tk/gAgbMMxR6fn1MgMaM1HpU1ryAmBWWitnxG5OhuNXeX0cbpbgV5jA4AIpQJVNoyOfOevTtO6WX+rPs+EFqaQ==",
"requires": {
"glob": "^7.1.1",
- "npm-package-arg": "^8.1.0",
+ "npm-package-arg": "^8.1.2",
"promzard": "^0.3.0",
"read": "~1.0.1",
- "read-package-json": "^3.0.0",
- "semver": "^7.3.2",
+ "read-package-json": "^3.0.1",
+ "semver": "^7.3.5",
"validate-npm-package-license": "^3.0.4",
"validate-npm-package-name": "^3.0.0"
}
@@ -13963,6 +13993,23 @@
"tar": "^6.1.0"
}
},
+ "libnpmexec": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/libnpmexec/-/libnpmexec-1.0.1.tgz",
+ "integrity": "sha512-YK2kEhZNCcaDEqOIUWjadhJl9MgS69YHgMmGD5P3yntF0UXNOQfEqABoMTZv9ngPOJTJQGlU4Dfp2xb3bpUKzw==",
+ "requires": {
+ "@npmcli/arborist": "^2.3.0",
+ "@npmcli/ci-detect": "^1.3.0",
+ "@npmcli/run-script": "^1.8.4",
+ "chalk": "^4.1.0",
+ "mkdirp-infer-owner": "^2.0.0",
+ "npm-package-arg": "^8.1.2",
+ "pacote": "^11.3.1",
+ "proc-log": "^1.0.0",
+ "read": "^1.0.7",
+ "read-package-json-fast": "^2.0.2"
+ }
+ },
"libnpmfund": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/libnpmfund/-/libnpmfund-1.0.2.tgz",
@@ -15032,6 +15079,11 @@
"integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
"dev": true
},
+ "proc-log": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-1.0.0.tgz",
+ "integrity": "sha512-aCk8AO51s+4JyuYGg3Q/a6gnrlDO09NpVWePtjp7xwphcoQ04x5WAfCyugcsbLooWcMJ87CLkD4+604IckEdhg=="
+ },
"process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
diff --git a/package.json b/package.json
index 8d45f7c3e..a10d5377a 100644
--- a/package.json
+++ b/package.json
@@ -61,12 +61,13 @@
"graceful-fs": "^4.2.6",
"hosted-git-info": "^4.0.2",
"ini": "^2.0.0",
- "init-package-json": "^2.0.2",
+ "init-package-json": "^2.0.3",
"is-cidr": "^4.0.2",
"json-parse-even-better-errors": "^2.3.1",
"leven": "^3.1.0",
"libnpmaccess": "^4.0.1",
"libnpmdiff": "^2.0.4",
+ "libnpmexec": "^1.0.1",
"libnpmfund": "^1.0.2",
"libnpmhook": "^6.0.1",
"libnpmorg": "^2.0.1",
@@ -113,6 +114,7 @@
"@npmcli/arborist",
"@npmcli/ci-detect",
"@npmcli/config",
+ "@npmcli/name-from-folder",
"@npmcli/run-script",
"abbrev",
"ansicolors",
@@ -135,6 +137,7 @@
"leven",
"libnpmaccess",
"libnpmdiff",
+ "libnpmexec",
"libnpmfund",
"libnpmhook",
"libnpmorg",
diff --git a/tap-snapshots/test/lib/init.js.test.cjs b/tap-snapshots/test/lib/init.js.test.cjs
index 25015aab6..043d8b641 100644
--- a/tap-snapshots/test/lib/init.js.test.cjs
+++ b/tap-snapshots/test/lib/init.js.test.cjs
@@ -5,15 +5,14 @@
* Make sure to inspect the output below. Do not ignore changes!
*/
'use strict'
-exports[`test/lib/init.js TAP classic npm init no args > should print helper info 1`] = `
-This utility will walk you through creating a package.json file.
-It only covers the most common items, and tries to guess sensible defaults.
+exports[`test/lib/init.js TAP workspaces no args > should print helper info 1`] = `
-See \`npm help init\` for definitive documentation on these fields
-and exactly what they do.
+`
+
+exports[`test/lib/init.js TAP workspaces no args, existing folder > should print helper info 1`] = `
+
+`
-Use \`npm install <pkg>\` afterwards to install a package and
-save it as a dependency in the package.json file.
+exports[`test/lib/init.js TAP workspaces with arg but missing workspace folder > should print helper info 1`] = `
-Press ^C at any time to quit.
`
diff --git a/tap-snapshots/test/lib/utils/config/describe-all.js.test.cjs b/tap-snapshots/test/lib/utils/config/describe-all.js.test.cjs
index 6c2659062..ce520e748 100644
--- a/tap-snapshots/test/lib/utils/config/describe-all.js.test.cjs
+++ b/tap-snapshots/test/lib/utils/config/describe-all.js.test.cjs
@@ -966,7 +966,8 @@ installation of packages specified according to the pattern
* Default: '/bin/sh' on POSIX systems, 'cmd.exe' on Windows
* Type: null or String
-The shell to use for scripts run with the \`npm run\` command.
+The shell to use for scripts run with the \`npm exec\`, \`npm run\` and \`npm
+init <pkg>\` commands.
#### \`searchexclude\`
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 e32d5e9f4..946cfba90 100644
--- a/tap-snapshots/test/lib/utils/npm-usage.js.test.cjs
+++ b/tap-snapshots/test/lib/utils/npm-usage.js.test.cjs
@@ -467,6 +467,10 @@ All commands:
npm init <@scope> (same as \`npx <@scope>/create\`)
npm init [<@scope>/]<name> (same as \`npx [<@scope>/]create-<name>\`)
+ Options:
+ [-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]]
+ [-ws|--workspaces]
+
aliases: create, innit
Run "npm help init" for more info
diff --git a/test/lib/exec.js b/test/lib/exec.js
index 0c577e42f..5ecc73274 100644
--- a/test/lib/exec.js
+++ b/test/lib/exec.js
@@ -34,7 +34,7 @@ const config = {
yes: true,
call: '',
package: [],
- shell: 'shell-cmd',
+ 'script-shell': 'shell-cmd',
}
const npm = mockNpm({
flatOptions,
@@ -86,12 +86,14 @@ const PATH = require('../../lib/utils/path.js')
let CI_NAME = 'travis-ci'
const mocks = {
- '@npmcli/arborist': Arborist,
- '@npmcli/run-script': runScript,
- '@npmcli/ci-detect': () => CI_NAME,
- pacote,
- read,
- 'mkdirp-infer-owner': mkdirp,
+ libnpmexec: t.mock('libnpmexec', {
+ '@npmcli/arborist': Arborist,
+ '@npmcli/run-script': runScript,
+ '@npmcli/ci-detect': () => CI_NAME,
+ pacote,
+ read,
+ 'mkdirp-infer-owner': mkdirp,
+ }),
}
const Exec = t.mock('../../lib/exec.js', mocks)
const exec = new Exec(npm)
@@ -108,6 +110,7 @@ t.afterEach(() => {
PROGRESS_IGNORED = false
flatOptions.legacyPeerDeps = false
config.color = false
+ config['script-shell'] = 'shell-cmd'
config.package = []
flatOptions.package = []
config.call = ''
@@ -288,6 +291,29 @@ t.test('npm exec <noargs>, run interactive shell', t => {
})
})
+ t.test('not defined script-shell config value', t => {
+ CI_NAME = null
+ process.stdin.isTTY = true
+ config['script-shell'] = undefined
+
+ exec.exec([], er => {
+ if (er)
+ throw er
+
+ t.match(RUN_SCRIPTS, [{
+ pkg: { scripts: { npx: undefined } },
+ }])
+
+ LOG_WARN.length = 0
+ ARB_CTOR.length = 0
+ MKDIRPS.length = 0
+ ARB_REIFY.length = 0
+ OUTPUT.length = 0
+ RUN_SCRIPTS.length = 0
+ t.end()
+ })
+ })
+
t.end()
})
diff --git a/test/lib/init.js b/test/lib/init.js
index 11273e4c3..0964bb5ce 100644
--- a/test/lib/init.js
+++ b/test/lib/init.js
@@ -1,3 +1,5 @@
+const fs = require('fs')
+const { resolve } = require('path')
const t = require('tap')
const mockNpm = require('../fixtures/mock-npm')
@@ -12,121 +14,195 @@ const npmLog = {
}
const config = {
'init-module': '~/.npm-init.js',
+ yes: true,
}
const npm = mockNpm({
config,
log: npmLog,
- commands: {},
output: (...msg) => {
result += msg.join('\n')
},
})
const mocks = {
- 'init-package-json': (dir, initFile, config, cb) => cb(null, 'data'),
'../../lib/utils/usage.js': () => 'usage instructions',
}
const Init = t.mock('../../lib/init.js', mocks)
const init = new Init(npm)
+const _cwd = process.cwd()
+const _consolelog = console.log
+const noop = () => {}
t.afterEach(() => {
result = ''
+ config.yes = true
config.package = undefined
- npm.commands = {}
npm.log = npmLog
+ process.chdir(_cwd)
+ console.log = _consolelog
})
-t.test('classic npm init no args', t => {
+t.test('classic npm init -y', t => {
+ npm.localPrefix = t.testdir({})
+
+ // init-package-json prints directly to console.log
+ // this avoids poluting test output with those logs
+ console.log = noop
+
+ process.chdir(npm.localPrefix)
init.exec([], err => {
- t.error(err, 'npm init no args')
- t.matchSnapshot(result, 'should print helper info')
+ if (err)
+ throw err
+
+ const pkg = require(resolve(npm.localPrefix, 'package.json'))
+ t.equal(pkg.version, '1.0.0')
+ t.equal(pkg.license, 'ISC')
t.end()
})
})
-t.test('classic npm init -y', t => {
- t.plan(7)
- config.yes = true
- Object.defineProperty(npm, 'flatOptions', { value: { yes: true} })
- npm.log = { ...npm.log }
- npm.log.silly = (title, msg) => {
- t.equal(title, 'package data', 'should print title')
- t.equal(msg, 'data', 'should print pkg data info')
- }
- npm.log.resume = () => {
- t.ok('should resume logs')
- }
- npm.log.info = (title, msg) => {
- t.equal(title, 'init', 'should print title')
- t.equal(msg, 'written successfully', 'should print done info')
- }
+t.test('classic interactive npm init', t => {
+ npm.localPrefix = t.testdir({})
+ config.yes = undefined
+
+ const Init = t.mock('../../lib/init.js', {
+ ...mocks,
+ 'init-package-json': (path, initFile, config, cb) => {
+ t.equal(
+ path,
+ resolve(npm.localPrefix),
+ 'should start init package.json in expected path'
+ )
+ cb()
+ },
+ })
+ const init = new Init(npm)
+
+ process.chdir(npm.localPrefix)
init.exec([], err => {
- t.error(err, 'npm init -y')
- t.equal(result, '')
+ if (err)
+ throw err
+
+ t.end()
})
})
t.test('npm init <arg>', t => {
- t.plan(3)
- npm.commands.exec = (arr, cb) => {
- t.same(config.package, [], 'should set empty array value')
- t.same(
- arr,
- ['create-react-app'],
- 'should npx with listed packages'
- )
- cb()
- }
+ t.plan(1)
+ npm.localPrefix = t.testdir({})
+
+ const Init = t.mock('../../lib/init.js', {
+ libnpmexec: ({ args }) => {
+ t.same(
+ args,
+ ['create-react-app'],
+ 'should npx with listed packages'
+ )
+ },
+ })
+ const init = new Init(npm)
+
+ process.chdir(npm.localPrefix)
init.exec(['react-app'], err => {
- t.error(err, 'npm init react-app')
+ if (err)
+ throw err
+ })
+})
+
+t.test('npm init <arg> -- other-args', t => {
+ t.plan(1)
+ npm.localPrefix = t.testdir({})
+
+ const Init = t.mock('../../lib/init.js', {
+ libnpmexec: ({ args }) => {
+ t.same(
+ args,
+ ['create-react-app', 'my-path', '--some-option', 'some-value'],
+ 'should npm exec with expected args'
+ )
+ },
})
+ const init = new Init(npm)
+
+ process.chdir(npm.localPrefix)
+ init.exec(
+ ['react-app', 'my-path', '--some-option', 'some-value'],
+ err => {
+ if (err)
+ throw err
+ }
+ )
})
t.test('npm init @scope/name', t => {
- t.plan(2)
- npm.commands.exec = (arr, cb) => {
- t.same(
- arr,
- ['@npmcli/create-something'],
- 'should npx with scoped packages'
- )
- cb()
- }
+ t.plan(1)
+ npm.localPrefix = t.testdir({})
+
+ const Init = t.mock('../../lib/init.js', {
+ libnpmexec: ({ args }) => {
+ t.same(
+ args,
+ ['@npmcli/create-something'],
+ 'should npx with scoped packages'
+ )
+ },
+ })
+ const init = new Init(npm)
+
+ process.chdir(npm.localPrefix)
init.exec(['@npmcli/something'], err => {
- t.error(err, 'npm init init @scope/name')
+ if (err)
+ throw err
})
})
t.test('npm init git spec', t => {
- t.plan(2)
- npm.commands.exec = (arr, cb) => {
- t.same(
- arr,
- ['npm/create-something'],
- 'should npx with git-spec packages'
- )
- cb()
- }
+ t.plan(1)
+ npm.localPrefix = t.testdir({})
+
+ const Init = t.mock('../../lib/init.js', {
+ libnpmexec: ({ args }) => {
+ t.same(
+ args,
+ ['npm/create-something'],
+ 'should npx with git-spec packages'
+ )
+ },
+ })
+ const init = new Init(npm)
+
+ process.chdir(npm.localPrefix)
init.exec(['npm/something'], err => {
- t.error(err, 'npm init init @scope/name')
+ if (err)
+ throw err
})
})
t.test('npm init @scope', t => {
- t.plan(2)
- npm.commands.exec = (arr, cb) => {
- t.same(
- arr,
- ['@npmcli/create'],
- 'should npx with @scope/create pkgs'
- )
- cb()
- }
+ t.plan(1)
+ npm.localPrefix = t.testdir({})
+
+ const Init = t.mock('../../lib/init.js', {
+ libnpmexec: ({ args }) => {
+ t.same(
+ args,
+ ['@npmcli/create'],
+ 'should npx with @scope/create pkgs'
+ )
+ },
+ })
+ const init = new Init(npm)
+
+ process.chdir(npm.localPrefix)
init.exec(['@npmcli'], err => {
- t.error(err, 'npm init init @scope/create')
+ if (err)
+ throw err
})
})
t.test('npm init tgz', t => {
+ npm.localPrefix = t.testdir({})
+
+ process.chdir(npm.localPrefix)
init.exec(['something.tgz'], err => {
t.match(
err,
@@ -138,24 +214,38 @@ t.test('npm init tgz', t => {
})
t.test('npm init <arg>@next', t => {
- t.plan(2)
- npm.commands.exec = (arr, cb) => {
- t.same(
- arr,
- ['create-something@next'],
- 'should npx with something@next'
- )
- cb()
- }
+ t.plan(1)
+ npm.localPrefix = t.testdir({})
+
+ const Init = t.mock('../../lib/init.js', {
+ libnpmexec: ({ args }) => {
+ t.same(
+ args,
+ ['create-something@next'],
+ 'should npx with something@next'
+ )
+ },
+ })
+ const init = new Init(npm)
+
+ process.chdir(npm.localPrefix)
init.exec(['something@next'], err => {
- t.error(err, 'npm init init something@next')
+ if (err)
+ throw err
})
})
t.test('npm init exec error', t => {
- npm.commands.exec = (arr, cb) => {
- cb(new Error('ERROR'))
- }
+ npm.localPrefix = t.testdir({})
+
+ const Init = t.mock('../../lib/init.js', {
+ libnpmexec: async ({ args }) => {
+ throw new Error('ERROR')
+ },
+ })
+ const init = new Init(npm)
+
+ process.chdir(npm.localPrefix)
init.exec(['something@next'], err => {
t.match(
err,
@@ -167,23 +257,31 @@ t.test('npm init exec error', t => {
})
t.test('should not rewrite flatOptions', t => {
- t.plan(3)
- npm.commands.exec = (arr, cb) => {
- t.same(config.package, [], 'should set empty array value')
- t.same(
- arr,
- ['create-react-app', 'my-app'],
- 'should npx with extra args'
- )
- cb()
- }
+ t.plan(1)
+ npm.localPrefix = t.testdir({})
+
+ const Init = t.mock('../../lib/init.js', {
+ libnpmexec: async ({ args }) => {
+ t.same(
+ args,
+ ['create-react-app', 'my-app'],
+ 'should npx with extra args'
+ )
+ },
+ })
+ const init = new Init(npm)
+
+ process.chdir(npm.localPrefix)
init.exec(['react-app', 'my-app'], err => {
- t.error(err, 'npm init react-app')
+ if (err)
+ throw err
})
})
t.test('npm init cancel', t => {
- t.plan(3)
+ t.plan(2)
+ npm.localPrefix = t.testdir({})
+
const Init = t.mock('../../lib/init.js', {
...mocks,
'init-package-json': (dir, initFile, config, cb) => cb(
@@ -196,12 +294,17 @@ t.test('npm init cancel', t => {
t.equal(title, 'init', 'should have init title')
t.equal(msg, 'canceled', 'should log canceled')
}
+
+ process.chdir(npm.localPrefix)
init.exec([], err => {
- t.error(err, 'npm init cancel')
+ if (err)
+ throw err
})
})
t.test('npm init error', t => {
+ npm.localPrefix = t.testdir({})
+
const Init = t.mock('../../lib/init.js', {
...mocks,
'init-package-json': (dir, initFile, config, cb) => cb(
@@ -209,8 +312,204 @@ t.test('npm init error', t => {
),
})
const init = new Init(npm)
+
+ process.chdir(npm.localPrefix)
init.exec([], err => {
t.match(err, /Unknown Error/, 'should throw error')
t.end()
})
})
+
+t.test('workspaces', t => {
+ t.test('no args', t => {
+ npm.localPrefix = t.testdir({
+ 'package.json': JSON.stringify({
+ name: 'top-level',
+ }),
+ })
+
+ const Init = t.mock('../../lib/init.js', {
+ ...mocks,
+ 'init-package-json': (dir, initFile, config, cb) => {
+ t.equal(dir, resolve(npm.localPrefix, 'a'), 'should use the ws path')
+ cb()
+ },
+ })
+ const init = new Init(npm)
+ init.execWorkspaces([], ['a'], err => {
+ if (err)
+ throw err
+
+ t.matchSnapshot(result, 'should print helper info')
+ t.end()
+ })
+ })
+
+ t.test('no args, existing folder', t => {
+ // init-package-json prints directly to console.log
+ // this avoids poluting test output with those logs
+ console.log = noop
+
+ npm.localPrefix = t.testdir({
+ packages: {
+ a: {
+ 'package.json': JSON.stringify({
+ name: 'a',
+ version: '1.0.0',
+ }),
+ },
+ },
+ 'package.json': JSON.stringify({
+ name: 'top-level',
+ workspaces: ['packages/a'],
+ }),
+ })
+
+ init.execWorkspaces([], ['packages/a'], err => {
+ if (err)
+ throw err
+
+ t.matchSnapshot(result, 'should print helper info')
+ t.end()
+ })
+ })
+
+ t.test('with arg but missing workspace folder', t => {
+ // init-package-json prints directly to console.log
+ // this avoids poluting test output with those logs
+ console.log = noop
+
+ npm.localPrefix = t.testdir({
+ node_modules: {
+ a: t.fixture('symlink', '../a'),
+ 'create-index': {
+ 'index.js': ``,
+ },
+ },
+ a: {
+ 'package.json': JSON.stringify({
+ name: 'a',
+ version: '1.0.0',
+ }),
+ },
+ 'package.json': JSON.stringify({
+ name: 'top-level',
+ }),
+ })
+
+ init.execWorkspaces([], ['packages/a'], err => {
+ if (err)
+ throw err
+
+ t.matchSnapshot(result, 'should print helper info')
+ t.end()
+ })
+ })
+
+ t.test('fail parsing top-level package.json to set workspace', t => {
+ // init-package-json prints directly to console.log
+ // this avoids poluting test output with those logs
+ console.log = noop
+
+ npm.localPrefix = t.testdir({
+ 'package.json': JSON.stringify({
+ name: 'top-level',
+ }),
+ })
+
+ const Init = t.mock('../../lib/init.js', {
+ ...mocks,
+ 'json-parse-even-better-errors': () => {
+ throw new Error('ERR')
+ },
+ })
+ const init = new Init(npm)
+
+ init.execWorkspaces([], ['a'], err => {
+ t.match(
+ err,
+ /Invalid package.json: Error: ERR/,
+ 'should exit with error'
+ )
+ t.end()
+ })
+ })
+
+ t.test('missing top-level package.json when settting workspace', t => {
+ // init-package-json prints directly to console.log
+ // this avoids poluting test output with those logs
+ console.log = noop
+
+ npm.localPrefix = t.testdir({
+ 'package.json': JSON.stringify({
+ name: 'top-level',
+ }),
+ })
+
+ const Init = t.mock('../../lib/init.js', {
+ ...mocks,
+ fs: {
+ statSync () {
+ return true
+ },
+ readFileSync () {
+ throw new Error('ERR')
+ },
+ },
+ })
+ const init = new Init(npm)
+
+ init.execWorkspaces([], ['a'], err => {
+ t.match(
+ err,
+ /package.json not found/,
+ 'should exit with error'
+ )
+ t.end()
+ })
+ })
+
+ t.test('using args', t => {
+ npm.localPrefix = t.testdir({
+ b: {
+ 'package.json': JSON.stringify({
+ name: 'b',
+ }),
+ },
+ 'package.json': JSON.stringify({
+ name: 'top-level',
+ workspaces: ['b'],
+ }),
+ })
+
+ const Init = t.mock('../../lib/init.js', {
+ ...mocks,
+ libnpmexec: ({ args, path }) => {
+ t.same(
+ args,
+ ['create-react-app'],
+ 'should npx with listed packages'
+ )
+ t.same(
+ path,
+ resolve(npm.localPrefix, 'a'),
+ 'should use workspace path'
+ )
+ fs.writeFileSync(
+ resolve(npm.localPrefix, 'a/package.json'),
+ JSON.stringify({ name: 'a' })
+ )
+ },
+ })
+
+ const init = new Init(npm)
+ init.execWorkspaces(['react-app'], ['a'], err => {
+ if (err)
+ throw err
+
+ t.end()
+ })
+ })
+
+ t.end()
+})