66 lines
2.5 KiB
JavaScript
66 lines
2.5 KiB
JavaScript
import { readFile, readdir, stat } from 'node:fs/promises';
|
|
import path from 'node:path';
|
|
import { fail, pass, repositoryRoot, sha256 } from './lib.mjs';
|
|
|
|
const manifestName = 'PHASE_12_PACKAGE_MANIFEST_v1.0.json';
|
|
const excludedDirectories = new Set([
|
|
'.git',
|
|
'.next',
|
|
'node_modules',
|
|
'coverage',
|
|
'playwright-report',
|
|
'test-results',
|
|
]);
|
|
const excludedFiles = new Set([manifestName, 'tsconfig.tsbuildinfo']);
|
|
async function walk(directory) {
|
|
const files = [];
|
|
for (const entry of await readdir(directory, { withFileTypes: true })) {
|
|
if (entry.isDirectory() && excludedDirectories.has(entry.name)) continue;
|
|
const fullPath = path.join(directory, entry.name);
|
|
if (entry.isDirectory()) files.push(...(await walk(fullPath)));
|
|
else {
|
|
const relative = path.relative(repositoryRoot, fullPath).split(path.sep).join('/');
|
|
if (!excludedFiles.has(relative)) files.push(relative);
|
|
}
|
|
}
|
|
return files;
|
|
}
|
|
const manifest = JSON.parse(await readFile(path.join(repositoryRoot, manifestName), 'utf8'));
|
|
const failures = [];
|
|
if (manifest.project !== 'RentalDriveGo' || manifest.phase !== 12)
|
|
failures.push('Phase 12 manifest identity is invalid.');
|
|
if (!Array.isArray(manifest.files) || manifest.file_count !== manifest.files.length)
|
|
failures.push('Manifest file count is invalid.');
|
|
const listed = new Set();
|
|
for (const entry of manifest.files ?? []) {
|
|
if (
|
|
typeof entry.path !== 'string' ||
|
|
path.isAbsolute(entry.path) ||
|
|
entry.path.split('/').includes('..')
|
|
) {
|
|
failures.push(`Unsafe manifest path: ${String(entry.path)}`);
|
|
continue;
|
|
}
|
|
if (listed.has(entry.path)) {
|
|
failures.push(`${entry.path}: duplicate manifest entry.`);
|
|
continue;
|
|
}
|
|
listed.add(entry.path);
|
|
try {
|
|
const filePath = path.join(repositoryRoot, entry.path);
|
|
const details = await stat(filePath);
|
|
if (!details.isFile() || details.size !== entry.bytes)
|
|
failures.push(`${entry.path}: file metadata changed.`);
|
|
if ((await sha256(filePath)) !== entry.sha256) failures.push(`${entry.path}: SHA-256 changed.`);
|
|
} catch {
|
|
failures.push(`${entry.path}: file is missing.`);
|
|
}
|
|
}
|
|
const actual = new Set(await walk(repositoryRoot));
|
|
for (const file of actual)
|
|
if (!listed.has(file)) failures.push(`${file}: unlisted repository file.`);
|
|
for (const file of listed)
|
|
if (!actual.has(file)) failures.push(`${file}: listed but excluded or absent.`);
|
|
if (failures.length) fail(failures);
|
|
else pass(`Phase 12 package manifest verifies ${listed.size} repository files with no extras.`);
|