/** * scaffold-extension-config — CLI entry contract: the entry point runs the * Zod-PARSED spec (one contract for every scaffolder), and the documented * SKILL invocation runs end to end. * * QC 2026-09-24 (lot 13): the lot-1 "parity" test compared generate(parse(x)) * to generate(parse(parse(x))) — schema output on both sides, so a schema * stripping a key the renderer reads could never be detected. The parity * block now holds two REAL properties (lib/__tests__/helpers/spec-parity): * no-strip against the RAW example, defaults-only against the schema's * DECLARED defaults (none here — the parsed spec IS the raw spec) — each * proven to fire. */ import { describe, it, expect } from 'vitest' import { dirname, resolve } from 'node:path' import { fileURLToPath } from 'node:url' import { z } from 'zod' import { runCli } from '../../../../../../lib/__tests__/helpers/run-cli.js' import { specParity } from '../../../../../../lib/__tests__/helpers/spec-parity.js' import { validate } from '../validate.js' import { generate } from '../generate.js' import { ScaffoldExtensionConfigInputSchema } from '../types.js' const INDEX = resolve(dirname(fileURLToPath(import.meta.url)), '../index.ts') /** The invocation documented in frontend/extension-config/SKILL.md, verbatim. */ const DOC_EXAMPLE = { module: 'hrm', appCode: 'myapp', entities: ['Employee', 'Contract'], projectPath: '/path' } describe('scaffold-extension-config — the CLI entry runs the PARSED spec', () => { it('runs the documented SKILL example in --dry_run', () => { const r = runCli(INDEX, ['--spec', JSON.stringify(DOC_EXAMPLE), '--dry_run']) expect(r.envelope, r.stderr).not.toBeNull() expect(r.envelope.success, JSON.stringify(r.envelope.errors)).toBe(true) expect(r.status).toBe(0) expect(r.envelope.data.files).toEqual(['src/extensions/hrmExtensions.ts']) }) it('validate() returns the parsed spec, and no data when refused', () => { expect(validate(DOC_EXAMPLE).data).toEqual(DOC_EXAMPLE) const bad = validate({ ...DOC_EXAMPLE, entities: ['employee'] }) expect(bad.valid).toBe(false) expect(bad.data).toBeUndefined() }) }) describe('scaffold-extension-config — parity RAW ↔ PARSED (real properties, not schema-vs-schema)', () => { const parsed = () => validate(DOC_EXAMPLE).data! it('(a) no-strip: every key path of the documented example survives the parse with its value', () => { const report = specParity(DOC_EXAMPLE, parsed(), ScaffoldExtensionConfigInputSchema) expect(report.rawPaths).toEqual(['module', 'appCode', 'entities', 'entities[0]', 'entities[1]', 'projectPath']) expect(report.stripped).toEqual([]) expect(report.changed).toEqual([]) }) it('(b) defaults-only: the schema declares no default, so the parse adds NOTHING', () => { const report = specParity(DOC_EXAMPLE, parsed(), ScaffoldExtensionConfigInputSchema) expect(report.added).toEqual([]) expect(report.undeclared).toEqual([]) }) it('secondary: generate() reads nothing a JSON round-trip of the parsed spec would lose', () => { const p = parsed() expect(generate(p)).toEqual(generate(JSON.parse(JSON.stringify(p)))) }) it('FIRES (a): a strip-mode copy of the schema without `entities` is reported at that path (shallowest only)', () => { const stripping = z.object(ScaffoldExtensionConfigInputSchema.shape).omit({ entities: true }) const report = specParity(DOC_EXAMPLE, stripping.parse(DOC_EXAMPLE), stripping) expect(report.stripped).toEqual(['entities']) expect(report.changed).toEqual([]) }) it('FIRES (b): a key injected after the parse is reported — no default declares it', () => { expect(specParity(DOC_EXAMPLE, { ...parsed(), slots: [] }, ScaffoldExtensionConfigInputSchema).undeclared).toEqual(['slots']) }) })