105 lines
4.1 KiB
JavaScript
105 lines
4.1 KiB
JavaScript
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 } 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('stale generated Next state 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');
|
|
const old = new Date('2020-01-01T00:00:00Z');
|
|
await utimes(generated, old, old);
|
|
const fresh = path.join(root, 'apps', 'web', '.next', 'types', 'routes.ts');
|
|
await writeFile(fresh, 'fresh generated output');
|
|
const future = new Date('2030-01-01T00:00:00Z');
|
|
await utimes(fresh, future, future);
|
|
|
|
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/);
|
|
});
|