import { createHash } from 'node:crypto'; import { constants } from 'node:fs'; import { lstat, mkdir, open, readFile, readdir } from 'node:fs/promises'; import { dirname, join, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; const scriptPath = fileURLToPath(import.meta.url); const defaultRepositoryRoot = resolve(dirname(scriptPath), '..'); const isErrno = (error, code) => typeof error === 'object' && error !== null && 'code' in error && error.code === code; const directoryIdentity = (stats) => ({ dev: stats.dev, ino: stats.ino }); const hasSameDirectoryIdentity = (left, right) => left.dev === right.dev && left.ino === right.ino; const assertNonSymlinkDirectory = (stats) => { if (stats.isSymbolicLink() || !stats.isDirectory()) { throw new Error('Prepared asset path must be a non-symlink directory.'); } }; export const assertPreparedAssetDirectoryIdentity = async (preparedAssetsPath, expectedIdentity) => { const current = await lstat(preparedAssetsPath); assertNonSymlinkDirectory(current); if (!hasSameDirectoryIdentity(directoryIdentity(current), expectedIdentity)) { throw new Error('Prepared asset directory identity changed during validation.'); } }; const assertOpenedDirectoryIdentity = async (preparedAssetsPath, expectedIdentity) => { const noFollowDirectoryFlags = constants.O_RDONLY | (constants.O_DIRECTORY ?? 0) | (constants.O_NOFOLLOW ?? 0); let handle; try { handle = await open(preparedAssetsPath, noFollowDirectoryFlags); } catch (error) { if (isErrno(error, 'ELOOP')) { throw new Error('Prepared asset path must be a non-symlink directory.'); } if (isErrno(error, 'EINVAL') || isErrno(error, 'ENOTSUP') || isErrno(error, 'ENOSYS')) return; throw error; } try { const opened = await handle.stat(); assertNonSymlinkDirectory(opened); if (!hasSameDirectoryIdentity(directoryIdentity(opened), expectedIdentity)) { throw new Error('Prepared asset directory identity changed during validation.'); } } finally { await handle.close(); } }; const readJson = async (path, label) => { let value; try { value = JSON.parse(await readFile(path, 'utf8')); } catch (error) { throw new Error(`${label} must be valid JSON.`, { cause: error }); } if (typeof value !== 'object' || value === null || Array.isArray(value)) { throw new Error(`${label} must be a JSON object.`); } return value; }; const readManifest = async (repositoryRoot) => { const manifestPath = resolve(repositoryRoot, 'content/public/asset-manifest.json'); const manifest = await readJson(manifestPath, 'Public asset manifest'); if (Object.keys(manifest).length !== 1 || !Array.isArray(manifest.assets)) { throw new Error('Public asset manifest must contain exactly one assets array.'); } const ids = new Set(); const fileNames = new Set(); const assets = manifest.assets.map((asset) => { if (typeof asset !== 'object' || asset === null || Array.isArray(asset)) { throw new Error('Every manifest asset must be an object.'); } const { id, fileName, sha256 } = asset; if (typeof id !== 'string' || !/^[a-z][a-z0-9-]{0,63}$/u.test(id)) { throw new Error('Manifest asset ID must be a generated safe identifier.'); } if (ids.has(id)) throw new Error(`Duplicate manifest asset ID: ${id}`); ids.add(id); if (typeof fileName !== 'string' || !/^[a-z0-9][a-z0-9-]*\.(avif|jpg|png|svg|webp|mp4|webm|opus|mp3)$/u.test(fileName)) { throw new Error(`Manifest filename must be a generated safe filename: ${id}`); } const foldedFileName = fileName.toLocaleLowerCase('en-US'); if (fileNames.has(foldedFileName)) throw new Error(`Duplicate manifest filename: ${fileName}`); fileNames.add(foldedFileName); if (typeof sha256 !== 'string' || !/^[a-f0-9]{64}$/u.test(sha256)) { throw new Error(`Manifest asset must have a lowercase SHA-256 digest: ${id}`); } return { id, fileName, sha256 }; }); return assets; }; const assetReferences = (content) => { const references = []; const projects = content.projects; if (typeof projects !== 'object' || projects === null || Array.isArray(projects)) { throw new Error('Portfolio content must contain project collections.'); } for (const collectionName of ['selected', 'additional']) { const collection = projects[collectionName]; if (!Array.isArray(collection)) throw new Error(`Portfolio ${collectionName} projects must be an array.`); for (const project of collection) { if (typeof project !== 'object' || project === null || Array.isArray(project) || !Array.isArray(project.assetIds)) { throw new Error('Every public project must contain an assetIds array.'); } references.push(...project.assetIds); } } if (!Array.isArray(content.toolkit)) throw new Error('Portfolio toolkit must be an array.'); for (const group of content.toolkit) { if (typeof group !== 'object' || group === null || Array.isArray(group) || !Array.isArray(group.technologies)) { throw new Error('Every toolkit group must contain a technologies array.'); } for (const technology of group.technologies) { if (typeof technology !== 'object' || technology === null || Array.isArray(technology)) { throw new Error('Every toolkit technology must be an object.'); } if (technology.assetId !== undefined) references.push(technology.assetId); } } if (typeof content.metadata !== 'object' || content.metadata === null || Array.isArray(content.metadata)) { throw new Error('Portfolio content must contain metadata.'); } if (content.metadata.socialPreviewAssetId !== undefined) references.push(content.metadata.socialPreviewAssetId); if (content.soundtrack !== undefined) { if (typeof content.soundtrack !== 'object' || content.soundtrack === null || Array.isArray(content.soundtrack)) { throw new Error('Portfolio soundtrack must be an object.'); } for (const regionName of ['intro', 'main', 'afterglow', 'driveLift']) { const region = content.soundtrack[regionName]; if (typeof region !== 'object' || region === null || Array.isArray(region)) { throw new Error(`Portfolio soundtrack ${regionName} must be an object.`); } references.push(region.opusAssetId, region.mp3AssetId); } } return references; }; const assertExactReferenceClosure = (assets, content) => { const manifestIds = new Set(assets.map((asset) => asset.id)); const referencedIds = new Set(); for (const assetId of assetReferences(content)) { if (typeof assetId !== 'string' || !/^[a-z][a-z0-9-]{0,63}$/u.test(assetId)) { throw new Error('Public asset reference must be a generated safe identifier.'); } if (referencedIds.has(assetId)) throw new Error(`Duplicate public asset reference: ${assetId}`); referencedIds.add(assetId); if (!manifestIds.has(assetId)) throw new Error(`Unknown public asset reference: ${assetId}`); } for (const assetId of manifestIds) { if (!referencedIds.has(assetId)) throw new Error(`Unreferenced manifest asset: ${assetId}`); } }; const ensurePreparedAssetDirectory = async (repositoryRoot) => { const preparedAssetsPath = resolve(repositoryRoot, 'inputs/prepared-assets'); let initialStats; try { initialStats = await lstat(preparedAssetsPath); } catch (error) { if (!isErrno(error, 'ENOENT')) throw error; await mkdir(preparedAssetsPath, { recursive: true }); initialStats = await lstat(preparedAssetsPath); } assertNonSymlinkDirectory(initialStats); const initialIdentity = directoryIdentity(initialStats); await assertOpenedDirectoryIdentity(preparedAssetsPath, initialIdentity); await assertPreparedAssetDirectoryIdentity(preparedAssetsPath, initialIdentity); return { preparedAssetsPath, initialIdentity }; }; const sha256 = (bytes) => createHash('sha256').update(bytes).digest('hex'); const readPreparedRegularFile = async (filePath, fileName) => { const noFollowFlags = constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0); let handle; try { handle = await open(filePath, noFollowFlags); } catch (error) { if (isErrno(error, 'ELOOP')) { throw new Error(`Prepared asset must be a regular non-linked file: ${fileName}`); } throw error; } try { const stats = await handle.stat(); if (!stats.isFile() || stats.nlink !== 1) { throw new Error(`Prepared asset must be a regular non-linked file: ${fileName}`); } return await handle.readFile(); } finally { await handle.close(); } }; const verifyPreparedAssetFiles = async (repositoryRoot, assets) => { const { preparedAssetsPath, initialIdentity } = await ensurePreparedAssetDirectory(repositoryRoot); const expectedByName = new Map(assets.map((asset) => [asset.fileName, asset])); const foundNames = new Set(); const members = await readdir(preparedAssetsPath, { withFileTypes: true }); for (const member of members) { const asset = expectedByName.get(member.name); if (asset === undefined) throw new Error(`Extra prepared asset: ${member.name}`); if (member.isSymbolicLink() || !member.isFile()) { throw new Error(`Prepared asset must be a regular non-linked file: ${member.name}`); } const filePath = join(preparedAssetsPath, member.name); const stats = await lstat(filePath); if (stats.isSymbolicLink() || !stats.isFile() || stats.nlink !== 1) { throw new Error(`Prepared asset must be a regular non-linked file: ${member.name}`); } if (sha256(await readPreparedRegularFile(filePath, member.name)) !== asset.sha256) { throw new Error(`Prepared asset hash does not match manifest: ${member.name}`); } foundNames.add(member.name); } await assertPreparedAssetDirectoryIdentity(preparedAssetsPath, initialIdentity); for (const asset of assets) { if (!foundNames.has(asset.fileName)) throw new Error(`Missing prepared asset: ${asset.fileName}`); } }; export const verifyApprovedAssetSet = async (repositoryRoot) => { const resolvedRepositoryRoot = resolve(repositoryRoot); const [assets, content] = await Promise.all([ readManifest(resolvedRepositoryRoot), readJson(resolve(resolvedRepositoryRoot, 'content/public/portfolio-content.json'), 'Portfolio content'), ]); assertExactReferenceClosure(assets, content); await verifyPreparedAssetFiles(resolvedRepositoryRoot, assets); }; export const materializeEmptyAssetSet = verifyApprovedAssetSet; if (process.argv[1] !== undefined && resolve(process.argv[1]) === scriptPath) { await verifyApprovedAssetSet(defaultRepositoryRoot); }