diff --git a/package.json b/package.json index bca0277..c586fb4 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,9 @@ "description": "", "main": "index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "echo \"Error: no test specified\" && exit 1", + "shared:prune": "node scripts/ts-prune-filter.mjs packages/shared/tsconfig.json packages/shared/ts-prune-allowlist.json", + "shared:prune:check": "node scripts/ts-prune-filter.mjs packages/shared/tsconfig.json packages/shared/ts-prune-allowlist.json --fail-on-findings" }, "keywords": [], "author": "", diff --git a/packages/shared/ts-prune-allowlist.json b/packages/shared/ts-prune-allowlist.json new file mode 100644 index 0000000..e63bfc8 --- /dev/null +++ b/packages/shared/ts-prune-allowlist.json @@ -0,0 +1,7 @@ +{ + "ignoreFiles": [ + "packages/shared/src/index.ts", + "packages/shared/src/config/index.ts" + ], + "ignoreExports": [] +} \ No newline at end of file diff --git a/scripts/ts-prune-filter.mjs b/scripts/ts-prune-filter.mjs new file mode 100644 index 0000000..37a48a5 --- /dev/null +++ b/scripts/ts-prune-filter.mjs @@ -0,0 +1,64 @@ +import { readFileSync } from "node:fs"; +import { spawnSync } from "node:child_process"; + +const [, , tsconfigPath, allowlistPath, ...restArgs] = process.argv; + +if (!tsconfigPath || !allowlistPath) { + console.error( + "Usage: node scripts/ts-prune-filter.mjs [--fail-on-findings]", + ); + process.exit(2); +} + +const failOnFindings = restArgs.includes("--fail-on-findings"); + +const allowlist = JSON.parse(readFileSync(allowlistPath, "utf8")); +const ignoreFiles = new Set(allowlist.ignoreFiles ?? []); +const ignoreExports = new Set(allowlist.ignoreExports ?? []); + +const run = spawnSync( + "pnpm", + ["dlx", "ts-prune", "-p", tsconfigPath], + { encoding: "utf8" }, +); + +if (run.error) { + console.error(run.error.message); + process.exit(2); +} + +const stdout = run.stdout ?? ""; +const stderr = run.stderr ?? ""; +if (stderr.trim()) { + process.stderr.write(stderr); +} + +const lines = stdout + .split("\n") + .map((line) => line.trimEnd()) + .filter((line) => line.length > 0); + +const findings = lines.filter((line) => { + const match = line.match(/^(.+?):\d+\s+-\s+([^\s].*?)\s*(?:\(used in module\))?$/); + if (!match) { + return true; + } + + const filePath = match[1]; + const symbolName = match[2]; + if (ignoreFiles.has(filePath)) { + return false; + } + + return !ignoreExports.has(`${filePath}#${symbolName}`); +}); + +if (findings.length > 0) { + process.stdout.write(`${findings.join("\n")}\n`); +} + +if (failOnFindings && findings.length > 0) { + process.exit(1); +} + +process.exit(0); \ No newline at end of file