import assert from 'node:assert/strict'; import { chmod, mkdir, rm, symlink, utimes, writeFile } from 'node:fs/promises'; import path from 'node:path'; import test from 'node:test'; import { runPreflight, sourceFingerprint } from './preflight.mjs'; const fixtureRoot = path.join(process.cwd(), '.mosaic-test-work', `preflight-${process.pid}`); const requiredBins = ['eslint', 'husky', 'prettier', 'tsc', 'turbo', 'vitest']; async function fixture(name) { const root = path.join(fixtureRoot, name); await mkdir(path.join(root, 'apps', 'web', 'src', 'app'), { recursive: true }); await writeFile(path.join(root, 'apps', 'web', 'src', 'app', 'page.tsx'), 'export default 1;\n'); return root; } async function installRequiredBins(root) { const binDir = path.join(root, 'node_modules', '.bin'); await mkdir(binDir, { recursive: true }); await Promise.all( requiredBins.map(async (name) => { const target = path.join(binDir, name); await writeFile(target, ''); await chmod(target, 0o755); }), ); } test.after(async () => { await rm(fixtureRoot, { recursive: true, force: true }); }); test('missing dependencies have a dedicated exit code and install remediation', async () => { const root = await fixture('missing-deps'); const result = await runPreflight({ root }); assert.equal(result.code, 42); assert.match(result.message, /MOSAIC_PREFLIGHT_MISSING_DEPS/); assert.match(result.message, /run pnpm install/i); }); test('a partial dependency install keeps the dedicated missing-deps result', async () => { const root = await fixture('partial-deps'); await mkdir(path.join(root, 'node_modules', '.bin'), { recursive: true }); await writeFile(path.join(root, 'node_modules', '.bin', 'tsc'), '', { mode: 0o755 }); const result = await runPreflight({ root }); assert.equal(result.code, 42); assert.match(result.message, /turbo/); }); test('a dangling required dependency shim keeps the dedicated missing-deps result', async () => { const root = await fixture('dangling-deps'); await installRequiredBins(root); const turbo = path.join(root, 'node_modules', '.bin', 'turbo'); await rm(turbo); await symlink(path.join(root, 'node_modules', 'missing-turbo'), turbo); const result = await runPreflight({ root }); assert.equal(result.code, 42); assert.match(result.message, /turbo/); }); test('installed dependencies pass when generated state is absent', async () => { const root = await fixture('clean'); await installRequiredBins(root); assert.deepEqual(await runPreflight({ root }), { code: 0, message: 'checkout preflight passed' }); }); test('foreign-owned generated Next state is identified separately from source errors', async () => { const root = await fixture('foreign-next'); await installRequiredBins(root); const generated = path.join(root, 'apps', 'web', '.next', 'types', 'validator.ts'); await mkdir(path.dirname(generated), { recursive: true }); await writeFile(generated, 'generated output'); const result = await runPreflight({ root, uid: (process.getuid?.() ?? 0) + 1 }); assert.equal(result.code, 43); assert.match(result.message, /MOSAIC_PREFLIGHT_GENERATED_STATE/); assert.match(result.message, /foreign-owned/); }); test('a generated marker mismatch is identified separately from source errors', async () => { const root = await fixture('stale-next'); await installRequiredBins(root); const generated = path.join(root, 'apps', 'web', '.next', 'types', 'validator.ts'); await mkdir(path.dirname(generated), { recursive: true }); await writeFile(generated, 'stale generated output'); await writeFile(path.join(root, 'apps', 'web', '.next', '.mosaic-source-hash'), 'old-source'); const result = await runPreflight({ root }); assert.equal(result.code, 43); assert.match(result.message, /MOSAIC_PREFLIGHT_GENERATED_STATE/); assert.match(result.message, /apps\/web\/\.next/); assert.match(result.message, /pnpm clean:generated/); }); test('a matching generation marker accepts incremental output with mixed mtimes', async () => { const root = await fixture('incremental-next'); await installRequiredBins(root); const generated = path.join(root, 'apps', 'web', '.next', 'types', 'validator.ts'); await mkdir(path.dirname(generated), { recursive: true }); await writeFile(generated, 'unchanged generated output'); await utimes(generated, new Date('2020-01-01T00:00:00Z'), new Date('2020-01-01T00:00:00Z')); const fresh = path.join(root, 'apps', 'web', '.next', 'types', 'routes.ts'); await writeFile(fresh, 'fresh generated output'); await writeFile( path.join(root, 'apps', 'web', '.next', '.mosaic-source-hash'), await sourceFingerprint(root), ); assert.deepEqual(await runPreflight({ root }), { code: 0, message: 'checkout preflight passed' }); });