From 95b8611bd9bbc282193591e472e2e19c617e5035 Mon Sep 17 00:00:00 2001 From: Citrons <909229731@qq.com> Date: Thu, 13 Aug 2026 19:49:42 +0800 Subject: [PATCH] =?UTF-8?q?fix(GOV-009):=20=E5=B0=81=E5=A0=B5=E5=A4=96?= =?UTF-8?q?=E9=83=A8=E8=84=9A=E6=9C=AC=E8=BE=B9=E7=95=8C=E7=BB=95=E8=BF=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/deployment/docker-compose-production.md | 2 +- package.json | 1 + pnpm-lock.yaml | 3 ++ .../boundaries/verify-project-boundaries.mjs | 15 ++++++---- .../verify-project-boundaries.test.mjs | 28 +++++++++++-------- 5 files changed, 31 insertions(+), 18 deletions(-) diff --git a/docs/deployment/docker-compose-production.md b/docs/deployment/docker-compose-production.md index 8e19fe0..e9caebe 100644 --- a/docs/deployment/docker-compose-production.md +++ b/docs/deployment/docker-compose-production.md @@ -11,7 +11,7 @@ docker build -f web/Dockerfile -t registry.example/a-share-web:<提交SHA> . docker build -f server/Dockerfile -t registry.example/a-share-server:<提交SHA> . ``` -把四份生产模板复制到服务器的 `/etc/a-share-analysis/`,由运维填写并设置最小权限。Web 配置在镜像构建期固化,因此 `web.env` 只作为发布参数记录,不挂载到容器;API、Scheduler、Worker 分别读取自己的外部 env 文件。所有镜像引用必须使用明确版本或内容摘要,禁止 `latest`。 +把四份生产模板复制到服务器的 `/etc/a-share-analysis/`,由运维填写并设置最小权限。Web 配置在镜像构建期固化,因此 `web.env` 只作为发布参数记录,不挂载到容器;API、Scheduler、Worker 分别读取自己的外部 env 文件。所有生产镜像引用必须使用内容摘要,禁止 `latest` 和任何普通 tag。 ## 发布与验证 diff --git a/package.json b/package.json index db60235..f99147b 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ }, "devDependencies": { "@eslint/js": "9.39.5", + "@vue/compiler-sfc": "3.5.41", "eslint": "9.39.5", "eslint-plugin-vue": "9.33.0", "globals": "17.11.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c0a513c..1f673fb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,9 @@ importers: '@eslint/js': specifier: 9.39.5 version: 9.39.5 + '@vue/compiler-sfc': + specifier: 3.5.41 + version: 3.5.41 eslint: specifier: 9.39.5 version: 9.39.5 diff --git a/tools/boundaries/verify-project-boundaries.mjs b/tools/boundaries/verify-project-boundaries.mjs index da2bb5b..117b210 100644 --- a/tools/boundaries/verify-project-boundaries.mjs +++ b/tools/boundaries/verify-project-boundaries.mjs @@ -2,6 +2,7 @@ import { readdir, readFile, stat } from 'node:fs/promises'; import path from 'node:path'; import process from 'node:process'; import ts from 'typescript'; +import { parse } from '@vue/compiler-sfc'; const SOURCE_EXTENSIONS = new Set(['.ts', '.tsx', '.mts', '.cts', '.vue']); const INTERNAL_MESSAGE_MARKERS = ['MessageEnvelope', 'message-contracts']; @@ -20,16 +21,18 @@ async function collectSourceFiles(directory) { function extractTypeScriptSource(sourcePath, sourceText) { if (path.extname(sourcePath) !== '.vue') return sourceText; - const externalScript = sourceText.match(/]*\bsrc\s*=\s*["']([^"']+)["'][^>]*>/i); + const { descriptor, errors } = parse(sourceText, { filename: sourcePath }); + const externalScript = descriptor.script?.src ?? descriptor.scriptSetup?.src; if (externalScript) { throw new Error( - `Web Vue 组件禁止使用外部 script src:${path.basename(sourcePath)} -> ${externalScript[1]}`, + `Web Vue 组件禁止使用外部 script src:${path.basename(sourcePath)} -> ${externalScript}`, ); } - // 边界门禁只需解析 SFC 的脚本块;模板和样式不会产生 TypeScript 模块依赖。 - return [...sourceText.matchAll(/]*>([\s\S]*?)<\/script>/gi)] - .map((match) => match[1]) - .join('\n'); + if (errors.length > 0) { + throw new Error(`Vue 单文件组件解析失败:${path.basename(sourcePath)}`); + } + // 使用 Vue 官方解析器读取普通脚本和 setup 脚本,避免 HTML 属性写法绕过正则门禁。 + return [descriptor.script?.content, descriptor.scriptSetup?.content].filter(Boolean).join('\n'); } function collectModuleSpecifiers(sourceFile) { diff --git a/tools/boundaries/verify-project-boundaries.test.mjs b/tools/boundaries/verify-project-boundaries.test.mjs index a7fe230..c33a884 100644 --- a/tools/boundaries/verify-project-boundaries.test.mjs +++ b/tools/boundaries/verify-project-boundaries.test.mjs @@ -108,19 +108,25 @@ const value: Internal | undefined = undefined; assert.match(result.stderr, /IllegalView\.vue/); }); -test('拒绝 Vue 外部 script src 绕过源码边界', async () => { - const repositoryRoot = await createFixture(); - await writeFile( - path.join(repositoryRoot, 'web/src/ExternalScript.vue'), - `\n\n`, - ); +for (const [name, scriptTag] of [ + ['双引号', '\n\n`, + ); - const result = await runChecker(repositoryRoot); + const result = await runChecker(repositoryRoot); - assert.equal(result.exitCode, 1); - assert.match(result.stderr, /禁止使用外部 script src/); - assert.match(result.stderr, /ExternalScript\.vue/); -}); + 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();