Commit 971cbfc
Changed files (6)
src/core.ts
@@ -44,6 +44,7 @@ import {
isString,
isStringLiteral,
noop,
+ once,
parseDuration,
preferLocalBin,
quote,
@@ -320,52 +321,50 @@ export class ProcessPromise extends Promise<ProcessOutput> {
// Stderr should be printed regardless of piping.
$.log({ kind: 'stderr', data, verbose: !self.isQuiet() })
},
- end: (
- { error, stdout, stderr, stdall, status, signal, duration },
- c
- ) => {
+ // prettier-ignore
+ end: (data, c) => {
self._resolved = true
- // Ensures EOL
- if (stderr && !stderr.endsWith('\n')) c.on.stderr?.(eol, c)
- if (stdout && !stdout.endsWith('\n')) c.on.stdout?.(eol, c)
+ const { error, status, signal, duration, ctx } = data
+ const { stdout, stderr, stdall } = ctx.store
+
+ // Lazy getters
+ const _stdout = once(() => stdout.join(''))
+ const _stderr = once(() => stderr.join(''))
+ const _stdall = once(() => stdall.join(''))
+ const _duration = () => duration
+ let _code = () => status
+ let _signal = () => signal
+ let _message = once(() => ProcessOutput.getExitMessage(
+ status,
+ signal,
+ _stderr(),
+ self._from
+ ))
+ // Ensures EOL
+ if (stdout.length && !stdout[stdout.length - 1]?.toString().endsWith('\n')) c.on.stdout?.(eol, c)
+ if (stderr.length && !stderr[stderr.length - 1]?.toString().endsWith('\n')) c.on.stderr?.(eol, c)
if (error) {
- const message = ProcessOutput.getErrorMessage(error, self._from)
- // Should we enable this?
- // (nothrow ? self._resolve : self._reject)(
- const output = new ProcessOutput(
- null,
- null,
- stdout,
- stderr,
- stdall,
- message,
- duration
- )
- self._output = output
+ _code = () => null
+ _signal = () => null
+ _message = () => ProcessOutput.getErrorMessage(error, self._from)
+ }
+
+ const output = new ProcessOutput({
+ code: _code,
+ signal: _signal,
+ stdout: _stdout,
+ stderr: _stderr,
+ stdall: _stdall,
+ message: _message,
+ duration: _duration
+ })
+ self._output = output
+
+ if (error || status !== 0 && !self.isNothrow()) {
self._reject(output)
} else {
- const message = ProcessOutput.getExitMessage(
- status,
- signal,
- stderr,
- self._from
- )
- const output = new ProcessOutput(
- status,
- signal,
- stdout,
- stderr,
- stdall,
- message,
- duration
- )
- self._output = output
- if (status === 0 || self.isNothrow()) {
- self._resolve(output)
- } else {
- self._reject(output)
- }
+ self._resolve(output)
}
},
},
@@ -572,14 +571,27 @@ export class ProcessPromise extends Promise<ProcessOutput> {
}
}
+type GettersRecord<T extends Record<any, any>> = { [K in keyof T]: () => T[K] }
+
+type ProcessOutputLazyDto = GettersRecord<{
+ code: number | null
+ signal: NodeJS.Signals | null
+ stdout: string
+ stderr: string
+ stdall: string
+ message: string
+ duration: number
+}>
+
export class ProcessOutput extends Error {
- private readonly _code: number | null
+ private readonly _code: number | null = null
private readonly _signal: NodeJS.Signals | null
private readonly _stdout: string
private readonly _stderr: string
private readonly _combined: string
private readonly _duration: number
+ constructor(dto: ProcessOutputLazyDto)
constructor(
code: number | null,
signal: NodeJS.Signals | null,
@@ -587,15 +599,36 @@ export class ProcessOutput extends Error {
stderr: string,
combined: string,
message: string,
+ duration?: number
+ )
+ constructor(
+ code: number | null | ProcessOutputLazyDto,
+ signal: NodeJS.Signals | null = null,
+ stdout: string = '',
+ stderr: string = '',
+ combined: string = '',
+ message: string = '',
duration: number = 0
) {
super(message)
- this._code = code
this._signal = signal
this._stdout = stdout
this._stderr = stderr
this._combined = combined
this._duration = duration
+ if (code !== null && typeof code === 'object') {
+ Object.defineProperties(this, {
+ _code: { get: code.code },
+ _signal: { get: code.signal },
+ _duration: { get: code.duration },
+ _stdout: { get: code.stdout },
+ _stderr: { get: code.stderr },
+ _combined: { get: code.stdall },
+ message: { get: code.message },
+ })
+ } else {
+ this._code = code
+ }
}
toString() {
src/util.ts
@@ -449,3 +449,13 @@ export function getCallerLocationFromString(stackString = 'unknown') {
?.trim() || stackString
)
}
+
+export const once = <T extends (...args: any[]) => any>(fn: T) => {
+ let called = false
+ let result: ReturnType<T>
+ return (...args: Parameters<T>): ReturnType<T> => {
+ if (called) return result
+ called = true
+ return (result = fn(...args))
+ }
+}
test-d/core.test-d.ts
@@ -14,7 +14,7 @@
import assert from 'node:assert'
import { Readable, Writable } from 'node:stream'
-import { expectType } from 'tsd'
+import { expectError, expectType } from 'tsd'
import { $, ProcessPromise, ProcessOutput, within } from 'zx'
let p = $`cmd`
@@ -40,5 +40,19 @@ expectType<string>(o.stdout)
expectType<string>(o.stderr)
expectType<number | null>(o.exitCode)
expectType<NodeJS.Signals | null>(o.signal)
+// prettier-ignore
+expectType<ProcessOutput>(new ProcessOutput({
+ code() { return null },
+ signal() { return null },
+ stdall() { return '' },
+ stderr() { return '' },
+ stdout() { return '' },
+ duration() { return 0 },
+ message() { return '' },
+}))
+
+expectType<ProcessOutput>(new ProcessOutput(null, null, '', '', '', '', 1))
+expectType<ProcessOutput>(new ProcessOutput(null, null, '', '', '', ''))
+expectError(new ProcessOutput(null, null))
expectType<'banana'>(within(() => 'apple' as 'banana'))
.size-limit.json
@@ -2,21 +2,21 @@
{
"name": "zx/core",
"path": ["build/core.cjs", "build/util.cjs", "build/vendor-core.cjs"],
- "limit": "70 kB",
+ "limit": "71 kB",
"brotli": false,
"gzip": false
},
{
"name": "zx/index",
"path": "build/*.{js,cjs}",
- "limit": "796 kB",
+ "limit": "797 kB",
"brotli": false,
"gzip": false
},
{
"name": "dts libdefs",
"path": "build/*.d.ts",
- "limit": "35 kB",
+ "limit": "36 kB",
"brotli": false,
"gzip": false
},
@@ -30,7 +30,7 @@
{
"name": "all",
"path": "build/*",
- "limit": "830 kB",
+ "limit": "832 kB",
"brotli": false,
"gzip": false
}
package-lock.json
@@ -46,7 +46,7 @@
"typescript": "^5.6.2",
"which": "^4.0.0",
"yaml": "^2.5.1",
- "zurk": "^0.3.2"
+ "zurk": "^0.3.4"
},
"engines": {
"node": ">= 12.17.0"
@@ -5557,9 +5557,9 @@
}
},
"node_modules/zurk": {
- "version": "0.3.2",
- "resolved": "https://registry.npmjs.org/zurk/-/zurk-0.3.2.tgz",
- "integrity": "sha512-bMihBxEc0ECXBfZbC3se+4E8jc07QvHh8stapRvPR1hwp8Jq27B7QS/sGqCwuwFWWe7EkK3IwXhhVYEgm0ghuw==",
+ "version": "0.3.4",
+ "resolved": "https://registry.npmjs.org/zurk/-/zurk-0.3.4.tgz",
+ "integrity": "sha512-Mu0uXIAgYezo9zprkW/jzjgBo6jkCCGNPpdpQr4W5aRUTDRHCcy9KvOY+cKg6XYfPp0ArqsQsFbeZSf2f8Ka6Q==",
"dev": true,
"license": "MIT"
}
package.json
@@ -125,7 +125,7 @@
"typescript": "^5.6.2",
"which": "^4.0.0",
"yaml": "^2.5.1",
- "zurk": "^0.3.2"
+ "zurk": "^0.3.4"
},
"publishConfig": {
"registry": "https://wombat-dressing-room.appspot.com"