| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121 |
- #!/usr/bin/env node
- // Copyright (c) 未来飞马
- //
- // This Source Code Form is subject to the terms of the Mozilla Public
- // License, v. 2.0. If a copy of the MPL was not distributed with this
- // file, You can obtain one at https://mozilla.org/MPL/2.0/.
- //
- // Trademark Notice:
- // The MPL-2.0 license grants copyright permissions for source code only.
- // It does NOT grant any rights to use trademarks including "未来飞马",
- // "Harness Loop", "RSI", and associated slogan "让AI进化提前发生,让AI落地快人一步".
- // Any use of these trademarks requires separate written permission.
- /**
- * 冒烟检查:不联网、不调模型、不写仓库。
- *
- * 检查项:
- * 1. 必需文件齐全(含标签字典可解析)
- * 2. 运行器自检(office 解包 + 分组 + 打标 + 合规 + reorder)
- * 3. 单元测试(node --test)
- * 4. 未把 token / 私钥 / 内部地址写进包内任何文件
- */
- import fs from 'node:fs';
- import path from 'node:path';
- import { spawnSync } from 'node:child_process';
- import { fileURLToPath } from 'node:url';
- const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
- const failures = [];
- const checks = [];
- function record(name, ok, detail) {
- checks.push({ name, ok, detail: String(detail ?? '') });
- if (!ok) failures.push(name);
- }
- // 1. 必需文件
- const REQUIRED = [
- 'SKILL.md', 'README.md', 'package.json', 'skill-package-manifest.json',
- 'references/tag-dictionary.json',
- 'scripts/case-intake.mjs', 'scripts/office_extract.py', 'scripts/vision-bridge.mjs',
- 'scripts/listen-bridge.mjs', 'scripts/cloud-client.mjs', 'scripts/grouping.mjs',
- 'scripts/classify.mjs', 'scripts/image-size.mjs', 'scripts/lib.mjs',
- 'bin/skill-case-get.js',
- ];
- {
- const missing = REQUIRED.filter((rel) => !fs.existsSync(path.join(ROOT, rel)));
- record('必需文件齐全', missing.length === 0, missing.length ? `缺少:${missing.join(', ')}` : `${REQUIRED.length} 个文件`);
- }
- // 2. 标签字典可解析
- {
- try {
- const dict = JSON.parse(fs.readFileSync(path.join(ROOT, 'references', 'tag-dictionary.json'), 'utf8'));
- const dims = Object.keys(dict.dimensions || {});
- record('标签字典可解析', dims.length >= 8, `维度:${dims.length} 个(${dims.join(', ')})`);
- } catch (error) {
- record('标签字典可解析', false, error.message);
- }
- }
- // 3. 运行器自检
- {
- const run = spawnSync(process.execPath, [path.join(ROOT, 'scripts', 'case-intake.mjs'), 'selftest'], { encoding: 'utf8' });
- record('case-intake selftest', run.status === 0, run.stdout ? run.stdout.slice(0, 200) : run.stderr);
- }
- // 4. 单元测试
- {
- const run = spawnSync(process.execPath, ['--test', path.join(ROOT, 'scripts', 'tests')], { encoding: 'utf8' });
- // node --test 传目录在部分版本上会当模块解析;退回 glob
- const globRun = run.status === 0 ? run : spawnSync(
- process.execPath,
- ['--test', path.join(ROOT, 'scripts', 'tests', 'grouping.test.mjs'), path.join(ROOT, 'scripts', 'tests', 'classify.test.mjs')],
- { encoding: 'utf8' },
- );
- const output = `${globRun.stdout || ''}${globRun.stderr || ''}`;
- const passMatch = output.match(/# pass (\d+)/) || output.match(/ℹ pass (\d+)/);
- const failMatch = output.match(/# fail (\d+)/) || output.match(/ℹ fail (\d+)/);
- record('单元测试', globRun.status === 0, `pass=${passMatch ? passMatch[1] : '?'} fail=${failMatch ? failMatch[1] : '?'}`);
- }
- // 5. 包内不得出现凭据 / 内部地址
- {
- const FORBIDDEN = [
- { label: 'internal-address', re: new RegExp(`\\b${['local', 'host'].join('')}\\b`, 'i') },
- { label: 'loopback', re: /\b(127\.0\.0\.1|0\.0\.0\.0)\b/ },
- { label: 'parse-session-token', re: /\br:[a-f0-9]{20,}\b/i },
- { label: 'api-key', re: /\bsk-[a-zA-Z0-9-]{20,}\b/ },
- { label: 'mongo-uri', re: /mongodb(\+srv)?:\/\//i },
- ];
- const BINARY = new Set(['.png', '.jpg', '.jpeg', '.gif', '.zip', '.pdf']);
- // 本文件自身就写着这些正则(和仓库 self-check.mjs 一样自跳过),否则会自己命中自己
- const SELF = new Set([path.resolve(fileURLToPath(import.meta.url))]);
- const offenders = [];
- const walk = (dir) => {
- for (const entry of fs.readdirSync(dir)) {
- if (['node_modules', 'outputs', '.git', '.claude', 'case-get-outputs'].includes(entry)) continue;
- const full = path.join(dir, entry);
- const stat = fs.statSync(full);
- if (stat.isDirectory()) { walk(full); continue; }
- if (SELF.has(path.resolve(full))) continue;
- if (BINARY.has(path.extname(full).toLowerCase())) continue;
- let content = '';
- try { content = fs.readFileSync(full, 'utf8'); } catch { continue; }
- for (const item of FORBIDDEN) {
- if (item.re.test(content)) offenders.push(`${path.relative(ROOT, full)}:${item.label}`);
- }
- }
- };
- walk(ROOT);
- record('包内无凭据 / 无内网地址', offenders.length === 0, offenders.length ? offenders.join(', ') : '干净');
- }
- // 输出
- const passed = checks.filter((c) => c.ok).length;
- for (const check of checks) {
- process.stdout.write(`${check.ok ? '✔' : '✖'} ${check.name} — ${check.detail}\n`);
- }
- process.stdout.write(`\n冒烟检查:${passed}/${checks.length} 通过\n`);
- process.exit(failures.length ? 1 : 0);
|