import test from 'node:test' import assert from 'node:assert/strict' import { execFile } from 'node:child_process' import { mkdir, readFile, writeFile } from 'node:fs/promises' import { join } from 'node:path' import { tmpdir } from 'node:os' import { mkdtemp } from 'node:fs/promises' import { promisify } from 'node:util' const exec = promisify(execFile) const cli = new URL('../src/cli.js', import.meta.url).pathname test('CLI runs doctor and scan against one prototype', async () => { const cwd = await mkdtemp(join(tmpdir(), 'ht-e2e-')) await mkdir(join(cwd, 'packages/prototype'), { recursive: true }) await writeFile(join(cwd, 'packages/prototype/index.html'), `

Customer Portal

`) await writeFile(join(cwd, 'packages/prototype/portal.md'), ` # Portal Guide | 雛型檔 | 舊 JSP 來源 | 功能 | | --- | --- | --- | | [\`index.html\`](index.html) | \`legacy/index.jsp\` | Customer portal entry | `) const doctor = await exec('node', [cli, 'doctor'], { cwd }) await exec('node', [cli, 'scan'], { cwd }) const contract = await readFile(join(cwd, '.ht/spec/index.ui-contract.md'), 'utf8') const spec = JSON.parse(await readFile(join(cwd, '.ht/spec/index.spec.json'), 'utf8')) const validation = JSON.parse(await readFile(join(cwd, '.ht/spec/index.validation.json'), 'utf8')) const appMap = JSON.parse(await readFile(join(cwd, '.ht/app-map.json'), 'utf8')) assert.match(doctor.stdout, /ok prototype directory/) assert.match(contract, /Customer Portal/) assert.equal(spec.pageContract.title, null) assert.deepEqual(pick(spec.pageContract.forms[0].fields[0], ['name', 'label', 'type', 'required']), { name: 'email', label: 'Email', type: 'input', required: true }) assert.equal(validation.requiresHumanReview, false) assert.equal(appMap.routes[0].prototype, 'index.html') assert.equal(appMap.routes[0].kind, 'feature-page') assert.equal(appMap.routes[0].layout, 'template-app') assert.equal(appMap.routes[0].guide.legacyJsp, 'legacy/index.jsp') assert.equal(appMap.routes[0].guide.description, 'Customer portal entry') assert.equal(appMap.guideSources[0].source, 'portal.md') }) function pick(object, keys) { return Object.fromEntries(keys.map((key) => [key, object[key]])) } test('CLI help only exposes MVP commands', async () => { const result = await exec('node', [cli, 'help']) assert.match(result.stdout, /doctor/) assert.match(result.stdout, /scan/) assert.doesNotMatch(result.stdout, /plan/) assert.doesNotMatch(result.stdout, /run/) assert.doesNotMatch(result.stdout, /diff/) assert.doesNotMatch(result.stdout, /verify/) }) test('CLI rejects removed pipeline commands', async () => { await assert.rejects( exec('node', [cli, 'plan']), (error) => { assert.equal(error.code, 1) assert.match(error.stdout, /doctor/) assert.match(error.stdout, /scan/) return true } ) })