Skip to content

Commit

Permalink
Add types
Browse files Browse the repository at this point in the history
  • Loading branch information
ehmicky committed Sep 1, 2024
1 parent a633081 commit e3e3bc8
Show file tree
Hide file tree
Showing 4 changed files with 209 additions and 11 deletions.
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
"node": ">=18.19"
},
"scripts": {
"test": "xo && c8 ava && tsc ./source/index.d.ts"
"test": "xo && c8 ava && npm run type",
"type": "tsd -t ./source/index.d.ts -f ./source/index.test-d.ts"
},
"files": [
"source/**/*.js",
Expand All @@ -47,11 +48,13 @@
"execa"
],
"devDependencies": {
"@types/node": "^22.5.4",
"ava": "^6.1.3",
"c8": "^10.1.2",
"get-node": "^15.0.1",
"path-key": "^4.0.0",
"tempy": "^3.1.0",
"tsd": "^0.31.2",
"typescript": "^5.5.4",
"xo": "^0.59.3",
"yoctocolors": "^2.1.1"
Expand Down
60 changes: 50 additions & 10 deletions source/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,52 @@
export type Options = {
readonly timeout: number;
readonly signal: AbortSignal;
// Readonly nativeOptions;
import type {ChildProcess, SpawnOptions} from 'node:child_process';

type StdioOption = Readonly<Exclude<SpawnOptions['stdio'], undefined>[number]>;
type StdinOption = StdioOption | {readonly string?: string};

export type Options = Omit<SpawnOptions, 'env' | 'stdio'> & Readonly<Partial<{
stdin: StdinOption;

stdout: StdioOption;

stderr: StdioOption;

stdio: SpawnOptions['stdio'] | readonly [StdinOption, ...readonly StdioOption[]];

preferLocal: boolean;

// Fixes issues with Remix and Next.js
// See https://github.com/sindresorhus/execa/pull/1141
env: Readonly<Partial<Record<string, string>>>;
}>>;

export type Result = {
stdout: string;

stderr: string;

output: string;

command: string;

durationMs: number;
};

export type SubprocessError = Error & Result & {
exitCode?: number;

signalName?: string;
};

export type Subprocess = Promise<Result> & AsyncIterable<string> & {
nodeChildProcess: Promise<ChildProcess>;

stdout: AsyncIterable<string>;

stderr: AsyncIterable<string>;

pipe(file: string, arguments?: readonly string[], options?: Options): Subprocess;
pipe(file: string, options?: Options): Subprocess;
};

// TODO: Finish this when the API is decided on.
export function nanoSpawn(
command: string,
arguments: readonly string[],
options?: Options
): Promise<void>;
export default function nanoSpawn(file: string, arguments?: readonly string[], options?: Options): Subprocess;
export default function nanoSpawn(file: string, options?: Options): Subprocess;
144 changes: 144 additions & 0 deletions source/index.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import type {ChildProcess} from 'node:child_process';
import {
expectType,
expectAssignable,
expectNotAssignable,
expectError,
} from 'tsd';
import nanoSpawn, {
type Options,
type Result,
type SubprocessError,
type Subprocess,
} from './index.js';

try {
const result = await nanoSpawn('test');
expectType<Result>(result);
expectType<string>(result.stdout);
expectType<string>(result.stderr);
expectType<string>(result.output);
expectType<string>(result.command);
expectType<number>(result.durationMs);
expectNotAssignable<Error>(result);
expectError(result.exitCode);
expectError(result.signalName);
expectError(result.other);
} catch (error) {
const subprocessError = error as SubprocessError;
expectType<string>(subprocessError.stdout);
expectType<string>(subprocessError.stderr);
expectType<string>(subprocessError.output);
expectType<string>(subprocessError.command);
expectType<number>(subprocessError.durationMs);
expectAssignable<Error>(subprocessError);
expectType<number | undefined>(subprocessError.exitCode);
expectType<string | undefined>(subprocessError.signalName);
expectError(subprocessError.other);
}

expectAssignable<Options>({} as const);
expectAssignable<Options>({argv0: 'test'} as const);
expectNotAssignable<Options>({other: 'test'} as const);
expectNotAssignable<Options>('test');

await nanoSpawn('test', {argv0: 'test'} as const);
expectError(await nanoSpawn('test', {argv0: true} as const));
await nanoSpawn('test', {preferLocal: true} as const);
expectError(await nanoSpawn('test', {preferLocal: 'true'} as const));
await nanoSpawn('test', {env: {}} as const);
// eslint-disable-next-line @typescript-eslint/naming-convention
await nanoSpawn('test', {env: {TEST: 'test'}} as const);
expectError(await nanoSpawn('test', {env: true} as const));
// eslint-disable-next-line @typescript-eslint/naming-convention
expectError(await nanoSpawn('test', {env: {TEST: true}} as const));
await nanoSpawn('test', {stdin: 'pipe'} as const);
await nanoSpawn('test', {stdin: {string: 'test'} as const} as const);
expectError(await nanoSpawn('test', {stdin: {string: true} as const} as const));
expectError(await nanoSpawn('test', {stdin: {other: 'test'} as const} as const));
expectError(await nanoSpawn('test', {stdin: true} as const));
await nanoSpawn('test', {stdout: 'pipe'} as const);
expectError(await nanoSpawn('test', {stdout: {string: 'test'} as const} as const));
expectError(await nanoSpawn('test', {stdout: true} as const));
await nanoSpawn('test', {stderr: 'pipe'} as const);
expectError(await nanoSpawn('test', {stderr: {string: 'test'} as const} as const));
expectError(await nanoSpawn('test', {stderr: true} as const));
await nanoSpawn('test', {stdio: ['pipe', 'pipe', 'pipe'] as const} as const);
await nanoSpawn('test', {stdio: [{string: 'test'} as const, 'pipe', 'pipe'] as const} as const);
expectError(await nanoSpawn('test', {stdio: ['pipe', {string: 'test'} as const, 'pipe'] as const} as const));
expectError(await nanoSpawn('test', {stdio: ['pipe', 'pipe', {string: 'test'} as const] as const} as const));
expectError(await nanoSpawn('test', {stdio: [{string: true} as const, 'pipe', 'pipe'] as const} as const));
expectError(await nanoSpawn('test', {stdio: [{other: 'test'} as const, 'pipe', 'pipe'] as const} as const));
expectError(await nanoSpawn('test', {stdio: [true, true, true] as const} as const));
await nanoSpawn('test', {stdio: 'pipe'} as const);
expectError(await nanoSpawn('test', {stdio: true} as const));
expectError(await nanoSpawn('test', {other: 'test'} as const));

expectError(await nanoSpawn());
expectError(await nanoSpawn(true));
await nanoSpawn('test', [] as const);
await nanoSpawn('test', ['one'] as const);
expectError(await nanoSpawn('test', [true] as const));
await nanoSpawn('test', {} as const);
expectError(await nanoSpawn('test', true));
await nanoSpawn('test', ['one'] as const, {} as const);
expectError(await nanoSpawn('test', ['one'] as const, true));
expectError(await nanoSpawn('test', ['one'] as const, {} as const, true));

expectError(await nanoSpawn('test').pipe());
expectError(await nanoSpawn('test').pipe(true));
await nanoSpawn('test').pipe('test', [] as const);
await nanoSpawn('test').pipe('test', ['one'] as const);
expectError(await nanoSpawn('test').pipe('test', [true] as const));
await nanoSpawn('test').pipe('test', {} as const);
expectError(await nanoSpawn('test').pipe('test', true));
await nanoSpawn('test').pipe('test', ['one'] as const, {} as const);
expectError(await nanoSpawn('test').pipe('test', ['one'] as const, true));
expectError(await nanoSpawn('test').pipe('test', ['one'] as const, {} as const, true));

expectError(await nanoSpawn('test').pipe('test').pipe());
expectError(await nanoSpawn('test').pipe('test').pipe(true));
await nanoSpawn('test').pipe('test').pipe('test', [] as const);
await nanoSpawn('test').pipe('test').pipe('test', ['one'] as const);
expectError(await nanoSpawn('test').pipe('test').pipe('test', [true] as const));
await nanoSpawn('test').pipe('test').pipe('test', {} as const);
expectError(await nanoSpawn('test').pipe('test').pipe('test', true));
await nanoSpawn('test').pipe('test').pipe('test', ['one'] as const, {} as const);
expectError(await nanoSpawn('test').pipe('test').pipe('test', ['one'] as const, true));
expectError(await nanoSpawn('test').pipe('test').pipe('test', ['one'] as const, {} as const, true));

expectType<Subprocess>(nanoSpawn('test').pipe('test'));
expectType<Subprocess>(nanoSpawn('test').pipe('test').pipe('test'));
expectType<Result>(await nanoSpawn('test').pipe('test'));
expectType<Result>(await nanoSpawn('test').pipe('test').pipe('test'));

for await (const line of nanoSpawn('test')) {
expectType<string>(line);
}

for await (const line of nanoSpawn('test').pipe('test')) {
expectType<string>(line);
}

for await (const line of nanoSpawn('test').stdout) {
expectType<string>(line);
}

for await (const line of nanoSpawn('test').pipe('test').stdout) {
expectType<string>(line);
}

for await (const line of nanoSpawn('test').stderr) {
expectType<string>(line);
}

for await (const line of nanoSpawn('test').pipe('test').stderr) {
expectType<string>(line);
}

const subprocess = nanoSpawn('test');
expectType<Subprocess>(subprocess);

const nodeChildProcess = await subprocess.nodeChildProcess;
expectType<ChildProcess>(nodeChildProcess);
expectType<number | undefined>(nodeChildProcess.pid);
11 changes: 11 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"compilerOptions": {
"module": "nodenext",
"moduleResolution": "nodenext",
"target": "ES2022",
"strict": true
},
"files": [
"source/index.d.ts"
]
}

0 comments on commit e3e3bc8

Please sign in to comment.