@ttsc/lint 0.21.0 → 0.23.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.
@@ -0,0 +1,99 @@
1
+ export const CONFIG_EVALUATOR_MAX_BUFFER = 16 * 1024 * 1024;
2
+ export const CONFIG_EVALUATOR_TIMEOUT_MS = 60_000;
3
+ export const CONFIG_EVALUATOR_TEARDOWN_GRACE_MS = 5_000;
4
+ export const CONFIG_EVALUATOR_STATUS_FD = 3;
5
+ export const CONFIG_EVALUATOR_PROCESS_OPTIONS = Object.freeze({
6
+ killSignal: "SIGKILL" as const,
7
+ maxBuffer: CONFIG_EVALUATOR_MAX_BUFFER,
8
+ timeout: CONFIG_EVALUATOR_TIMEOUT_MS + CONFIG_EVALUATOR_TEARDOWN_GRACE_MS,
9
+ });
10
+
11
+ interface ConfigEvaluatorProcessResult {
12
+ error?: Error;
13
+ output?: readonly (string | null)[] | null;
14
+ signal: NodeJS.Signals | null;
15
+ status: number | null;
16
+ stderr: string | null | undefined;
17
+ }
18
+
19
+ /**
20
+ * Classify the ways the isolated lint-config evaluator can stop.
21
+ *
22
+ * Node reports both timeout and max-buffer termination with the configured
23
+ * signal, so the process error code must take precedence over the signal. The
24
+ * evaluator uses `SIGKILL`: Node's synchronous process API otherwise keeps
25
+ * waiting when a POSIX child handles the default `SIGTERM` without exiting. A
26
+ * bare signal is an external termination and a non-zero status is an evaluator
27
+ * failure whose stderr tail contains the useful user-config diagnostic.
28
+ */
29
+ export function configEvaluatorProcessFailure(
30
+ result: ConfigEvaluatorProcessResult,
31
+ configPath: string,
32
+ ): Error | undefined {
33
+ const code = (result.error as NodeJS.ErrnoException | undefined)?.code;
34
+ const nestedCode = result.output?.[CONFIG_EVALUATOR_STATUS_FD]?.trim() ?? "";
35
+ if (code === "ETIMEDOUT" || nestedCode === "ETIMEDOUT") {
36
+ return new Error(
37
+ `@ttsc/lint: ttsx evaluation of ${configPath} timed out after ` +
38
+ `${CONFIG_EVALUATOR_TIMEOUT_MS / 1_000} seconds. ` +
39
+ "Simplify the config or move heavy work out of top-level.",
40
+ );
41
+ }
42
+ if (code === "ENOBUFS" || nestedCode === "ENOBUFS") {
43
+ return new Error(
44
+ `@ttsc/lint: ttsx evaluation of ${configPath} exceeded the ` +
45
+ `${CONFIG_EVALUATOR_MAX_BUFFER / (1024 * 1024)} MiB output limit. ` +
46
+ "Reduce console output from the config and its dependencies.",
47
+ );
48
+ }
49
+ if (result.error) {
50
+ return new Error(
51
+ `@ttsc/lint: failed to spawn ttsx for ${configPath}: ${result.error.message}`,
52
+ );
53
+ }
54
+ if (result.signal) {
55
+ return new Error(
56
+ `@ttsc/lint: ttsx evaluation of ${configPath} was killed by signal ${result.signal}.`,
57
+ );
58
+ }
59
+ if (result.status !== 0) {
60
+ const reason = configEvaluatorFailureReason(result.stderr);
61
+ return new Error(
62
+ `@ttsc/lint: lint config ${configPath} evaluation failed with exit code ${String(result.status)}` +
63
+ (reason === "" ? "" : "\n" + reason),
64
+ );
65
+ }
66
+ return undefined;
67
+ }
68
+
69
+ /**
70
+ * Pass the semantic deadline and private status pipe through the `ttsx` wrapper
71
+ * to the runtime child that actually executes the config.
72
+ */
73
+ export function configEvaluatorBoundaryEnvironment(
74
+ now: number = Date.now(),
75
+ ): NodeJS.ProcessEnv {
76
+ return {
77
+ TTSC_TTSX_EVALUATOR_DEADLINE_MS: String(now + CONFIG_EVALUATOR_TIMEOUT_MS),
78
+ TTSC_TTSX_EVALUATOR_MAX_BUFFER_BYTES: String(CONFIG_EVALUATOR_MAX_BUFFER),
79
+ TTSC_TTSX_EVALUATOR_STATUS_FD: String(CONFIG_EVALUATOR_STATUS_FD),
80
+ };
81
+ }
82
+
83
+ /**
84
+ * Return the useful tail of evaluator stderr without turning an exception into
85
+ * an unbounded duplicate of the already-forwarded child stream.
86
+ */
87
+ function configEvaluatorFailureReason(
88
+ stderr: string | null | undefined,
89
+ ): string {
90
+ const bounded = (stderr ?? "").slice(-CONFIG_EVALUATOR_REASON_MAX_CHARS);
91
+ const lines = bounded
92
+ .split(/\r?\n/)
93
+ .map((line) => line.trimEnd())
94
+ .filter((line) => line.trim() !== "");
95
+ return lines.slice(-CONFIG_EVALUATOR_REASON_LINES).join("\n");
96
+ }
97
+
98
+ const CONFIG_EVALUATOR_REASON_LINES = 5;
99
+ const CONFIG_EVALUATOR_REASON_MAX_CHARS = 8 * 1024;