hacksh 0.13.0 → 0.14.0
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/README.md +108 -89
- package/dist/command-CfWrbHLN.d.ts +378 -0
- package/dist/index.d.ts +17 -418
- package/dist/index.js +139 -1159
- package/dist/index.js.map +1 -1
- package/dist/pretty-BFub611x.js +1012 -0
- package/dist/pretty-BFub611x.js.map +1 -0
- package/dist/printer-api.d.ts +13 -0
- package/dist/printer-api.js +2 -0
- package/package.json +5 -1
|
@@ -0,0 +1,1012 @@
|
|
|
1
|
+
import { inspect, stripVTControlCharacters, styleText } from "node:util";
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
//#region src/errors.ts
|
|
4
|
+
var HackshUsageError = class extends Error {
|
|
5
|
+
command;
|
|
6
|
+
constructor(message, command, options) {
|
|
7
|
+
super(message, options);
|
|
8
|
+
this.name = "HackshUsageError";
|
|
9
|
+
this.command = command;
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
var HackshValidationError = class extends HackshUsageError {
|
|
13
|
+
issues;
|
|
14
|
+
constructor(command, issues) {
|
|
15
|
+
super(`Invalid arguments:\n${issues.map(({ arg, message }) => ` ${arg}: ${message}`).join("\n")}`, command);
|
|
16
|
+
this.name = "HackshValidationError";
|
|
17
|
+
this.issues = issues;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
/** An input request that could not be completed safely. */
|
|
21
|
+
var HackshPromptError = class extends Error {
|
|
22
|
+
reason;
|
|
23
|
+
prompt;
|
|
24
|
+
constructor(reason, prompt, message, options) {
|
|
25
|
+
super(message, options);
|
|
26
|
+
this.name = "HackshPromptError";
|
|
27
|
+
this.reason = reason;
|
|
28
|
+
this.prompt = prompt;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
/** An external command that did not succeed; `result` is not enumerable so error summaries omit child output. */
|
|
32
|
+
var HackshExecError = class extends Error {
|
|
33
|
+
#result;
|
|
34
|
+
constructor(message, result, options) {
|
|
35
|
+
super(message, options);
|
|
36
|
+
this.name = "HackshExecError";
|
|
37
|
+
this.#result = result;
|
|
38
|
+
}
|
|
39
|
+
get result() {
|
|
40
|
+
return this.#result;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
/** A requested process exit status; `runMain` exits with it and reports only a non-empty message. */
|
|
44
|
+
var HackshExitError = class extends Error {
|
|
45
|
+
exitCode;
|
|
46
|
+
constructor(exitCode, message, options) {
|
|
47
|
+
if (!Number.isInteger(exitCode) || exitCode < 0 || exitCode > 255) throw new TypeError("Exit code must be an integer from 0 to 255");
|
|
48
|
+
super(message, options);
|
|
49
|
+
this.name = "HackshExitError";
|
|
50
|
+
this.exitCode = exitCode;
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
function safeString$1(value, fallback) {
|
|
54
|
+
try {
|
|
55
|
+
return String(value);
|
|
56
|
+
} catch {
|
|
57
|
+
return fallback;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function readProperty(value, key) {
|
|
61
|
+
try {
|
|
62
|
+
return Reflect.get(value, key);
|
|
63
|
+
} catch {
|
|
64
|
+
return "[Unserializable]";
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function enumerableKeys(value) {
|
|
68
|
+
try {
|
|
69
|
+
return Object.keys(value);
|
|
70
|
+
} catch {
|
|
71
|
+
return [];
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function hasToJSON(value) {
|
|
75
|
+
try {
|
|
76
|
+
return typeof Reflect.get(value, "toJSON") === "function";
|
|
77
|
+
} catch {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function isPlainObject(value) {
|
|
82
|
+
try {
|
|
83
|
+
const prototype = Object.getPrototypeOf(value);
|
|
84
|
+
return prototype === Object.prototype || prototype === null;
|
|
85
|
+
} catch {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function summarizeIssue({ arg, message, issue }, seen) {
|
|
90
|
+
if (!issue) return {
|
|
91
|
+
arg,
|
|
92
|
+
message
|
|
93
|
+
};
|
|
94
|
+
const details = {};
|
|
95
|
+
for (const key of enumerableKeys(issue)) {
|
|
96
|
+
if ([
|
|
97
|
+
"code",
|
|
98
|
+
"path",
|
|
99
|
+
"message",
|
|
100
|
+
"inst"
|
|
101
|
+
].includes(key)) continue;
|
|
102
|
+
details[key] = summarizeErrors(readProperty(issue, key), seen);
|
|
103
|
+
}
|
|
104
|
+
const code = readProperty(issue, "code");
|
|
105
|
+
const path = readProperty(issue, "path");
|
|
106
|
+
return {
|
|
107
|
+
arg,
|
|
108
|
+
message,
|
|
109
|
+
...typeof code === "string" ? { code } : {},
|
|
110
|
+
...Array.isArray(path) ? { path } : {},
|
|
111
|
+
...Object.keys(details).length === 0 ? {} : { details }
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
function errorKind(error) {
|
|
115
|
+
if (error instanceof HackshPromptError) return "prompt";
|
|
116
|
+
if (error instanceof HackshValidationError) return "validation";
|
|
117
|
+
if (error instanceof HackshUsageError) return "usage";
|
|
118
|
+
return "error";
|
|
119
|
+
}
|
|
120
|
+
function summarizeErrorWithSeen(error, seen) {
|
|
121
|
+
const summary = {
|
|
122
|
+
kind: errorKind(error),
|
|
123
|
+
name: safeString$1(readProperty(error, "name"), "Error"),
|
|
124
|
+
message: safeString$1(readProperty(error, "message"), "[Unserializable error message]")
|
|
125
|
+
};
|
|
126
|
+
seen.set(error, summary);
|
|
127
|
+
const stack = readProperty(error, "stack");
|
|
128
|
+
const cause = readProperty(error, "cause");
|
|
129
|
+
if (typeof stack === "string") summary.stack = stack;
|
|
130
|
+
if (cause !== void 0) summary.cause = summarizeErrors(cause, seen);
|
|
131
|
+
if (error instanceof HackshValidationError) summary.issues = error.issues.map((issue) => summarizeIssue(issue, seen));
|
|
132
|
+
const reserved = /* @__PURE__ */ new Set([
|
|
133
|
+
"name",
|
|
134
|
+
"message",
|
|
135
|
+
"stack",
|
|
136
|
+
"cause",
|
|
137
|
+
"command",
|
|
138
|
+
"issues"
|
|
139
|
+
]);
|
|
140
|
+
const properties = {};
|
|
141
|
+
for (const key of enumerableKeys(error)) {
|
|
142
|
+
if (reserved.has(key)) continue;
|
|
143
|
+
properties[key] = summarizeErrors(readProperty(error, key), seen);
|
|
144
|
+
}
|
|
145
|
+
if (Object.keys(properties).length > 0) summary.properties = properties;
|
|
146
|
+
return summary;
|
|
147
|
+
}
|
|
148
|
+
/** Recursively replace Error values with command- and schema-free summaries. */
|
|
149
|
+
function summarizeErrors(value, seen = /* @__PURE__ */ new WeakMap()) {
|
|
150
|
+
if (typeof value !== "object" || value === null) return value;
|
|
151
|
+
const existing = seen.get(value);
|
|
152
|
+
if (existing !== void 0) return existing;
|
|
153
|
+
if (value instanceof Error) return summarizeErrorWithSeen(value, seen);
|
|
154
|
+
if (Array.isArray(value)) {
|
|
155
|
+
const result = [];
|
|
156
|
+
seen.set(value, result);
|
|
157
|
+
let changed = false;
|
|
158
|
+
for (const item of value) {
|
|
159
|
+
const summarized = summarizeErrors(item, seen);
|
|
160
|
+
result.push(summarized);
|
|
161
|
+
changed ||= summarized !== item;
|
|
162
|
+
}
|
|
163
|
+
if (changed) return result;
|
|
164
|
+
seen.set(value, value);
|
|
165
|
+
return value;
|
|
166
|
+
}
|
|
167
|
+
if (value instanceof Map) {
|
|
168
|
+
const result = /* @__PURE__ */ new Map();
|
|
169
|
+
seen.set(value, result);
|
|
170
|
+
let changed = false;
|
|
171
|
+
for (const [key, item] of value) {
|
|
172
|
+
const summarizedKey = summarizeErrors(key, seen);
|
|
173
|
+
const summarizedItem = summarizeErrors(item, seen);
|
|
174
|
+
result.set(summarizedKey, summarizedItem);
|
|
175
|
+
changed ||= summarizedKey !== key || summarizedItem !== item;
|
|
176
|
+
}
|
|
177
|
+
if (changed) return result;
|
|
178
|
+
seen.set(value, value);
|
|
179
|
+
return value;
|
|
180
|
+
}
|
|
181
|
+
if (value instanceof Set) {
|
|
182
|
+
const result = /* @__PURE__ */ new Set();
|
|
183
|
+
seen.set(value, result);
|
|
184
|
+
let changed = false;
|
|
185
|
+
for (const item of value) {
|
|
186
|
+
const summarized = summarizeErrors(item, seen);
|
|
187
|
+
result.add(summarized);
|
|
188
|
+
changed ||= summarized !== item;
|
|
189
|
+
}
|
|
190
|
+
if (changed) return result;
|
|
191
|
+
seen.set(value, value);
|
|
192
|
+
return value;
|
|
193
|
+
}
|
|
194
|
+
if (ArrayBuffer.isView(value) || hasToJSON(value) || !isPlainObject(value)) return value;
|
|
195
|
+
const result = {};
|
|
196
|
+
seen.set(value, result);
|
|
197
|
+
let changed = false;
|
|
198
|
+
for (const key of enumerableKeys(value)) {
|
|
199
|
+
const original = readProperty(value, key);
|
|
200
|
+
const summarized = summarizeErrors(original, seen);
|
|
201
|
+
result[key] = summarized;
|
|
202
|
+
changed ||= summarized !== original;
|
|
203
|
+
}
|
|
204
|
+
if (changed) return result;
|
|
205
|
+
seen.set(value, value);
|
|
206
|
+
return value;
|
|
207
|
+
}
|
|
208
|
+
/** Convert any thrown value into a canonical error description. */
|
|
209
|
+
function summarizeError(error) {
|
|
210
|
+
if (error instanceof Error) return summarizeErrorWithSeen(error, /* @__PURE__ */ new WeakMap());
|
|
211
|
+
return {
|
|
212
|
+
kind: "thrown",
|
|
213
|
+
name: "NonErrorThrown",
|
|
214
|
+
message: safeString$1(error, "[Unserializable thrown value]"),
|
|
215
|
+
value: summarizeErrors(error)
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
//#endregion
|
|
219
|
+
//#region src/exec.ts
|
|
220
|
+
const OPTION_KEYS = /* @__PURE__ */ new Set([
|
|
221
|
+
"cwd",
|
|
222
|
+
"env",
|
|
223
|
+
"input",
|
|
224
|
+
"reject",
|
|
225
|
+
"timeout",
|
|
226
|
+
"signal",
|
|
227
|
+
"maxBuffer",
|
|
228
|
+
"forceKillAfterDelay",
|
|
229
|
+
"stdin",
|
|
230
|
+
"stdout",
|
|
231
|
+
"stderr",
|
|
232
|
+
"interactive",
|
|
233
|
+
"display",
|
|
234
|
+
"redact",
|
|
235
|
+
"dryRun"
|
|
236
|
+
]);
|
|
237
|
+
const MAX_DELAY = 2147483647;
|
|
238
|
+
const REFUSAL_MESSAGE = {
|
|
239
|
+
"json-output": "Command cannot use the terminal in JSON output mode",
|
|
240
|
+
"non-interactive": "Command requires an interactive terminal",
|
|
241
|
+
overlapping: "Command cannot use the terminal while it is in use"
|
|
242
|
+
};
|
|
243
|
+
function isRecord(value) {
|
|
244
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
245
|
+
}
|
|
246
|
+
function assertNoNul(value, label) {
|
|
247
|
+
if (value.includes("\0")) throw new TypeError(`${label} must not contain NUL characters`);
|
|
248
|
+
}
|
|
249
|
+
function readInteger(value, label, minimum, maximum) {
|
|
250
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < minimum || value > maximum) throw new TypeError(`${label} must be an integer from ${minimum} to ${maximum}`);
|
|
251
|
+
return value;
|
|
252
|
+
}
|
|
253
|
+
function readEnum(value, label, allowed) {
|
|
254
|
+
if (typeof value === "string" && allowed.includes(value)) return value;
|
|
255
|
+
throw new TypeError(`Exec option \`${label}\` must be ${allowed.map((item) => JSON.stringify(item)).join(" or ")}`);
|
|
256
|
+
}
|
|
257
|
+
function readBoolean(value, label) {
|
|
258
|
+
if (typeof value !== "boolean") throw new TypeError(`Exec option \`${label}\` must be a boolean`);
|
|
259
|
+
return value;
|
|
260
|
+
}
|
|
261
|
+
function readRedact(value) {
|
|
262
|
+
if (!Array.isArray(value)) throw new TypeError("Exec option `redact` must be an array");
|
|
263
|
+
for (const item of value) {
|
|
264
|
+
if (typeof item !== "string" || item === "") throw new TypeError("Exec option `redact` must contain only non-empty strings");
|
|
265
|
+
assertNoNul(item, "Exec option `redact`");
|
|
266
|
+
}
|
|
267
|
+
return value;
|
|
268
|
+
}
|
|
269
|
+
function mergeEnvironment(base, overrides) {
|
|
270
|
+
if (overrides === void 0 && base === void 0) return void 0;
|
|
271
|
+
if (overrides !== void 0 && !isRecord(overrides)) throw new TypeError("Exec option `env` must be an object");
|
|
272
|
+
const merged = {};
|
|
273
|
+
for (const [key, value] of Object.entries(base ?? process.env)) if (value !== void 0) merged[key] = value;
|
|
274
|
+
for (const [key, value] of Object.entries(overrides ?? {})) {
|
|
275
|
+
if (key === "" || key.includes("=") || key.includes("\0")) throw new TypeError(`Invalid environment variable name ${JSON.stringify(key)}`);
|
|
276
|
+
if (value === void 0) {
|
|
277
|
+
delete merged[key];
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
if (typeof value !== "string") throw new TypeError(`Environment variable ${key} must be a string or undefined`);
|
|
281
|
+
assertNoNul(value, `Environment variable ${key}`);
|
|
282
|
+
merged[key] = value;
|
|
283
|
+
}
|
|
284
|
+
return merged;
|
|
285
|
+
}
|
|
286
|
+
function redactValue(value, secrets) {
|
|
287
|
+
let redacted = value;
|
|
288
|
+
for (const secret of [...secrets].sort((left, right) => right.length - left.length)) redacted = redacted.split(secret).join("***");
|
|
289
|
+
return redacted;
|
|
290
|
+
}
|
|
291
|
+
function redactCommand(command, secrets) {
|
|
292
|
+
if (secrets.length === 0) return command;
|
|
293
|
+
return {
|
|
294
|
+
file: redactValue(command.file, secrets),
|
|
295
|
+
args: command.args.map((arg) => redactValue(arg, secrets)),
|
|
296
|
+
cwd: command.cwd === void 0 ? void 0 : redactValue(command.cwd, secrets)
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
function redactError(error, secrets) {
|
|
300
|
+
const source = error;
|
|
301
|
+
const redacted = new Error(redactValue(error.message, secrets));
|
|
302
|
+
if (typeof error.stack === "string") redacted.stack = redactValue(error.stack, secrets);
|
|
303
|
+
if (source.code !== void 0) redacted.code = source.code;
|
|
304
|
+
if (source.errno !== void 0) redacted.errno = source.errno;
|
|
305
|
+
if (typeof source.syscall === "string") redacted.syscall = redactValue(source.syscall, secrets);
|
|
306
|
+
if (typeof source.path === "string") redacted.path = redactValue(source.path, secrets);
|
|
307
|
+
if (Array.isArray(source.spawnargs)) redacted.spawnargs = source.spawnargs.map((arg) => typeof arg === "string" ? redactValue(arg, secrets) : arg);
|
|
308
|
+
return redacted;
|
|
309
|
+
}
|
|
310
|
+
function observedError(plan, error) {
|
|
311
|
+
return plan.redact.length === 0 ? error : redactError(error, plan.redact);
|
|
312
|
+
}
|
|
313
|
+
function parsePlan(file, args, options, context) {
|
|
314
|
+
if (typeof file !== "string" || file === "") throw new TypeError("Exec file must be a non-empty string");
|
|
315
|
+
assertNoNul(file, "Exec file");
|
|
316
|
+
const argv = args === void 0 ? [] : args;
|
|
317
|
+
if (!Array.isArray(argv) || !argv.every((arg) => typeof arg === "string")) throw new TypeError("Exec args must be an array of strings");
|
|
318
|
+
for (const arg of argv) assertNoNul(arg, "Exec args");
|
|
319
|
+
const raw = options === void 0 ? {} : options;
|
|
320
|
+
if (!isRecord(raw)) throw new TypeError("Exec options must be an object");
|
|
321
|
+
for (const key of Object.keys(raw)) if (!OPTION_KEYS.has(key)) throw new TypeError(`Unknown exec option \`${key}\``);
|
|
322
|
+
const { cwd, input, reject, timeout, signal, maxBuffer, forceKillAfterDelay } = raw;
|
|
323
|
+
if (cwd !== void 0) {
|
|
324
|
+
if (typeof cwd !== "string" || cwd === "") throw new TypeError("Exec option `cwd` must be a non-empty string");
|
|
325
|
+
assertNoNul(cwd, "Exec option `cwd`");
|
|
326
|
+
}
|
|
327
|
+
if (input !== void 0 && typeof input !== "string") throw new TypeError("Exec option `input` must be a string");
|
|
328
|
+
if (reject !== void 0 && typeof reject !== "boolean") throw new TypeError("Exec option `reject` must be a boolean");
|
|
329
|
+
if (signal !== void 0 && !(signal instanceof AbortSignal)) throw new TypeError("Exec option `signal` must be an AbortSignal");
|
|
330
|
+
const stdin = raw.stdin === void 0 ? "pipe" : readEnum(raw.stdin, "stdin", ["pipe", "inherit"]);
|
|
331
|
+
const stdout = raw.stdout === void 0 ? "pipe" : readEnum(raw.stdout, "stdout", ["pipe", "inherit"]);
|
|
332
|
+
const stderr = raw.stderr === void 0 ? "pipe" : readEnum(raw.stderr, "stderr", ["pipe", "inherit"]);
|
|
333
|
+
if (input !== void 0 && stdin === "inherit") throw new TypeError("Exec option `input` cannot be combined with stdin inherit");
|
|
334
|
+
const command = {
|
|
335
|
+
file,
|
|
336
|
+
args: [...argv],
|
|
337
|
+
cwd
|
|
338
|
+
};
|
|
339
|
+
const redact = raw.redact === void 0 ? [] : readRedact(raw.redact);
|
|
340
|
+
return {
|
|
341
|
+
command,
|
|
342
|
+
reported: redactCommand(command, redact),
|
|
343
|
+
env: mergeEnvironment(context.env, raw.env),
|
|
344
|
+
input,
|
|
345
|
+
reject: reject ?? true,
|
|
346
|
+
timeout: timeout === void 0 ? void 0 : readInteger(timeout, "Exec option `timeout`", 1, MAX_DELAY),
|
|
347
|
+
signal: signal === void 0 ? context.signal : AbortSignal.any([context.signal, signal]),
|
|
348
|
+
maxBuffer: maxBuffer === void 0 ? 1e8 : readInteger(maxBuffer, "Exec option `maxBuffer`", 1, Number.MAX_SAFE_INTEGER),
|
|
349
|
+
forceKillAfterDelay: forceKillAfterDelay === void 0 ? 5e3 : readInteger(forceKillAfterDelay, "Exec option `forceKillAfterDelay`", 0, MAX_DELAY),
|
|
350
|
+
stdin,
|
|
351
|
+
stdout,
|
|
352
|
+
stderr,
|
|
353
|
+
interactive: raw.interactive === void 0 ? false : readBoolean(raw.interactive, "interactive"),
|
|
354
|
+
display: raw.display === void 0 ? true : readBoolean(raw.display, "display"),
|
|
355
|
+
redact,
|
|
356
|
+
dryRun: raw.dryRun === void 0 ? false : readBoolean(raw.dryRun, "dryRun")
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
function stripFinalNewline(text) {
|
|
360
|
+
if (text.endsWith("\r\n")) return text.slice(0, -2);
|
|
361
|
+
if (text.endsWith("\n")) return text.slice(0, -1);
|
|
362
|
+
return text;
|
|
363
|
+
}
|
|
364
|
+
function collect(stream, limit, onLimit) {
|
|
365
|
+
const chunks = [];
|
|
366
|
+
let length = 0;
|
|
367
|
+
let exceeded = false;
|
|
368
|
+
stream.on("data", (chunk) => {
|
|
369
|
+
if (exceeded) return;
|
|
370
|
+
const remaining = limit - length;
|
|
371
|
+
if (chunk.length <= remaining) {
|
|
372
|
+
chunks.push(chunk);
|
|
373
|
+
length += chunk.length;
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
chunks.push(chunk.subarray(0, remaining));
|
|
377
|
+
length = limit;
|
|
378
|
+
exceeded = true;
|
|
379
|
+
onLimit();
|
|
380
|
+
});
|
|
381
|
+
return () => stripFinalNewline(Buffer.concat(chunks).toString("utf8"));
|
|
382
|
+
}
|
|
383
|
+
function toResult(command, outcome, stdout, stderr) {
|
|
384
|
+
const failed = {
|
|
385
|
+
status: "failed",
|
|
386
|
+
command,
|
|
387
|
+
stdout,
|
|
388
|
+
stderr
|
|
389
|
+
};
|
|
390
|
+
if (outcome.cause) return {
|
|
391
|
+
...failed,
|
|
392
|
+
reason: outcome.cause
|
|
393
|
+
};
|
|
394
|
+
if (outcome.spawnError) return {
|
|
395
|
+
...failed,
|
|
396
|
+
reason: "spawn",
|
|
397
|
+
error: outcome.spawnError
|
|
398
|
+
};
|
|
399
|
+
const { code, signal } = outcome.exit ?? {
|
|
400
|
+
code: null,
|
|
401
|
+
signal: null
|
|
402
|
+
};
|
|
403
|
+
if (signal !== null) return {
|
|
404
|
+
...failed,
|
|
405
|
+
reason: "signal",
|
|
406
|
+
signal
|
|
407
|
+
};
|
|
408
|
+
if (code !== 0) return {
|
|
409
|
+
...failed,
|
|
410
|
+
reason: "exit-code",
|
|
411
|
+
exitCode: code ?? 1
|
|
412
|
+
};
|
|
413
|
+
return {
|
|
414
|
+
status: "succeeded",
|
|
415
|
+
command,
|
|
416
|
+
stdout,
|
|
417
|
+
stderr,
|
|
418
|
+
exitCode: 0
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
function isSpawnErrno(error) {
|
|
422
|
+
return error instanceof Error && "syscall" in error;
|
|
423
|
+
}
|
|
424
|
+
function execEvent(plan, phase) {
|
|
425
|
+
const { file, args, cwd } = plan.reported;
|
|
426
|
+
return {
|
|
427
|
+
type: "exec",
|
|
428
|
+
phase,
|
|
429
|
+
command: cwd === void 0 ? {
|
|
430
|
+
file,
|
|
431
|
+
args
|
|
432
|
+
} : {
|
|
433
|
+
file,
|
|
434
|
+
args,
|
|
435
|
+
cwd
|
|
436
|
+
},
|
|
437
|
+
stdio: commandStdio(plan),
|
|
438
|
+
interactive: plan.interactive
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
function commandStdio(plan) {
|
|
442
|
+
return {
|
|
443
|
+
stdin: plan.stdin === "inherit" ? "inherit" : plan.input === void 0 ? "ignore" : "input",
|
|
444
|
+
stdout: plan.stdout,
|
|
445
|
+
stderr: plan.stderr
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
function usesTerminal(plan, stdio) {
|
|
449
|
+
return plan.interactive || stdio.stdin === "inherit" || stdio.stdout === "inherit" || stdio.stderr === "inherit";
|
|
450
|
+
}
|
|
451
|
+
function startChild(plan) {
|
|
452
|
+
const stdio = commandStdio(plan);
|
|
453
|
+
try {
|
|
454
|
+
return spawn(plan.command.file, plan.command.args, {
|
|
455
|
+
cwd: plan.command.cwd,
|
|
456
|
+
env: plan.env,
|
|
457
|
+
shell: false,
|
|
458
|
+
windowsHide: true,
|
|
459
|
+
stdio: [
|
|
460
|
+
stdio.stdin === "inherit" ? "inherit" : stdio.stdin === "input" ? "pipe" : "ignore",
|
|
461
|
+
stdio.stdout,
|
|
462
|
+
stdio.stderr
|
|
463
|
+
]
|
|
464
|
+
});
|
|
465
|
+
} catch (error) {
|
|
466
|
+
if (isSpawnErrno(error)) return error;
|
|
467
|
+
throw error;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
function runPlan(plan) {
|
|
471
|
+
const outcome = {
|
|
472
|
+
cause: void 0,
|
|
473
|
+
spawnError: void 0,
|
|
474
|
+
exit: void 0
|
|
475
|
+
};
|
|
476
|
+
if (plan.signal.aborted) {
|
|
477
|
+
outcome.cause = "aborted";
|
|
478
|
+
return Promise.resolve(toResult(plan.reported, outcome, "", ""));
|
|
479
|
+
}
|
|
480
|
+
const child = startChild(plan);
|
|
481
|
+
if (child instanceof Error) {
|
|
482
|
+
outcome.spawnError = observedError(plan, child);
|
|
483
|
+
return Promise.resolve(toResult(plan.reported, outcome, "", ""));
|
|
484
|
+
}
|
|
485
|
+
return new Promise((resolve) => {
|
|
486
|
+
const stdout = child.stdout;
|
|
487
|
+
const stderr = child.stderr;
|
|
488
|
+
let settled = false;
|
|
489
|
+
let killTimer;
|
|
490
|
+
const settle = () => {
|
|
491
|
+
if (settled) return;
|
|
492
|
+
settled = true;
|
|
493
|
+
clearTimeout(timeoutTimer);
|
|
494
|
+
clearTimeout(killTimer);
|
|
495
|
+
plan.signal.removeEventListener("abort", onAbort);
|
|
496
|
+
resolve(toResult(plan.reported, outcome, readStdout(), readStderr()));
|
|
497
|
+
};
|
|
498
|
+
const release = () => {
|
|
499
|
+
stdout?.destroy();
|
|
500
|
+
stderr?.destroy();
|
|
501
|
+
settle();
|
|
502
|
+
};
|
|
503
|
+
const terminate = (cause) => {
|
|
504
|
+
if (settled || outcome.cause) return;
|
|
505
|
+
outcome.cause = cause;
|
|
506
|
+
if (outcome.exit) {
|
|
507
|
+
release();
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
child.kill("SIGTERM");
|
|
511
|
+
killTimer = setTimeout(() => child.kill("SIGKILL"), plan.forceKillAfterDelay);
|
|
512
|
+
};
|
|
513
|
+
const onAbort = () => terminate("aborted");
|
|
514
|
+
const readStdout = stdout ? collect(stdout, plan.maxBuffer, () => terminate("output-limit")) : () => "";
|
|
515
|
+
const readStderr = stderr ? collect(stderr, plan.maxBuffer, () => terminate("output-limit")) : () => "";
|
|
516
|
+
const timeoutTimer = plan.timeout === void 0 ? void 0 : setTimeout(() => terminate("timeout"), plan.timeout);
|
|
517
|
+
plan.signal.addEventListener("abort", onAbort, { once: true });
|
|
518
|
+
child.on("error", (error) => {
|
|
519
|
+
if (child.pid !== void 0) return;
|
|
520
|
+
outcome.spawnError = observedError(plan, error);
|
|
521
|
+
release();
|
|
522
|
+
});
|
|
523
|
+
child.on("exit", (code, signal) => {
|
|
524
|
+
outcome.exit = {
|
|
525
|
+
code,
|
|
526
|
+
signal
|
|
527
|
+
};
|
|
528
|
+
clearTimeout(killTimer);
|
|
529
|
+
if (outcome.cause) release();
|
|
530
|
+
});
|
|
531
|
+
child.on("close", settle);
|
|
532
|
+
if (child.stdin) {
|
|
533
|
+
child.stdin.on("error", () => void 0);
|
|
534
|
+
child.stdin.end(plan.input);
|
|
535
|
+
}
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
function formatArgument(value) {
|
|
539
|
+
return value === "" || /[\s"'\\$`;|&<>*?()[\]{}~#!]/.test(value) ? JSON.stringify(value) : value;
|
|
540
|
+
}
|
|
541
|
+
function formatExecCommand(command) {
|
|
542
|
+
return [command.file, ...command.args].map(formatArgument).join(" ");
|
|
543
|
+
}
|
|
544
|
+
function failureMessage(plan, result) {
|
|
545
|
+
const display = formatExecCommand(plan.reported);
|
|
546
|
+
switch (result.reason) {
|
|
547
|
+
case "exit-code": return `Command failed with exit code ${result.exitCode}: ${display}`;
|
|
548
|
+
case "signal": return `Command was terminated by ${result.signal}: ${display}`;
|
|
549
|
+
case "spawn": return `Command could not be started (${result.error.code ?? result.error.message}): ${display}`;
|
|
550
|
+
case "timeout": return `Command timed out after ${plan.timeout} ms: ${display}`;
|
|
551
|
+
case "aborted": return `Command was aborted: ${display}`;
|
|
552
|
+
case "output-limit": return `Command output exceeded ${plan.maxBuffer} bytes: ${display}`;
|
|
553
|
+
case "json-output":
|
|
554
|
+
case "non-interactive":
|
|
555
|
+
case "overlapping": return `${REFUSAL_MESSAGE[result.reason]}: ${display}`;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
function emptyFailure(plan, reason) {
|
|
559
|
+
return {
|
|
560
|
+
status: "failed",
|
|
561
|
+
command: plan.reported,
|
|
562
|
+
stdout: "",
|
|
563
|
+
stderr: "",
|
|
564
|
+
reason
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
function complete(plan, result) {
|
|
568
|
+
if (result.status === "failed" && plan.reject) throw new HackshExecError(failureMessage(plan, result), result, result.reason === "spawn" ? { cause: result.error } : void 0);
|
|
569
|
+
return result;
|
|
570
|
+
}
|
|
571
|
+
function blockedReason(plan, context, stdio) {
|
|
572
|
+
const inherited = stdio.stdin === "inherit" || stdio.stdout === "inherit" || stdio.stderr === "inherit";
|
|
573
|
+
if (context.mode.format === "json" && inherited) return "json-output";
|
|
574
|
+
if (plan.interactive && !context.mode.interactive) return "non-interactive";
|
|
575
|
+
}
|
|
576
|
+
async function execute(file, args, options, context) {
|
|
577
|
+
const plan = parsePlan(file, args, options, context);
|
|
578
|
+
if (plan.signal.aborted) return complete(plan, emptyFailure(plan, "aborted"));
|
|
579
|
+
if (plan.dryRun) {
|
|
580
|
+
if (plan.display) await context.print(execEvent(plan, "skip"));
|
|
581
|
+
return {
|
|
582
|
+
status: "skipped",
|
|
583
|
+
command: plan.reported
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
const stdio = commandStdio(plan);
|
|
587
|
+
const blocked = blockedReason(plan, context, stdio);
|
|
588
|
+
if (blocked) return complete(plan, emptyFailure(plan, blocked));
|
|
589
|
+
const holdTerminal = usesTerminal(plan, stdio);
|
|
590
|
+
if (holdTerminal && !context.terminal.acquire("exec")) return complete(plan, emptyFailure(plan, "overlapping"));
|
|
591
|
+
try {
|
|
592
|
+
if (plan.display) await context.print(execEvent(plan, "start"));
|
|
593
|
+
return complete(plan, await runPlan(plan));
|
|
594
|
+
} finally {
|
|
595
|
+
if (holdTerminal) context.terminal.release();
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
function createExec(context) {
|
|
599
|
+
return ((file, args, options) => execute(file, args, options, context));
|
|
600
|
+
}
|
|
601
|
+
//#endregion
|
|
602
|
+
//#region src/serialization.ts
|
|
603
|
+
function safeString(value) {
|
|
604
|
+
try {
|
|
605
|
+
return String(value);
|
|
606
|
+
} catch {
|
|
607
|
+
return "[Unserializable]";
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
function normalizeProperty(value, key, ancestors) {
|
|
611
|
+
try {
|
|
612
|
+
return normalizeJson(Reflect.get(value, key), ancestors);
|
|
613
|
+
} catch {
|
|
614
|
+
return "[Unserializable]";
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
function normalizedKeys(value) {
|
|
618
|
+
try {
|
|
619
|
+
return Object.keys(value);
|
|
620
|
+
} catch {
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
const noToJson = Symbol("noToJson");
|
|
625
|
+
function normalizedToJson(value, ancestors) {
|
|
626
|
+
let toJSON;
|
|
627
|
+
try {
|
|
628
|
+
toJSON = Reflect.get(value, "toJSON");
|
|
629
|
+
} catch {
|
|
630
|
+
return noToJson;
|
|
631
|
+
}
|
|
632
|
+
if (typeof toJSON !== "function") return noToJson;
|
|
633
|
+
try {
|
|
634
|
+
const result = Reflect.apply(toJSON, value, []);
|
|
635
|
+
return result === value ? noToJson : normalizeJson(result, ancestors);
|
|
636
|
+
} catch {
|
|
637
|
+
return noToJson;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
function isArrayBufferView(value) {
|
|
641
|
+
try {
|
|
642
|
+
return ArrayBuffer.isView(value);
|
|
643
|
+
} catch {
|
|
644
|
+
return false;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
/** Normalize arbitrary event data into values accepted by JSON.stringify. */
|
|
648
|
+
function normalizeJson(value, ancestors = /* @__PURE__ */ new Set()) {
|
|
649
|
+
switch (typeof value) {
|
|
650
|
+
case "string": return stripVTControlCharacters(value);
|
|
651
|
+
case "boolean": return value;
|
|
652
|
+
case "number": return Number.isFinite(value) ? value : null;
|
|
653
|
+
case "bigint": return value.toString();
|
|
654
|
+
case "undefined": return "[undefined]";
|
|
655
|
+
case "symbol": return `[symbol:${stripVTControlCharacters(value.description ?? "")}]`;
|
|
656
|
+
case "function": return `[function:${stripVTControlCharacters(value.name || "anonymous")}]`;
|
|
657
|
+
}
|
|
658
|
+
if (value === null) return null;
|
|
659
|
+
if (ancestors.has(value)) return "[Circular]";
|
|
660
|
+
const nextAncestors = new Set(ancestors).add(value);
|
|
661
|
+
if (value instanceof Error) return normalizeJson(summarizeError(value), nextAncestors);
|
|
662
|
+
const jsonValue = normalizedToJson(value, nextAncestors);
|
|
663
|
+
if (jsonValue !== noToJson) return jsonValue;
|
|
664
|
+
if (isArrayBufferView(value)) {
|
|
665
|
+
if (value instanceof DataView) return Array.from(new Uint8Array(value.buffer, value.byteOffset, value.byteLength));
|
|
666
|
+
try {
|
|
667
|
+
return Array.from(value, (item) => normalizeJson(item, nextAncestors));
|
|
668
|
+
} catch {
|
|
669
|
+
return "[Unserializable]";
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
if (value instanceof ArrayBuffer) return Array.from(new Uint8Array(value));
|
|
673
|
+
if (Array.isArray(value)) return Array.from({ length: value.length }, (_, index) => normalizeProperty(value, index, nextAncestors));
|
|
674
|
+
if (value instanceof Map) try {
|
|
675
|
+
return [...value].map(([key, item]) => [normalizeJson(key, nextAncestors), normalizeJson(item, nextAncestors)]);
|
|
676
|
+
} catch {
|
|
677
|
+
return "[Unserializable]";
|
|
678
|
+
}
|
|
679
|
+
if (value instanceof Set) try {
|
|
680
|
+
return [...value].map((item) => normalizeJson(item, nextAncestors));
|
|
681
|
+
} catch {
|
|
682
|
+
return "[Unserializable]";
|
|
683
|
+
}
|
|
684
|
+
const keys = normalizedKeys(value);
|
|
685
|
+
if (!keys) return safeString(value);
|
|
686
|
+
const result = {};
|
|
687
|
+
for (const key of keys) result[stripVTControlCharacters(key)] = normalizeProperty(value, key, nextAncestors);
|
|
688
|
+
return result;
|
|
689
|
+
}
|
|
690
|
+
//#endregion
|
|
691
|
+
//#region src/printer.ts
|
|
692
|
+
function fallbackValue(value) {
|
|
693
|
+
try {
|
|
694
|
+
return String(value);
|
|
695
|
+
} catch {
|
|
696
|
+
return "[Unserializable]";
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
function validatePreference(value, source) {
|
|
700
|
+
if (value === void 0) return void 0;
|
|
701
|
+
if (value === "auto" || value === "pretty" || value === "json") return value;
|
|
702
|
+
throw new TypeError(`Invalid ${source} value ${fallbackValue(value)}; expected auto, pretty, or json`);
|
|
703
|
+
}
|
|
704
|
+
function readEnvironment(env, key) {
|
|
705
|
+
try {
|
|
706
|
+
return env[key];
|
|
707
|
+
} catch {
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
/** Resolve output behavior once, before command routing or argument parsing. */
|
|
712
|
+
function resolveOutputMode(options = {}) {
|
|
713
|
+
const env = options.env ?? process.env;
|
|
714
|
+
const configured = validatePreference(options.output, "output");
|
|
715
|
+
const requested = validatePreference(readEnvironment(env, "HACKSH_OUTPUT"), "HACKSH_OUTPUT") ?? configured ?? "auto";
|
|
716
|
+
const ci = Boolean(readEnvironment(env, "CI"));
|
|
717
|
+
const format = requested === "auto" ? ci ? "json" : "pretty" : requested;
|
|
718
|
+
const inputIsTTY = options.inputIsTTY ?? process.stdin.isTTY === true;
|
|
719
|
+
const outputIsTTY = options.outputIsTTY ?? process.stdout.isTTY === true;
|
|
720
|
+
return {
|
|
721
|
+
format,
|
|
722
|
+
interactive: format === "pretty" && !ci && inputIsTTY && outputIsTTY,
|
|
723
|
+
color: format === "pretty" && outputIsTTY && !readEnvironment(env, "NO_COLOR")
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
function isErrorLevel(level) {
|
|
727
|
+
return level === "warning" || level === "error" || level === "fatal";
|
|
728
|
+
}
|
|
729
|
+
function eventStream(event) {
|
|
730
|
+
if (event.type === "error" || event.type === "exec") return "stderr";
|
|
731
|
+
if (event.type === "usage") return event.stream;
|
|
732
|
+
if (event.type === "log" && isErrorLevel(event.record.level)) return "stderr";
|
|
733
|
+
return "stdout";
|
|
734
|
+
}
|
|
735
|
+
/** Create a one-semantic-event-per-line JSON printer. */
|
|
736
|
+
function createJsonPrinter(options = {}) {
|
|
737
|
+
const stdout = options.stdout ?? process.stdout;
|
|
738
|
+
const stderr = options.stderr ?? process.stderr;
|
|
739
|
+
return { print(event) {
|
|
740
|
+
let serialized;
|
|
741
|
+
try {
|
|
742
|
+
serialized = JSON.stringify(normalizeJson(event));
|
|
743
|
+
} catch {
|
|
744
|
+
serialized = JSON.stringify({
|
|
745
|
+
type: event.type,
|
|
746
|
+
serializationError: "Event data could not be serialized"
|
|
747
|
+
});
|
|
748
|
+
}
|
|
749
|
+
const line = `${serialized}\n`;
|
|
750
|
+
(eventStream(event) === "stdout" ? stdout : stderr).write(line);
|
|
751
|
+
} };
|
|
752
|
+
}
|
|
753
|
+
/** Serialize printer calls and retain failures until the invocation drains its output. */
|
|
754
|
+
function createPrintQueue(target) {
|
|
755
|
+
let pending;
|
|
756
|
+
let failure;
|
|
757
|
+
let failed = false;
|
|
758
|
+
function recordFailure(error) {
|
|
759
|
+
if (!failed) failure = error;
|
|
760
|
+
failed = true;
|
|
761
|
+
}
|
|
762
|
+
function track(execution, retainFailure) {
|
|
763
|
+
const barrier = execution.catch((error) => {
|
|
764
|
+
if (retainFailure) recordFailure(error);
|
|
765
|
+
});
|
|
766
|
+
pending = barrier;
|
|
767
|
+
barrier.then(() => {
|
|
768
|
+
if (pending === barrier) pending = void 0;
|
|
769
|
+
});
|
|
770
|
+
return retainFailure ? barrier : execution;
|
|
771
|
+
}
|
|
772
|
+
function enqueue(event, retainFailure) {
|
|
773
|
+
if (pending) return track(pending.then(() => target.print(event)), retainFailure);
|
|
774
|
+
try {
|
|
775
|
+
const result = target.print(event);
|
|
776
|
+
if (result) return track(Promise.resolve(result), retainFailure);
|
|
777
|
+
} catch (error) {
|
|
778
|
+
if (retainFailure) recordFailure(error);
|
|
779
|
+
else return Promise.reject(error);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
return {
|
|
783
|
+
printer: { print(event) {
|
|
784
|
+
return enqueue(event, true);
|
|
785
|
+
} },
|
|
786
|
+
async printChecked(event) {
|
|
787
|
+
await enqueue(event, false);
|
|
788
|
+
},
|
|
789
|
+
async drain() {
|
|
790
|
+
await pending;
|
|
791
|
+
if (failed) throw failure;
|
|
792
|
+
}
|
|
793
|
+
};
|
|
794
|
+
}
|
|
795
|
+
//#endregion
|
|
796
|
+
//#region src/pretty.ts
|
|
797
|
+
const unicodeSymbols = {
|
|
798
|
+
bar: "│",
|
|
799
|
+
marks: {
|
|
800
|
+
trace: "·",
|
|
801
|
+
debug: "◆",
|
|
802
|
+
info: "●",
|
|
803
|
+
warning: "▲",
|
|
804
|
+
error: "■",
|
|
805
|
+
fatal: "✖",
|
|
806
|
+
step: "◇",
|
|
807
|
+
success: "✔",
|
|
808
|
+
usage: "◆",
|
|
809
|
+
version: "●",
|
|
810
|
+
errorEvent: "■",
|
|
811
|
+
prompt: "?",
|
|
812
|
+
exec: "$",
|
|
813
|
+
execSkip: "#"
|
|
814
|
+
}
|
|
815
|
+
};
|
|
816
|
+
const asciiSymbols = {
|
|
817
|
+
bar: "|",
|
|
818
|
+
marks: {
|
|
819
|
+
trace: ".",
|
|
820
|
+
debug: ":",
|
|
821
|
+
info: "i",
|
|
822
|
+
warning: "!",
|
|
823
|
+
error: "x",
|
|
824
|
+
fatal: "X",
|
|
825
|
+
step: ">",
|
|
826
|
+
success: "+",
|
|
827
|
+
usage: "?",
|
|
828
|
+
version: "i",
|
|
829
|
+
errorEvent: "x",
|
|
830
|
+
prompt: "?",
|
|
831
|
+
exec: "$",
|
|
832
|
+
execSkip: "#"
|
|
833
|
+
}
|
|
834
|
+
};
|
|
835
|
+
const markStyles = {
|
|
836
|
+
trace: "gray",
|
|
837
|
+
debug: "magenta",
|
|
838
|
+
info: "blue",
|
|
839
|
+
warning: "yellow",
|
|
840
|
+
error: "red",
|
|
841
|
+
fatal: [
|
|
842
|
+
"bgRed",
|
|
843
|
+
"white",
|
|
844
|
+
"bold"
|
|
845
|
+
],
|
|
846
|
+
step: "cyan",
|
|
847
|
+
success: "green",
|
|
848
|
+
usage: "cyan",
|
|
849
|
+
version: "blue",
|
|
850
|
+
errorEvent: "red",
|
|
851
|
+
prompt: "cyan",
|
|
852
|
+
exec: "gray",
|
|
853
|
+
execSkip: "gray"
|
|
854
|
+
};
|
|
855
|
+
function safelyInspect(value) {
|
|
856
|
+
if (typeof value === "string") return value;
|
|
857
|
+
try {
|
|
858
|
+
return inspect(value, {
|
|
859
|
+
breakLength: 80,
|
|
860
|
+
colors: false,
|
|
861
|
+
compact: false,
|
|
862
|
+
depth: null,
|
|
863
|
+
sorted: true
|
|
864
|
+
});
|
|
865
|
+
} catch {
|
|
866
|
+
return "[Unserializable]";
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
function propertyLines(properties) {
|
|
870
|
+
const lines = [];
|
|
871
|
+
for (const key of Object.keys(properties).sort()) {
|
|
872
|
+
const rendered = ` ${key}: ${safelyInspect(properties[key])}`.split("\n");
|
|
873
|
+
lines.push(rendered[0] ?? "");
|
|
874
|
+
for (const continuation of rendered.slice(1)) lines.push(` ${continuation}`);
|
|
875
|
+
}
|
|
876
|
+
return lines;
|
|
877
|
+
}
|
|
878
|
+
function logLines(record) {
|
|
879
|
+
const category = record.category.length > 0 ? `[${record.category.join(":")}] ` : "";
|
|
880
|
+
const message = record.message.split("\n");
|
|
881
|
+
return [
|
|
882
|
+
`${category}${message[0] ?? ""}`,
|
|
883
|
+
...message.slice(1),
|
|
884
|
+
...propertyLines(record.properties)
|
|
885
|
+
];
|
|
886
|
+
}
|
|
887
|
+
function errorLines(error) {
|
|
888
|
+
const lines = error.message.split("\n");
|
|
889
|
+
const details = {};
|
|
890
|
+
if (error.cause !== void 0) details.cause = error.cause;
|
|
891
|
+
if (error.properties !== void 0) Object.assign(details, error.properties);
|
|
892
|
+
if (error.value !== void 0) details.value = error.value;
|
|
893
|
+
return [...lines, ...propertyLines(details)];
|
|
894
|
+
}
|
|
895
|
+
function eventMark(event) {
|
|
896
|
+
if (event.type === "usage") return "usage";
|
|
897
|
+
if (event.type === "version") return "version";
|
|
898
|
+
if (event.type === "error") return "errorEvent";
|
|
899
|
+
if (event.type === "prompt") return event.prompt.status === "submitted" ? "success" : "prompt";
|
|
900
|
+
if (event.type === "exec") return event.phase === "skip" ? "execSkip" : "exec";
|
|
901
|
+
return event.record.kind === "message" ? event.record.level : event.record.kind;
|
|
902
|
+
}
|
|
903
|
+
function eventLines(event) {
|
|
904
|
+
switch (event.type) {
|
|
905
|
+
case "usage": return event.text.split("\n");
|
|
906
|
+
case "version": return event.version.split("\n");
|
|
907
|
+
case "error": return errorLines(event.error);
|
|
908
|
+
case "log": return logLines(event.record);
|
|
909
|
+
case "prompt": {
|
|
910
|
+
const { prompt } = event;
|
|
911
|
+
if (prompt.status === "submitted") {
|
|
912
|
+
const result = prompt.kind === "select" ? prompt.choices.find(({ value }) => value === prompt.value)?.label ?? prompt.value : prompt.kind === "confirm" ? prompt.value ? "Yes" : "No" : prompt.value;
|
|
913
|
+
return [`${prompt.message}: ${result}`];
|
|
914
|
+
}
|
|
915
|
+
const lines = [prompt.message];
|
|
916
|
+
if (prompt.kind === "select") for (const choice of prompt.choices) lines.push(`${choice.selected ? ">" : " "} ${choice.label}${choice.hint ? ` — ${choice.hint}` : ""}`);
|
|
917
|
+
else if (prompt.kind === "confirm") lines.push(`${prompt.value ? "Yes" : "No"}${prompt.defaultValue === void 0 ? "" : ` (default: ${prompt.defaultValue ? "yes" : "no"})`}`);
|
|
918
|
+
else lines.push(prompt.value || (prompt.placeholder ? `(${prompt.placeholder})` : ""));
|
|
919
|
+
if (prompt.error) lines.push(prompt.error);
|
|
920
|
+
return lines;
|
|
921
|
+
}
|
|
922
|
+
case "exec": {
|
|
923
|
+
const lines = [formatExecCommand(event.command)];
|
|
924
|
+
if (event.command.cwd !== void 0) lines.push(...propertyLines({ cwd: event.command.cwd }));
|
|
925
|
+
return lines;
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
function colorize(enabled, style, text) {
|
|
930
|
+
return enabled ? styleText(style, text, { validateStream: false }) : text;
|
|
931
|
+
}
|
|
932
|
+
const graphemeSegmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
|
|
933
|
+
const combiningMark = /\p{Mark}/u;
|
|
934
|
+
const pictograph = /\p{Extended_Pictographic}/u;
|
|
935
|
+
function isWideCodePoint(codePoint) {
|
|
936
|
+
return codePoint >= 4352 && (codePoint <= 4447 || codePoint === 9001 || codePoint === 9002 || codePoint >= 11904 && codePoint <= 42191 && codePoint !== 12351 || codePoint >= 44032 && codePoint <= 55203 || codePoint >= 63744 && codePoint <= 64255 || codePoint >= 65040 && codePoint <= 65049 || codePoint >= 65072 && codePoint <= 65135 || codePoint >= 65280 && codePoint <= 65376 || codePoint >= 65504 && codePoint <= 65510 || codePoint >= 131072 && codePoint <= 262141);
|
|
937
|
+
}
|
|
938
|
+
function displayWidth(text) {
|
|
939
|
+
let width = 0;
|
|
940
|
+
for (const { segment } of graphemeSegmenter.segment(stripVTControlCharacters(text))) {
|
|
941
|
+
if (segment === " ") {
|
|
942
|
+
width += 8 - width % 8;
|
|
943
|
+
continue;
|
|
944
|
+
}
|
|
945
|
+
if (pictograph.test(segment) || Array.from(segment).some((value) => isWideCodePoint(value.codePointAt(0)))) {
|
|
946
|
+
width += 2;
|
|
947
|
+
continue;
|
|
948
|
+
}
|
|
949
|
+
for (const value of segment) {
|
|
950
|
+
const codePoint = value.codePointAt(0);
|
|
951
|
+
if (codePoint < 32 || codePoint >= 127 && codePoint < 160) continue;
|
|
952
|
+
if (codePoint === 8205 || codePoint >= 65024 && codePoint <= 65039) continue;
|
|
953
|
+
if (!combiningMark.test(value)) width += 1;
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
return width;
|
|
957
|
+
}
|
|
958
|
+
function renderedRows(rendered, columns) {
|
|
959
|
+
const width = Number.isInteger(columns) && columns !== void 0 && columns > 0 ? columns : void 0;
|
|
960
|
+
return rendered.split("\n").reduce((rows, line) => {
|
|
961
|
+
if (!width) return rows + 1;
|
|
962
|
+
return rows + Math.max(1, Math.ceil(displayWidth(line) / width));
|
|
963
|
+
}, 0);
|
|
964
|
+
}
|
|
965
|
+
/** Create the fixed human-readable renderer used for pretty output. */
|
|
966
|
+
function createPrettyPrinter(options) {
|
|
967
|
+
const stdout = options.stdout ?? process.stdout;
|
|
968
|
+
const stderr = options.stderr ?? process.stderr;
|
|
969
|
+
const symbols = options.unicode ? unicodeSymbols : asciiSymbols;
|
|
970
|
+
let activePrompt;
|
|
971
|
+
function render(event) {
|
|
972
|
+
const mark = eventMark(event);
|
|
973
|
+
const lines = eventLines(event);
|
|
974
|
+
const firstPrefix = colorize(options.color, markStyles[mark], symbols.marks[mark]);
|
|
975
|
+
const barPrefix = colorize(options.color, "gray", symbols.bar);
|
|
976
|
+
return lines.map((line, index) => `${index === 0 ? firstPrefix : barPrefix}${line ? ` ${line}` : ""}`).join("\n");
|
|
977
|
+
}
|
|
978
|
+
function eraseActivePrompt() {
|
|
979
|
+
return activePrompt ? "\x1B[1A\x1B[2K".repeat(activePrompt.lines) : "";
|
|
980
|
+
}
|
|
981
|
+
return { print(event) {
|
|
982
|
+
const rendered = render(event);
|
|
983
|
+
if (event.type === "prompt") {
|
|
984
|
+
const erase = eraseActivePrompt();
|
|
985
|
+
if (event.prompt.status === "closed") {
|
|
986
|
+
activePrompt = void 0;
|
|
987
|
+
if (erase) stdout.write(erase);
|
|
988
|
+
return;
|
|
989
|
+
}
|
|
990
|
+
if (event.prompt.status === "submitted") activePrompt = void 0;
|
|
991
|
+
else activePrompt = {
|
|
992
|
+
rendered,
|
|
993
|
+
lines: renderedRows(rendered, stdout.columns)
|
|
994
|
+
};
|
|
995
|
+
stdout.write(`${erase}${rendered}\n`);
|
|
996
|
+
return;
|
|
997
|
+
}
|
|
998
|
+
const erase = eraseActivePrompt();
|
|
999
|
+
if (!activePrompt) {
|
|
1000
|
+
(eventStream(event) === "stdout" ? stdout : stderr).write(`${rendered}\n`);
|
|
1001
|
+
return;
|
|
1002
|
+
}
|
|
1003
|
+
stdout.write(erase);
|
|
1004
|
+
if (event.type === "error") activePrompt = void 0;
|
|
1005
|
+
(eventStream(event) === "stdout" ? stdout : stderr).write(`${rendered}\n`);
|
|
1006
|
+
if (activePrompt) stdout.write(`${activePrompt.rendered}\n`);
|
|
1007
|
+
} };
|
|
1008
|
+
}
|
|
1009
|
+
//#endregion
|
|
1010
|
+
export { createExec as a, HackshPromptError as c, summarizeError as d, summarizeErrors as f, resolveOutputMode as i, HackshUsageError as l, createJsonPrinter as n, HackshExecError as o, createPrintQueue as r, HackshExitError as s, createPrettyPrinter as t, HackshValidationError as u };
|
|
1011
|
+
|
|
1012
|
+
//# sourceMappingURL=pretty-BFub611x.js.map
|