update
This commit is contained in:
33
package/node_modules/libnpmexec/lib/file-exists.js
generated
vendored
Normal file
33
package/node_modules/libnpmexec/lib/file-exists.js
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
const { resolve } = require('node:path')
|
||||
const { stat } = require('node:fs/promises')
|
||||
const { walkUp } = require('walk-up-path')
|
||||
|
||||
const fileExists = async (file) => {
|
||||
try {
|
||||
const res = await stat(file)
|
||||
return res.isFile()
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const localFileExists = async (dir, binName, root) => {
|
||||
for (const path of walkUp(dir)) {
|
||||
const binDir = resolve(path, 'node_modules', '.bin')
|
||||
|
||||
if (await fileExists(resolve(binDir, binName))) {
|
||||
return binDir
|
||||
}
|
||||
|
||||
if (path.toLowerCase() === resolve(root).toLowerCase()) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
fileExists,
|
||||
localFileExists,
|
||||
}
|
22
package/node_modules/libnpmexec/lib/get-bin-from-manifest.js
generated
vendored
Normal file
22
package/node_modules/libnpmexec/lib/get-bin-from-manifest.js
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
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
|
296
package/node_modules/libnpmexec/lib/index.js
generated
vendored
Normal file
296
package/node_modules/libnpmexec/lib/index.js
generated
vendored
Normal file
@@ -0,0 +1,296 @@
|
||||
'use strict'
|
||||
|
||||
const { mkdir } = require('node:fs/promises')
|
||||
const Arborist = require('@npmcli/arborist')
|
||||
const ciInfo = require('ci-info')
|
||||
const crypto = require('node:crypto')
|
||||
const { log, input } = require('proc-log')
|
||||
const npa = require('npm-package-arg')
|
||||
const pacote = require('pacote')
|
||||
const { read } = require('read')
|
||||
const semver = require('semver')
|
||||
const { fileExists, localFileExists } = require('./file-exists.js')
|
||||
const getBinFromManifest = require('./get-bin-from-manifest.js')
|
||||
const noTTY = require('./no-tty.js')
|
||||
const runScript = require('./run-script.js')
|
||||
const isWindows = require('./is-windows.js')
|
||||
const { dirname, resolve } = require('node:path')
|
||||
|
||||
const binPaths = []
|
||||
|
||||
// when checking the local tree we look up manifests, cache those results by
|
||||
// spec.raw so we don't have to fetch again when we check npxCache
|
||||
const manifests = new Map()
|
||||
|
||||
const getManifest = async (spec, flatOptions) => {
|
||||
if (!manifests.has(spec.raw)) {
|
||||
const manifest = await pacote.manifest(spec, { ...flatOptions, preferOnline: true })
|
||||
manifests.set(spec.raw, manifest)
|
||||
}
|
||||
return manifests.get(spec.raw)
|
||||
}
|
||||
|
||||
// Returns the required manifest if the spec is missing from the tree
|
||||
// Returns the found node if it is in the tree
|
||||
const missingFromTree = async ({ spec, tree, flatOptions, isNpxTree, shallow }) => {
|
||||
// If asking for a spec by name only (spec.raw === spec.name):
|
||||
// - In local or global mode go with anything in the tree that matches
|
||||
// - If looking in the npx cache check if a newer version is available
|
||||
const npxByNameOnly = isNpxTree && spec.name === spec.raw
|
||||
if (spec.registry && spec.type !== 'tag' && !npxByNameOnly) {
|
||||
// registry spec that is not a specific tag.
|
||||
const nodesBySpec = tree.inventory.query('packageName', spec.name)
|
||||
for (const node of nodesBySpec) {
|
||||
// continue if node is not a top level node
|
||||
if (shallow && node.depth) {
|
||||
continue
|
||||
}
|
||||
if (spec.rawSpec === '*') {
|
||||
return { node }
|
||||
}
|
||||
// package requested by specific version
|
||||
if (spec.type === 'version' && (node.pkgid === spec.raw)) {
|
||||
return { node }
|
||||
}
|
||||
// package requested by version range, only remaining registry type
|
||||
if (semver.satisfies(node.package.version, spec.rawSpec)) {
|
||||
return { node }
|
||||
}
|
||||
}
|
||||
const manifest = await getManifest(spec, flatOptions)
|
||||
return { manifest }
|
||||
} else {
|
||||
// non-registry spec, or a specific tag, or name only in npx tree. Look up
|
||||
// manifest and check resolved to see if it's in the tree.
|
||||
const manifest = await getManifest(spec, flatOptions)
|
||||
if (spec.type === 'directory') {
|
||||
return { manifest }
|
||||
}
|
||||
const nodesByManifest = tree.inventory.query('packageName', manifest.name)
|
||||
for (const node of nodesByManifest) {
|
||||
if (node.package.resolved === manifest._resolved) {
|
||||
// we have a package by the same name and the same resolved destination, nothing to add.
|
||||
return { node }
|
||||
}
|
||||
}
|
||||
return { manifest }
|
||||
}
|
||||
}
|
||||
|
||||
// see if the package.json at `path` has an entry that matches `cmd`
|
||||
const hasPkgBin = (path, cmd, flatOptions) =>
|
||||
pacote.manifest(path, flatOptions)
|
||||
.then(manifest => manifest?.bin?.[cmd]).catch(() => null)
|
||||
|
||||
const exec = async (opts) => {
|
||||
const {
|
||||
args = [],
|
||||
call = '',
|
||||
localBin = resolve('./node_modules/.bin'),
|
||||
locationMsg = undefined,
|
||||
globalBin = '',
|
||||
globalPath,
|
||||
// dereference values because we manipulate it later
|
||||
packages: [...packages] = [],
|
||||
path = '.',
|
||||
runPath = '.',
|
||||
scriptShell = isWindows ? process.env.ComSpec || 'cmd' : 'sh',
|
||||
...flatOptions
|
||||
} = opts
|
||||
|
||||
let pkgPaths = opts.pkgPath
|
||||
if (typeof pkgPaths === 'string') {
|
||||
pkgPaths = [pkgPaths]
|
||||
}
|
||||
if (!pkgPaths) {
|
||||
pkgPaths = ['.']
|
||||
}
|
||||
let yes = opts.yes
|
||||
const run = () => runScript({
|
||||
args,
|
||||
call,
|
||||
flatOptions,
|
||||
locationMsg,
|
||||
path,
|
||||
binPaths,
|
||||
runPath,
|
||||
scriptShell,
|
||||
})
|
||||
|
||||
// interactive mode
|
||||
if (!call && !args.length && !packages.length) {
|
||||
return run()
|
||||
}
|
||||
|
||||
// Look in the local tree too
|
||||
pkgPaths.push(path)
|
||||
|
||||
let needPackageCommandSwap = (args.length > 0) && (packages.length === 0)
|
||||
// If they asked for a command w/o specifying a package, see if there is a
|
||||
// bin that directly matches that name:
|
||||
// - in any local packages (pkgPaths can have workspaces in them or just the root)
|
||||
// - in the local tree (path)
|
||||
// - globally
|
||||
if (needPackageCommandSwap) {
|
||||
// Local packages and local tree
|
||||
for (const p of pkgPaths) {
|
||||
if (await hasPkgBin(p, args[0], flatOptions)) {
|
||||
// we have to install the local package into the npx cache so that its
|
||||
// bin links get set up
|
||||
flatOptions.installLinks = false
|
||||
// args[0] will exist when the package is installed
|
||||
packages.push(p)
|
||||
yes = true
|
||||
needPackageCommandSwap = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if (needPackageCommandSwap) {
|
||||
// no bin entry in local packages or in tree, now we look for binPaths
|
||||
const dir = dirname(dirname(localBin))
|
||||
const localBinPath = await localFileExists(dir, args[0], '/')
|
||||
if (localBinPath) {
|
||||
binPaths.push(localBinPath)
|
||||
return await run()
|
||||
} else if (globalPath && await fileExists(`${globalBin}/${args[0]}`)) {
|
||||
binPaths.push(globalBin)
|
||||
return await run()
|
||||
}
|
||||
// We swap out args[0] with the bin from the manifest later
|
||||
packages.push(args[0])
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve any directory specs so that the npx directory is unique to the
|
||||
// resolved directory, not the potentially relative one (i.e. "npx .")
|
||||
for (const i in packages) {
|
||||
const pkg = packages[i]
|
||||
const spec = npa(pkg)
|
||||
if (spec.type === 'directory') {
|
||||
packages[i] = spec.fetchSpec
|
||||
}
|
||||
}
|
||||
|
||||
const localArb = new Arborist({ ...flatOptions, path })
|
||||
const localTree = await localArb.loadActual()
|
||||
|
||||
// Find anything that isn't installed locally
|
||||
const needInstall = []
|
||||
let commandManifest
|
||||
await Promise.all(packages.map(async (pkg, i) => {
|
||||
const spec = npa(pkg, path)
|
||||
const { manifest, node } = await missingFromTree({ spec, tree: localTree, flatOptions })
|
||||
if (manifest) {
|
||||
// Package does not exist in the local tree
|
||||
needInstall.push({ spec, manifest })
|
||||
if (i === 0) {
|
||||
commandManifest = manifest
|
||||
}
|
||||
} else if (i === 0) {
|
||||
// The node.package has enough to look up the bin
|
||||
commandManifest = node.package
|
||||
}
|
||||
}))
|
||||
|
||||
if (needPackageCommandSwap) {
|
||||
const spec = npa(args[0])
|
||||
|
||||
if (spec.type === 'directory') {
|
||||
yes = true
|
||||
}
|
||||
|
||||
args[0] = getBinFromManifest(commandManifest)
|
||||
|
||||
if (needInstall.length > 0 && globalPath) {
|
||||
// See if the package is installed globally, and run the translated bin
|
||||
const globalArb = new Arborist({ ...flatOptions, path: globalPath, global: true })
|
||||
const globalTree = await globalArb.loadActual()
|
||||
const { manifest: globalManifest } =
|
||||
await missingFromTree({ spec, tree: globalTree, flatOptions, shallow: true })
|
||||
if (!globalManifest && await fileExists(`${globalBin}/${args[0]}`)) {
|
||||
binPaths.push(globalBin)
|
||||
return await run()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const add = []
|
||||
if (needInstall.length > 0) {
|
||||
// Install things to the npx cache, if needed
|
||||
const { npxCache } = flatOptions
|
||||
if (!npxCache) {
|
||||
throw new Error('Must provide a valid npxCache path')
|
||||
}
|
||||
const hash = crypto.createHash('sha512')
|
||||
.update(packages.map(p => {
|
||||
// Keeps the npx directory unique to the resolved directory, not the
|
||||
// potentially relative one (i.e. "npx .")
|
||||
const spec = npa(p)
|
||||
if (spec.type === 'directory') {
|
||||
return spec.fetchSpec
|
||||
}
|
||||
return p
|
||||
}).sort((a, b) => a.localeCompare(b, 'en')).join('\n'))
|
||||
.digest('hex')
|
||||
.slice(0, 16)
|
||||
const installDir = resolve(npxCache, hash)
|
||||
await mkdir(installDir, { recursive: true })
|
||||
const npxArb = new Arborist({
|
||||
...flatOptions,
|
||||
path: installDir,
|
||||
})
|
||||
const npxTree = await npxArb.loadActual()
|
||||
await Promise.all(needInstall.map(async ({ spec }) => {
|
||||
const { manifest } = await missingFromTree({
|
||||
spec,
|
||||
tree: npxTree,
|
||||
flatOptions,
|
||||
isNpxTree: true,
|
||||
})
|
||||
if (manifest) {
|
||||
// Manifest is not in npxCache, we need to install it there
|
||||
if (!spec.registry) {
|
||||
add.push(manifest._from)
|
||||
} else {
|
||||
add.push(manifest._id)
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
if (add.length) {
|
||||
if (!yes) {
|
||||
const addList = add.map(a => `${a.replace(/@$/, '')}`)
|
||||
|
||||
// set -n to always say no
|
||||
if (yes === false) {
|
||||
// Error message lists missing package(s) when process is canceled
|
||||
/* eslint-disable-next-line max-len */
|
||||
throw new Error(`npx canceled due to missing packages and no YES option: ${JSON.stringify(addList)}`)
|
||||
}
|
||||
|
||||
if (noTTY() || ciInfo.isCI) {
|
||||
/* eslint-disable-next-line max-len */
|
||||
log.warn('exec', `The following package${add.length === 1 ? ' was' : 's were'} not found and will be installed: ${addList.join(', ')}`)
|
||||
} else {
|
||||
const confirm = await input.read(() => read({
|
||||
/* eslint-disable-next-line max-len */
|
||||
prompt: `Need to install the following packages:\n${addList.join('\n')}\nOk to proceed? `,
|
||||
default: 'y',
|
||||
}))
|
||||
if (confirm.trim().toLowerCase().charAt(0) !== 'y') {
|
||||
throw new Error('canceled')
|
||||
}
|
||||
}
|
||||
}
|
||||
await npxArb.reify({
|
||||
...flatOptions,
|
||||
add,
|
||||
})
|
||||
}
|
||||
binPaths.push(resolve(installDir, 'node_modules/.bin'))
|
||||
}
|
||||
|
||||
return await run()
|
||||
}
|
||||
|
||||
module.exports = exec
|
1
package/node_modules/libnpmexec/lib/is-windows.js
generated
vendored
Normal file
1
package/node_modules/libnpmexec/lib/is-windows.js
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = process.platform === 'win32'
|
1
package/node_modules/libnpmexec/lib/no-tty.js
generated
vendored
Normal file
1
package/node_modules/libnpmexec/lib/no-tty.js
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = () => !process.stdin.isTTY
|
61
package/node_modules/libnpmexec/lib/run-script.js
generated
vendored
Normal file
61
package/node_modules/libnpmexec/lib/run-script.js
generated
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
const ciInfo = require('ci-info')
|
||||
const runScript = require('@npmcli/run-script')
|
||||
const readPackageJson = require('read-package-json-fast')
|
||||
const { log, output } = require('proc-log')
|
||||
const noTTY = require('./no-tty.js')
|
||||
|
||||
const run = async ({
|
||||
args,
|
||||
call,
|
||||
flatOptions,
|
||||
locationMsg,
|
||||
path,
|
||||
binPaths,
|
||||
runPath,
|
||||
scriptShell,
|
||||
}) => {
|
||||
// turn list of args into command string
|
||||
const script = call || args.shift() || scriptShell
|
||||
|
||||
// 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 (script === scriptShell) {
|
||||
if (!noTTY()) {
|
||||
if (ciInfo.isCI) {
|
||||
return log.warn('exec', 'Interactive mode disabled in CI environment')
|
||||
}
|
||||
|
||||
const { chalk } = flatOptions
|
||||
|
||||
output.standard(`${
|
||||
chalk.reset('\nEntering npm script environment')
|
||||
}${
|
||||
chalk.reset(locationMsg || ` at location:\n${chalk.dim(runPath)}`)
|
||||
}${
|
||||
chalk.bold('\nType \'exit\' or ^D when finished\n')
|
||||
}`)
|
||||
}
|
||||
}
|
||||
return runScript({
|
||||
...flatOptions,
|
||||
pkg,
|
||||
// we always run in cwd, not --prefix
|
||||
path: runPath,
|
||||
binPaths,
|
||||
event: 'npx',
|
||||
args,
|
||||
stdio: 'inherit',
|
||||
scriptShell,
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = run
|
Reference in New Issue
Block a user