Skip to content

Commit 0a7b4f8

Browse files
authored
Type the std* generator transform chunk argument (#1247)
1 parent 487a3a4 commit 0a7b4f8

12 files changed

Lines changed: 469 additions & 98 deletions

‎docs/api.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1288,7 +1288,7 @@ If `true`, iterate over arbitrary chunks of `Uint8Array`s instead of line `strin
12881288
_Type:_ `boolean`\
12891289
_Default:_ `false`
12901290

1291-
If `true`, keep newlines in each `line` argument. Also, this allows multiple `yield`s to produces a single line.
1291+
If `true`, keep newlines in each `line` argument. Also, this allows multiple `yield`s to produce a single line.
12921292

12931293
[More info.](lines.md#transforms)
12941294

‎test-d/methods/script.test-d.ts‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,13 @@ expectAssignable<{stdout: Uint8Array}>(await $({encoding: 'buffer'})`unicorns`);
4141
expectAssignable<{stdout: Uint8Array}>(await $({})({encoding: 'buffer'})`unicorns`);
4242
expectAssignable<{stdout: Uint8Array}>(await $({encoding: 'buffer'})({})`unicorns`);
4343

44+
const uint8ArrayGenerator = function * (chunk: Uint8Array) {
45+
yield chunk;
46+
};
47+
48+
expectError($({encoding: 'utf8', stdout: uint8ArrayGenerator}));
49+
$({encoding: 'buffer', stdout: uint8ArrayGenerator});
50+
4451
expectType<Result<{}>>(await $`${'unicorns'}`);
4552
expectType<Result<{}>>(await $`unicorns ${'foo'}`);
4653
expectType<Result<{}>>(await $`unicorns ${'foo'} ${'bar'}`);
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
import {expectError, expectAssignable, expectNotAssignable} from 'tsd';
2+
import {
3+
execa,
4+
execaSync,
5+
type StdinOption,
6+
type StdinSyncOption,
7+
type StdoutStderrOption,
8+
type StdoutStderrSyncOption,
9+
} from '../../../index.js';
10+
11+
// With `binary: true`, the `chunk` argument is an `Uint8Array`.
12+
const uint8ArrayBinary = {
13+
* transform(chunk: Uint8Array) {
14+
yield chunk;
15+
},
16+
binary: true,
17+
} as const;
18+
19+
await execa('unicorns', {stdin: uint8ArrayBinary});
20+
execaSync('unicorns', {stdin: uint8ArrayBinary});
21+
await execa('unicorns', {stdin: [uint8ArrayBinary]});
22+
execaSync('unicorns', {stdin: [uint8ArrayBinary]});
23+
await execa('unicorns', {stdout: uint8ArrayBinary});
24+
execaSync('unicorns', {stdout: uint8ArrayBinary});
25+
await execa('unicorns', {stderr: uint8ArrayBinary});
26+
execaSync('unicorns', {stderr: uint8ArrayBinary});
27+
await execa('unicorns', {stdio: ['pipe', 'pipe', 'pipe', uint8ArrayBinary]});
28+
29+
expectAssignable<StdinOption>(uint8ArrayBinary);
30+
expectAssignable<StdinSyncOption>(uint8ArrayBinary);
31+
expectAssignable<StdinOption>([uint8ArrayBinary]);
32+
expectAssignable<StdinSyncOption>([uint8ArrayBinary]);
33+
expectAssignable<StdoutStderrOption>(uint8ArrayBinary);
34+
expectAssignable<StdoutStderrSyncOption>(uint8ArrayBinary);
35+
expectAssignable<StdoutStderrOption>([uint8ArrayBinary]);
36+
expectAssignable<StdoutStderrSyncOption>([uint8ArrayBinary]);
37+
38+
// `objectMode: true` takes precedence over `binary: true` for stdin, so the `chunk` argument is `unknown`.
39+
const uint8ArrayBinaryObjectMode = {
40+
* transform(chunk: Uint8Array) {
41+
yield chunk;
42+
},
43+
binary: true,
44+
objectMode: true,
45+
} as const;
46+
47+
const stdinObjectModeBinary = {
48+
* transform(chunk: unknown) {
49+
yield chunk;
50+
},
51+
binary: true,
52+
objectMode: true,
53+
} as const;
54+
55+
expectError(await execa('unicorns', {stdin: uint8ArrayBinaryObjectMode}));
56+
expectError(execaSync('unicorns', {stdin: uint8ArrayBinaryObjectMode}));
57+
await execa('unicorns', {stdout: uint8ArrayBinaryObjectMode});
58+
execaSync('unicorns', {stdout: uint8ArrayBinaryObjectMode});
59+
await execa('unicorns', {stderr: uint8ArrayBinaryObjectMode});
60+
execaSync('unicorns', {stderr: uint8ArrayBinaryObjectMode});
61+
62+
await execa('unicorns', {stdin: stdinObjectModeBinary});
63+
execaSync('unicorns', {stdin: stdinObjectModeBinary});
64+
65+
expectNotAssignable<StdinOption>(uint8ArrayBinaryObjectMode);
66+
expectNotAssignable<StdinSyncOption>(uint8ArrayBinaryObjectMode);
67+
expectAssignable<StdoutStderrOption>(uint8ArrayBinaryObjectMode);
68+
expectAssignable<StdoutStderrSyncOption>(uint8ArrayBinaryObjectMode);
69+
expectAssignable<StdinOption>(stdinObjectModeBinary);
70+
expectAssignable<StdinSyncOption>(stdinObjectModeBinary);
71+
72+
const stdoutAnnotatedBinaryObjectMode: StdoutStderrOption = {
73+
* transform(chunk: Uint8Array) {
74+
yield chunk;
75+
},
76+
binary: true,
77+
objectMode: true,
78+
};
79+
80+
expectAssignable<StdoutStderrOption>(stdoutAnnotatedBinaryObjectMode);
81+
82+
// A `string` argument is invalid when `binary: true`.
83+
const stringBinary = {
84+
* transform(line: string) {
85+
yield line;
86+
},
87+
binary: true,
88+
} as const;
89+
90+
const stringBinaryObjectMode = {
91+
* transform(line: string) {
92+
yield line;
93+
},
94+
binary: true,
95+
objectMode: true,
96+
} as const;
97+
98+
expectError(await execa('unicorns', {stdin: stringBinary}));
99+
expectError(execaSync('unicorns', {stdin: stringBinary}));
100+
expectError(await execa('unicorns', {stdin: [stringBinary]}));
101+
expectError(execaSync('unicorns', {stdin: [stringBinary]}));
102+
expectError(await execa('unicorns', {stdout: stringBinary}));
103+
expectError(execaSync('unicorns', {stdout: stringBinary}));
104+
expectError(await execa('unicorns', {stderr: stringBinary}));
105+
expectError(execaSync('unicorns', {stderr: stringBinary}));
106+
expectError(await execa('unicorns', {stdio: ['pipe', 'pipe', 'pipe', stringBinary]}));
107+
expectError(await execa('unicorns', {stdout: stringBinaryObjectMode}));
108+
expectError(execaSync('unicorns', {stdout: stringBinaryObjectMode}));
109+
expectError(await execa('unicorns', {stderr: stringBinaryObjectMode}));
110+
expectError(execaSync('unicorns', {stderr: stringBinaryObjectMode}));
111+
112+
expectNotAssignable<StdinOption>(stringBinary);
113+
expectNotAssignable<StdinSyncOption>(stringBinary);
114+
expectNotAssignable<StdinOption>([stringBinary]);
115+
expectNotAssignable<StdinSyncOption>([stringBinary]);
116+
expectNotAssignable<StdoutStderrOption>(stringBinary);
117+
expectNotAssignable<StdoutStderrSyncOption>(stringBinary);
118+
expectNotAssignable<StdoutStderrOption>([stringBinary]);
119+
expectNotAssignable<StdoutStderrSyncOption>([stringBinary]);
120+
expectNotAssignable<StdoutStderrOption>(stringBinaryObjectMode);
121+
expectNotAssignable<StdoutStderrSyncOption>(stringBinaryObjectMode);
122+
123+
// An `Uint8Array` argument is invalid without `binary: true`, since the `chunk` is then a `string`.
124+
const uint8ArrayLine = {
125+
* transform(chunk: Uint8Array) {
126+
yield chunk;
127+
},
128+
} as const;
129+
130+
expectError(await execa('unicorns', {stdin: uint8ArrayLine}));
131+
expectError(execaSync('unicorns', {stdin: uint8ArrayLine}));
132+
expectError(await execa('unicorns', {stdin: [uint8ArrayLine]}));
133+
expectError(execaSync('unicorns', {stdin: [uint8ArrayLine]}));
134+
expectError(await execa('unicorns', {stdout: uint8ArrayLine}));
135+
expectError(execaSync('unicorns', {stdout: uint8ArrayLine}));
136+
expectError(await execa('unicorns', {stderr: uint8ArrayLine}));
137+
expectError(execaSync('unicorns', {stderr: uint8ArrayLine}));
138+
expectError(await execa('unicorns', {stdio: ['pipe', 'pipe', 'pipe', uint8ArrayLine]}));
139+
140+
expectNotAssignable<StdinOption>(uint8ArrayLine);
141+
expectNotAssignable<StdinSyncOption>(uint8ArrayLine);
142+
expectNotAssignable<StdinOption>([uint8ArrayLine]);
143+
expectNotAssignable<StdinSyncOption>([uint8ArrayLine]);
144+
expectNotAssignable<StdoutStderrOption>(uint8ArrayLine);
145+
expectNotAssignable<StdoutStderrSyncOption>(uint8ArrayLine);
146+
expectNotAssignable<StdoutStderrOption>([uint8ArrayLine]);
147+
expectNotAssignable<StdoutStderrSyncOption>([uint8ArrayLine]);
148+
149+
// Binary encodings also make output transforms receive binary chunks.
150+
await execa('unicorns', {encoding: 'buffer', stdin: uint8ArrayLine});
151+
execaSync('unicorns', {encoding: 'buffer', stdin: uint8ArrayLine});
152+
await execa('unicorns', {encoding: 'buffer', stdout: uint8ArrayLine});
153+
execaSync('unicorns', {encoding: 'buffer', stdout: uint8ArrayLine});
154+
await execa('unicorns', {encoding: 'buffer', stderr: uint8ArrayLine});
155+
execaSync('unicorns', {encoding: 'buffer', stderr: uint8ArrayLine});
156+
await execa('unicorns', {encoding: 'buffer', stdio: [uint8ArrayLine, uint8ArrayLine, uint8ArrayLine, uint8ArrayLine]});
157+
execaSync('unicorns', {encoding: 'buffer', stdio: [uint8ArrayLine, uint8ArrayLine, uint8ArrayLine, uint8ArrayLine]});
158+
159+
// A bare generator function always receives string lines unless `encoding` is binary.
160+
const uint8ArrayGenerator = function * (chunk: Uint8Array) {
161+
yield chunk;
162+
};
163+
164+
expectError(await execa('unicorns', {stdin: uint8ArrayGenerator}));
165+
expectError(execaSync('unicorns', {stdin: uint8ArrayGenerator}));
166+
expectError(await execa('unicorns', {stdout: uint8ArrayGenerator}));
167+
expectError(execaSync('unicorns', {stdout: uint8ArrayGenerator}));
168+
expectError(await execa('unicorns', {stderr: uint8ArrayGenerator}));
169+
expectError(execaSync('unicorns', {stderr: uint8ArrayGenerator}));
170+
expectError(await execa('unicorns', {stdio: ['pipe', 'pipe', 'pipe', uint8ArrayGenerator]}));
171+
172+
expectNotAssignable<StdinOption>(uint8ArrayGenerator);
173+
expectNotAssignable<StdinSyncOption>(uint8ArrayGenerator);
174+
expectNotAssignable<StdoutStderrOption>(uint8ArrayGenerator);
175+
expectNotAssignable<StdoutStderrSyncOption>(uint8ArrayGenerator);
176+
177+
await execa('unicorns', {encoding: 'buffer', stdin: uint8ArrayGenerator});
178+
execaSync('unicorns', {encoding: 'buffer', stdin: uint8ArrayGenerator});
179+
await execa('unicorns', {encoding: 'buffer', stdout: uint8ArrayGenerator});
180+
execaSync('unicorns', {encoding: 'buffer', stdout: uint8ArrayGenerator});
181+
await execa('unicorns', {encoding: 'buffer', stderr: uint8ArrayGenerator});
182+
execaSync('unicorns', {encoding: 'buffer', stderr: uint8ArrayGenerator});
183+
await execa('unicorns', {encoding: 'buffer', stdio: [uint8ArrayGenerator, uint8ArrayGenerator, uint8ArrayGenerator, uint8ArrayGenerator]});
184+
execaSync('unicorns', {encoding: 'buffer', stdio: [uint8ArrayGenerator, uint8ArrayGenerator, uint8ArrayGenerator, uint8ArrayGenerator]});
185+
186+
// Conversely, a binary `encoding` makes the `chunk` a `Uint8Array`, so a `string` transform is rejected.
187+
const stringGenerator = function * (line: string) {
188+
yield line;
189+
};
190+
191+
expectError(await execa('unicorns', {encoding: 'buffer', stdin: stringGenerator}));
192+
expectError(execaSync('unicorns', {encoding: 'buffer', stdin: stringGenerator}));
193+
expectError(await execa('unicorns', {encoding: 'buffer', stdout: stringGenerator}));
194+
expectError(execaSync('unicorns', {encoding: 'buffer', stdout: stringGenerator}));
195+
196+
// This applies to every binary `encoding`, not just `'buffer'`.
197+
await execa('unicorns', {encoding: 'hex', stdout: uint8ArrayGenerator});
198+
execaSync('unicorns', {encoding: 'hex', stdout: uint8ArrayGenerator});
199+
await execa('unicorns', {encoding: 'base64', stdout: uint8ArrayGenerator});
200+
execaSync('unicorns', {encoding: 'base64', stdout: uint8ArrayGenerator});
201+
expectError(await execa('unicorns', {encoding: 'hex', stdout: stringGenerator}));
202+
expectError(execaSync('unicorns', {encoding: 'hex', stdout: stringGenerator}));

‎test-d/stdio/option/generator-object-full.test-d.ts‎

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {expectError, expectAssignable} from 'tsd';
1+
import {expectError, expectAssignable, expectNotAssignable} from 'tsd';
22
import {
33
execa,
44
execaSync,
@@ -15,11 +15,23 @@ const objectGeneratorFull = {
1515
objectMode: true,
1616
} as const;
1717

18+
const stringInputObjectGeneratorFull = {
19+
* transform(line: string) {
20+
yield JSON.parse(line) as object;
21+
},
22+
objectMode: true,
23+
} as const;
24+
1825
await execa('unicorns', {stdin: objectGeneratorFull});
1926
execaSync('unicorns', {stdin: objectGeneratorFull});
2027
await execa('unicorns', {stdin: [objectGeneratorFull]});
2128
execaSync('unicorns', {stdin: [objectGeneratorFull]});
2229

30+
expectError(await execa('unicorns', {stdin: stringInputObjectGeneratorFull}));
31+
expectError(execaSync('unicorns', {stdin: stringInputObjectGeneratorFull}));
32+
expectError(await execa('unicorns', {stdin: [stringInputObjectGeneratorFull]}));
33+
expectError(execaSync('unicorns', {stdin: [stringInputObjectGeneratorFull]}));
34+
2335
await execa('unicorns', {stdout: objectGeneratorFull});
2436
execaSync('unicorns', {stdout: objectGeneratorFull});
2537
await execa('unicorns', {stdout: [objectGeneratorFull]});
@@ -30,6 +42,35 @@ execaSync('unicorns', {stderr: objectGeneratorFull});
3042
await execa('unicorns', {stderr: [objectGeneratorFull]});
3143
execaSync('unicorns', {stderr: [objectGeneratorFull]});
3244

45+
await execa('unicorns', {stdout: stringInputObjectGeneratorFull});
46+
execaSync('unicorns', {stdout: stringInputObjectGeneratorFull});
47+
await execa('unicorns', {stdout: [stringInputObjectGeneratorFull]});
48+
execaSync('unicorns', {stdout: [stringInputObjectGeneratorFull]});
49+
50+
// The array index does not determine the pipeline position, so a `string`-input transform is valid at any position.
51+
await execa('unicorns', {stdout: [stringInputObjectGeneratorFull, objectGeneratorFull]});
52+
execaSync('unicorns', {stdout: [stringInputObjectGeneratorFull, objectGeneratorFull]});
53+
await execa('unicorns', {stdout: [objectGeneratorFull, stringInputObjectGeneratorFull]});
54+
execaSync('unicorns', {stdout: [objectGeneratorFull, stringInputObjectGeneratorFull]});
55+
56+
// A transform preceded by a non-transform target still receives subprocess `string` lines.
57+
await execa('unicorns', {stdout: ['pipe', stringInputObjectGeneratorFull]});
58+
execaSync('unicorns', {stdout: ['pipe', stringInputObjectGeneratorFull]});
59+
60+
// A non-tuple array variable stays assignable.
61+
const stdoutArray = [stringInputObjectGeneratorFull];
62+
await execa('unicorns', {stdout: stdoutArray});
63+
execaSync('unicorns', {stdout: stdoutArray});
64+
65+
await execa('unicorns', {stderr: stringInputObjectGeneratorFull});
66+
execaSync('unicorns', {stderr: stringInputObjectGeneratorFull});
67+
await execa('unicorns', {stderr: [stringInputObjectGeneratorFull]});
68+
execaSync('unicorns', {stderr: [stringInputObjectGeneratorFull]});
69+
await execa('unicorns', {stderr: [stringInputObjectGeneratorFull, objectGeneratorFull]});
70+
execaSync('unicorns', {stderr: [stringInputObjectGeneratorFull, objectGeneratorFull]});
71+
await execa('unicorns', {stderr: [objectGeneratorFull, stringInputObjectGeneratorFull]});
72+
execaSync('unicorns', {stderr: [objectGeneratorFull, stringInputObjectGeneratorFull]});
73+
3374
expectError(await execa('unicorns', {stdio: objectGeneratorFull}));
3475
expectError(execaSync('unicorns', {stdio: objectGeneratorFull}));
3576

@@ -42,8 +83,16 @@ expectAssignable<StdinOption>(objectGeneratorFull);
4283
expectAssignable<StdinSyncOption>(objectGeneratorFull);
4384
expectAssignable<StdinOption>([objectGeneratorFull]);
4485
expectAssignable<StdinSyncOption>([objectGeneratorFull]);
86+
expectNotAssignable<StdinOption>(stringInputObjectGeneratorFull);
87+
expectNotAssignable<StdinSyncOption>(stringInputObjectGeneratorFull);
88+
expectNotAssignable<StdinOption>([stringInputObjectGeneratorFull]);
89+
expectNotAssignable<StdinSyncOption>([stringInputObjectGeneratorFull]);
4590

4691
expectAssignable<StdoutStderrOption>(objectGeneratorFull);
4792
expectAssignable<StdoutStderrSyncOption>(objectGeneratorFull);
4893
expectAssignable<StdoutStderrOption>([objectGeneratorFull]);
4994
expectAssignable<StdoutStderrSyncOption>([objectGeneratorFull]);
95+
expectAssignable<StdoutStderrOption>(stringInputObjectGeneratorFull);
96+
expectAssignable<StdoutStderrSyncOption>(stringInputObjectGeneratorFull);
97+
expectAssignable<StdoutStderrOption>([stringInputObjectGeneratorFull]);
98+
expectAssignable<StdoutStderrSyncOption>([stringInputObjectGeneratorFull]);
Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {expectError, expectNotAssignable} from 'tsd';
1+
import {expectError, expectAssignable} from 'tsd';
22
import {
33
execa,
44
execaSync,
@@ -14,35 +14,35 @@ const stringGeneratorFull = {
1414
},
1515
} as const;
1616

17-
expectError(await execa('unicorns', {stdin: stringGeneratorFull}));
18-
expectError(execaSync('unicorns', {stdin: stringGeneratorFull}));
19-
expectError(await execa('unicorns', {stdin: [stringGeneratorFull]}));
20-
expectError(execaSync('unicorns', {stdin: [stringGeneratorFull]}));
17+
await execa('unicorns', {stdin: stringGeneratorFull});
18+
execaSync('unicorns', {stdin: stringGeneratorFull});
19+
await execa('unicorns', {stdin: [stringGeneratorFull]});
20+
execaSync('unicorns', {stdin: [stringGeneratorFull]});
2121

22-
expectError(await execa('unicorns', {stdout: stringGeneratorFull}));
23-
expectError(execaSync('unicorns', {stdout: stringGeneratorFull}));
24-
expectError(await execa('unicorns', {stdout: [stringGeneratorFull]}));
25-
expectError(execaSync('unicorns', {stdout: [stringGeneratorFull]}));
22+
await execa('unicorns', {stdout: stringGeneratorFull});
23+
execaSync('unicorns', {stdout: stringGeneratorFull});
24+
await execa('unicorns', {stdout: [stringGeneratorFull]});
25+
execaSync('unicorns', {stdout: [stringGeneratorFull]});
2626

27-
expectError(await execa('unicorns', {stderr: stringGeneratorFull}));
28-
expectError(execaSync('unicorns', {stderr: stringGeneratorFull}));
29-
expectError(await execa('unicorns', {stderr: [stringGeneratorFull]}));
30-
expectError(execaSync('unicorns', {stderr: [stringGeneratorFull]}));
27+
await execa('unicorns', {stderr: stringGeneratorFull});
28+
execaSync('unicorns', {stderr: stringGeneratorFull});
29+
await execa('unicorns', {stderr: [stringGeneratorFull]});
30+
execaSync('unicorns', {stderr: [stringGeneratorFull]});
3131

3232
expectError(await execa('unicorns', {stdio: stringGeneratorFull}));
3333
expectError(execaSync('unicorns', {stdio: stringGeneratorFull}));
3434

35-
expectError(await execa('unicorns', {stdio: ['pipe', 'pipe', 'pipe', stringGeneratorFull]}));
36-
expectError(execaSync('unicorns', {stdio: ['pipe', 'pipe', 'pipe', stringGeneratorFull]}));
37-
expectError(await execa('unicorns', {stdio: ['pipe', 'pipe', 'pipe', [stringGeneratorFull]]}));
38-
expectError(execaSync('unicorns', {stdio: ['pipe', 'pipe', 'pipe', [stringGeneratorFull]]}));
35+
await execa('unicorns', {stdio: ['pipe', 'pipe', 'pipe', stringGeneratorFull]});
36+
execaSync('unicorns', {stdio: ['pipe', 'pipe', 'pipe', stringGeneratorFull]});
37+
await execa('unicorns', {stdio: ['pipe', 'pipe', 'pipe', [stringGeneratorFull]]});
38+
execaSync('unicorns', {stdio: ['pipe', 'pipe', 'pipe', [stringGeneratorFull]]});
3939

40-
expectNotAssignable<StdinOption>(stringGeneratorFull);
41-
expectNotAssignable<StdinSyncOption>(stringGeneratorFull);
42-
expectNotAssignable<StdinOption>([stringGeneratorFull]);
43-
expectNotAssignable<StdinSyncOption>([stringGeneratorFull]);
40+
expectAssignable<StdinOption>(stringGeneratorFull);
41+
expectAssignable<StdinSyncOption>(stringGeneratorFull);
42+
expectAssignable<StdinOption>([stringGeneratorFull]);
43+
expectAssignable<StdinSyncOption>([stringGeneratorFull]);
4444

45-
expectNotAssignable<StdoutStderrOption>(stringGeneratorFull);
46-
expectNotAssignable<StdoutStderrSyncOption>(stringGeneratorFull);
47-
expectNotAssignable<StdoutStderrOption>([stringGeneratorFull]);
48-
expectNotAssignable<StdoutStderrSyncOption>([stringGeneratorFull]);
45+
expectAssignable<StdoutStderrOption>(stringGeneratorFull);
46+
expectAssignable<StdoutStderrSyncOption>(stringGeneratorFull);
47+
expectAssignable<StdoutStderrOption>([stringGeneratorFull]);
48+
expectAssignable<StdoutStderrSyncOption>([stringGeneratorFull]);

0 commit comments

Comments
 (0)