fix(GOV-009): 完成脚手架审查整改

This commit is contained in:
2026-08-13 18:30:57 +08:00
parent dcdf634457
commit 3a8120a33a
47 changed files with 752 additions and 101 deletions
+14 -6
View File
@@ -3,21 +3,29 @@ import path from 'node:path';
import process from 'node:process';
import ts from 'typescript';
const TYPESCRIPT_EXTENSIONS = new Set(['.ts', '.tsx', '.mts', '.cts']);
const SOURCE_EXTENSIONS = new Set(['.ts', '.tsx', '.mts', '.cts', '.vue']);
const INTERNAL_MESSAGE_MARKERS = ['MessageEnvelope', 'message-contracts'];
async function collectTypeScriptFiles(directory) {
async function collectSourceFiles(directory) {
const entries = await readdir(directory, { withFileTypes: true });
const nestedFiles = await Promise.all(
entries.map(async (entry) => {
const entryPath = path.join(directory, entry.name);
if (entry.isDirectory()) return collectTypeScriptFiles(entryPath);
return TYPESCRIPT_EXTENSIONS.has(path.extname(entry.name)) ? [entryPath] : [];
if (entry.isDirectory()) return collectSourceFiles(entryPath);
return SOURCE_EXTENSIONS.has(path.extname(entry.name)) ? [entryPath] : [];
}),
);
return nestedFiles.flat();
}
function extractTypeScriptSource(sourcePath, sourceText) {
if (path.extname(sourcePath) !== '.vue') return sourceText;
// 边界门禁只需解析 SFC 的脚本块;模板和样式不会产生 TypeScript 模块依赖。
return [...sourceText.matchAll(/<script\b[^>]*>([\s\S]*?)<\/script>/gi)]
.map((match) => match[1])
.join('\n');
}
function collectModuleSpecifiers(sourceFile) {
const specifiers = [];
@@ -64,11 +72,11 @@ export async function verifyProjectBoundaries(repositoryRoot) {
const compilerOptions = configFile.error
? { module: ts.ModuleKind.ESNext, moduleResolution: ts.ModuleResolutionKind.Bundler }
: ts.parseJsonConfigFileContent(configFile.config, ts.sys, path.dirname(configPath)).options;
const files = await collectTypeScriptFiles(webSourceRoot);
const files = await collectSourceFiles(webSourceRoot);
const violations = [];
for (const sourcePath of files) {
const sourceText = await readFile(sourcePath, 'utf8');
const sourceText = extractTypeScriptSource(sourcePath, await readFile(sourcePath, 'utf8'));
const sourceFile = ts.createSourceFile(
sourcePath,
sourceText,
@@ -89,6 +89,25 @@ test('拒绝通过 tsconfig 路径别名依赖 Server', async () => {
assert.match(result.stderr, /Web 禁止依赖 Server/);
});
test('拒绝 Vue 单文件组件脚本越界依赖 Server', async () => {
const repositoryRoot = await createFixture();
await writeFile(
path.join(repositoryRoot, 'web/src/IllegalView.vue'),
`<script setup lang="ts">
import type { Internal } from '../../server/libs/internal';
const value: Internal | undefined = undefined;
</script>
<template><main>{{ value }}</main></template>
`,
);
const result = await runChecker(repositoryRoot);
assert.equal(result.exitCode, 1);
assert.match(result.stderr, /Web 禁止依赖 Server/);
assert.match(result.stderr, /IllegalView\.vue/);
});
test('接受只消费公开 OpenAPI 类型的 Web 工程', async () => {
const repositoryRoot = await createFixture();
await writeFile(