Vendor OpenClaw source as Adolf fork baseline
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Adolf is a fork/vendored clone of github.com/openclaw/openclaw (v2026.6.11), free to diverge. Tree copied sans upstream .git; upstream remote added for future syncs. Node pinned to 24 (.nvmrc); engines already require >=22.19. Preserves docs/ARCHITECTURE.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
This commit is contained in:
426
scripts/check-deadcode-unused-files.mjs
Normal file
426
scripts/check-deadcode-unused-files.mjs
Normal file
@@ -0,0 +1,426 @@
|
||||
#!/usr/bin/env node
|
||||
// Runs knip unused-file detection and compares results to the allowlist.
|
||||
import { spawn } from "node:child_process";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import {
|
||||
KNIP_OPTIONAL_UNUSED_FILE_ALLOWLIST,
|
||||
KNIP_UNUSED_FILE_ALLOWLIST,
|
||||
} from "./deadcode-unused-files.allowlist.mjs";
|
||||
import { createPnpmRunnerSpawnSpec } from "./pnpm-runner.mjs";
|
||||
|
||||
const KNIP_VERSION = "6.8.0";
|
||||
/**
|
||||
* Timeout for the unused-file knip child process.
|
||||
*/
|
||||
export const KNIP_TIMEOUT_MS = 10 * 60 * 1000;
|
||||
/**
|
||||
* Grace period before force-killing a timed-out knip child process.
|
||||
*/
|
||||
export const KNIP_KILL_GRACE_MS = 5_000;
|
||||
const KNIP_PROCESS_TREE_EXIT_POLL_MS = 25;
|
||||
const KNIP_POST_FORCE_KILL_WAIT_MS = 1_000;
|
||||
/**
|
||||
* Heartbeat interval used while knip runs without output.
|
||||
*/
|
||||
export const KNIP_HEARTBEAT_MS = 60_000;
|
||||
/**
|
||||
* Maximum buffered knip output retained for diagnostics.
|
||||
*/
|
||||
export const KNIP_MAX_BUFFER_BYTES = 16 * 1024 * 1024;
|
||||
const KNIP_ARGS = [
|
||||
"--config",
|
||||
"config/knip.config.ts",
|
||||
"--production",
|
||||
"--no-progress",
|
||||
"--reporter",
|
||||
"compact",
|
||||
"--files",
|
||||
"--no-config-hints",
|
||||
];
|
||||
|
||||
function normalizeRepoPath(value) {
|
||||
return value.replaceAll("\\", "/").replace(/^\.\//u, "");
|
||||
}
|
||||
|
||||
function uniqueSorted(values) {
|
||||
return [...new Set(values.map(normalizeRepoPath))].toSorted((left, right) =>
|
||||
left.localeCompare(right),
|
||||
);
|
||||
}
|
||||
|
||||
function isLikelyRepoFilePath(value) {
|
||||
return /^(apps|docs|extensions|packages|scripts|src|test|ui)\//u.test(normalizeRepoPath(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses compact knip output into unused file paths.
|
||||
*/
|
||||
export function parseKnipCompactUnusedFiles(output) {
|
||||
const files = [];
|
||||
let inUnusedFilesSection = false;
|
||||
let sawUnusedFilesSection = false;
|
||||
|
||||
for (const line of output.split(/\r?\n/u)) {
|
||||
if (/^Unused files \(\d+\)$/u.test(line)) {
|
||||
inUnusedFilesSection = true;
|
||||
sawUnusedFilesSection = true;
|
||||
continue;
|
||||
}
|
||||
if (inUnusedFilesSection && line.trim() === "") {
|
||||
break;
|
||||
}
|
||||
|
||||
const separatorIndex = line.lastIndexOf(": ");
|
||||
if (separatorIndex === -1) {
|
||||
continue;
|
||||
}
|
||||
if (sawUnusedFilesSection && !inUnusedFilesSection) {
|
||||
continue;
|
||||
}
|
||||
const file = line.slice(separatorIndex + 2).trim();
|
||||
if (isLikelyRepoFilePath(file)) {
|
||||
files.push(file);
|
||||
}
|
||||
}
|
||||
|
||||
return uniqueSorted(files);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares detected unused files against the checked-in allowlist.
|
||||
*/
|
||||
export function compareUnusedFilesToAllowlist(
|
||||
actualFiles,
|
||||
allowlistFiles,
|
||||
optionalAllowlistFiles = [],
|
||||
) {
|
||||
const actual = uniqueSorted(actualFiles);
|
||||
const allowed = uniqueSorted(allowlistFiles);
|
||||
const optionalAllowed = uniqueSorted(optionalAllowlistFiles);
|
||||
const allowedOrOptionalSet = new Set([...allowed, ...optionalAllowed]);
|
||||
const actualSet = new Set(actual);
|
||||
|
||||
return {
|
||||
actual,
|
||||
allowed,
|
||||
unexpected: actual.filter((file) => !allowedOrOptionalSet.has(file)),
|
||||
stale: allowed.filter((file) => !actualSet.has(file)),
|
||||
duplicateAllowedCount: allowlistFiles.length - new Set(allowlistFiles).size,
|
||||
allowlistIsSorted:
|
||||
JSON.stringify(allowlistFiles.map(normalizeRepoPath)) === JSON.stringify(allowed),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats unused-file allowlist drift for CLI output.
|
||||
*/
|
||||
export function formatUnusedFileComparison(comparison) {
|
||||
const lines = [];
|
||||
if (!comparison.allowlistIsSorted) {
|
||||
lines.push("deadcode unused-file allowlist is not sorted.");
|
||||
}
|
||||
if (comparison.duplicateAllowedCount > 0) {
|
||||
lines.push(
|
||||
`deadcode unused-file allowlist contains ${comparison.duplicateAllowedCount} duplicate entr${
|
||||
comparison.duplicateAllowedCount === 1 ? "y" : "ies"
|
||||
}.`,
|
||||
);
|
||||
}
|
||||
if (comparison.unexpected.length > 0) {
|
||||
lines.push("Unexpected unused files:");
|
||||
lines.push(...comparison.unexpected.map((file) => ` ${file}`));
|
||||
}
|
||||
if (comparison.stale.length > 0) {
|
||||
lines.push("Stale allowlist entries:");
|
||||
lines.push(...comparison.stale.map((file) => ` ${file}`));
|
||||
}
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
function spawnErrorCode(error) {
|
||||
return error && typeof error === "object" && "code" in error ? String(error.code) : undefined;
|
||||
}
|
||||
|
||||
function signalProcessTree(child, signal) {
|
||||
if (!child.pid) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (process.platform === "win32") {
|
||||
process.kill(child.pid, signal);
|
||||
} else {
|
||||
process.kill(-child.pid, signal);
|
||||
}
|
||||
} catch {
|
||||
// The child may have exited between the timeout and signal delivery.
|
||||
}
|
||||
}
|
||||
|
||||
function processTreeAlive(child) {
|
||||
if (!child.pid) {
|
||||
return false;
|
||||
}
|
||||
if (process.platform === "win32") {
|
||||
return child.exitCode === null && child.signalCode === null;
|
||||
}
|
||||
try {
|
||||
process.kill(-child.pid, 0);
|
||||
return true;
|
||||
} catch (error) {
|
||||
return error?.code === "EPERM";
|
||||
}
|
||||
}
|
||||
|
||||
async function waitForProcessTreeExit(child, timeoutMs) {
|
||||
const deadlineAt = Date.now() + timeoutMs;
|
||||
while (Date.now() < deadlineAt) {
|
||||
if (!processTreeAlive(child)) {
|
||||
return true;
|
||||
}
|
||||
await new Promise((resolvePoll) => {
|
||||
setTimeout(resolvePoll, KNIP_PROCESS_TREE_EXIT_POLL_MS);
|
||||
});
|
||||
}
|
||||
return !processTreeAlive(child);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs knip and returns parsed unused-file results.
|
||||
*/
|
||||
export async function runKnipUnusedFiles(params = {}) {
|
||||
const run = params.spawnCommand ?? spawn;
|
||||
const timeoutMs = params.timeoutMs ?? KNIP_TIMEOUT_MS;
|
||||
const heartbeatMs = params.heartbeatMs ?? KNIP_HEARTBEAT_MS;
|
||||
const maxBufferBytes = params.maxBufferBytes ?? KNIP_MAX_BUFFER_BYTES;
|
||||
const killGraceMs = params.killGraceMs ?? KNIP_KILL_GRACE_MS;
|
||||
const writeStatus = params.writeStatus ?? ((message) => process.stderr.write(`${message}\n`));
|
||||
const args = [
|
||||
"--config.minimum-release-age=0",
|
||||
"dlx",
|
||||
"--package",
|
||||
`knip@${KNIP_VERSION}`,
|
||||
"knip",
|
||||
...KNIP_ARGS,
|
||||
];
|
||||
|
||||
return await new Promise((resolve) => {
|
||||
const startedAt = Date.now();
|
||||
let settled = false;
|
||||
let timedOut = false;
|
||||
let bufferExceeded = false;
|
||||
let outputBytes = 0;
|
||||
const output = [];
|
||||
let killTimer;
|
||||
let exitStatus = null;
|
||||
let exitSignal = null;
|
||||
|
||||
const pnpm = createPnpmRunnerSpawnSpec({
|
||||
detached: process.platform !== "win32",
|
||||
env: params.env,
|
||||
nodeExecPath: params.nodeExecPath,
|
||||
npmExecPath: params.npmExecPath,
|
||||
platform: params.platform,
|
||||
pnpmArgs: args,
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
});
|
||||
const child = run(pnpm.command, pnpm.args, {
|
||||
...pnpm.options,
|
||||
detached: process.platform !== "win32",
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
});
|
||||
const parentSignalHandlers = [];
|
||||
const cleanupParentSignalHandlers = () => {
|
||||
for (const { signal, handler } of parentSignalHandlers) {
|
||||
process.off(signal, handler);
|
||||
}
|
||||
parentSignalHandlers.length = 0;
|
||||
};
|
||||
const relayParentSignal = (signal) => {
|
||||
const handler = () => {
|
||||
signalProcessTree(child, signal);
|
||||
signalProcessTree(child, "SIGKILL");
|
||||
cleanupParentSignalHandlers();
|
||||
process.kill(process.pid, signal);
|
||||
};
|
||||
parentSignalHandlers.push({ signal, handler });
|
||||
process.once(signal, handler);
|
||||
};
|
||||
if (process.platform !== "win32") {
|
||||
relayParentSignal("SIGINT");
|
||||
relayParentSignal("SIGTERM");
|
||||
relayParentSignal("SIGHUP");
|
||||
}
|
||||
|
||||
const heartbeatTimer = setInterval(() => {
|
||||
writeStatus(
|
||||
`[deadcode] Knip unused-file scan still running after ${Math.round(
|
||||
(Date.now() - startedAt) / 1000,
|
||||
)}s.`,
|
||||
);
|
||||
}, heartbeatMs);
|
||||
|
||||
const timeoutTimer = setTimeout(() => {
|
||||
timedOut = true;
|
||||
clearInterval(heartbeatTimer);
|
||||
writeStatus(
|
||||
`[deadcode] Knip unused-file scan timed out after ${Math.round(timeoutMs / 1000)}s; terminating.`,
|
||||
);
|
||||
signalProcessTree(child, "SIGTERM");
|
||||
killTimer = setTimeout(() => signalProcessTree(child, "SIGKILL"), killGraceMs);
|
||||
}, timeoutMs);
|
||||
|
||||
const finish = (result) => {
|
||||
if (settled) {
|
||||
return;
|
||||
}
|
||||
settled = true;
|
||||
clearTimeout(timeoutTimer);
|
||||
clearInterval(heartbeatTimer);
|
||||
clearTimeout(killTimer);
|
||||
cleanupParentSignalHandlers();
|
||||
resolve({
|
||||
...result,
|
||||
output: output.join(""),
|
||||
});
|
||||
};
|
||||
const finishAfterProcessTreeCleanup = async (result) => {
|
||||
if (processTreeAlive(child)) {
|
||||
await waitForProcessTreeExit(child, killGraceMs);
|
||||
}
|
||||
if (processTreeAlive(child)) {
|
||||
signalProcessTree(child, "SIGKILL");
|
||||
await waitForProcessTreeExit(child, KNIP_POST_FORCE_KILL_WAIT_MS);
|
||||
}
|
||||
finish(result);
|
||||
};
|
||||
|
||||
const appendOutput = (chunk) => {
|
||||
if (settled) {
|
||||
return;
|
||||
}
|
||||
if (bufferExceeded) {
|
||||
return;
|
||||
}
|
||||
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk));
|
||||
const remainingBytes = maxBufferBytes - outputBytes;
|
||||
if (buffer.length <= remainingBytes) {
|
||||
output.push(buffer.toString("utf8"));
|
||||
outputBytes += buffer.length;
|
||||
return;
|
||||
}
|
||||
if (remainingBytes > 0) {
|
||||
output.push(buffer.subarray(0, remainingBytes).toString("utf8"));
|
||||
outputBytes = maxBufferBytes;
|
||||
}
|
||||
if (!bufferExceeded) {
|
||||
bufferExceeded = true;
|
||||
writeStatus(
|
||||
`[deadcode] Knip unused-file scan exceeded ${maxBufferBytes} output bytes; terminating.`,
|
||||
);
|
||||
child.stdout?.off?.("data", appendOutput);
|
||||
child.stderr?.off?.("data", appendOutput);
|
||||
child.stdout?.destroy?.();
|
||||
child.stderr?.destroy?.();
|
||||
clearInterval(heartbeatTimer);
|
||||
signalProcessTree(child, "SIGTERM");
|
||||
killTimer = setTimeout(() => signalProcessTree(child, "SIGKILL"), killGraceMs);
|
||||
}
|
||||
};
|
||||
|
||||
child.stdout?.on("data", appendOutput);
|
||||
child.stderr?.on("data", appendOutput);
|
||||
child.on("error", (error) =>
|
||||
finish({
|
||||
errorCode: spawnErrorCode(error),
|
||||
errorMessage: error.message,
|
||||
signal: null,
|
||||
status: null,
|
||||
}),
|
||||
);
|
||||
child.on("exit", (status, signal) => {
|
||||
exitStatus = status;
|
||||
exitSignal = signal;
|
||||
});
|
||||
child.on("close", (status, signal) => {
|
||||
exitStatus = exitStatus ?? status;
|
||||
exitSignal = exitSignal ?? signal;
|
||||
const elapsedSeconds = Math.round((Date.now() - startedAt) / 1000);
|
||||
if (timedOut) {
|
||||
void finishAfterProcessTreeCleanup({
|
||||
errorCode: "ETIMEDOUT",
|
||||
errorMessage: `Knip unused-file scan timed out after ${elapsedSeconds}s`,
|
||||
signal: exitSignal,
|
||||
status: exitStatus,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (bufferExceeded) {
|
||||
void finishAfterProcessTreeCleanup({
|
||||
errorCode: "ENOBUFS",
|
||||
errorMessage: `Knip unused-file scan exceeded ${maxBufferBytes} output bytes`,
|
||||
signal: exitSignal,
|
||||
status: exitStatus,
|
||||
});
|
||||
return;
|
||||
}
|
||||
finish({
|
||||
errorCode: undefined,
|
||||
errorMessage: undefined,
|
||||
signal: exitSignal,
|
||||
status: exitStatus,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Checks detected unused files against the current allowlist.
|
||||
*/
|
||||
export function checkUnusedFiles(
|
||||
output,
|
||||
allowlistFiles = KNIP_UNUSED_FILE_ALLOWLIST,
|
||||
optionalAllowlistFiles = KNIP_OPTIONAL_UNUSED_FILE_ALLOWLIST,
|
||||
) {
|
||||
const actual = parseKnipCompactUnusedFiles(output);
|
||||
const comparison = compareUnusedFilesToAllowlist(actual, allowlistFiles, optionalAllowlistFiles);
|
||||
return {
|
||||
ok:
|
||||
comparison.allowlistIsSorted &&
|
||||
comparison.duplicateAllowedCount === 0 &&
|
||||
comparison.unexpected.length === 0 &&
|
||||
comparison.stale.length === 0,
|
||||
comparison,
|
||||
message: formatUnusedFileComparison(comparison),
|
||||
};
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const result = await runKnipUnusedFiles();
|
||||
if (result.errorCode || result.status === null) {
|
||||
console.error(
|
||||
`deadcode unused-file scan failed: ${result.errorCode ?? result.signal ?? "unknown"}${
|
||||
result.errorMessage ? `: ${result.errorMessage}` : ""
|
||||
}`,
|
||||
);
|
||||
if (result.output) {
|
||||
console.error(result.output);
|
||||
}
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
const check = checkUnusedFiles(result.output);
|
||||
if (!check.ok) {
|
||||
if (check.message) {
|
||||
console.error(check.message);
|
||||
}
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(
|
||||
`[deadcode] Knip unused-file allowlist matched ${check.comparison.actual.length} intentional entries.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
||||
await main();
|
||||
}
|
||||
Reference in New Issue
Block a user