fix(GOV-009): 完成脚手架审查整改
This commit is contained in:
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user