import { afterEach, test } from 'node:test'; import assert from 'node:assert/strict'; import { mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { dirname, join } from 'node:path'; import { scanImportBoundaries } from '../../scripts/lib/import-boundaries.mjs'; const roots = []; afterEach(async () => { await Promise.all(roots.splice(0).map((root) => rm(root, { recursive: true, force: true }))); }); async function createFixtureRoot() { const root = await mkdtemp(join(tmpdir(), 'youle-import-boundaries-')); roots.push(root); return root; } async function writeFixture(root, relativePath, source) { const file = join(root, relativePath); await mkdir(dirname(file), { recursive: true }); await writeFile(file, source, 'utf8'); } function scan(root) { return scanImportBoundaries({ frameworkDir: join(root, 'framework'), gamesDir: join(root, 'games'), }); } test('scanner rejects sdk contracts importing a framework platform path', async () => { const root = await createFixtureRoot(); await writeFixture(root, 'framework/sdk/contracts/bad.ts', "import '../platform/session.ts'\n"); assert.match(scan(root)[0].message, /sdk.*platform/); }); test('scanner rejects game imports of framework net internals', async () => { const root = await createFixtureRoot(); await writeFixture(root, 'games/a/assets/game/bad.ts', "import '../../../framework/net/net-client.ts'\n"); assert.match(scan(root)[0].message, /game.*framework\/net/); }); test('scanner permits a game import of the public sdk barrel', async () => { const root = await createFixtureRoot(); await writeFixture(root, 'games/a/assets/game/allowed.ts', "import '../../../../framework/sdk/index.ts'\n"); assert.deepEqual(scan(root), []); }); test('scanner rejects games importing migration-only sdk declarations', async () => { const root = await createFixtureRoot(); await writeFixture(root, 'games/a/assets/game/bad.ts', "import type { GameContext } from '../../../../framework/sdk/index.ts'\n"); assert.match(scan(root)[0].message, /game.*GameContext.*migration-only/); }); test('scanner rejects cc imports in sdk contracts', async () => { const root = await createFixtureRoot(); await writeFixture(root, 'framework/sdk/contracts/bad.ts', "import { Node } from 'cc'\n"); assert.match(scan(root)[0].message, /sdk\/contracts.*cc/); }); test('scanner permits cc imports in game code', async () => { const root = await createFixtureRoot(); await writeFixture(root, 'games/a/assets/game/allowed.ts', "import { Node } from 'cc'\n"); assert.deepEqual(scan(root), []); }); test('scanner rejects framework imports resolving under games', async () => { const root = await createFixtureRoot(); await writeFixture(root, 'framework/ui/bad.ts', "import '../../games/a/assets/game/entry.ts'\n"); assert.match(scan(root)[0].message, /framework.*games/); }); test('scanner rejects new production imports of a quarantined legacy runtime', async () => { const root = await createFixtureRoot(); await writeFixture(root, 'framework/application/bad.ts', "import '../net/net-client.ts'\n"); const violations = scan(root); assert.equal(violations.length, 1); assert.match(violations[0].message, /new production code.*legacy runtime/); }); test('scanner rejects string-literal dynamic imports that cross a boundary', async () => { const root = await createFixtureRoot(); await writeFixture(root, 'games/a/assets/game/bad.ts', "await import('../../../framework/net/net-client.ts')\n"); assert.match(scan(root)[0].message, /game.*framework\/net/); });