fix: make checkout gates environment-aware

This commit is contained in:
mosaic-coder
2026-07-31 17:50:58 -05:00
parent 01e966f36d
commit a0209ee102
11 changed files with 634 additions and 8 deletions
+127
View File
@@ -0,0 +1,127 @@
#!/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();
}