#!/usr/bin/env node import { constants } from 'node:fs'; import { access, lstat, readdir } from 'node:fs/promises'; import { fileURLToPath } from 'node:url'; import path from 'node:path'; export const MISSING_DEPS_EXIT = 42; export const GENERATED_STATE_EXIT = 43; async function entries(root) { const result = []; async function walk(current) { let children; try { children = await readdir(current, { withFileTypes: true }); } catch (error) { if (error.code === 'ENOENT') return; throw error; } for (const child of children) { const target = path.join(current, child.name); result.push(target); if (child.isDirectory() && !child.isSymbolicLink()) await walk(target); } } await walk(root); return result; } async function fileMtimeRange(paths) { let newest = 0; let oldest = Number.POSITIVE_INFINITY; for (const target of paths) { const stats = await lstat(target); if (stats.isFile()) { newest = Math.max(newest, stats.mtimeMs); oldest = Math.min(oldest, stats.mtimeMs); } } return { newest, oldest: Number.isFinite(oldest) ? oldest : 0 }; } export async function runPreflight({ root = process.cwd(), uid = process.getuid?.() } = {}) { const binDir = path.join(root, 'node_modules', '.bin'); const requiredBinaries = ['eslint', 'husky', 'prettier', 'tsc', 'turbo', 'vitest']; const missingBinaries = []; for (const binary of requiredBinaries) { try { await access(path.join(binDir, binary), constants.X_OK); } catch { missingBinaries.push(binary); } } if (missingBinaries.length > 0) { return { code: MISSING_DEPS_EXIT, message: `MOSAIC_PREFLIGHT_MISSING_DEPS: dependency installation is missing ${missingBinaries.join(', ')}; run pnpm install --frozen-lockfile`, }; } const nextDir = path.join(root, 'apps', 'web', '.next'); let generated = []; try { await lstat(nextDir); generated = [nextDir, ...(await entries(nextDir))]; } catch (error) { if (error.code !== 'ENOENT') throw error; } if (generated.length > 0) { const foreign = []; if (uid !== undefined) { for (const target of generated) { if ((await lstat(target)).uid !== uid) foreign.push(path.relative(root, target)); } } const sourceRoots = [ path.join(root, 'apps', 'web', 'src'), path.join(root, 'apps', 'web', 'next.config.ts'), path.join(root, 'apps', 'web', 'package.json'), path.join(root, 'apps', 'web', 'tsconfig.json'), ]; const source = []; for (const sourceRoot of sourceRoots) { try { const stats = await lstat(sourceRoot); source.push(sourceRoot); if (stats.isDirectory()) source.push(...(await entries(sourceRoot))); } catch (error) { if (error.code !== 'ENOENT') throw error; } } const generatedTypes = generated.filter((target) => target.startsWith(path.join(nextDir, 'types') + path.sep), ); const sourceMtime = await fileMtimeRange(source); const generatedMtime = await fileMtimeRange(generatedTypes); const stale = source.length > 0 && generatedTypes.length > 0 && sourceMtime.newest > generatedMtime.oldest; if (foreign.length > 0 || stale) { const reasons = [ foreign.length > 0 ? `foreign-owned paths: ${foreign.slice(0, 3).join(', ')}` : '', stale ? 'generated output is older than web source/configuration' : '', ].filter(Boolean); return { code: GENERATED_STATE_EXIT, message: `MOSAIC_PREFLIGHT_GENERATED_STATE: apps/web/.next is not trustworthy (${reasons.join('; ')}); run pnpm clean:generated, then rerun the gate`, }; } } return { code: 0, message: 'checkout preflight passed' }; } async function main() { const result = await runPreflight(); const stream = result.code === 0 ? process.stdout : process.stderr; stream.write(`${result.message}\n`); process.exitCode = result.code; } if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) { await main(); }