libnpmexec 4.0.8 → 4.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/file-exists.js +12 -10
- package/lib/index.js +158 -105
- package/lib/run-script.js +3 -6
- package/package.json +4 -2
- package/lib/cache-install-dir.js +0 -20
package/lib/file-exists.js
CHANGED
|
@@ -1,23 +1,25 @@
|
|
|
1
1
|
const { resolve } = require('path')
|
|
2
|
-
const
|
|
3
|
-
const stat = promisify(require('fs').stat)
|
|
2
|
+
const fs = require('@npmcli/fs')
|
|
4
3
|
const walkUp = require('walk-up-path')
|
|
5
4
|
|
|
6
|
-
const fileExists = (file) =>
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
5
|
+
const fileExists = async (file) => {
|
|
6
|
+
try {
|
|
7
|
+
const res = await fs.stat(file)
|
|
8
|
+
return res.isFile()
|
|
9
|
+
} catch {
|
|
10
|
+
return false
|
|
11
|
+
}
|
|
12
|
+
}
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
const localFileExists = async (dir, binName, root) => {
|
|
15
|
+
for (const path of walkUp(dir)) {
|
|
14
16
|
const binDir = resolve(path, 'node_modules', '.bin')
|
|
15
17
|
|
|
16
18
|
if (await fileExists(resolve(binDir, binName))) {
|
|
17
19
|
return binDir
|
|
18
20
|
}
|
|
19
21
|
|
|
20
|
-
if (path.toLowerCase() === root) {
|
|
22
|
+
if (path.toLowerCase() === resolve(root).toLowerCase()) {
|
|
21
23
|
return false
|
|
22
24
|
}
|
|
23
25
|
}
|
package/lib/index.js
CHANGED
|
@@ -1,27 +1,81 @@
|
|
|
1
|
-
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
2
3
|
const { promisify } = require('util')
|
|
3
|
-
const read = promisify(require('read'))
|
|
4
4
|
|
|
5
5
|
const Arborist = require('@npmcli/arborist')
|
|
6
6
|
const ciDetect = require('@npmcli/ci-detect')
|
|
7
|
+
const crypto = require('crypto')
|
|
7
8
|
const log = require('proc-log')
|
|
8
|
-
const npmlog = require('npmlog')
|
|
9
9
|
const mkdirp = require('mkdirp-infer-owner')
|
|
10
10
|
const npa = require('npm-package-arg')
|
|
11
|
+
const npmlog = require('npmlog')
|
|
11
12
|
const pacote = require('pacote')
|
|
13
|
+
const read = promisify(require('read'))
|
|
14
|
+
const semver = require('semver')
|
|
12
15
|
|
|
13
|
-
const cacheInstallDir = require('./cache-install-dir.js')
|
|
14
16
|
const { fileExists, localFileExists } = require('./file-exists.js')
|
|
15
17
|
const getBinFromManifest = require('./get-bin-from-manifest.js')
|
|
16
18
|
const noTTY = require('./no-tty.js')
|
|
17
19
|
const runScript = require('./run-script.js')
|
|
18
20
|
const isWindows = require('./is-windows.js')
|
|
19
|
-
const _localManifest = Symbol('localManifest')
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
const { dirname, resolve } = require('path')
|
|
23
|
+
|
|
24
|
+
const binPaths = []
|
|
25
|
+
|
|
26
|
+
// when checking the local tree we look up manifests, cache those results by
|
|
27
|
+
// spec.raw so we don't have to fetch again when we check npxCache
|
|
28
|
+
const manifests = new Map()
|
|
29
|
+
|
|
30
|
+
const getManifest = async (spec, flatOptions) => {
|
|
31
|
+
if (!manifests.has(spec.raw)) {
|
|
32
|
+
const manifest = await pacote.manifest(spec, { ...flatOptions, preferOnline: true })
|
|
33
|
+
manifests.set(spec.raw, manifest)
|
|
34
|
+
}
|
|
35
|
+
return manifests.get(spec.raw)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Returns the required manifest if the spec is missing from the tree
|
|
39
|
+
// Returns the found node if it is in the tree
|
|
40
|
+
const missingFromTree = async ({ spec, tree, flatOptions }) => {
|
|
41
|
+
if (spec.registry && (spec.rawSpec === '' || spec.type !== 'tag')) {
|
|
42
|
+
// registry spec that is not a specific tag.
|
|
43
|
+
const nodesBySpec = tree.inventory.query('packageName', spec.name)
|
|
44
|
+
for (const node of nodesBySpec) {
|
|
45
|
+
if (spec.type === 'tag') {
|
|
46
|
+
// package requested by name only
|
|
47
|
+
return { node }
|
|
48
|
+
} else if (spec.type === 'version') {
|
|
49
|
+
// package requested by specific version
|
|
50
|
+
if (node.pkgid === spec.raw) {
|
|
51
|
+
return { node }
|
|
52
|
+
}
|
|
53
|
+
} else {
|
|
54
|
+
// package requested by version range, only remaining registry type
|
|
55
|
+
if (semver.satisfies(node.package.version, spec.rawSpec)) {
|
|
56
|
+
return { node }
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const manifest = await getManifest(spec, flatOptions)
|
|
61
|
+
return { manifest }
|
|
62
|
+
} else {
|
|
63
|
+
// non-registry spec, or a specific tag. Look up manifest and check
|
|
64
|
+
// resolved to see if it's in the tree.
|
|
65
|
+
const manifest = await getManifest(spec, flatOptions)
|
|
66
|
+
if (spec.type === 'directory') {
|
|
67
|
+
return { manifest }
|
|
68
|
+
}
|
|
69
|
+
const nodesByManifest = tree.inventory.query('packageName', manifest.name)
|
|
70
|
+
for (const node of nodesByManifest) {
|
|
71
|
+
if (node.package.resolved === manifest._resolved) {
|
|
72
|
+
// we have a package by the same name and the same resolved destination, nothing to add.
|
|
73
|
+
return { node }
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return { manifest }
|
|
77
|
+
}
|
|
78
|
+
}
|
|
25
79
|
|
|
26
80
|
const exec = async (opts) => {
|
|
27
81
|
const {
|
|
@@ -31,19 +85,18 @@ const exec = async (opts) => {
|
|
|
31
85
|
localBin = resolve('./node_modules/.bin'),
|
|
32
86
|
locationMsg = undefined,
|
|
33
87
|
globalBin = '',
|
|
88
|
+
globalPath,
|
|
34
89
|
output,
|
|
35
|
-
|
|
90
|
+
// dereference values because we manipulate it later
|
|
91
|
+
packages: [...packages] = [],
|
|
36
92
|
path = '.',
|
|
37
93
|
runPath = '.',
|
|
38
94
|
scriptShell = isWindows ? process.env.ComSpec || 'cmd' : 'sh',
|
|
39
|
-
yes = undefined,
|
|
40
95
|
...flatOptions
|
|
41
96
|
} = opts
|
|
42
97
|
|
|
43
|
-
|
|
44
|
-
const
|
|
45
|
-
const pathArr = [...PATH]
|
|
46
|
-
const _run = () => runScript({
|
|
98
|
+
let yes = opts.yes
|
|
99
|
+
const run = () => runScript({
|
|
47
100
|
args,
|
|
48
101
|
call,
|
|
49
102
|
color,
|
|
@@ -51,125 +104,125 @@ const exec = async (opts) => {
|
|
|
51
104
|
locationMsg,
|
|
52
105
|
output,
|
|
53
106
|
path,
|
|
54
|
-
|
|
107
|
+
binPaths,
|
|
55
108
|
runPath,
|
|
56
109
|
scriptShell,
|
|
57
110
|
})
|
|
58
111
|
|
|
59
|
-
//
|
|
112
|
+
// interactive mode
|
|
60
113
|
if (!call && !args.length && !packages.length) {
|
|
61
|
-
return
|
|
114
|
+
return run()
|
|
62
115
|
}
|
|
63
116
|
|
|
64
|
-
const needPackageCommandSwap = args.length &&
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
// the argument and run it if it exists, otherwise fall through to
|
|
68
|
-
// the behavior of treating the single argument as a package name
|
|
117
|
+
const needPackageCommandSwap = (args.length > 0) && (packages.length === 0)
|
|
118
|
+
// If they asked for a command w/o specifying a package, see if there is a
|
|
119
|
+
// bin that directly matches that name either globally or in the local tree.
|
|
69
120
|
if (needPackageCommandSwap) {
|
|
70
|
-
let binExists = false
|
|
71
121
|
const dir = dirname(dirname(localBin))
|
|
72
|
-
const localBinPath = await localFileExists(dir, args[0])
|
|
122
|
+
const localBinPath = await localFileExists(dir, args[0], '/')
|
|
73
123
|
if (localBinPath) {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
} else if (await fileExists(`${globalBin}/${args[0]}`)) {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
if (binExists) {
|
|
82
|
-
return await _run()
|
|
124
|
+
binPaths.push(localBinPath)
|
|
125
|
+
return await run()
|
|
126
|
+
} else if (globalPath && await fileExists(`${globalBin}/${args[0]}`)) {
|
|
127
|
+
binPaths.push(globalBin)
|
|
128
|
+
return await run()
|
|
83
129
|
}
|
|
84
130
|
|
|
131
|
+
// We swap out args[0] with the bin from the manifest later
|
|
85
132
|
packages.push(args[0])
|
|
86
133
|
}
|
|
87
134
|
|
|
88
|
-
//
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
const getLocalManifest = ({ tree, name }) => {
|
|
96
|
-
// look up the package name in the current tree inventory,
|
|
97
|
-
// if it's found then return that normalized pkg data
|
|
98
|
-
const [node] = tree.inventory.query('packageName', name)
|
|
99
|
-
|
|
100
|
-
if (node) {
|
|
101
|
-
return {
|
|
102
|
-
_id: node.pkgid,
|
|
103
|
-
...node.package,
|
|
104
|
-
[_localManifest]: true,
|
|
105
|
-
}
|
|
135
|
+
// Resolve any directory specs so that the npx directory is unique to the
|
|
136
|
+
// resolved directory, not the potentially relative one (i.e. "npx .")
|
|
137
|
+
for (const i in packages) {
|
|
138
|
+
const pkg = packages[i]
|
|
139
|
+
const spec = npa(pkg)
|
|
140
|
+
if (spec.type === 'directory') {
|
|
141
|
+
packages[i] = spec.fetchSpec
|
|
106
142
|
}
|
|
107
143
|
}
|
|
108
144
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
//
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
})
|
|
121
|
-
if (
|
|
122
|
-
|
|
145
|
+
const localArb = new Arborist({ ...flatOptions, path })
|
|
146
|
+
const localTree = await localArb.loadActual()
|
|
147
|
+
|
|
148
|
+
// Find anything that isn't installed locally
|
|
149
|
+
const needInstall = []
|
|
150
|
+
let commandManifest
|
|
151
|
+
await Promise.all(packages.map(async (pkg, i) => {
|
|
152
|
+
const spec = npa(pkg, path)
|
|
153
|
+
const { manifest, node } = await missingFromTree({ spec, tree: localTree, flatOptions })
|
|
154
|
+
if (manifest) {
|
|
155
|
+
// Package does not exist in the local tree
|
|
156
|
+
needInstall.push({ spec, manifest })
|
|
157
|
+
if (i === 0) {
|
|
158
|
+
commandManifest = manifest
|
|
123
159
|
}
|
|
160
|
+
} else if (i === 0) {
|
|
161
|
+
// The node.package has enough to look up the bin
|
|
162
|
+
commandManifest = node.package
|
|
124
163
|
}
|
|
125
|
-
// Force preferOnline to true so we are making sure to pull in the latest
|
|
126
|
-
// This is especially useful if the user didn't give us a version, and
|
|
127
|
-
// they expect to be running @latest
|
|
128
|
-
return await pacote.manifest(p, {
|
|
129
|
-
...flatOptions,
|
|
130
|
-
preferOnline: true,
|
|
131
|
-
})
|
|
132
164
|
}))
|
|
133
165
|
|
|
134
166
|
if (needPackageCommandSwap) {
|
|
135
|
-
|
|
136
|
-
}
|
|
167
|
+
const spec = npa(args[0])
|
|
137
168
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
169
|
+
if (spec.type === 'directory') {
|
|
170
|
+
yes = true
|
|
171
|
+
}
|
|
141
172
|
|
|
142
|
-
|
|
173
|
+
args[0] = getBinFromManifest(commandManifest)
|
|
174
|
+
|
|
175
|
+
if (needInstall.length > 0 && globalPath) {
|
|
176
|
+
// See if the package is installed globally, and run the translated bin
|
|
177
|
+
const globalArb = new Arborist({ ...flatOptions, path: globalPath, global: true })
|
|
178
|
+
const globalTree = await globalArb.loadActual()
|
|
179
|
+
const { manifest: globalManifest } =
|
|
180
|
+
await missingFromTree({ spec, tree: globalTree, flatOptions })
|
|
181
|
+
if (!globalManifest && await fileExists(`${globalBin}/${args[0]}`)) {
|
|
182
|
+
binPaths.push(globalBin)
|
|
183
|
+
return await run()
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const add = []
|
|
189
|
+
if (needInstall.length > 0) {
|
|
190
|
+
// Install things to the npx cache, if needed
|
|
143
191
|
const { npxCache } = flatOptions
|
|
144
|
-
|
|
192
|
+
if (!npxCache) {
|
|
193
|
+
throw new Error('Must provide a valid npxCache path')
|
|
194
|
+
}
|
|
195
|
+
const hash = crypto.createHash('sha512')
|
|
196
|
+
.update(packages.map(p => {
|
|
197
|
+
// Keeps the npx directory unique to the resolved directory, not the
|
|
198
|
+
// potentially relative one (i.e. "npx .")
|
|
199
|
+
const spec = npa(p)
|
|
200
|
+
if (spec.type === 'directory') {
|
|
201
|
+
return spec.fetchSpec
|
|
202
|
+
}
|
|
203
|
+
return p
|
|
204
|
+
}).sort((a, b) => a.localeCompare(b, 'en')).join('\n'))
|
|
205
|
+
.digest('hex')
|
|
206
|
+
.slice(0, 16)
|
|
207
|
+
const installDir = resolve(npxCache, hash)
|
|
145
208
|
await mkdirp(installDir)
|
|
146
|
-
const
|
|
209
|
+
const npxArb = new Arborist({
|
|
147
210
|
...flatOptions,
|
|
148
211
|
path: installDir,
|
|
149
212
|
})
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
213
|
+
const npxTree = await npxArb.loadActual()
|
|
214
|
+
await Promise.all(needInstall.map(async ({ spec }) => {
|
|
215
|
+
const { manifest } = await missingFromTree({ spec, tree: npxTree, flatOptions })
|
|
216
|
+
if (manifest) {
|
|
217
|
+
// Manifest is not in npxCache, we need to install it there
|
|
218
|
+
if (!spec.registry) {
|
|
219
|
+
add.push(manifest._from)
|
|
220
|
+
} else {
|
|
221
|
+
add.push(manifest._id)
|
|
222
|
+
}
|
|
159
223
|
}
|
|
160
|
-
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// at this point, we have to ensure that we get the exact same
|
|
164
|
-
// version, because it's something that has only ever been installed
|
|
165
|
-
// by npm exec in the cache install directory
|
|
166
|
-
const add = manis
|
|
167
|
-
.filter(mani => !mani[_localManifest])
|
|
168
|
-
.filter(filterMissingPackagesFromInstallDir)
|
|
169
|
-
.map(mani => mani._id || mani._from)
|
|
170
|
-
.sort((a, b) => a.localeCompare(b, 'en'))
|
|
224
|
+
}))
|
|
171
225
|
|
|
172
|
-
// no need to install if already present
|
|
173
226
|
if (add.length) {
|
|
174
227
|
if (!yes) {
|
|
175
228
|
// set -n to always say no
|
|
@@ -196,15 +249,15 @@ const exec = async (opts) => {
|
|
|
196
249
|
}
|
|
197
250
|
}
|
|
198
251
|
}
|
|
199
|
-
await
|
|
252
|
+
await npxArb.reify({
|
|
200
253
|
...flatOptions,
|
|
201
254
|
add,
|
|
202
255
|
})
|
|
203
256
|
}
|
|
204
|
-
|
|
257
|
+
binPaths.push(resolve(installDir, 'node_modules/.bin'))
|
|
205
258
|
}
|
|
206
259
|
|
|
207
|
-
return await
|
|
260
|
+
return await run()
|
|
208
261
|
}
|
|
209
262
|
|
|
210
263
|
module.exports = exec
|
package/lib/run-script.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
const { delimiter } = require('path')
|
|
2
|
-
|
|
3
1
|
const chalk = require('chalk')
|
|
4
2
|
const ciDetect = require('@npmcli/ci-detect')
|
|
5
3
|
const runScript = require('@npmcli/run-script')
|
|
@@ -22,7 +20,7 @@ const run = async ({
|
|
|
22
20
|
locationMsg,
|
|
23
21
|
output = () => {},
|
|
24
22
|
path,
|
|
25
|
-
|
|
23
|
+
binPaths,
|
|
26
24
|
runPath,
|
|
27
25
|
scriptShell,
|
|
28
26
|
}) => {
|
|
@@ -71,12 +69,11 @@ const run = async ({
|
|
|
71
69
|
// we always run in cwd, not --prefix
|
|
72
70
|
path: runPath,
|
|
73
71
|
stdioString: true,
|
|
72
|
+
binPaths,
|
|
74
73
|
event: 'npx',
|
|
75
74
|
args,
|
|
76
|
-
env: {
|
|
77
|
-
PATH: pathArr.join(delimiter),
|
|
78
|
-
},
|
|
79
75
|
stdio: 'inherit',
|
|
76
|
+
scriptShell,
|
|
80
77
|
})
|
|
81
78
|
} finally {
|
|
82
79
|
npmlog.enableProgress()
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "libnpmexec",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.11",
|
|
4
4
|
"files": [
|
|
5
5
|
"bin/",
|
|
6
6
|
"lib/"
|
|
@@ -57,7 +57,8 @@
|
|
|
57
57
|
"dependencies": {
|
|
58
58
|
"@npmcli/arborist": "^5.0.0",
|
|
59
59
|
"@npmcli/ci-detect": "^2.0.0",
|
|
60
|
-
"@npmcli/
|
|
60
|
+
"@npmcli/fs": "^2.1.1",
|
|
61
|
+
"@npmcli/run-script": "^4.2.0",
|
|
61
62
|
"chalk": "^4.1.0",
|
|
62
63
|
"mkdirp-infer-owner": "^2.0.0",
|
|
63
64
|
"npm-package-arg": "^9.0.1",
|
|
@@ -66,6 +67,7 @@
|
|
|
66
67
|
"proc-log": "^2.0.0",
|
|
67
68
|
"read": "^1.0.7",
|
|
68
69
|
"read-package-json-fast": "^2.0.2",
|
|
70
|
+
"semver": "^7.3.7",
|
|
69
71
|
"walk-up-path": "^1.0.0"
|
|
70
72
|
},
|
|
71
73
|
"templateOSS": {
|
package/lib/cache-install-dir.js
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
const crypto = require('crypto')
|
|
2
|
-
|
|
3
|
-
const { resolve } = require('path')
|
|
4
|
-
|
|
5
|
-
const cacheInstallDir = ({ npxCache, packages }) => {
|
|
6
|
-
if (!npxCache) {
|
|
7
|
-
throw new Error('Must provide a valid npxCache path')
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
// only packages not found in ${prefix}/node_modules
|
|
11
|
-
return resolve(npxCache, getHash(packages))
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const getHash = (packages) =>
|
|
15
|
-
crypto.createHash('sha512')
|
|
16
|
-
.update(packages.sort((a, b) => a.localeCompare(b, 'en')).join('\n'))
|
|
17
|
-
.digest('hex')
|
|
18
|
-
.slice(0, 16)
|
|
19
|
-
|
|
20
|
-
module.exports = cacheInstallDir
|