import assert from 'node:assert/strict'; import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { spawnSync } from 'node:child_process'; import { createHash } from 'node:crypto'; import { performance } from 'node:perf_hooks'; import test from 'node:test'; const guard = fileURLToPath(new URL('./ai-generation-guard.mjs', import.meta.url)); const external = String.raw`D:\디자인시스템\HMDesign-main\hmdesign`; function fixture(files, callback) { const tempRoot = fs.realpathSync(os.tmpdir()); const root = fs.mkdtempSync(path.join(tempRoot, 'hm-fast-core-')); assert.equal(path.dirname(root), tempRoot); try { for (const [name, text] of Object.entries({ 'index.html': '', 'src/main.tsx': 'import App from "./App"; import "./styles.css"; export default App;', 'src/styles.css': '@import "./tokens.css";\n/* app-specific styles are valid */ .custom { color: coral; }', 'src/tokens.css': '@import "tailwindcss";\n@import "@hm-design/ui/theme.css";', 'tsconfig.app.json': '{ /* JSONC */ "compilerOptions": { "paths": { "@/*": ["./src/*"] } } }', ...files, })) { const dest = path.join(root, name); fs.mkdirSync(path.dirname(dest), { recursive: true }); fs.writeFileSync(dest, text); } callback(root); } finally { // Only the verified mkdtemp directory created by this test is removed. assert.equal(path.dirname(path.resolve(root)), tempRoot); assert.ok(path.basename(root).startsWith('hm-fast-core-')); fs.rmSync(root, { recursive: true, force: true }); } } function run(target, extra, json = true) { const started = performance.now(); const result = spawnSync(process.execPath, [guard, '--target', target, ...extra, ...(json ? ['--json'] : [])], { encoding: 'utf8' }); assert.ifError(result.error); assert.equal(result.stderr, ''); const report = json ? JSON.parse(result.stdout) : null; return { ...result, report, wallMs: Math.round(performance.now() - started) }; } test('1. normal GNB_ONLY; indirect CSS, aliases, unrelated new UI and unused Stories', () => { fixture({ 'src/hm.ts': 'export { GNB as Header, GNBMobileMenu as Menu, Button } from "@hm-design/ui";', 'src/App.tsx': 'import { Header, Menu, Button } from "@/hm"; import NewUI from "./NewUI";\nexport default () => <>
;', 'src/NewUI.tsx': 'import "./new.css"; function Sidebar(){ return }\nexport default Sidebar;', 'src/new.css': '.sb-page-inner { color: purple; } .sidebar { display: flex; }', 'src/Unused.stories.tsx': 'import { GNB, DashboardSidebar } from "@hm-design/ui"; export const Example = () => <>;', }, root => { const result = run(root, ['--scope', 'FULL_SCREEN', '--mode', 'GNB_ONLY'], false); assert.equal(result.status, 0); const lines = result.stdout.trim().split(/\r?\n/); assert.equal(lines.length, 3); assert.equal(lines[0], 'HMDesign FAST CORE: PASS'); assert.equal(lines[1], 'HMDesign: Button, GNB, GNBMobileMenu'); assert.match(lines[2], /Issues: 0/); console.log(`Normal GNB wall time: ${result.wallMs} ms`); }); }); test('2. normal LNB_ONLY; namespace and local alias resolve to the official LNB', () => { fixture({ 'src/App.tsx': 'import * as HM from "@hm-design/ui"; const Menu = HM.LNB;\nexport default () => Home;', }, root => { const { status, report } = run(root, ['--scope', 'FULL_SCREEN', '--mode', 'LNB_ONLY']); assert.equal(status, 0); assert.equal(report.status, 'PASS'); assert.deepEqual(report.checkedHMElements, ['LNB', 'LNBItem', 'LNBNav']); assert.ok(report.checkedGroups.every(g => g.status === 'PASS')); console.log(`Normal LNB static analysis: ${report.durationMs} ms`); }); }); test('3. GNB_ONLY detects a reused official Sidebar through a re-export alias', () => { fixture({ 'src/hm.ts': 'export { GNB, DashboardSidebar as Side } from "@hm-design/ui";', 'src/App.tsx': 'import { GNB, Side } from "./hm";\nexport default () => <>;', }, root => { const { status, report } = run(root, ['--scope', 'FULL_SCREEN', '--mode', 'GNB_ONLY']); assert.equal(status, 1); assert.equal(report.status, 'FAIL'); assert.ok(report.issues.some(i => i.code === 'NAV-001' && i.file === 'src/App.tsx' && i.line === 2)); assert.ok(report.checkedHMElements.includes('DashboardSidebar')); }); }); test('4. Production Story import, StoryPage helper and Storybook CSS are detected', () => { fixture({ 'src/App.tsx': 'import { Button } from "@hm-design/ui";\nimport { Demo } from "./Button.stories";\nimport { StoryPage } from "./story-page";\nexport default () =>