scripts/verification-fixtures.mjs
Rama main · réplica de lectura del repositorio original.
#!/usr/bin/env node
import { lstatSync, readFileSync } from 'node:fs';
import { isAbsolute, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import { parseArgs } from 'node:util';
import { outsideGit } from './lib/private-evidence.mjs';
import { fixtureReadiness } from './lib/verification-fixtures.mjs';
function readPrivateJson(file) {
if (!isAbsolute(file)) throw new Error('Input paths must be absolute.');
const initial = lstatSync(file);
if (!initial.isFile() || initial.isSymbolicLink() || initial.size > 8 * 1024 * 1024) throw new Error('Input must be a bounded private regular file.');
const path = outsideGit(file);
const stat = lstatSync(path);
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 8 * 1024 * 1024) throw new Error('Input must be a bounded private regular file.');
return JSON.parse(readFileSync(path, 'utf8'));
}
export function runFixtureCli(argv, io = {}) {
const stdout = io.stdout ?? ((value) => process.stdout.write(value));
const stderr = io.stderr ?? ((value) => process.stderr.write(value));
try {
const { values, positionals } = parseArgs({ args: argv, allowPositionals: true, options: {
spec: { type: 'string' }, inventory: { type: 'string' }, operation: { type: 'string' },
'reset-rights': { type: 'string' }, from: { type: 'string' }, to: { type: 'string' },
'max-age-ms': { type: 'string' }, help: { type: 'boolean' },
} });
if (values.help) { stdout('Usage: verification-fixtures.mjs readiness --spec FILE --inventory FILE [--operation ID] [--reset-rights RIGHT] [--from RELEASE --to RELEASE] [--max-age-ms MS]\nRead-only advisory: this command creates no fixture reservation or authorization.\n'); return 0; }
if (positionals.length !== 1 || positionals[0] !== 'readiness' || !values.spec || !values.inventory) throw new Error('Choose readiness and pass --spec and --inventory.');
if ((values.from === undefined) !== (values.to === undefined)) throw new Error('Pass --from and --to together.');
if (values['reset-rights'] && !['none', 'restore', 'dispose'].includes(values['reset-rights'])) throw new Error('Choose reset rights none, restore, or dispose.');
for (const value of [values.operation, values.from, values.to].filter(Boolean)) {
if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u.test(value)) throw new Error('Operation and release selectors must be aliases.');
}
const maxAgeMs = values['max-age-ms'] === undefined ? undefined : Number(values['max-age-ms']);
const result = fixtureReadiness(readPrivateJson(values.spec), readPrivateJson(values.inventory), {
operation: values.operation, resetRights: values['reset-rights'], fromRelease: values.from,
toRelease: values.to, maxAgeMs,
});
stdout(`${JSON.stringify(result, null, 2)}\n`);
return result.cases.every(({ ready }) => ready) ? 0 : 1;
} catch (error) {
stderr(`Fixture readiness failed: ${error instanceof Error ? error.message : 'Invalid input.'}\n`);
return 2;
}
}
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
process.exitCode = runFixtureCli(process.argv.slice(2));
}