import assert from 'node:assert/strict'; import { execFile } from 'node:child_process'; import { mkdtemp, mkdir, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import path from 'node:path'; import test from 'node:test'; import { promisify } from 'node:util'; const execFileAsync = promisify(execFile); const checkerPath = path.resolve('tools/boundaries/verify-project-boundaries.mjs'); async function createFixture() { const repositoryRoot = await mkdtemp(path.join(tmpdir(), 'a-share-boundary-')); await mkdir(path.join(repositoryRoot, 'web/src/api/generated'), { recursive: true }); await mkdir(path.join(repositoryRoot, 'server/libs'), { recursive: true }); await writeFile( path.join(repositoryRoot, 'web/src/api/generated/schema.ts'), 'export interface paths {}\n', ); return repositoryRoot; } async function runChecker(repositoryRoot) { try { await execFileAsync(process.execPath, [checkerPath, '--root', repositoryRoot]); return { exitCode: 0, stderr: '' }; } catch (error) { return { exitCode: error.code, stderr: String(error.stderr), }; } } test('拒绝 Web 通过静态、动态或导出语句越界依赖 Server', async () => { const repositoryRoot = await createFixture(); await writeFile( path.join(repositoryRoot, 'web/src/illegal.ts'), [ "import '../../server/libs/static';", "export { value } from '../../server/libs/exported';", "void import('../../server/libs/dynamic');", ].join('\n'), ); const result = await runChecker(repositoryRoot); assert.equal(result.exitCode, 1); assert.match(result.stderr, /Web 禁止依赖 Server/); assert.match(result.stderr, /illegal\.ts/); }); test('拒绝生成契约泄漏 RocketMQ 内部消息结构', async () => { const repositoryRoot = await createFixture(); await writeFile( path.join(repositoryRoot, 'web/src/api/generated/schema.ts'), 'export interface MessageEnvelope { messageId: string }\n', ); const result = await runChecker(repositoryRoot); assert.equal(result.exitCode, 1); assert.match(result.stderr, /生成契约包含 Server 内部消息/); }); test('拒绝通过 tsconfig 路径别名依赖 Server', async () => { const repositoryRoot = await createFixture(); await writeFile( path.join(repositoryRoot, 'web/tsconfig.json'), JSON.stringify({ compilerOptions: { baseUrl: '.', paths: { '@server/*': ['../server/*'] }, }, }), ); await writeFile( path.join(repositoryRoot, 'web/src/aliased.ts'), "import type { Internal } from '@server/libs/internal';\nexport type Leaked = Internal;\n", ); await writeFile( path.join(repositoryRoot, 'server/libs/internal.ts'), 'export interface Internal { secret: string }\n', ); const result = await runChecker(repositoryRoot); assert.equal(result.exitCode, 1); assert.match(result.stderr, /Web 禁止依赖 Server/); }); test('拒绝 Vue 单文件组件脚本越界依赖 Server', async () => { const repositoryRoot = await createFixture(); await writeFile( path.join(repositoryRoot, 'web/src/IllegalView.vue'), ` `, ); const result = await runChecker(repositoryRoot); assert.equal(result.exitCode, 1); assert.match(result.stderr, /Web 禁止依赖 Server/); assert.match(result.stderr, /IllegalView\.vue/); }); for (const [name, scriptTag] of [ ['双引号', '\n\n`, ); const result = await runChecker(repositoryRoot); assert.equal(result.exitCode, 1); assert.match(result.stderr, /禁止使用外部 script src/); assert.match(result.stderr, /ExternalScript\.vue/); }); } test('接受只消费公开 OpenAPI 类型的 Web 工程', async () => { const repositoryRoot = await createFixture(); await writeFile( path.join(repositoryRoot, 'web/src/legal.ts'), "import type { paths } from './api/generated/schema';\nexport type ApiPaths = paths;\n", ); const result = await runChecker(repositoryRoot); assert.equal(result.exitCode, 0); assert.equal(result.stderr, ''); });