-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathscript.ts
More file actions
131 lines (118 loc) · 4.5 KB
/
script.ts
File metadata and controls
131 lines (118 loc) · 4.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
/**
* @file Cross-tool script runner — picks the right package manager by detecting
* the nearest lockfile and dispatches to its exec function. Falls back to
* running `node --run` directly when no lockfile is found. Lockfile
* precedence (first match wins, walking up from cwd):
*
* 1. pnpm-lock.yaml → execPnpm(['run', scriptName, ...args])
* 2. package-lock.json → execNpm(['run', scriptName, ...args])
* 3. yarn.lock → execYarn(['run', scriptName, ...args])
* 4. (no lockfile) → node --run scriptName (or `node <npm-cli> run` on older
* Node where `node --run` isn't available) Honors `shell: true` by passing
* through to spawn() unchanged.
*/
import process from 'node:process'
import {
NPM_REAL_EXEC_PATH,
PACKAGE_LOCK_JSON,
PNPM_LOCK_YAML,
YARN_LOCK,
} from '../../constants/agents'
import {
getExecPath,
getNodeNoWarningsFlags,
supportsNodeRun,
} from '../../constants/node'
import { findUpSync } from '../../fs/find-up'
import { getOwn } from '../../objects/inspect'
import { ArrayIsArray } from '../../primordials/array'
import { ErrorCtor } from '../../primordials/error'
import { spawn } from '../../process/spawn/child'
import { execNpm } from './npm/exec'
import { execPnpm } from './pnpm/exec'
import { execYarn } from './yarnpkg/yarn/exec'
import type { SpawnOptions } from '../../process/spawn/types'
export interface ExecScriptOptions extends SpawnOptions {
prepost?: boolean | undefined
}
/**
* Execute a package.json script using the detected package manager.
*
* @param scriptName - The package.json script to run.
* @param args - Either the script arguments or an options object.
* @param options - Spawn options plus `prepost` to force npm-style pre/post
* scripts.
*
* @returns The spawned `ChildProcess`-like promise from the underlying runner.
*/
export function execScript(
scriptName: string,
args?: string[] | readonly string[] | ExecScriptOptions | undefined,
options?: ExecScriptOptions | undefined,
) {
// Overloaded signatures: execScript(name, options) or execScript(name, args, options).
let resolvedOptions: ExecScriptOptions | undefined
let resolvedArgs: string[]
if (!ArrayIsArray(args) && args !== null && typeof args === 'object') {
resolvedOptions = args as ExecScriptOptions
resolvedArgs = []
} else {
resolvedOptions = options
resolvedArgs = (args || []) as string[]
}
const { prepost, ...spawnOptions } = {
__proto__: null,
...resolvedOptions,
} as ExecScriptOptions
// shell: true bypasses agent detection — caller wants direct execution.
if (spawnOptions.shell === true) {
return spawn(scriptName, resolvedArgs, spawnOptions)
}
const useNodeRun = !prepost && supportsNodeRun()
const cwd =
(getOwn(spawnOptions, 'cwd') as string | undefined) ?? process.cwd()
const pnpmLockPath = findUpSync(PNPM_LOCK_YAML, { cwd }) as string | undefined
if (pnpmLockPath) {
return execPnpm(['run', scriptName, ...resolvedArgs], spawnOptions)
}
// package-lock.json and yarn.lock fallback paths fire only in
// npm/yarn workspaces; the fleet uses pnpm, so the pnpm-lock branch
// hits and these are unreachable in fleet test runs.
/* c8 ignore start */
const packageLockPath = findUpSync(PACKAGE_LOCK_JSON, { cwd }) as
| string
| undefined
if (packageLockPath) {
return execNpm(['run', scriptName, ...resolvedArgs], spawnOptions)
}
const yarnLockPath = findUpSync(YARN_LOCK, { cwd }) as string | undefined
if (yarnLockPath) {
return execYarn(['run', scriptName, ...resolvedArgs], spawnOptions)
}
/* c8 ignore stop */
// No-lockfile fallback. findUpSync walks ancestor directories, so
// reaching this in unit tests requires a tmpdir whose ancestors have
// no pnpm-lock/package-lock/yarn.lock.
/* c8 ignore start */
// When `node --run` isn't usable we shell out to npm's real exec path;
// if npm itself can't be resolved there's no runner left, so fail with
// a clear message instead of spawning with an `undefined` argv entry.
if (!useNodeRun && !NPM_REAL_EXEC_PATH) {
throw new ErrorCtor(
`Cannot run script "${scriptName}": no npm executable found and ` +
'this Node.js lacks `node --run`. Install npm or upgrade to Node ' +
'22.5+ for `--run` support.',
)
}
const runnerArgs = useNodeRun
? ['--run']
: [NPM_REAL_EXEC_PATH as string, 'run']
return spawn(
getExecPath(),
[...getNodeNoWarningsFlags(), ...runnerArgs, scriptName, ...resolvedArgs],
{
...spawnOptions,
},
)
/* c8 ignore stop */
}