smoke.mjs 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960
  1. #!/usr/bin/env node
  2. /**
  3. * smoke.mjs —— 技能自检。
  4. * 生成一个临时项目并校验:文件齐全、JS 语法通过、无装饰器。
  5. * 用法:node scripts/smoke.mjs
  6. */
  7. import { mkdtempSync, rmSync, readdirSync, statSync, readFileSync } from 'node:fs';
  8. import { join, dirname } from 'node:path';
  9. import { tmpdir } from 'node:os';
  10. import { execFileSync } from 'node:child_process';
  11. import { fileURLToPath } from 'node:url';
  12. const __dirname = dirname(fileURLToPath(import.meta.url));
  13. const scaffold = join(__dirname, 'scaffold.mjs');
  14. const newGen = join(__dirname, 'new.mjs');
  15. let fail = 0;
  16. const check = (name, cond) => { console.log(`${cond ? '✓' : '✗'} ${name}`); if (!cond) fail++; };
  17. function walk(d, out = []) {
  18. for (const e of readdirSync(d, { withFileTypes: true })) {
  19. const p = join(d, e.name);
  20. if (e.isDirectory()) walk(p, out); else out.push(p);
  21. }
  22. return out;
  23. }
  24. const dir = mkdtempSync(join(tmpdir(), 'fmode-code-lit-smoke-'));
  25. try {
  26. execFileSync(process.execPath, [scaffold, dir, '--project', 'smoke-app'], { stdio: 'pipe' });
  27. const files = walk(dir);
  28. check('scaffold 生成 index.html', files.some((f) => f.endsWith('src/index.html')));
  29. check('scaffold 生成 importmap.json', files.some((f) => f.endsWith('src/importmap.json')));
  30. check('scaffold 生成 app.js', files.some((f) => f.endsWith('src/app.js')));
  31. check('scaffold 生成分层目录', ['components', 'services', 'utils'].every((d2) => files.some((f) => f.includes(`/src/${d2}/`))));
  32. execFileSync(process.execPath, [newGen, 'component', 'smoke-card', '--dir', join(dir, 'src/components')], { stdio: 'pipe' });
  33. execFileSync(process.execPath, [newGen, 'service', 'smoke-service', '--dir', join(dir, 'src/services')], { stdio: 'pipe' });
  34. check('new component 生成', files.some((f) => f.endsWith('smoke-card.js')) || walk(dir).some((f) => f.endsWith('smoke-card.js')));
  35. check('new service 无重复后缀', walk(dir).some((f) => f.endsWith('smoke-service.js')) && !walk(dir).some((f) => f.endsWith('smoke-service-service.js')));
  36. // 全量语法校验 + 装饰器扫描
  37. let syntaxOk = true, decoratorFound = false;
  38. for (const f of walk(dir).filter((f) => f.endsWith('.js'))) {
  39. try { execFileSync(process.execPath, ['--check', f], { stdio: 'pipe' }); } catch { syntaxOk = false; console.log(' 语法失败:', f); }
  40. if (/^\s*@(customElement|property|state|consume|provide|query)\b/m.test(readFileSync(f, 'utf8'))) decoratorFound = true;
  41. }
  42. check('生成物全部通过语法检查', syntaxOk);
  43. check('生成物无装饰器语法', !decoratorFound);
  44. // importmap JSON 合法
  45. let mapOk = false;
  46. try { JSON.parse(readFileSync(join(dir, 'src/importmap.json'), 'utf8')); mapOk = true; } catch {}
  47. check('importmap.json 为合法 JSON', mapOk);
  48. } finally {
  49. rmSync(dir, { recursive: true, force: true });
  50. }
  51. console.log(fail === 0 ? '\nSMOKE OK' : `\nSMOKE FAILED(${fail})`);
  52. process.exit(fail === 0 ? 0 : 1);