init project
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
import { readFile, readdir, stat } from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import { fail, pass, repositoryRoot, sha256 } from './lib.mjs';
|
||||
|
||||
const manifestName = 'PHASE_11_PACKAGE_MANIFEST_v1.0.json';
|
||||
const manifestPath = path.join(repositoryRoot, manifestName);
|
||||
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 = [];
|
||||
const entries = await readdir(directory, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
if (entry.isDirectory() && excludedDirectories.has(entry.name)) continue;
|
||||
const fullPath = path.join(directory, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
files.push(...(await walk(fullPath)));
|
||||
continue;
|
||||
}
|
||||
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(manifestPath, 'utf8'));
|
||||
const failures = [];
|
||||
|
||||
if (manifest.project !== 'RentalDriveGo' || manifest.phase !== 11) {
|
||||
failures.push('Phase 11 manifest identity is invalid.');
|
||||
}
|
||||
if (!Array.isArray(manifest.files)) {
|
||||
failures.push('Manifest files property must be an array.');
|
||||
} else if (manifest.file_count !== manifest.files.length) {
|
||||
failures.push(
|
||||
`Manifest file_count ${manifest.file_count} does not match ${manifest.files.length} entries.`,
|
||||
);
|
||||
}
|
||||
|
||||
const listedPaths = new Set();
|
||||
for (const entry of manifest.files ?? []) {
|
||||
if (
|
||||
typeof entry.path !== 'string' ||
|
||||
entry.path.length === 0 ||
|
||||
path.isAbsolute(entry.path) ||
|
||||
entry.path.split('/').includes('..')
|
||||
) {
|
||||
failures.push(`Unsafe manifest path: ${String(entry.path)}`);
|
||||
continue;
|
||||
}
|
||||
if (listedPaths.has(entry.path)) {
|
||||
failures.push(`${entry.path}: duplicate manifest entry.`);
|
||||
continue;
|
||||
}
|
||||
listedPaths.add(entry.path);
|
||||
|
||||
const filePath = path.join(repositoryRoot, entry.path);
|
||||
try {
|
||||
const details = await stat(filePath);
|
||||
if (!details.isFile()) failures.push(`${entry.path}: is not a regular file.`);
|
||||
if (details.size !== entry.bytes) failures.push(`${entry.path}: byte size changed.`);
|
||||
const digest = await sha256(filePath);
|
||||
if (digest !== entry.sha256) failures.push(`${entry.path}: SHA-256 changed.`);
|
||||
} catch {
|
||||
failures.push(`${entry.path}: file is missing.`);
|
||||
}
|
||||
}
|
||||
|
||||
const actualPaths = new Set(await walk(repositoryRoot));
|
||||
for (const actual of actualPaths) {
|
||||
if (!listedPaths.has(actual)) failures.push(`${actual}: unlisted repository file.`);
|
||||
}
|
||||
for (const listed of listedPaths) {
|
||||
if (!actualPaths.has(listed)) failures.push(`${listed}: listed but excluded or absent.`);
|
||||
}
|
||||
|
||||
if (failures.length) fail(failures);
|
||||
else {
|
||||
pass(`Phase 11 package manifest verifies ${listedPaths.size} repository files with no extras.`);
|
||||
}
|
||||
Reference in New Issue
Block a user