/** * Drift check: every active module's committed `celilo/types.d.ts` * must match what `generateModuleTypes()` would produce from its * current `manifest.yml`. * * This is the CI-level backstop for `celilo module types check`. * If a manifest changes `variables.owns`/`variables.imports` but the * committed types file is not regenerated, this test fails. * * Scope: `modules//manifest.yml` at the repo root. Archived * modules (`modules/__archive__/**`) and scratch extract dirs * (`modules/.tmp-*`) are excluded — they are not active modules. */ import { describe, expect, test } from 'bun:test'; import { readFileSync } from 'node:fs'; import { dirname, join, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; import { Glob } from 'bun'; import { validateManifest } from '../manifest/validate'; import { generateModuleTypes } from './module-types-generator'; const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '../../../..'); const MODULES_DIR = join(REPO_ROOT, 'modules'); function findActiveManifests(): string[] { const glob = new Glob('*/manifest.yml'); const results: string[] = []; for (const match of glob.scanSync({ cwd: MODULES_DIR, onlyFiles: true })) { results.push(join(MODULES_DIR, match)); } return results.sort(); } describe('module types drift check', () => { const manifests = findActiveManifests(); test('discovers at least one active module', () => { expect(manifests.length).toBeGreaterThan(0); }); for (const manifestPath of manifests) { const moduleDir = dirname(manifestPath); const moduleId = moduleDir.split('/').pop() ?? moduleDir; test(`${moduleId}: committed types.d.ts matches generated output`, () => { const yaml = readFileSync(manifestPath, 'utf-8'); const result = validateManifest(yaml); if (!result.success) { const errorMessages = result.errors.map((e) => ` ${e.path}: ${e.message}`).join('\n'); throw new Error(`Manifest validation failed for ${moduleId}:\n${errorMessages}`); } const expected = generateModuleTypes(result.data); const typesPath = join(moduleDir, 'celilo', 'types.d.ts'); let actual: string; try { actual = readFileSync(typesPath, 'utf-8'); } catch { throw new Error( `Missing committed types file: ${typesPath}\nRun: celilo module types generate ${moduleDir}`, ); } if (actual !== expected) { throw new Error( `Types file is stale: ${typesPath}\nIt does not match what would be generated from the current manifest.yml.\nRun: celilo module types generate ${moduleDir}`, ); } }); } });