@contentful/experience-design-system-cli 2.7.5-dev-build-752dc9c.0 → 2.10.1-dev-build-90df010.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/dist/package.json +1 -1
  2. package/dist/src/analyze/build-analyze-view-rows.d.ts +23 -0
  3. package/dist/src/analyze/build-analyze-view-rows.js +39 -0
  4. package/dist/src/analyze/command.js +13 -10
  5. package/dist/src/analyze/extract/validate.d.ts +13 -1
  6. package/dist/src/analyze/extract/validate.js +32 -5
  7. package/dist/src/analyze/select/command.d.ts +37 -0
  8. package/dist/src/analyze/select/command.js +200 -9
  9. package/dist/src/analyze/select/tui/App.js +1 -9
  10. package/dist/src/analyze/select/tui/components/Sidebar.d.ts +1 -1
  11. package/dist/src/analyze/select/tui/components/Sidebar.js +26 -3
  12. package/dist/src/analyze/select-agent/command.js +22 -10
  13. package/dist/src/analyze/tui/AnalyzeView.d.ts +8 -0
  14. package/dist/src/analyze/tui/AnalyzeView.js +5 -2
  15. package/dist/src/apply/api-client.d.ts +20 -0
  16. package/dist/src/apply/api-client.js +71 -5
  17. package/dist/src/generate/command.js +21 -2
  18. package/dist/src/import/orchestrator.d.ts +21 -0
  19. package/dist/src/import/orchestrator.js +95 -14
  20. package/dist/src/import/tui/WizardApp.d.ts +13 -0
  21. package/dist/src/import/tui/WizardApp.js +224 -52
  22. package/dist/src/import/tui/steps/GateStep.d.ts +2 -1
  23. package/dist/src/import/tui/steps/GateStep.js +4 -2
  24. package/dist/src/import/tui/steps/PreviewValidationErrorStep.d.ts +11 -0
  25. package/dist/src/import/tui/steps/PreviewValidationErrorStep.js +14 -0
  26. package/dist/src/import/tui/wizard-422-helpers.d.ts +48 -0
  27. package/dist/src/import/tui/wizard-422-helpers.js +60 -0
  28. package/dist/src/program.d.ts +17 -0
  29. package/dist/src/program.js +27 -4
  30. package/dist/src/session/db.d.ts +13 -0
  31. package/dist/src/session/db.js +50 -0
  32. package/dist/src/types.d.ts +1 -1
  33. package/package.json +2 -2
  34. package/skills/generate-components.md +2 -0
@@ -1,2 +1,19 @@
1
1
  import { Command } from 'commander';
2
+ type SpawnedChild = {
3
+ on(event: 'error', listener: (err: unknown) => void): unknown;
4
+ on(event: 'exit', listener: (code: number | null) => void): unknown;
5
+ };
6
+ /**
7
+ * Run the spawn lifecycle for the build command. Extracted so the
8
+ * error-surfacing path is testable: previously `child.on('error', ...)`
9
+ * silently returned exit code 1 with no context, leaving the user
10
+ * staring at a generic non-zero exit when `pnpm` wasn't on PATH.
11
+ */
12
+ export declare function runBuild(opts: {
13
+ spawnFn: () => SpawnedChild;
14
+ stderrWrite: (chunk: string) => void;
15
+ }): Promise<{
16
+ exitCode: number;
17
+ }>;
2
18
  export declare function createProgram(): Command;
19
+ export {};
@@ -12,6 +12,30 @@ import { registerImportCommand } from './import/command.js';
12
12
  import { registerSetupCommand } from './setup/command.js';
13
13
  const require = createRequire(import.meta.url);
14
14
  const pkg = require('../package.json');
15
+ /**
16
+ * Run the spawn lifecycle for the build command. Extracted so the
17
+ * error-surfacing path is testable: previously `child.on('error', ...)`
18
+ * silently returned exit code 1 with no context, leaving the user
19
+ * staring at a generic non-zero exit when `pnpm` wasn't on PATH.
20
+ */
21
+ export async function runBuild(opts) {
22
+ return new Promise((resolvePromise) => {
23
+ const child = opts.spawnFn();
24
+ let settled = false;
25
+ const settle = (exitCode) => {
26
+ if (settled)
27
+ return;
28
+ settled = true;
29
+ resolvePromise({ exitCode });
30
+ };
31
+ child.on('error', (err) => {
32
+ const message = err instanceof Error ? err.message : String(err);
33
+ opts.stderrWrite(`Error: failed to spawn build subprocess: ${message}\n`);
34
+ settle(1);
35
+ });
36
+ child.on('exit', (code) => settle(code ?? 1));
37
+ });
38
+ }
15
39
  function registerBuildCommand(program) {
16
40
  program
17
41
  .command('build')
@@ -19,10 +43,9 @@ function registerBuildCommand(program) {
19
43
  .action(async () => {
20
44
  const pkgRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..');
21
45
  process.stderr.write('⚙ Building from source...\n');
22
- const exitCode = await new Promise((resolvePromise) => {
23
- const child = spawn('pnpm', ['build'], { cwd: pkgRoot, stdio: 'inherit' });
24
- child.on('error', () => resolvePromise(1));
25
- child.on('exit', (code) => resolvePromise(code ?? 1));
46
+ const { exitCode } = await runBuild({
47
+ spawnFn: () => spawn('pnpm', ['build'], { cwd: pkgRoot, stdio: 'inherit' }),
48
+ stderrWrite: (s) => process.stderr.write(s),
26
49
  });
27
50
  process.exit(exitCode);
28
51
  });
@@ -53,6 +53,19 @@ export type RawComponentWithId = RawComponentDefinition & {
53
53
  component_id: string;
54
54
  };
55
55
  export declare function loadRawComponents(db: DatabaseSync, sessionId: string, allowedNames?: Set<string>): RawComponentWithId[];
56
+ /**
57
+ * Rename empty-named slots in the DB to heuristic names before generation.
58
+ * The LLM classify_slot tool call is matched back to the DB row by name, so a
59
+ * row with name="" can never be updated by the LLM — renaming it here makes it
60
+ * classifiable. Returns one warning string per renamed slot.
61
+ */
62
+ export declare function renameEmptySlots(db: DatabaseSync, sessionId: string, componentId: string, componentName: string, slotCount: number): {
63
+ renames: Array<{
64
+ oldName: string;
65
+ newName: string;
66
+ }>;
67
+ warnings: string[];
68
+ };
56
69
  export declare function storeCDFComponents(db: DatabaseSync, sessionId: string, components: Array<{
57
70
  key: string;
58
71
  entry: CDFComponentEntry;
@@ -563,6 +563,45 @@ export function loadRawComponents(db, sessionId, allowedNames) {
563
563
  }),
564
564
  }));
565
565
  }
566
+ /**
567
+ * Rename empty-named slots in the DB to heuristic names before generation.
568
+ * The LLM classify_slot tool call is matched back to the DB row by name, so a
569
+ * row with name="" can never be updated by the LLM — renaming it here makes it
570
+ * classifiable. Returns one warning string per renamed slot.
571
+ */
572
+ export function renameEmptySlots(db, sessionId, componentId, componentName, slotCount) {
573
+ const emptySlots = db
574
+ .prepare(`SELECT name, position FROM raw_slots
575
+ WHERE session_id = ? AND component_id = ? AND trim(name) = ''
576
+ ORDER BY position`)
577
+ .all(sessionId, componentId);
578
+ if (emptySlots.length === 0)
579
+ return { renames: [], warnings: [] };
580
+ const renames = [];
581
+ const warnings = [];
582
+ const rename = db.prepare(`UPDATE raw_slots SET name = ? WHERE session_id = ? AND component_id = ? AND name = ? AND position = ?`);
583
+ // Multi-statement write — wrap in a transaction so a SIGINT or driver error
584
+ // mid-loop leaves raw_slots either fully renamed or fully untouched, never
585
+ // half-renamed (which would corrupt the prompt-vs-DB invariant the LLM
586
+ // relies on for classify_slot). Matches the existing BEGIN/COMMIT pattern
587
+ // throughout this file (see storeRawComponents, createStep, etc.).
588
+ db.exec('BEGIN');
589
+ try {
590
+ for (const slot of emptySlots) {
591
+ // Single unnamed slot → "children". Multiple → positional names.
592
+ const newName = slotCount === 1 ? 'children' : `slot_${slot.position}`;
593
+ rename.run(newName, sessionId, componentId, slot.name, slot.position);
594
+ renames.push({ oldName: slot.name, newName });
595
+ warnings.push(`${componentName}: slot at position ${slot.position} had empty name — renamed to "${newName}" for classification`);
596
+ }
597
+ db.exec('COMMIT');
598
+ }
599
+ catch (e) {
600
+ db.exec('ROLLBACK');
601
+ throw e;
602
+ }
603
+ return { renames, warnings };
604
+ }
566
605
  function groupBy(items, key) {
567
606
  const map = new Map();
568
607
  for (const item of items) {
@@ -676,6 +715,12 @@ export function loadCDFComponents(db, sessionId) {
676
715
  return null;
677
716
  const $properties = {};
678
717
  for (const p of compProps) {
718
+ // Hallucination insurance: drop any prop whose name didn't survive trim.
719
+ // The pre-generate rename guard catches empty-named slots, but if the LLM
720
+ // hallucinated a classify_prop with an empty name into the DB, surface
721
+ // nothing to the manifest builder rather than emitting "$properties: { '': ... }".
722
+ if (!p.name.trim())
723
+ continue;
679
724
  const av = allowedValuesByProp.get(`${component_id}::${p.name}`);
680
725
  const propDef = {
681
726
  $type: p.cdf_type,
@@ -702,6 +747,11 @@ export function loadCDFComponents(db, sessionId) {
702
747
  const compSlots = slotsByComponent.get(component_id) ?? [];
703
748
  const $slots = {};
704
749
  for (const s of compSlots) {
750
+ // Hallucination insurance: same as $properties — drop empty-named slots
751
+ // before they reach buildManifest, which faithfully passes empty keys
752
+ // through and triggers a 422 from the preview API.
753
+ if (!s.name.trim())
754
+ continue;
705
755
  const ac = allowedComponentsBySlot.get(`${component_id}::${s.name}`);
706
756
  const slotDef = {};
707
757
  if (s.description !== null)
@@ -1,5 +1,5 @@
1
1
  import type { DesignTokenType } from '@contentful/experience-design-system-types';
2
- export type ExtractionValidationIssueCode = 'EMPTY_COMPONENT_NAME' | 'EMPTY_PROP_NAME' | 'EMPTY_SLOT_NAME' | 'PROP_SLOT_NAME_COLLISION' | 'DUPLICATE_COMPONENT_NAME' | 'EMPTY_COMPONENT';
2
+ export type ExtractionValidationIssueCode = 'EMPTY_COMPONENT_NAME' | 'EMPTY_PROP_NAME' | 'EMPTY_SLOT_NAME' | 'PROP_SLOT_NAME_COLLISION' | 'DUPLICATE_COMPONENT_NAME' | 'EMPTY_COMPONENT' | 'SERVER_VALIDATION_FAILED';
3
3
  export type ExtractionValidationIssue = {
4
4
  severity: 'error' | 'warning';
5
5
  code: ExtractionValidationIssueCode;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contentful/experience-design-system-cli",
3
- "version": "2.7.5-dev-build-752dc9c.0",
3
+ "version": "2.10.1-dev-build-90df010.0",
4
4
  "description": "Contentful Experiences design system import CLI",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -36,7 +36,7 @@
36
36
  "react-dom": "^18.3.1",
37
37
  "ts-morph": "^27.0.2",
38
38
  "typescript": "^5.9.3",
39
- "@contentful/experience-design-system-types": "2.7.5-dev-build-752dc9c.0"
39
+ "@contentful/experience-design-system-types": "2.10.1-dev-build-90df010.0"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@tsconfig/node24": "^24.0.3",
@@ -251,6 +251,8 @@ For each `RawSlotDefinition`:
251
251
  - `false` if clearly optional (icon slot, footer slot with a default, decorative slot)
252
252
  - Default to `true` when the source gives no signal
253
253
 
254
+ **Pre-named slots:** If the input contains a slot whose `name` was already inferred by the pipeline (e.g. `"children"`, `"slot_0"`), treat it as you would any named slot — classify it normally. The pipeline renames empty-named slots to heuristic names before passing them to you; your job is to confirm or enrich the classification (set `required`, `description`, `allowed_components`), not to rename again.
255
+
254
256
  ---
255
257
 
256
258
  ## Examples