#!/usr/bin/env node
/**
* cli:derive-fk-specs — entry point.
*
* Deterministically derives every FK field's `fkTo` block from a module's
* `entité.md` Rel: lines, so ba-develop never hand-derives them (the silent-drop
* bug behind "lookups show the raw Guid" — an un-derived FK fell back to a
* free-text Guid `` with no error). The orchestrator splices each
* entity's `fields[].fkTo` VERBATIM into scaffold-component / scaffold-api-client.
*
* Invocation:
* npx --prefer-offline tsx skills/ba-develop/cli/derive-fk-specs/index.ts \
* --spec '{"moduleRoot":".smartstack/ba//"}'
*
* Exit code: always 0 on a successful derivation — a module without FKs is a
* valid empty answer. `unresolved[]` non-empty is the BLOCKING signal:
* scaffold-component's FK gate refuses the un-enriched field anyway, so fix the
* BA docs instead of scaffolding through.
*/
import { parseArgs } from 'node:util'
import { validate } from './validate.js'
import { deriveFkSpecs } from './derive.js'
import { executeEnvelope, failExecute, printEnvelope } from '../../../lib/output.js'
import type { DeriveFkSpecsReport } from './types.js'
const COMMAND = 'derive-fk-specs'
function main(): void {
const { values } = parseArgs({ options: { spec: { type: 'string' } }, strict: true })
if (!values.spec) {
printEnvelope(failExecute(COMMAND, ['--spec is required']))
process.exit(1)
}
let raw: unknown
try {
raw = JSON.parse(values.spec)
} catch {
printEnvelope(failExecute(COMMAND, ['Invalid JSON in --spec']))
process.exit(1)
}
const validation = validate(raw)
if (!validation.valid || !validation.spec) {
printEnvelope(failExecute(COMMAND, validation.errors))
process.exit(1)
}
const report = deriveFkSpecs(validation.spec)
const nextSteps: string[] = [
"Phase 3a: splice each entity's fields[].fkTo VERBATIM into the scaffold-component / scaffold-api-client field entries (match by field name, case-insensitive on the first letter). Use `required` for the field's required flag and `role` (when present) as the field label.",
'Then run the coverage gates — audit-dev-frontend DEV-UI-022/033. A FK-shaped field without fkTo now hard-fails scaffold-component validation (noLookup:true is the explicit opt-out for genuine non-FK identifiers).',
]
// An unresolved FK is NOT a silent skip: scaffolding the source entity anyway
// ships a raw-Guid field (form input, list column, detail dd). Surface it
// loudly — fix the BA tree (create the target screen/pagespec or repair the
// Rel: line), never hand-patch the generated page.
if (report.unresolved.length > 0) {
nextSteps.unshift(
`BLOCKING — ${report.unresolved.length} FK(s) could not be resolved to a lookup route: ` +
report.unresolved.map((u) => `${u.entity}.${u.field} → ${u.target} (${u.reason})`).join(' ; ') +
'. Do NOT scaffold these fields without fkTo.',
)
}
printEnvelope(
executeEnvelope(COMMAND, {
success: true,
data: {
entities: report.totals.entities,
fks: report.totals.fks,
unresolved: report.totals.unresolved,
},
report,
warnings: report.warnings,
nextSteps,
}),
)
process.exit(0)
}
main()