mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-10-30 17:27:09 +00:00
* fix(cli): a finished capture exits even when a site's DNS never answers Capture resolved every page request with the system lookup and gave up after 10 s, but a system lookup cannot be cancelled: the abandoned one kept the process alive until the OS resolver timed out, so a capture that had printed its result could run past the app's 5-minute cap and lose its folder. Resolve through c-ares instead, as hostAnswers does, and cancel it at the same 10 s deadline. * test(cli): a capture lookup that answers must exit right after too Dropping the timer cleanup kept every successful lookup alive 10 s and no test noticed. * fix(cli): capture falls back to the system lookup and exits once its result is out c-ares answers first. When it has no answer, the system lookup (hosts file, OS-only DNS) runs in a child process killed at the 10 s deadline, cached per host, because Node waits for an in-process system lookup even in process.exit. Capture then asks the CLI to exit 100 ms after printing its result, as render does. * fix(cli): capture reads the hosts file first and kills its lookup child on exit The hosts file answers before DNS, as the system lookup does (overrides and 0.0.0.0 blocks keep working). A host whose system answer is cached skips c-ares, a caller that joins a running lookup keeps its own deadline, and a pending lookup child is killed when the CLI exits. * fix(cli): capture's forced exit waits until a slow reader has the whole result The root exit now waits for stdout and stderr to hand over what is already written before it calls process.exit, so a large JSON result on a slow pipe arrives complete. A hostname listed several times in the hosts file now has every address checked, not only the first. * fix(cli): keep process exit in the CLI root and let test fixtures exit The output wait is now a helper that only waits; cli.ts still owns the process.exit. The process-ownership check treats test fixtures like tests, since each one runs as its own process. * chore(cli): run the process-ownership check before every commit A process.exit outside a process root used to pass the local hooks and fail only in CI's Preflight.
70 lines
2.4 KiB
JavaScript
70 lines
2.4 KiB
JavaScript
// fallow-ignore-file complexity
|
|
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
import { join, relative } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import ts from "typescript";
|
|
|
|
const ROOT = join(import.meta.dirname, "..");
|
|
|
|
export function listDirectProcessTermination(source, filename = "source.ts") {
|
|
const sourceFile = ts.createSourceFile(filename, source, ts.ScriptTarget.Latest, true);
|
|
const issues = [];
|
|
|
|
// AST traversal mirrors the small set of process-termination spellings.
|
|
// fallow-ignore-next-line complexity
|
|
function visit(node) {
|
|
if (
|
|
ts.isPropertyAccessExpression(node) &&
|
|
ts.isIdentifier(node.expression) &&
|
|
node.expression.text === "process" &&
|
|
(node.name.text === "exit" || node.name.text === "exitCode")
|
|
) {
|
|
const position = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
|
|
issues.push(
|
|
`${filename}:${position.line + 1}:${position.character + 1} uses process.${node.name.text}`,
|
|
);
|
|
}
|
|
ts.forEachChild(node, visit);
|
|
}
|
|
|
|
visit(sourceFile);
|
|
return issues;
|
|
}
|
|
|
|
// Each is the root of its own process: the CLI, the warm check process, and the check client in bin.
|
|
const PROCESS_ROOTS = ["cli.ts", "warmCheck.ts", "warmCheckClient.ts"];
|
|
|
|
function listTypeScriptFiles(directory, roots) {
|
|
// fallow-ignore-next-line complexity
|
|
return readdirSync(directory).flatMap((name) => {
|
|
const path = join(directory, name);
|
|
if (statSync(path).isDirectory()) return listTypeScriptFiles(path, roots);
|
|
if (!path.endsWith(".ts") || /\.(test|fixture)\.ts$/.test(path) || roots.includes(path)) {
|
|
return [];
|
|
}
|
|
return [path];
|
|
});
|
|
}
|
|
|
|
export function checkCliProcessOwnership(root = ROOT) {
|
|
const sourceRoot = join(root, "packages/cli/src");
|
|
if (!existsSync(sourceRoot)) return [];
|
|
const roots = PROCESS_ROOTS.map((name) => join(sourceRoot, name));
|
|
return listTypeScriptFiles(sourceRoot, roots).flatMap((path) =>
|
|
listDirectProcessTermination(readFileSync(path, "utf8"), relative(root, path)),
|
|
);
|
|
}
|
|
|
|
function main() {
|
|
const issues = checkCliProcessOwnership();
|
|
if (issues.length > 0) {
|
|
console.error("CLI process ownership violations:");
|
|
issues.forEach((issue) => console.error(`- ${issue}`));
|
|
process.exitCode = 1;
|
|
return;
|
|
}
|
|
console.log("CLI process ownership verified: only process roots terminate the process.");
|
|
}
|
|
|
|
if (process.argv[1] === fileURLToPath(import.meta.url)) main();
|