Skip to content

Commit

Permalink
Merge branch 'main' into perf-code-patterns
Browse files Browse the repository at this point in the history
  • Loading branch information
antonmedv authored Sep 19, 2024
2 parents 42aa263 + 971cbfc commit e208d4c
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 53 deletions.
8 changes: 4 additions & 4 deletions .size-limit.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
},
Expand All @@ -30,7 +30,7 @@
{
"name": "all",
"path": "build/*",
"limit": "830 kB",
"limit": "832 kB",
"brotli": false,
"gzip": false
}
Expand Down
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
119 changes: 76 additions & 43 deletions src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import {
isString,
isStringLiteral,
noop,
once,
parseDuration,
preferLocalBin,
quote,
Expand Down Expand Up @@ -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)
}
},
},
Expand Down Expand Up @@ -572,30 +571,64 @@ 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,
stdout: string,
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() {
Expand Down
10 changes: 10 additions & 0 deletions src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
}
16 changes: 15 additions & 1 deletion test-d/core.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand All @@ -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'))

0 comments on commit e208d4c

Please sign in to comment.