firedrill-run 3.0.2 → 3.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assembly.json +1 -1
- package/dist/cli/src/request-input.js +75 -0
- package/package.json +1 -1
package/dist/assembly.json
CHANGED
|
@@ -2,13 +2,88 @@ import { constants } from 'node:fs';
|
|
|
2
2
|
import { lstat, open } from 'node:fs/promises';
|
|
3
3
|
import { resolve } from 'node:path';
|
|
4
4
|
import { CliError } from './http.js';
|
|
5
|
+
/** Consume a pipe once without leaving an abort waiting for another byte. */
|
|
6
|
+
async function stdinInput(signal, maximumBytes) {
|
|
7
|
+
signal.throwIfAborted();
|
|
8
|
+
const input = process.stdin;
|
|
9
|
+
if (input.isTTY || input.readableEncoding !== null) {
|
|
10
|
+
throw new CliError('request_stdin_invalid', 'Pipe one UTF-8 JSON object into --file -. Nothing was submitted.', 2);
|
|
11
|
+
}
|
|
12
|
+
const chunks = await new Promise((accept, reject) => {
|
|
13
|
+
const collected = [];
|
|
14
|
+
let size = 0;
|
|
15
|
+
let settled = false;
|
|
16
|
+
const invalid = () => new CliError('request_stdin_invalid', 'Standard input must contain one JSON object within transport capacity. Nothing was submitted.', 2);
|
|
17
|
+
const finish = (error) => {
|
|
18
|
+
if (settled)
|
|
19
|
+
return;
|
|
20
|
+
settled = true;
|
|
21
|
+
input.pause();
|
|
22
|
+
input.removeListener('data', data);
|
|
23
|
+
input.removeListener('end', end);
|
|
24
|
+
input.removeListener('error', failed);
|
|
25
|
+
input.removeListener('close', closed);
|
|
26
|
+
signal.removeEventListener('abort', aborted);
|
|
27
|
+
if (error !== undefined)
|
|
28
|
+
reject(error);
|
|
29
|
+
else
|
|
30
|
+
accept(collected);
|
|
31
|
+
};
|
|
32
|
+
const data = (chunk) => {
|
|
33
|
+
if (!Buffer.isBuffer(chunk)) {
|
|
34
|
+
finish(invalid());
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
size += chunk.length;
|
|
38
|
+
if (size > maximumBytes) {
|
|
39
|
+
finish(invalid());
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
collected.push(Buffer.from(chunk));
|
|
43
|
+
};
|
|
44
|
+
const end = () => finish();
|
|
45
|
+
const failed = () => finish(invalid());
|
|
46
|
+
const closed = () => { if (!input.readableEnded)
|
|
47
|
+
finish(invalid()); };
|
|
48
|
+
const aborted = () => finish(signal.reason ?? new DOMException('The operation was aborted.', 'AbortError'));
|
|
49
|
+
input.on('data', data);
|
|
50
|
+
input.once('end', end);
|
|
51
|
+
input.once('error', failed);
|
|
52
|
+
input.once('close', closed);
|
|
53
|
+
signal.addEventListener('abort', aborted, { once: true });
|
|
54
|
+
// EOF or cancellation can precede listener installation. Never wait for an
|
|
55
|
+
// event that has already happened, including an idle pipe on cancellation.
|
|
56
|
+
if (signal.aborted)
|
|
57
|
+
aborted();
|
|
58
|
+
else if (input.readableEnded)
|
|
59
|
+
end();
|
|
60
|
+
else if (input.destroyed)
|
|
61
|
+
failed();
|
|
62
|
+
else
|
|
63
|
+
input.resume();
|
|
64
|
+
});
|
|
65
|
+
signal.throwIfAborted();
|
|
66
|
+
try {
|
|
67
|
+
const raw = new TextDecoder('utf-8', { fatal: true }).decode(Buffer.concat(chunks));
|
|
68
|
+
const value = JSON.parse(raw);
|
|
69
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
70
|
+
throw new Error();
|
|
71
|
+
return raw;
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
throw new CliError('request_stdin_invalid', 'Standard input is not a UTF-8 JSON object. Nothing was submitted.', 2);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
5
77
|
/** Read one bounded, unchanged JSON object without rewriting number literals. */
|
|
6
78
|
export async function requestInput(path, signal, maximumBytes = 8 * 1024 * 1024) {
|
|
79
|
+
signal.throwIfAborted();
|
|
7
80
|
if (!Number.isSafeInteger(maximumBytes) || maximumBytes < 2 || maximumBytes > 8 * 1024 * 1024) {
|
|
8
81
|
throw new CliError('request_capacity_invalid', 'Choose a supported request capacity.', 2);
|
|
9
82
|
}
|
|
10
83
|
if (!path)
|
|
11
84
|
return '{}';
|
|
85
|
+
if (path === '-')
|
|
86
|
+
return stdinInput(signal, maximumBytes);
|
|
12
87
|
const before = await lstat(resolve(path));
|
|
13
88
|
if (!before.isFile() || before.isSymbolicLink() || before.size > maximumBytes) {
|
|
14
89
|
throw new CliError('request_file_invalid', 'Choose a regular JSON file within transport capacity.', 2);
|