| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111 |
- #!/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.
- const fs = require('fs');
- const path = require('path');
- const os = require('os');
- const { pathToFileURL } = require('url');
- const ROOT = path.resolve(__dirname, '..');
- const SKILL_DIR = path.join(ROOT, 'skills', 'skill-image');
- function fail(msg) { console.error('SMOKE FAIL: ' + msg); process.exit(1); }
- // 1. 包结构
- for (const rel of ['SKILL.md']) {
- if (!fs.existsSync(path.join(SKILL_DIR, rel))) fail('missing skills/skill-image/' + rel);
- }
- for (const rel of ['bin/skill-image.js', 'lib/skill-image.mjs', 'skill-package-manifest.json', '.claude-plugin/plugin.json']) {
- if (!fs.existsSync(path.join(ROOT, rel))) fail('missing ' + rel);
- }
- // 2. 元数据一致性:包名/版本/bin 三处对齐
- const pkg = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8'));
- const manifest = JSON.parse(fs.readFileSync(path.join(ROOT, 'skill-package-manifest.json'), 'utf8'));
- const plugin = JSON.parse(fs.readFileSync(path.join(ROOT, '.claude-plugin', 'plugin.json'), 'utf8'));
- if (pkg.name !== 'skill-image') fail('package.json name should be skill-image, got ' + pkg.name);
- if (manifest.name !== 'skill-image') fail('manifest name should be skill-image, got ' + manifest.name);
- if (plugin.name !== 'skill-image') fail('plugin.json name should be skill-image, got ' + plugin.name);
- if (manifest.version !== pkg.version) fail(`version mismatch: manifest ${manifest.version} vs package ${pkg.version}`);
- if (plugin.version !== pkg.version) fail(`version mismatch: plugin ${plugin.version} vs package ${pkg.version}`);
- if (!pkg.bin || !pkg.bin['skill-image']) fail('package.json bin should expose skill-image');
- // 3. 无残留旧名(模式由片段拼出,避免本文件自匹配)
- const OLD_NAME = ['fmode', 'image'].join('-');
- const OLD_RE = new RegExp(OLD_NAME);
- const stale = [];
- (function walk(dir) {
- for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
- if (entry.name === '.git' || entry.name === 'node_modules') continue;
- const full = path.join(dir, entry.name);
- if (entry.isDirectory()) { walk(full); continue; }
- if (!/\.(js|mjs|json|md)$/.test(entry.name)) continue;
- const text = fs.readFileSync(full, 'utf8');
- if (OLD_RE.test(text)) stale.push(path.relative(ROOT, full));
- }
- })(ROOT);
- if (stale.length) fail(`stale "${OLD_NAME}" references in: ` + stale.join(', '));
- (async () => {
- // 4. 模块导出
- const mod = await import(pathToFileURL(path.join(ROOT, 'lib', 'skill-image.mjs')).href);
- for (const fn of ['getApiKey', 'callImageAPI', 'gen', 'genArch', 'genApp', 'genExplode', 'genProduct', 'genScene', 'genDetail', 'genSlide', 'batch']) {
- if (typeof mod[fn] !== 'function') fail('export ' + fn + ' is not a function');
- }
- // 5. 模式注册表完整性
- const expected = ['--arch', '--app', '--explode', '--product', '--scene', '--detail', '--slide'];
- for (const mode of expected) {
- if (!mod.MODES[mode]) fail('missing mode ' + mode);
- for (const key of ['prefix', 'suffix', 'size', 'cost', 'desc']) {
- if (mod.MODES[mode][key] === undefined) fail(`mode ${mode} missing ${key}`);
- }
- }
- if (Object.keys(mod.MODES).length !== expected.length) {
- fail(`expected ${expected.length} modes, got ${Object.keys(mod.MODES).length}`);
- }
- // 6. 未知模式应抛错
- try {
- await mod.gen('--nope', 'x', 'smoke');
- fail('gen() should reject an unknown mode');
- } catch (e) {
- if (!/Unknown mode/.test(e.message)) fail('unexpected error for unknown mode: ' + e.message);
- }
- // 7. Key 解析链:环境变量优先,无任何来源时应抛错并指明设置方式
- const tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'skill-image-smoke-'));
- const oldHome = process.env.HOME;
- const savedKey = process.env.FMODE_API_KEY;
- const savedAuth = process.env.ANTHROPIC_AUTH_TOKEN;
- process.env.HOME = tmpHome;
- delete process.env.FMODE_API_KEY;
- delete process.env.ANTHROPIC_AUTH_TOKEN;
- try {
- try {
- mod.getApiKey();
- fail('getApiKey should throw when no key source exists');
- } catch (e) {
- if (!/FMODE_API_KEY/.test(e.message)) fail('error message should mention FMODE_API_KEY');
- }
- // 环境变量命中时应直接返回
- process.env.FMODE_API_KEY = 'sk-smoke-test-key-0123456789';
- if (mod.getApiKey() !== 'sk-smoke-test-key-0123456789') fail('getApiKey should prefer FMODE_API_KEY env');
- } finally {
- process.env.HOME = oldHome;
- if (savedKey) process.env.FMODE_API_KEY = savedKey; else delete process.env.FMODE_API_KEY;
- if (savedAuth) process.env.ANTHROPIC_AUTH_TOKEN = savedAuth;
- fs.rmSync(tmpHome, { recursive: true, force: true });
- }
- console.log('SMOKE OK: skill-image package structure + metadata + module exports + mode registry + key chain verified');
- })().catch(e => fail(e.message));
|