Commit 327dd7a

Anton Golub <antongolub@antongolub.com>
2025-02-08 18:55:25
refactor: simplify `ProcessOutput` constructor (#1095)
relates #1053 * refactor: simplify `ProcessOutput` constructor * refactor: make private lazy dto factory * chore: update dts tests
1 parent 5ba714d
Changed files (2)
src/core.ts
@@ -203,9 +203,7 @@ export const $: Shell & Options = new Proxy<Shell & Options>(
     },
   }
 )
-/**
- * State machine stages
- */
+
 type ProcessStage = 'initial' | 'halted' | 'running' | 'fulfilled' | 'rejected'
 
 type Resolve = (out: ProcessOutput) => void
@@ -312,35 +310,23 @@ export class ProcessPromise extends Promise<ProcessOutput> {
           $.log({ kind: 'stderr', data, verbose: !self.isQuiet(), id })
         },
         end: (data, c) => {
-          const { error, status, signal, duration, ctx } = data
-          const { stdout, stderr, stdall } = ctx.store
-          const dto: ProcessOutputLazyDto = {
-            code: () => status,
-            signal: () => signal,
-            duration: () => duration,
-            stdout: once(() => bufArrJoin(stdout)),
-            stderr: once(() => bufArrJoin(stderr)),
-            stdall: once(() => bufArrJoin(stdall)),
-            message: once(() => ProcessOutput.getExitMessage(
-              status,
-              signal,
-              dto.stderr(),
-              self._from
-            )),
-            ...error && {
-              code: () => null,
-              signal: () => null,
-              message: () => ProcessOutput.getErrorMessage(error, self._from)
-            }
-          }
+          const { error, status, signal, duration, ctx: {store} } = data
+          const { stdout, stderr } = store
+          const output = self._output = new ProcessOutput({
+            code: status,
+            signal,
+            error,
+            duration,
+            store,
+            from: self._from,
+          })
+
+          $.log({ kind: 'end', signal, exitCode: status, duration, error, verbose: self.isVerbose(), id })
 
           // Ensures EOL
           if (stdout.length && getLast(getLast(stdout)) !== BR_CC) c.on.stdout!(EOL, c)
           if (stderr.length && getLast(getLast(stderr)) !== BR_CC) c.on.stderr!(EOL, c)
 
-          $.log({ kind: 'end', signal, exitCode: status, duration, error, verbose: self.isVerbose(), id })
-          const output = self._output = new ProcessOutput(dto)
-
           if (error || status !== 0 && !self.isNothrow()) {
             self._stage = 'rejected'
             self._reject(output)
@@ -669,76 +655,72 @@ export class ProcessPromise extends Promise<ProcessOutput> {
   }
 }
 
-type GettersRecord<T extends Record<any, any>> = { [K in keyof T]: () => T[K] }
-
-type ProcessOutputLazyDto = GettersRecord<{
+type ProcessDto = {
   code: number | null
   signal: NodeJS.Signals | null
+  duration: number
+  error: any
+  from: string
+  store: TSpawnStore
+}
+
+type ProcessOutputDto = ProcessDto & {
   stdout: string
   stderr: string
   stdall: string
   message: string
-  duration: number
-}>
+}
 
 export class ProcessOutput extends Error {
-  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)
+  private readonly _dto: ProcessOutputDto
+  constructor(dto: ProcessDto)
   constructor(
     code: number | null,
     signal: NodeJS.Signals | null,
     stdout: string,
     stderr: string,
-    combined: string,
+    stdall: string,
     message: string,
     duration?: number
   )
   constructor(
-    code: number | null | ProcessOutputLazyDto,
+    code: number | null | ProcessDto,
     signal: NodeJS.Signals | null = null,
     stdout: string = '',
     stderr: string = '',
-    combined: string = '',
+    stdall: string = '',
     message: string = '',
     duration: number = 0
   ) {
     super(message)
-    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
-    }
+    Reflect.deleteProperty(this, 'message')
+    this._dto =
+      code !== null && typeof code === 'object'
+        ? ProcessOutput.createLazyDto(code)
+        : {
+            code,
+            signal,
+            duration,
+            stdout,
+            stderr,
+            stdall,
+            error: null,
+            from: '',
+            message,
+            store: { stdout: [], stderr: [], stdall: [] },
+          }
   }
 
   toString(): string {
-    return this._combined
+    return this._dto.stdall
   }
 
   json<T = any>(): T {
-    return JSON.parse(this._combined)
+    return JSON.parse(this._dto.stdall)
   }
 
   buffer(): Buffer {
-    return Buffer.from(this._combined)
+    return Buffer.from(this._dto.stdall)
   }
 
   blob(type = 'text/plain'): Blob {
@@ -760,27 +742,63 @@ export class ProcessOutput extends Error {
   }
 
   valueOf(): string {
-    return this._combined.trim()
+    return this._dto.stdall.trim()
   }
 
   get stdout(): string {
-    return this._stdout
+    return this._dto.stdout
   }
 
   get stderr(): string {
-    return this._stderr
+    return this._dto.stderr
   }
 
   get exitCode(): number | null {
-    return this._code
+    return this._dto.code
   }
 
   get signal(): NodeJS.Signals | null {
-    return this._signal
+    return this._dto.signal
   }
 
   get duration(): number {
-    return this._duration
+    return this._dto.duration
+  }
+
+  get message(): string {
+    return this._dto.message
+  }
+
+  private static createLazyDto({
+    code,
+    signal,
+    duration,
+    store: { stdout, stderr, stdall },
+    from,
+    error,
+  }: ProcessDto): ProcessOutputDto {
+    const dto = Object.defineProperties(
+      {
+        code,
+        signal,
+        duration,
+      },
+      {
+        stdout: { get: once(() => bufArrJoin(stdout)) },
+        stderr: { get: once(() => bufArrJoin(stderr)) },
+        stdall: { get: once(() => bufArrJoin(stdall)) },
+        message: {
+          get: once(() =>
+            ProcessOutput.getExitMessage(code, signal, dto.stderr, from)
+          ),
+        },
+        ...(error && {
+          message: { get: () => ProcessOutput.getErrorMessage(error, from) },
+        }),
+      }
+    ) as ProcessOutputDto
+
+    return dto
   }
 
   static getExitMessage = formatExitMessage
test-d/core.test-d.ts
@@ -46,13 +46,12 @@ 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 '' },
+  code: null,
+  signal: null,
+  duration: 0,
+  store: { stdout: [], stderr: [], stdall: [] },
+  error: null,
+  from: ''
 }))
 
 expectType<ProcessOutput>(new ProcessOutput(null, null, '', '', '', '', 1))