@@ -90,6 +90,19 @@ const yarnInstallLikeCommands = new Set([
9090 'import' ,
9191] )
9292
93+ export interface PnpmOptions extends SpawnOptions {
94+ allowLockfileUpdate ?: boolean
95+ }
96+
97+ export interface ExecScriptOptions extends SpawnOptions {
98+ prepost ?: boolean | undefined
99+ }
100+
101+ /**
102+ * Alias for isNpmLoglevelFlag for pnpm usage.
103+ */
104+ export const isPnpmLoglevelFlag = isNpmLoglevelFlag
105+
93106/**
94107 * Execute npm commands with optimized flags and settings.
95108 *
@@ -154,10 +167,6 @@ export function execNpm(args: string[], options?: SpawnOptions | undefined) {
154167 )
155168}
156169
157- export interface PnpmOptions extends SpawnOptions {
158- allowLockfileUpdate ?: boolean
159- }
160-
161170/**
162171 * Execute pnpm commands with optimized flags and settings.
163172 *
@@ -231,6 +240,83 @@ export function execPnpm(args: string[], options?: PnpmOptions | undefined) {
231240 )
232241}
233242
243+ /**
244+ * Execute a package.json script using the detected package manager.
245+ * Picks pnpm, npm, or yarn by walking up to the nearest lockfile; falls back
246+ * to running `node --run` or `npm run` directly when no lockfile is found.
247+ * Honors `shell: true` by passing through to `spawn()` unchanged.
248+ *
249+ * @param scriptName - The package.json script to run
250+ * @param args - Either the script arguments or an options object
251+ * @param options - Spawn options plus `prepost` to force npm-style pre/post scripts
252+ * @returns The spawned `ChildProcess`-like promise from the underlying runner.
253+ */
254+ export function execScript (
255+ scriptName : string ,
256+ args ?: string [ ] | readonly string [ ] | ExecScriptOptions | undefined ,
257+ options ?: ExecScriptOptions | undefined ,
258+ ) {
259+ // Handle overloaded signatures: execScript(name, options) or execScript(name, args, options).
260+ let resolvedOptions : ExecScriptOptions | undefined
261+ let resolvedArgs : string [ ]
262+ if ( ! Array . isArray ( args ) && args !== null && typeof args === 'object' ) {
263+ resolvedOptions = args as ExecScriptOptions
264+ resolvedArgs = [ ]
265+ } else {
266+ resolvedOptions = options
267+ resolvedArgs = ( args || [ ] ) as string [ ]
268+ }
269+ const { prepost, ...spawnOptions } = {
270+ __proto__ : null ,
271+ ...resolvedOptions ,
272+ } as ExecScriptOptions
273+
274+ // If shell: true is passed, run the command directly as a shell command.
275+ if ( spawnOptions . shell === true ) {
276+ return spawn ( scriptName , resolvedArgs , spawnOptions )
277+ }
278+
279+ const useNodeRun = ! prepost && supportsNodeRun ( )
280+
281+ // Detect package manager based on lockfile by traversing up from current directory.
282+ const cwd =
283+ ( getOwn ( spawnOptions , 'cwd' ) as string | undefined ) ?? process . cwd ( )
284+
285+ // Check for pnpm-lock.yaml.
286+ const pnpmLockPath = findUpSync ( PNPM_LOCK_YAML , { cwd } ) as string | undefined
287+ if ( pnpmLockPath ) {
288+ return execPnpm ( [ 'run' , scriptName , ...resolvedArgs ] , spawnOptions )
289+ }
290+
291+ // Check for package-lock.json.
292+ // When in an npm workspace, use npm run to ensure workspace binaries are available.
293+ const packageLockPath = findUpSync ( PACKAGE_LOCK_JSON , { cwd } ) as
294+ | string
295+ | undefined
296+ if ( packageLockPath ) {
297+ return execNpm ( [ 'run' , scriptName , ...resolvedArgs ] , spawnOptions )
298+ }
299+
300+ // Check for yarn.lock.
301+ const yarnLockPath = findUpSync ( YARN_LOCK , { cwd } ) as string | undefined
302+ if ( yarnLockPath ) {
303+ return execYarn ( [ 'run' , scriptName , ...resolvedArgs ] , spawnOptions )
304+ }
305+
306+ return spawn (
307+ getExecPath ( ) ,
308+ [
309+ ...getNodeNoWarningsFlags ( ) ,
310+ ...( useNodeRun ? [ '--run' ] : [ NPM_REAL_EXEC_PATH , 'run' ] ) ,
311+ scriptName ,
312+ ...resolvedArgs ,
313+ ] ,
314+ {
315+ ...spawnOptions ,
316+ } ,
317+ )
318+ }
319+
234320/**
235321 * Execute yarn commands with optimized flags and settings.
236322 *
@@ -374,33 +460,33 @@ export function isNpmProgressFlag(cmdArg: string): boolean {
374460}
375461
376462/**
377- * Check if a command argument is a pnpm ignore-scripts flag.
463+ * Check if a command argument is a pnpm frozen-lockfile flag.
378464 *
379465 * @example
380466 * ```typescript
381- * isPnpmIgnoreScriptsFlag ('--ignore-scripts ') // true
382- * isPnpmIgnoreScriptsFlag ('--no-ignore-scripts ') // true
383- * isPnpmIgnoreScriptsFlag ('--save') // false
467+ * isPnpmFrozenLockfileFlag ('--frozen-lockfile ') // true
468+ * isPnpmFrozenLockfileFlag ('--no-frozen-lockfile ') // true
469+ * isPnpmFrozenLockfileFlag ('--save') // false
384470 * ```
385471 */
386472/*@__NO_SIDE_EFFECTS__ */
387- export function isPnpmIgnoreScriptsFlag ( cmdArg : string ) : boolean {
388- return pnpmIgnoreScriptsFlags . has ( cmdArg )
473+ export function isPnpmFrozenLockfileFlag ( cmdArg : string ) : boolean {
474+ return pnpmFrozenLockfileFlags . has ( cmdArg )
389475}
390476
391477/**
392- * Check if a command argument is a pnpm frozen-lockfile flag.
478+ * Check if a command argument is a pnpm ignore-scripts flag.
393479 *
394480 * @example
395481 * ```typescript
396- * isPnpmFrozenLockfileFlag ('--frozen-lockfile ') // true
397- * isPnpmFrozenLockfileFlag ('--no-frozen-lockfile ') // true
398- * isPnpmFrozenLockfileFlag ('--save') // false
482+ * isPnpmIgnoreScriptsFlag ('--ignore-scripts ') // true
483+ * isPnpmIgnoreScriptsFlag ('--no-ignore-scripts ') // true
484+ * isPnpmIgnoreScriptsFlag ('--save') // false
399485 * ```
400486 */
401487/*@__NO_SIDE_EFFECTS__ */
402- export function isPnpmFrozenLockfileFlag ( cmdArg : string ) : boolean {
403- return pnpmFrozenLockfileFlags . has ( cmdArg )
488+ export function isPnpmIgnoreScriptsFlag ( cmdArg : string ) : boolean {
489+ return pnpmIgnoreScriptsFlags . has ( cmdArg )
404490}
405491
406492/**
@@ -417,99 +503,3 @@ export function isPnpmFrozenLockfileFlag(cmdArg: string): boolean {
417503export function isPnpmInstallCommand ( cmdArg : string ) : boolean {
418504 return pnpmInstallCommands . has ( cmdArg )
419505}
420-
421- /**
422- * Alias for isNpmLoglevelFlag for pnpm usage.
423- */
424- export const isPnpmLoglevelFlag = isNpmLoglevelFlag
425-
426- /**
427- * Execute a package.json script using the appropriate package manager.
428- * Automatically detects pnpm, yarn, or npm based on lockfiles.
429- *
430- * @example
431- * ```typescript
432- * await execScript('build')
433- * await execScript('test', ['--coverage'], { cwd: '/tmp/project' })
434- * ```
435- */
436- export interface ExecScriptOptions extends SpawnOptions {
437- prepost ?: boolean | undefined
438- }
439-
440- /**
441- * Execute a package.json script using the detected package manager.
442- * Picks pnpm, npm, or yarn by walking up to the nearest lockfile; falls back
443- * to running `node --run` or `npm run` directly when no lockfile is found.
444- * Honors `shell: true` by passing through to `spawn()` unchanged.
445- *
446- * @param scriptName - The package.json script to run
447- * @param args - Either the script arguments or an options object
448- * @param options - Spawn options plus `prepost` to force npm-style pre/post scripts
449- * @returns The spawned `ChildProcess`-like promise from the underlying runner.
450- */
451- export function execScript (
452- scriptName : string ,
453- args ?: string [ ] | readonly string [ ] | ExecScriptOptions | undefined ,
454- options ?: ExecScriptOptions | undefined ,
455- ) {
456- // Handle overloaded signatures: execScript(name, options) or execScript(name, args, options).
457- let resolvedOptions : ExecScriptOptions | undefined
458- let resolvedArgs : string [ ]
459- if ( ! Array . isArray ( args ) && args !== null && typeof args === 'object' ) {
460- resolvedOptions = args as ExecScriptOptions
461- resolvedArgs = [ ]
462- } else {
463- resolvedOptions = options
464- resolvedArgs = ( args || [ ] ) as string [ ]
465- }
466- const { prepost, ...spawnOptions } = {
467- __proto__ : null ,
468- ...resolvedOptions ,
469- } as ExecScriptOptions
470-
471- // If shell: true is passed, run the command directly as a shell command.
472- if ( spawnOptions . shell === true ) {
473- return spawn ( scriptName , resolvedArgs , spawnOptions )
474- }
475-
476- const useNodeRun = ! prepost && supportsNodeRun ( )
477-
478- // Detect package manager based on lockfile by traversing up from current directory.
479- const cwd =
480- ( getOwn ( spawnOptions , 'cwd' ) as string | undefined ) ?? process . cwd ( )
481-
482- // Check for pnpm-lock.yaml.
483- const pnpmLockPath = findUpSync ( PNPM_LOCK_YAML , { cwd } ) as string | undefined
484- if ( pnpmLockPath ) {
485- return execPnpm ( [ 'run' , scriptName , ...resolvedArgs ] , spawnOptions )
486- }
487-
488- // Check for package-lock.json.
489- // When in an npm workspace, use npm run to ensure workspace binaries are available.
490- const packageLockPath = findUpSync ( PACKAGE_LOCK_JSON , { cwd } ) as
491- | string
492- | undefined
493- if ( packageLockPath ) {
494- return execNpm ( [ 'run' , scriptName , ...resolvedArgs ] , spawnOptions )
495- }
496-
497- // Check for yarn.lock.
498- const yarnLockPath = findUpSync ( YARN_LOCK , { cwd } ) as string | undefined
499- if ( yarnLockPath ) {
500- return execYarn ( [ 'run' , scriptName , ...resolvedArgs ] , spawnOptions )
501- }
502-
503- return spawn (
504- getExecPath ( ) ,
505- [
506- ...getNodeNoWarningsFlags ( ) ,
507- ...( useNodeRun ? [ '--run' ] : [ NPM_REAL_EXEC_PATH , 'run' ] ) ,
508- scriptName ,
509- ...resolvedArgs ,
510- ] ,
511- {
512- ...spawnOptions ,
513- } ,
514- )
515- }
0 commit comments