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:
Diffstat (limited to 'lib/exec.js')
-rw-r--r--lib/exec.js302
1 files changed, 29 insertions, 273 deletions
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