Skip to content

Commit f6a3f9c

Browse files
committed
feat: enhance parameter handling and type definitions in exec module
1 parent 4c38290 commit f6a3f9c

File tree

7 files changed

+115
-115
lines changed

7 files changed

+115
-115
lines changed

src/exec/params.ts

Lines changed: 32 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,18 @@
11
import { tokenizeArgs } from "args-tokenizer";
22

3-
import { concatTemplateStrings, getErrorMessage } from "@/common";
3+
import { getErrorMessage } from "@/common";
44
import { isArray, isPlainObject, isString } from "@/remeda";
55

6-
import type { BaseProcessOptions, ExecParams, SpawnCommand, SpawnOptions } from "./types";
6+
import type {
7+
BaseProcessOptions,
8+
ExecCommandStringParams,
9+
ExecCommandTemplateParams,
10+
ExecFactoryParams,
11+
ExecParams,
12+
ExecSpawnParams,
13+
SpawnCommand,
14+
SpawnOptions,
15+
} from "./types";
716

817
export const defaultOptions: Partial<BaseProcessOptions> = {
918
timeout: undefined,
@@ -14,7 +23,7 @@ export const defaultSpawnOptions: SpawnOptions = {
1423
windowsHide: true,
1524
};
1625

17-
const parseCommandString = (input: string): SpawnCommand => {
26+
export const parseCommandString = (input: string): SpawnCommand => {
1827
let tokens;
1928
try {
2029
tokens = tokenizeArgs(input);
@@ -29,32 +38,28 @@ const parseCommandString = (input: string): SpawnCommand => {
2938
return { command: tokens[0], args: tokens.slice(1) };
3039
};
3140

32-
export const getSpawnCommand = (params: ExecParams): SpawnCommand => {
33-
const [templateOrString, ...rest] = params;
34-
35-
let cmd;
36-
37-
if (isArray(templateOrString)) {
38-
// Template string case, should parse input string into command and args
39-
const input = concatTemplateStrings(templateOrString, rest);
40-
cmd = parseCommandString(input);
41-
} else if (isString(templateOrString)) {
42-
// Simple command string case
43-
cmd = isArray(rest[0])
44-
? { command: templateOrString, args: rest[0] as string[] } // Args array provided
45-
: parseCommandString(templateOrString); // No args provided, should parse string into command and args
46-
} else {
47-
throw new Error(
48-
`Invalid first parameter for exec: expected string or template string, got ${typeof templateOrString}.`,
49-
);
50-
}
41+
export const isSpawnParams = (params: ExecParams): params is ExecSpawnParams => {
42+
const [a, b, c] = params;
43+
44+
return isString(a) && (b === undefined || isArray(b)) && (c === undefined || isPlainObject(c));
45+
};
46+
47+
export const isCommandTemplateParams = (
48+
params: ExecParams,
49+
): params is ExecCommandTemplateParams => {
50+
const [a] = params;
51+
52+
return isArray(a);
53+
};
54+
55+
export const isCommandStringParams = (params: ExecParams): params is ExecCommandStringParams => {
56+
const [a, b, c] = params;
5157

52-
return cmd;
58+
return isString(a) && b === undefined && c === undefined;
5359
};
5460

55-
export const getProcessOptions = (params: ExecParams): Partial<BaseProcessOptions> | undefined => {
56-
const [maybeOptions] = params;
57-
if (isPlainObject(maybeOptions)) return maybeOptions;
61+
export const isFactoryParams = (params: ExecParams): params is ExecFactoryParams => {
62+
const [a, b, c] = params;
5863

59-
return undefined;
64+
return isPlainObject(a) && b === undefined && c === undefined;
6065
};

src/exec/process.ts

Lines changed: 44 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,43 @@
11
import { isString } from "remeda";
22

3-
import { createPromiseWithResolvers } from "@/common";
3+
import { concatTemplateStrings, createPromiseWithResolvers } from "@/common";
44

55
import { isAbortError } from "./error";
6-
import { defaultOptions, getProcessOptions, getSpawnCommand } from "./params";
6+
import {
7+
defaultOptions,
8+
isCommandStringParams,
9+
isCommandTemplateParams,
10+
isFactoryParams,
11+
isSpawnParams,
12+
parseCommandString,
13+
} from "./params";
714
import { spawnProcess } from "./spawn";
815

916
import type {
1017
BaseExec,
1118
BaseProcessInstance,
1219
BaseProcessOptions,
20+
ExecCommandStringParams,
21+
ExecCommandTemplateParams,
1322
ExecParams,
1423
KillSignal,
1524
} from "./types";
1625
import type { Nullable } from "@/types";
1726
import type { ChildProcess } from "node:child_process";
1827
import type { Readable } from "node:stream";
1928

20-
export abstract class BaseProcess implements BaseProcessInstance {
21-
protected static execImpl(self: BaseProcess, params: ExecParams): BaseExec {
22-
const exec = (...params: ExecParams): any => {
29+
export abstract class BaseProcess<P, I> implements BaseProcessInstance<P, I> {
30+
protected static execImpl<P, I>(self: BaseProcess<P, I>, params: ExecParams) {
31+
const execCommandTemplateOrString = (
32+
...params: ExecCommandTemplateParams | ExecCommandStringParams
33+
) => {
34+
const input = isCommandTemplateParams(params)
35+
? concatTemplateStrings(params[0], params.slice(1))
36+
: params[0];
37+
2338
let cmd;
2439
try {
25-
cmd = getSpawnCommand(params);
40+
cmd = parseCommandString(input);
2641
} catch (error) {
2742
self.thrownError = error as Error;
2843
}
@@ -31,16 +46,23 @@ export abstract class BaseProcess implements BaseProcessInstance {
3146
return cmd ? self.spawn(cmd.command, cmd.args) : self;
3247
};
3348

34-
// Factory case
35-
const options = getProcessOptions(params);
36-
if (options) {
37-
self.options = { ...self.options, ...options };
49+
if (isCommandTemplateParams(params) || isCommandStringParams(params)) {
50+
return execCommandTemplateOrString(...params);
51+
} else if (isSpawnParams(params)) {
52+
self.options = { ...self.options, ...params[2] };
3853

39-
return exec;
40-
}
54+
return self.spawn(params[0], params[1] || []);
55+
} else if (isFactoryParams(params)) {
56+
self.options = { ...self.options, ...params[0] };
4157

42-
// Command string or template case
43-
return exec(...params);
58+
return execCommandTemplateOrString;
59+
} else {
60+
self.thrownError = new Error(
61+
`Invalid parameters passed to exec: ${JSON.stringify(params)}`,
62+
);
63+
64+
return self;
65+
}
4466
}
4567

4668
protected _process: ChildProcess | undefined;
@@ -82,12 +104,12 @@ export abstract class BaseProcess implements BaseProcessInstance {
82104
createPromiseWithResolvers<void>());
83105
}
84106

85-
exec: any = (...params: ExecParams) => {
107+
exec: BaseExec<BaseProcessInstance<P, I>> = (...params: ExecParams): any => {
86108
return BaseProcess.execImpl(this, params);
87109
};
88110

89-
pipe: any = (...params: ExecParams) => {
90-
const self = this.construct();
111+
pipe: BaseExec<BaseProcessInstance<P, I>> = (...params: ExecParams): any => {
112+
const self = this.child();
91113

92114
return BaseProcess.execImpl(self, params);
93115
};
@@ -96,8 +118,8 @@ export abstract class BaseProcess implements BaseProcessInstance {
96118
return this.process?.kill(signal) === true;
97119
}
98120

99-
then<TResult1 = any, TResult2 = never>(
100-
onfulfilled?: ((value: any) => TResult1 | PromiseLike<TResult1>) | null,
121+
then<TResult1 = P, TResult2 = never>(
122+
onfulfilled?: ((value: P) => TResult1 | PromiseLike<TResult1>) | null,
101123
onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null,
102124
): Promise<TResult1 | TResult2> {
103125
return this.thenImpl().then(onfulfilled, onrejected);
@@ -148,9 +170,9 @@ export abstract class BaseProcess implements BaseProcessInstance {
148170
this.process?.removeAllListeners();
149171
}
150172

151-
abstract [Symbol.asyncIterator](): AsyncIterator<any>;
173+
abstract [Symbol.asyncIterator](): AsyncIterator<I>;
152174

153-
protected abstract thenImpl(): Promise<any>;
175+
protected abstract thenImpl(): Promise<P>;
154176

155-
protected abstract construct(): BaseProcess;
177+
protected abstract child(): BaseProcess<P, I>;
156178
}

src/exec/safe/index.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,13 @@ import { BaseProcess } from "../process";
55
import { readStreamLines, readStreams } from "../stream";
66

77
import type { Exec, ProcessInstance, ProcessOptions } from "./types";
8-
import type { Output } from "../types";
8+
import type { ExecParams, Output } from "../types";
99
import type { Result } from "@/result";
1010

11-
export class Process extends BaseProcess implements ProcessInstance {
11+
export class Process
12+
extends BaseProcess<Result<Output, Error>, Result<string, Error>>
13+
implements ProcessInstance
14+
{
1215
declare exec: Exec;
1316
declare pipe: Exec;
1417

@@ -62,12 +65,12 @@ export class Process extends BaseProcess implements ProcessInstance {
6265
return err(new NonZeroExitError(this, output));
6366
}
6467

65-
protected override construct(): Process {
68+
protected override child() {
6669
return new Process({ stdin: this });
6770
}
6871
}
6972

70-
export const exec: Exec = (...params: [any, ...any[]]): any => {
73+
export const exec: Exec = (...params: ExecParams): any => {
7174
const self = new Process();
7275

7376
return BaseProcess["execImpl"](self, params);

src/exec/safe/types.ts

Lines changed: 6 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,27 +8,12 @@ import type {
88
Output,
99
} from "../types";
1010
import type { Result } from "@/result";
11-
import type { SetReturnType } from "@/types";
1211

13-
export type ProcessOptions = BaseProcessOptions & ExtendProcessOptions;
12+
export type ProcessOptions = BaseProcessOptions<ProcessInstance>;
1413

15-
export interface ExtendProcessOptions {
16-
stdin: ProcessInstance | string;
17-
}
14+
export type ProcessInstance = BaseProcessInstance<Result<Output, Error>, Result<string, Error>>;
1815

19-
export type ProcessInstance = BaseProcessInstance & ExtendProcessInstance;
20-
21-
export interface ExtendProcessInstance
22-
extends PromiseLike<Result<Output, Error>>, AsyncIterable<Result<string, Error>> {
23-
exec: Exec;
24-
pipe: Exec;
25-
}
26-
27-
export type Exec = SetReturnType<BaseExecSpawn, ProcessInstance> &
28-
SetReturnType<BaseExecCommandTemplate, ProcessInstance> &
29-
SetReturnType<BaseExecCommandString, ProcessInstance> &
30-
SetReturnType<
31-
BaseExecFactory,
32-
SetReturnType<BaseExecCommandTemplate, ProcessInstance> &
33-
SetReturnType<BaseExecCommandString, ProcessInstance>
34-
>;
16+
export type Exec = BaseExecSpawn<ProcessInstance> &
17+
BaseExecCommandTemplate<ProcessInstance> &
18+
BaseExecCommandString<ProcessInstance> &
19+
BaseExecFactory<ProcessInstance>;

src/exec/types.ts

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,24 +15,24 @@ export interface SpawnCommand {
1515
}
1616
export type { SpawnOptions } from "node:child_process";
1717

18-
export interface BaseProcessOptions {
19-
stdin: BaseProcessInstance | string;
18+
export interface BaseProcessOptions<T extends BaseProcessInstance = BaseProcessInstance> {
19+
stdin: T | string;
2020
signal: AbortSignal;
2121
spawnOptions: SpawnOptions;
2222
timeout: number;
2323
persist: boolean;
2424
throwOnError: boolean;
2525
}
2626

27-
export interface BaseProcessInstance extends PromiseLike<any>, AsyncIterable<any> {
27+
export interface BaseProcessInstance<P = any, I = any> extends PromiseLike<P>, AsyncIterable<I> {
2828
get process(): ChildProcess | undefined;
2929
get aborted(): boolean;
3030
get killed(): boolean;
3131
get pid(): number | undefined;
3232
get exitCode(): number | undefined;
3333

34-
exec: (...params: any[]) => any;
35-
pipe: (...params: any[]) => any;
34+
exec: BaseExec<BaseProcessInstance<P, I>>;
35+
pipe: BaseExec<BaseProcessInstance<P, I>>;
3636
kill: (signal?: KillSignal) => boolean;
3737
}
3838

@@ -41,25 +41,24 @@ export type ExecParams =
4141
| ExecCommandTemplateParams
4242
| ExecCommandStringParams
4343
| ExecFactoryParams;
44-
export type BaseExec = BaseExecSpawn &
45-
BaseExecCommandTemplate &
46-
BaseExecCommandString &
47-
BaseExecFactory;
4844

4945
export type ExecSpawnParams = [
5046
command: string,
5147
args?: string[],
5248
options?: Partial<BaseProcessOptions>,
5349
];
54-
export type BaseExecSpawn = (...params: ExecSpawnParams) => unknown;
55-
5650
export type ExecCommandTemplateParams = Parameters<TemplateFn>;
57-
export type BaseExecCommandTemplate = (...params: ExecCommandTemplateParams) => unknown;
58-
5951
export type ExecCommandStringParams = [command: string];
60-
export type BaseExecCommandString = (...params: ExecCommandStringParams) => unknown;
61-
6252
export type ExecFactoryParams = [options: Partial<BaseProcessOptions>];
63-
export type BaseExecFactory = (
53+
54+
export type BaseExec<T> = BaseExecSpawn<T> &
55+
BaseExecCommandTemplate<T> &
56+
BaseExecCommandString<T> &
57+
BaseExecFactory<T>;
58+
59+
export type BaseExecSpawn<T> = (...params: ExecSpawnParams) => T;
60+
export type BaseExecCommandTemplate<T> = (...params: ExecCommandTemplateParams) => T;
61+
export type BaseExecCommandString<T> = (...params: ExecCommandStringParams) => T;
62+
export type BaseExecFactory<T> = (
6463
...params: ExecFactoryParams
65-
) => BaseExecCommandTemplate & BaseExecCommandString;
64+
) => BaseExecCommandTemplate<T> & BaseExecCommandString<T>;

src/exec/unsafe/index.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ import { BaseProcess } from "../process";
33
import { readStreamLines, readStreams } from "../stream";
44

55
import type { Exec, ProcessInstance, ProcessOptions } from "./types";
6-
import type { Output } from "../types";
6+
import type { ExecParams, Output } from "../types";
77

8-
export class Process extends BaseProcess implements ProcessInstance {
8+
export class Process extends BaseProcess<Output, string> implements ProcessInstance {
99
declare exec: Exec;
1010
declare pipe: Exec;
1111

@@ -56,12 +56,12 @@ export class Process extends BaseProcess implements ProcessInstance {
5656
throw new NonZeroExitError(this, output);
5757
}
5858

59-
protected override construct(): Process {
59+
protected override child() {
6060
return new Process({ stdin: this });
6161
}
6262
}
6363

64-
export const exec: Exec = (...params: [any, ...any[]]): any => {
64+
export const exec: Exec = (...params: ExecParams): any => {
6565
const self = new Process();
6666

6767
return BaseProcess["execImpl"](self, params);

src/exec/unsafe/types.ts

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,12 @@ import type {
77
BaseProcessOptions,
88
Output,
99
} from "../types";
10-
import type { SetReturnType } from "@/types";
1110

12-
export type ProcessOptions = BaseProcessOptions & ExtendProcessOptions;
11+
export type ProcessOptions = BaseProcessOptions<ProcessInstance>;
1312

14-
export interface ExtendProcessOptions {
15-
stdin: ProcessInstance;
16-
}
13+
export type ProcessInstance = BaseProcessInstance<Output, string>;
1714

18-
export type ProcessInstance = BaseProcessInstance & ExtendProcessInstance;
19-
20-
export interface ExtendProcessInstance extends PromiseLike<Output>, AsyncIterable<string> {
21-
exec: Exec;
22-
pipe: Exec;
23-
}
24-
25-
export type Exec = SetReturnType<BaseExecSpawn, ProcessInstance> &
26-
SetReturnType<BaseExecCommandTemplate, ProcessInstance> &
27-
SetReturnType<BaseExecCommandString, ProcessInstance> &
28-
SetReturnType<
29-
BaseExecFactory,
30-
SetReturnType<BaseExecCommandTemplate, ProcessInstance> &
31-
SetReturnType<BaseExecCommandString, ProcessInstance>
32-
>;
15+
export type Exec = BaseExecSpawn<ProcessInstance> &
16+
BaseExecCommandTemplate<ProcessInstance> &
17+
BaseExecCommandString<ProcessInstance> &
18+
BaseExecFactory<ProcessInstance>;

0 commit comments

Comments
 (0)