feat(audit): add rolling phase0 artifact retention tooling
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { readdir, rm } from 'node:fs/promises';
|
||||
import { resolve } from 'node:path';
|
||||
import { parseArgs } from 'node:util';
|
||||
import {
|
||||
planRollingPhase0ArtifactRetention,
|
||||
type Phase0RollingArtifactRetentionPlan,
|
||||
} from '../src/audit/phase0BaselineArtifactRetention.js';
|
||||
|
||||
function usage(): string {
|
||||
return [
|
||||
'Usage: node --import tsx/esm scripts/prune-phase0-baseline-artifacts.ts [options]',
|
||||
'',
|
||||
'Options:',
|
||||
' --artifacts-dir <path> Artifacts directory (default: docs/plans/artifacts)',
|
||||
' --keep-per-family <num> Keep newest rolling tags per family (default: 8)',
|
||||
' --apply Apply deletions (default: dry-run)',
|
||||
' --format <text|json> Output format (default: text)',
|
||||
' --help Show usage',
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
function parseOptionalNumber(raw: string | undefined, flag: string): number | undefined {
|
||||
if (!raw) {
|
||||
return undefined;
|
||||
}
|
||||
const parsed = Number(raw);
|
||||
if (!Number.isFinite(parsed)) {
|
||||
throw new Error(`Invalid ${flag} value "${raw}". Expected a number.`);
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
|
||||
function renderText(plan: Phase0RollingArtifactRetentionPlan, artifactsDir: string, keepPerFamily: number, apply: boolean): string {
|
||||
const lines: string[] = [];
|
||||
lines.push('# Phase-0 Rolling Artifact Prune');
|
||||
lines.push('');
|
||||
lines.push(`Artifacts dir: ${artifactsDir}`);
|
||||
lines.push(`Keep per family: ${keepPerFamily}`);
|
||||
lines.push(`Mode: ${apply ? 'apply' : 'dry-run'}`);
|
||||
lines.push(`Keep files: ${plan.keep.length}`);
|
||||
lines.push(`Remove files: ${plan.remove.length}`);
|
||||
lines.push('');
|
||||
lines.push('## Families');
|
||||
for (const row of plan.families) {
|
||||
lines.push(`- ${row.family}: tags total=${row.total_tags} keep=${row.keep_tags} remove=${row.remove_tags}`);
|
||||
}
|
||||
lines.push('');
|
||||
lines.push('## Remove List');
|
||||
if (plan.remove.length === 0) {
|
||||
lines.push('- none');
|
||||
} else {
|
||||
for (const row of plan.remove) {
|
||||
lines.push(`- ${row.file_name}`);
|
||||
}
|
||||
}
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
async function main(): Promise<void> {
|
||||
const { values } = parseArgs({
|
||||
options: {
|
||||
'artifacts-dir': { type: 'string' },
|
||||
'keep-per-family': { type: 'string' },
|
||||
apply: { type: 'boolean' },
|
||||
format: { type: 'string' },
|
||||
help: { type: 'boolean', short: 'h' },
|
||||
},
|
||||
strict: true,
|
||||
allowPositionals: false,
|
||||
});
|
||||
|
||||
if (values.help) {
|
||||
process.stdout.write(`${usage()}\n`);
|
||||
return;
|
||||
}
|
||||
|
||||
const artifactsDir = resolve(values['artifacts-dir'] ?? 'docs/plans/artifacts');
|
||||
const keepPerFamily = parseOptionalNumber(values['keep-per-family'], '--keep-per-family') ?? 8;
|
||||
const apply = Boolean(values.apply);
|
||||
const format = values.format ?? 'text';
|
||||
|
||||
if (format !== 'text' && format !== 'json') {
|
||||
throw new Error(`Invalid --format value "${format}".`);
|
||||
}
|
||||
|
||||
const files = await readdir(artifactsDir);
|
||||
const plan = planRollingPhase0ArtifactRetention(files, keepPerFamily);
|
||||
|
||||
if (apply) {
|
||||
for (const row of plan.remove) {
|
||||
await rm(resolve(artifactsDir, row.file_name));
|
||||
}
|
||||
}
|
||||
|
||||
if (format === 'json') {
|
||||
process.stdout.write(`${JSON.stringify({
|
||||
generated_at: new Date().toISOString(),
|
||||
artifacts_dir: artifactsDir,
|
||||
keep_per_family: Math.floor(keepPerFamily),
|
||||
apply,
|
||||
plan,
|
||||
}, null, 2)}\n`);
|
||||
} else {
|
||||
process.stdout.write(`${renderText(plan, artifactsDir, Math.floor(keepPerFamily), apply)}\n`);
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
process.stderr.write(`${message}\n\n${usage()}\n`);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
Reference in New Issue
Block a user