Skip to content

Commit a86a1de

Browse files
committed
fix(tooling): reject malformed tsdown watchdog limits
1 parent be336cc commit a86a1de

2 files changed

Lines changed: 72 additions & 12 deletions

File tree

scripts/tsdown-build.mjs

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -294,26 +294,34 @@ function findFatalUnresolvedImport(lines) {
294294
return null;
295295
}
296296

297-
function parsePositiveInteger(value) {
297+
function parsePositiveIntegerEnv(value, name) {
298298
if (typeof value !== "string" || value.trim() === "") {
299299
return null;
300300
}
301-
const parsed = Number(value);
302-
if (!Number.isFinite(parsed) || parsed <= 0) {
303-
return null;
301+
const text = value.trim();
302+
if (!/^\d+$/u.test(text)) {
303+
throw new Error(`${name} must be a positive integer`);
304304
}
305-
return Math.trunc(parsed);
305+
const parsed = Number(text);
306+
if (!Number.isSafeInteger(parsed) || parsed <= 0) {
307+
throw new Error(`${name} must be a positive safe integer`);
308+
}
309+
return parsed;
306310
}
307311

308-
function parseNonNegativeInteger(value) {
312+
function parseNonNegativeIntegerEnv(value, name) {
309313
if (typeof value !== "string" || value.trim() === "") {
310314
return null;
311315
}
312-
const parsed = Number(value);
313-
if (!Number.isFinite(parsed) || parsed < 0) {
314-
return null;
316+
const text = value.trim();
317+
if (!/^\d+$/u.test(text)) {
318+
throw new Error(`${name} must be a non-negative integer`);
315319
}
316-
return Math.trunc(parsed);
320+
const parsed = Number(text);
321+
if (!Number.isSafeInteger(parsed)) {
322+
throw new Error(`${name} must be a non-negative safe integer`);
323+
}
324+
return parsed;
317325
}
318326

319327
function parseCgroupMemoryLimitBytes(value) {
@@ -582,9 +590,15 @@ export async function runTsdownBuildInvocation(invocation, params = {}) {
582590
const stderr = params.stderr ?? process.stderr;
583591
const env = params.env ?? process.env;
584592
const scanner = params.scanner ?? createTsdownOutputScanner();
585-
const timeoutMs = parsePositiveInteger(env.OPENCLAW_TSDOWN_TIMEOUT_MS);
593+
const timeoutMs = parsePositiveIntegerEnv(
594+
env.OPENCLAW_TSDOWN_TIMEOUT_MS,
595+
"OPENCLAW_TSDOWN_TIMEOUT_MS",
596+
);
586597
const heartbeatMs =
587-
parseNonNegativeInteger(env.OPENCLAW_TSDOWN_HEARTBEAT_MS) ?? DEFAULT_HEARTBEAT_MS;
598+
parseNonNegativeIntegerEnv(
599+
env.OPENCLAW_TSDOWN_HEARTBEAT_MS,
600+
"OPENCLAW_TSDOWN_HEARTBEAT_MS",
601+
) ?? DEFAULT_HEARTBEAT_MS;
588602
let timedOut = false;
589603
let settled = false;
590604
let lastOutputAt = Date.now();

test/scripts/tsdown-build.test.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,52 @@ describe("runTsdownBuildInvocation", () => {
535535
expect(output.chunks.join("")).toContain("stdout-ok");
536536
});
537537

538+
it("rejects malformed OPENCLAW_TSDOWN_TIMEOUT_MS values", async () => {
539+
const invocation = {
540+
command: process.execPath,
541+
args: ["-e", "process.exit(0)"],
542+
options: {
543+
stdio: ["ignore", "pipe", "pipe"],
544+
shell: false,
545+
env: process.env,
546+
},
547+
};
548+
549+
for (const value of ["1.5", "1e3", "10ms", "0"]) {
550+
await expect(
551+
runTsdownBuildInvocation(invocation, {
552+
env: {
553+
...process.env,
554+
OPENCLAW_TSDOWN_TIMEOUT_MS: value,
555+
},
556+
}),
557+
).rejects.toThrow("OPENCLAW_TSDOWN_TIMEOUT_MS must be");
558+
}
559+
});
560+
561+
it("rejects malformed OPENCLAW_TSDOWN_HEARTBEAT_MS values", async () => {
562+
const invocation = {
563+
command: process.execPath,
564+
args: ["-e", "process.exit(0)"],
565+
options: {
566+
stdio: ["ignore", "pipe", "pipe"],
567+
shell: false,
568+
env: process.env,
569+
},
570+
};
571+
572+
for (const value of ["1.5", "1e3", "10ms", "-1"]) {
573+
await expect(
574+
runTsdownBuildInvocation(invocation, {
575+
env: {
576+
...process.env,
577+
OPENCLAW_TSDOWN_HEARTBEAT_MS: value,
578+
},
579+
}),
580+
).rejects.toThrow("OPENCLAW_TSDOWN_HEARTBEAT_MS must be");
581+
}
582+
});
583+
538584
it("terminates the child when OPENCLAW_TSDOWN_TIMEOUT_MS elapses", async () => {
539585
const output = createWriteSink();
540586
const result = await runTsdownBuildInvocation(

0 commit comments

Comments
 (0)