Commit fbf2ddf
Changed files (5)
test
src/core.ts
@@ -44,7 +44,7 @@ import {
isString,
noop,
parseDuration,
- preferNmBin,
+ preferLocalBin,
quote,
quotePowerShell,
} from './util.js'
@@ -70,7 +70,7 @@ export interface Options {
signal?: AbortSignal
input?: string | Buffer | Readable | ProcessOutput | ProcessPromise
timeout?: Duration
- timeoutSignal?: string
+ timeoutSignal?: NodeJS.Signals
stdio: StdioOptions
verbose: boolean
sync: boolean
@@ -82,12 +82,13 @@ export interface Options {
quote?: typeof quote
quiet: boolean
detached: boolean
- preferLocal: boolean
+ preferLocal: boolean | string | string[]
spawn: typeof spawn
spawnSync: typeof spawnSync
store?: TSpawnStore
log: typeof log
kill: typeof kill
+ killSignal?: NodeJS.Signals
}
const storage = new AsyncLocalStorage<Options>()
@@ -125,6 +126,8 @@ export const defaults: Options = {
spawnSync,
log,
kill,
+ killSignal: 'SIGTERM',
+ timeoutSignal: 'SIGTERM',
}
export function usePowerShell() {
@@ -168,9 +171,9 @@ export const $: Shell & Options = new Proxy<Shell & Options>(
if (!Array.isArray(pieces)) {
return function (this: any, ...args: any) {
const self = this
- return within(() => {
- return Object.assign($, snapshot, pieces).apply(self, args)
- })
+ return within(() =>
+ Object.assign($, snapshot, pieces).apply(self, args)
+ )
}
}
const from = getCallerLocation()
@@ -237,7 +240,7 @@ export class ProcessPromise extends Promise<ProcessOutput> {
private _quiet?: boolean
private _verbose?: boolean
private _timeout?: number
- private _timeoutSignal = 'SIGTERM'
+ private _timeoutSignal = $.timeoutSignal
private _resolved = false
private _halted = false
private _piped = false
@@ -270,7 +273,11 @@ export class ProcessPromise extends Promise<ProcessOutput> {
if (input) this.stdio('pipe')
if ($.timeout) this.timeout($.timeout, $.timeoutSignal)
- if ($.preferLocal) $.env = preferNmBin($.env, $.cwd, $[processCwd])
+ if ($.preferLocal) {
+ const dirs =
+ $.preferLocal === true ? [$.cwd, $[processCwd]] : [$.preferLocal].flat()
+ $.env = preferLocalBin($.env, ...dirs)
+ }
$.log({
kind: 'cmd',
@@ -486,7 +493,7 @@ export class ProcessPromise extends Promise<ProcessOutput> {
return this._snapshot.signal || this._snapshot.ac?.signal
}
- async kill(signal = 'SIGTERM'): Promise<void> {
+ async kill(signal = $.killSignal): Promise<void> {
if (!this.child)
throw new Error('Trying to kill a process without creating one.')
if (!this.child.pid) throw new Error('The process pid is undefined.')
@@ -530,7 +537,7 @@ export class ProcessPromise extends Promise<ProcessOutput> {
return this._nothrow ?? this._snapshot.nothrow
}
- timeout(d: Duration, signal = 'SIGTERM'): ProcessPromise {
+ timeout(d: Duration, signal = $.timeoutSignal): ProcessPromise {
this._timeout = parseDuration(d)
this._timeoutSignal = signal
return this
@@ -686,7 +693,7 @@ export function cd(dir: string | ProcessOutput) {
$[processCwd] = process.cwd()
}
-export async function kill(pid: number, signal?: string) {
+export async function kill(pid: number, signal = $.killSignal) {
let children = await ps.tree({ pid, recursive: true })
for (const p of children) {
try {
src/goods.ts
@@ -182,13 +182,12 @@ export async function spinner<T>(
return within(async () => {
$.verbose = false
const id = setInterval(spin, 100)
- let result: T
+
try {
- result = await callback!()
+ return await callback!()
} finally {
clearInterval(id as NodeJS.Timeout)
process.stderr.write(' '.repeat((process.stdout.columns || 1) - 1) + '\r')
}
- return result
})
}
src/util.ts
@@ -47,7 +47,7 @@ export function isString(obj: any) {
const pad = (v: string) => (v === ' ' ? ' ' : '')
-export function preferNmBin(
+export function preferLocalBin(
env: NodeJS.ProcessEnv,
...dirs: (string | undefined)[]
) {
@@ -58,7 +58,14 @@ export function preferNmBin(
.find((key) => key.toUpperCase() === 'PATH') || 'Path'
: 'PATH'
const pathValue = dirs
- .map((c) => c && path.resolve(c as string, 'node_modules', '.bin'))
+ .map(
+ (c) =>
+ c && [
+ path.resolve(c as string, 'node_modules', '.bin'),
+ path.resolve(c as string),
+ ]
+ )
+ .flat()
.concat(env[pathKey])
.filter(Boolean)
.join(path.delimiter)
test/core.test.js
@@ -220,10 +220,22 @@ describe('core', () => {
})
test('`preferLocal` preserves env', async () => {
- const path = await $({
- preferLocal: true,
- })`echo $PATH`
- assert(path.stdout.startsWith(`${process.cwd()}/node_modules/.bin:`))
+ const cases = [
+ [true, `${process.cwd()}/node_modules/.bin:${process.cwd()}:`],
+ ['/foo', `/foo/node_modules/.bin:/foo:`],
+ [
+ ['/bar', '/baz'],
+ `/bar/node_modules/.bin:/bar:/baz/node_modules/.bin:/baz`,
+ ],
+ ]
+
+ for (const [preferLocal, expected] of cases) {
+ const path = await $({
+ preferLocal,
+ env: { PATH: process.env.PATH },
+ })`echo $PATH`
+ assert(path.stdout.startsWith(expected))
+ }
})
test('supports custom intermediate store', async () => {
test/util.test.js
@@ -29,7 +29,7 @@ import {
getCallerLocationFromString,
tempdir,
tempfile,
- preferNmBin,
+ preferLocalBin,
} from '../build/util.js'
describe('util', () => {
@@ -169,10 +169,13 @@ test('tempfile() creates temporary files', () => {
assert.equal(fs.readFileSync(tf, 'utf-8'), 'bar')
})
-test('preferNmBin()', () => {
+test('preferLocalBin()', () => {
const env = {
PATH: '/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/usr/local/sbin',
}
- const _env = preferNmBin(env, process.cwd())
- assert.equal(_env.PATH, `${process.cwd()}/node_modules/.bin:${env.PATH}`)
+ const _env = preferLocalBin(env, process.cwd())
+ assert.equal(
+ _env.PATH,
+ `${process.cwd()}/node_modules/.bin:${process.cwd()}:${env.PATH}`
+ )
})