@wix/web50-cli 0.1.1 → 0.1.3

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 (108) hide show
  1. package/bin/web5.js +1 -2
  2. package/dist/cjs/auth/deviceFlow.js +96 -17
  3. package/dist/cjs/auth/deviceFlow.js.map +1 -1
  4. package/dist/cjs/auth/index.js +40 -10
  5. package/dist/cjs/auth/index.js.map +1 -1
  6. package/dist/cjs/cli.js +3 -3
  7. package/dist/cjs/cli.js.map +1 -1
  8. package/dist/cjs/commands/bundle.js +12 -4
  9. package/dist/cjs/commands/bundle.js.map +1 -1
  10. package/dist/cjs/commands/conversation.js.map +1 -1
  11. package/dist/cjs/commands/conversationWizard.js +36 -12
  12. package/dist/cjs/commands/conversationWizard.js.map +1 -1
  13. package/dist/cjs/commands/deploy.js +55 -10
  14. package/dist/cjs/commands/deploy.js.map +1 -1
  15. package/dist/cjs/commands/ecom.js +239 -0
  16. package/dist/cjs/commands/ecom.js.map +1 -0
  17. package/dist/cjs/commands/embed.js +41 -52
  18. package/dist/cjs/commands/embed.js.map +1 -1
  19. package/dist/cjs/commands/init.js +34 -3
  20. package/dist/cjs/commands/init.js.map +1 -1
  21. package/dist/cjs/commands/instructions.js +147 -51
  22. package/dist/cjs/commands/instructions.js.map +1 -1
  23. package/dist/cjs/commands/login.js +8 -4
  24. package/dist/cjs/commands/login.js.map +1 -1
  25. package/dist/cjs/commands/serve.js.map +1 -1
  26. package/dist/cjs/commands/validate.js +368 -16
  27. package/dist/cjs/commands/validate.js.map +1 -1
  28. package/dist/cjs/commands/whoami.js.map +1 -1
  29. package/dist/cjs/templates/aiInstructionsSchema.js +5 -1
  30. package/dist/cjs/templates/aiInstructionsSchema.js.map +1 -1
  31. package/dist/cjs/templates/cmsMappingSchema.js +132 -0
  32. package/dist/cjs/templates/cmsMappingSchema.js.map +1 -0
  33. package/dist/cjs/templates/embedDocs.js +488 -0
  34. package/dist/cjs/templates/embedDocs.js.map +1 -0
  35. package/dist/cjs/utils/project.js +13 -0
  36. package/dist/cjs/utils/project.js.map +1 -1
  37. package/dist/cjs/utils/wixApi.js +3 -1
  38. package/dist/cjs/utils/wixApi.js.map +1 -1
  39. package/dist/esm/auth/deviceFlow.js +95 -17
  40. package/dist/esm/auth/deviceFlow.js.map +1 -1
  41. package/dist/esm/auth/index.js +40 -11
  42. package/dist/esm/auth/index.js.map +1 -1
  43. package/dist/esm/cli.js +3 -3
  44. package/dist/esm/cli.js.map +1 -1
  45. package/dist/esm/commands/bundle.js +12 -4
  46. package/dist/esm/commands/bundle.js.map +1 -1
  47. package/dist/esm/commands/conversation.js.map +1 -1
  48. package/dist/esm/commands/conversationWizard.js +36 -12
  49. package/dist/esm/commands/conversationWizard.js.map +1 -1
  50. package/dist/esm/commands/deploy.js +55 -10
  51. package/dist/esm/commands/deploy.js.map +1 -1
  52. package/dist/esm/commands/ecom.js +234 -0
  53. package/dist/esm/commands/ecom.js.map +1 -0
  54. package/dist/esm/commands/embed.js +42 -52
  55. package/dist/esm/commands/embed.js.map +1 -1
  56. package/dist/esm/commands/init.js +34 -3
  57. package/dist/esm/commands/init.js.map +1 -1
  58. package/dist/esm/commands/instructions.js +152 -53
  59. package/dist/esm/commands/instructions.js.map +1 -1
  60. package/dist/esm/commands/login.js +8 -4
  61. package/dist/esm/commands/login.js.map +1 -1
  62. package/dist/esm/commands/serve.js.map +1 -1
  63. package/dist/esm/commands/validate.js +373 -19
  64. package/dist/esm/commands/validate.js.map +1 -1
  65. package/dist/esm/commands/whoami.js.map +1 -1
  66. package/dist/esm/templates/aiInstructionsSchema.js +5 -1
  67. package/dist/esm/templates/aiInstructionsSchema.js.map +1 -1
  68. package/dist/esm/templates/cmsMappingSchema.js +128 -0
  69. package/dist/esm/templates/cmsMappingSchema.js.map +1 -0
  70. package/dist/esm/templates/embedDocs.js +484 -0
  71. package/dist/esm/templates/embedDocs.js.map +1 -0
  72. package/dist/esm/utils/project.js +15 -0
  73. package/dist/esm/utils/project.js.map +1 -1
  74. package/dist/esm/utils/wixApi.js +4 -2
  75. package/dist/esm/utils/wixApi.js.map +1 -1
  76. package/dist/types/auth/deviceFlow.d.ts +1 -1
  77. package/dist/types/auth/deviceFlow.d.ts.map +1 -1
  78. package/dist/types/auth/index.d.ts +2 -1
  79. package/dist/types/auth/index.d.ts.map +1 -1
  80. package/dist/types/commands/bundle.d.ts.map +1 -1
  81. package/dist/types/commands/conversation.d.ts.map +1 -1
  82. package/dist/types/commands/conversationWizard.d.ts.map +1 -1
  83. package/dist/types/commands/deploy.d.ts.map +1 -1
  84. package/dist/types/commands/ecom.d.ts +3 -0
  85. package/dist/types/commands/ecom.d.ts.map +1 -0
  86. package/dist/types/commands/embed.d.ts.map +1 -1
  87. package/dist/types/commands/init.d.ts.map +1 -1
  88. package/dist/types/commands/instructions.d.ts.map +1 -1
  89. package/dist/types/commands/login.d.ts.map +1 -1
  90. package/dist/types/commands/serve.d.ts.map +1 -1
  91. package/dist/types/commands/validate.d.ts +4 -2
  92. package/dist/types/commands/validate.d.ts.map +1 -1
  93. package/dist/types/commands/whoami.d.ts.map +1 -1
  94. package/dist/types/templates/aiInstructionsSchema.d.ts.map +1 -1
  95. package/dist/types/templates/cmsMappingSchema.d.ts +2 -0
  96. package/dist/types/templates/cmsMappingSchema.d.ts.map +1 -0
  97. package/dist/types/templates/embedDocs.d.ts +2 -0
  98. package/dist/types/templates/embedDocs.d.ts.map +1 -0
  99. package/dist/types/utils/project.d.ts +3 -0
  100. package/dist/types/utils/project.d.ts.map +1 -1
  101. package/dist/types/utils/wixApi.d.ts.map +1 -1
  102. package/package.json +2 -2
  103. package/dist/cjs/commands/getClientId.js +0 -60
  104. package/dist/cjs/commands/getClientId.js.map +0 -1
  105. package/dist/esm/commands/getClientId.js +0 -56
  106. package/dist/esm/commands/getClientId.js.map +0 -1
  107. package/dist/types/commands/getClientId.d.ts +0 -3
  108. package/dist/types/commands/getClientId.d.ts.map +0 -1
@@ -6,13 +6,14 @@ import * as path from 'path';
6
6
  import chalk from 'chalk';
7
7
  import { error, info } from '../utils/print';
8
8
  import { cmsSchemaSchemaTemplate } from '../templates/cmsSchemaSchema';
9
+ import { cmsMappingSchemaTemplate } from '../templates/cmsMappingSchema';
9
10
  import { aiInstructionsSchemaTemplate } from '../templates/aiInstructionsSchema';
10
11
  import { actionYamlSchemaTemplate } from '../templates/actionYamlSchema';
11
-
12
12
  // ── Bundled schemas (never exposed to user's project) ────────────────────────
13
13
 
14
14
  const SCHEMAS = {
15
15
  'cms-schema.yaml': yamlLoad(cmsSchemaSchemaTemplate()),
16
+ 'cms-mapping.yaml': yamlLoad(cmsMappingSchemaTemplate()),
16
17
  'prompt-instructions.yaml': yamlLoad(aiInstructionsSchemaTemplate()),
17
18
  'action.yaml': yamlLoad(actionYamlSchemaTemplate())
18
19
  };
@@ -293,6 +294,91 @@ function buildCmsSchemaDomain(root) {
293
294
  }]
294
295
  };
295
296
  }
297
+ function buildCmsMappingDomain(root) {
298
+ const filePath = path.join(root, 'src', 'configuration', 'cms', 'cms-mapping.yaml');
299
+ const fileName = 'cms-mapping.yaml';
300
+ const ajv = new Ajv({
301
+ allErrors: true,
302
+ strict: false
303
+ });
304
+ const schema = SCHEMAS[fileName];
305
+ let parsed;
306
+ let fileExists = false;
307
+ let yamlOk = false;
308
+ return {
309
+ name: 'CMS Mapping',
310
+ checks: [{
311
+ label: `${fileName} — file exists`,
312
+ run() {
313
+ fileExists = existsSync(filePath);
314
+ if (fileExists) {
315
+ return {
316
+ ok: true,
317
+ errors: []
318
+ };
319
+ }
320
+ return {
321
+ ok: false,
322
+ errors: [{
323
+ message: `File not found: ${filePath}`,
324
+ fix: `Create 'src/configuration/cms/cms-mapping.yaml' in your project.`
325
+ }]
326
+ };
327
+ }
328
+ }, {
329
+ label: `${fileName} — valid YAML`,
330
+ run() {
331
+ if (!fileExists) {
332
+ return {
333
+ ok: false,
334
+ errors: [{
335
+ message: 'skipped — file does not exist'
336
+ }]
337
+ };
338
+ }
339
+ const result = tryParseYaml(filePath);
340
+ if (result.err !== undefined) {
341
+ return {
342
+ ok: false,
343
+ errors: [{
344
+ message: result.err,
345
+ fix: 'Fix the YAML syntax error above.'
346
+ }]
347
+ };
348
+ }
349
+ parsed = result.data;
350
+ yamlOk = true;
351
+ return {
352
+ ok: true,
353
+ errors: []
354
+ };
355
+ }
356
+ }, {
357
+ label: `${fileName} — schema valid`,
358
+ run() {
359
+ if (!yamlOk) {
360
+ return {
361
+ ok: false,
362
+ errors: [{
363
+ message: 'skipped — YAML parse failed'
364
+ }]
365
+ };
366
+ }
367
+ const validate = ajv.compile(schema);
368
+ if (validate(parsed)) {
369
+ return {
370
+ ok: true,
371
+ errors: []
372
+ };
373
+ }
374
+ return {
375
+ ok: false,
376
+ errors: (validate.errors ?? []).map(e => formatAjvError(e))
377
+ };
378
+ }
379
+ }]
380
+ };
381
+ }
296
382
  function buildActionsDomain(root) {
297
383
  const actionsDir = path.join(root, 'src', 'actions');
298
384
  const registryPath = path.join(root, 'src', 'createRegistry.ts');
@@ -666,6 +752,263 @@ function buildActionsDomain(root) {
666
752
  };
667
753
  }
668
754
 
755
+ // ── Sections helpers ──────────────────────────────────────────────────────────
756
+
757
+ /**
758
+ * Extracts the text of every `.register(...)` call that has a component array.
759
+ * Uses bracket-counting to handle nesting correctly.
760
+ * Skips system registrations that have no `[` (e.g. SkipNodesSectionDefinition).
761
+ */
762
+ function extractRegisterBlocks(content) {
763
+ const blocks = [];
764
+ const marker = '.register(';
765
+ let searchFrom = 0;
766
+ let start = content.indexOf(marker, searchFrom);
767
+ while (start !== -1) {
768
+ let depth = 0;
769
+ let i = start + marker.length - 1; // position of the opening '('
770
+ while (i < content.length) {
771
+ const ch = content[i];
772
+ if (ch === '(') {
773
+ depth++;
774
+ } else if (ch === ')') {
775
+ depth--;
776
+ if (depth === 0) {
777
+ break;
778
+ }
779
+ }
780
+ i++;
781
+ }
782
+ const block = content.slice(start, i + 1);
783
+ if (block.includes('[')) {
784
+ blocks.push(block);
785
+ }
786
+ searchFrom = i + 1;
787
+ start = content.indexOf(marker, searchFrom);
788
+ }
789
+ return blocks;
790
+ }
791
+
792
+ /**
793
+ * Scans all import lines that reference a `sections/` path and returns a
794
+ * Map<localName, stem>. Handles aliases and multi-name imports:
795
+ * import { KpiSection as KpiSectionComponent } from '...sections/KpiSection'
796
+ * → "KpiSectionComponent" → "KpiSection"
797
+ */
798
+ function buildSectionImportMap(content) {
799
+ const map = new Map();
800
+ const re = /import\s*\{([^}]+)\}\s*from\s*['"][^'"]*sections\/(\w+)['"]/g;
801
+ let m = re.exec(content);
802
+ while (m !== null) {
803
+ const stem = m[2];
804
+ const names = m[1].split(',');
805
+ for (const raw of names) {
806
+ const token = raw.trim();
807
+ if (!token) {
808
+ continue;
809
+ }
810
+ const asParts = token.split(/\s+as\s+/);
811
+ const localName = (asParts[1] ?? asParts[0]).trim();
812
+ if (localName) {
813
+ map.set(localName, stem);
814
+ }
815
+ }
816
+ m = re.exec(content);
817
+ }
818
+ return map;
819
+ }
820
+
821
+ // ── Sections domain ────────────────────────────────────────────────────────────
822
+
823
+ function buildSectionsDomain(root) {
824
+ const registryPath = path.join(root, 'src', 'createRegistry.ts');
825
+ const sectionsDir = path.join(root, 'src', 'components', 'sections');
826
+ let registryExists = false;
827
+ let registryContent = '';
828
+ let tsxFiles = [];
829
+ return {
830
+ name: 'Sections',
831
+ checks: [
832
+ // ── Check 1: createRegistry.ts exists ────────────────────────────────
833
+ {
834
+ label: 'createRegistry.ts — file exists',
835
+ run() {
836
+ registryExists = existsSync(registryPath);
837
+ if (!registryExists) {
838
+ return {
839
+ ok: false,
840
+ errors: [{
841
+ message: 'src/createRegistry.ts not found',
842
+ fix: 'Create src/createRegistry.ts and export a createRegistry() function that registers your section components.'
843
+ }]
844
+ };
845
+ }
846
+ registryContent = readText(registryPath);
847
+ tsxFiles = existsSync(sectionsDir) ? readdirSync(sectionsDir).filter(f => f.endsWith('.tsx')) : [];
848
+ return {
849
+ ok: true,
850
+ errors: []
851
+ };
852
+ }
853
+ },
854
+ // ── Check 2: all .tsx files imported in createRegistry.ts ─────────────
855
+ {
856
+ label: 'src/components/sections/ — all .tsx files imported in createRegistry.ts',
857
+ run() {
858
+ if (!registryExists) {
859
+ return {
860
+ ok: false,
861
+ errors: [{
862
+ message: 'skipped — createRegistry.ts not found'
863
+ }]
864
+ };
865
+ }
866
+ const errors = [];
867
+ for (const file of tsxFiles) {
868
+ const stem = path.basename(file, '.tsx');
869
+ if (!registryContent.includes(`sections/${stem}`)) {
870
+ errors.push({
871
+ message: `'${stem}.tsx' is not imported in src/createRegistry.ts`,
872
+ fix: `Add: import { ${stem} } from './components/sections/${stem}';`
873
+ });
874
+ }
875
+ }
876
+ return errors.length === 0 ? {
877
+ ok: true,
878
+ errors: []
879
+ } : {
880
+ ok: false,
881
+ errors
882
+ };
883
+ }
884
+ },
885
+ // ── Check 3: all .tsx files wired into a .register() call ─────────────
886
+ {
887
+ label: 'src/components/sections/ — all .tsx files wired into .register()',
888
+ run() {
889
+ if (!registryExists) {
890
+ return {
891
+ ok: false,
892
+ errors: [{
893
+ message: 'skipped — createRegistry.ts not found'
894
+ }]
895
+ };
896
+ }
897
+ const importMap = buildSectionImportMap(registryContent);
898
+ const blocks = extractRegisterBlocks(registryContent);
899
+ const blockText = blocks.join('\n');
900
+ const errors = [];
901
+ for (const file of tsxFiles) {
902
+ var _find;
903
+ const stem = path.basename(file, '.tsx');
904
+ // Find the local name for this stem (may have been aliased on import)
905
+ const localName = ((_find = [...importMap.entries()].find(_ref => {
906
+ let [, s] = _ref;
907
+ return s === stem;
908
+ })) == null ? void 0 : _find[0]) ?? stem;
909
+ if (!blockText.includes(localName)) {
910
+ errors.push({
911
+ message: `'${localName}' (${stem}.tsx) is imported but not passed to any .register() call`,
912
+ fix: `Add { component: ${localName} } inside a .register(new <Definition>(), [...]) call in src/createRegistry.ts.`
913
+ });
914
+ }
915
+ }
916
+ return errors.length === 0 ? {
917
+ ok: true,
918
+ errors: []
919
+ } : {
920
+ ok: false,
921
+ errors
922
+ };
923
+ }
924
+ },
925
+ // ── Check 4: every .register() block has a catch-all entry ────────────
926
+ {
927
+ label: 'createRegistry.ts — all .register() blocks have a catch-all component',
928
+ run() {
929
+ if (!registryExists) {
930
+ return {
931
+ ok: false,
932
+ errors: [{
933
+ message: 'skipped — createRegistry.ts not found'
934
+ }]
935
+ };
936
+ }
937
+ const blocks = extractRegisterBlocks(registryContent);
938
+ const errors = [];
939
+ for (const block of blocks) {
940
+ const defMatch = block.match(/\.register\(\s*new\s+(\w+)/);
941
+ const defName = defMatch ? defMatch[1] : '(unknown)';
942
+
943
+ // Split on "{ component:" to isolate each entry
944
+ const entries = block.split('{ component:').slice(1);
945
+ if (entries.length === 0) {
946
+ continue;
947
+ }
948
+ const allHaveIntent = entries.every(entry => entry.includes('intent:'));
949
+ if (allHaveIntent) {
950
+ errors.push({
951
+ message: `'${defName}' has no catch-all component — every entry specifies an intent`,
952
+ fix: `Add a catch-all entry to the .register(new ${defName}(), [...]) call:\n { component: YourDefaultComponent }`
953
+ });
954
+ }
955
+ }
956
+ return errors.length === 0 ? {
957
+ ok: true,
958
+ errors: []
959
+ } : {
960
+ ok: false,
961
+ errors
962
+ };
963
+ }
964
+ },
965
+ // ── Check 5: all component refs in .register() resolve to a .tsx file ─
966
+ {
967
+ label: 'createRegistry.ts — all .register() component references resolve to a .tsx file',
968
+ run() {
969
+ if (!registryExists) {
970
+ return {
971
+ ok: false,
972
+ errors: [{
973
+ message: 'skipped — createRegistry.ts not found'
974
+ }]
975
+ };
976
+ }
977
+ const importMap = buildSectionImportMap(registryContent);
978
+ const blocks = extractRegisterBlocks(registryContent);
979
+ const errors = [];
980
+ for (const block of blocks) {
981
+ const refs = [...block.matchAll(/\{\s*component:\s*(\w+)/g)].map(m => m[1]);
982
+ for (const ref of refs) {
983
+ const stem = importMap.get(ref);
984
+ if (stem === undefined) {
985
+ errors.push({
986
+ message: `Component '${ref}' used in .register() has no matching import from sections/`,
987
+ fix: `Add: import { ${ref} } from './components/sections/${ref}'; or check the import alias.`
988
+ });
989
+ continue;
990
+ }
991
+ const filePath = path.join(sectionsDir, `${stem}.tsx`);
992
+ if (!existsSync(filePath)) {
993
+ errors.push({
994
+ message: `Component '${ref}' maps to '${stem}.tsx' which does not exist in src/components/sections/`,
995
+ fix: `Create src/components/sections/${stem}.tsx, or remove the stale .register() entry from src/createRegistry.ts.`
996
+ });
997
+ }
998
+ }
999
+ }
1000
+ return errors.length === 0 ? {
1001
+ ok: true,
1002
+ errors: []
1003
+ } : {
1004
+ ok: false,
1005
+ errors
1006
+ };
1007
+ }
1008
+ }]
1009
+ };
1010
+ }
1011
+
669
1012
  // ── Rendering ─────────────────────────────────────────────────────────────────
670
1013
 
671
1014
  function printDomain(domain, results, verbose) {
@@ -721,11 +1064,11 @@ function collectDomainJson(domain, results) {
721
1064
  return {
722
1065
  name: domain.name,
723
1066
  ok: results.every(r => r.result.ok),
724
- checks: results.map(_ref => {
1067
+ checks: results.map(_ref2 => {
725
1068
  let {
726
1069
  label,
727
1070
  result
728
- } = _ref;
1071
+ } = _ref2;
729
1072
  return {
730
1073
  label,
731
1074
  ok: result.ok,
@@ -831,16 +1174,22 @@ function buildScriptsDomain(root) {
831
1174
 
832
1175
  // ── Domain selection ──────────────────────────────────────────────────────────
833
1176
 
1177
+ const DOMAIN_NAME_MAP = {
1178
+ ai: 'AI Instructions',
1179
+ cms: 'CMS Schema',
1180
+ 'cms-mapping': 'CMS Mapping',
1181
+ scripts: 'Build Scripts',
1182
+ actions: 'Actions',
1183
+ sections: 'Sections'
1184
+ };
834
1185
  function selectDomains(root, filter) {
835
- const all = [buildAiInstructionsDomain(root), buildCmsSchemaDomain(root), buildActionsDomain(root), buildScriptsDomain(root)];
836
- if (!filter) return all;
837
- const map = {
838
- ai: 'AI Instructions',
839
- cms: 'CMS Schema',
840
- scripts: 'Build Scripts',
841
- actions: 'Actions'
842
- };
843
- return all.filter(d => d.name === map[filter]);
1186
+ const all = [buildAiInstructionsDomain(root), buildCmsSchemaDomain(root), buildCmsMappingDomain(root), buildActionsDomain(root), buildScriptsDomain(root), buildSectionsDomain(root)];
1187
+ if (!filter) {
1188
+ return all;
1189
+ }
1190
+ const filters = Array.isArray(filter) ? filter : [filter];
1191
+ const names = new Set(filters.map(f => DOMAIN_NAME_MAP[f]));
1192
+ return all.filter(d => names.has(d.name));
844
1193
  }
845
1194
 
846
1195
  // ── Core runner ───────────────────────────────────────────────────────────────
@@ -861,7 +1210,9 @@ function runDomains(domains, verbose, format) {
861
1210
  } else {
862
1211
  domainOk = printDomain(domain, results, verbose);
863
1212
  }
864
- if (!domainOk) hasErrors = true;
1213
+ if (!domainOk) {
1214
+ hasErrors = true;
1215
+ }
865
1216
  }
866
1217
  return {
867
1218
  ok: !hasErrors,
@@ -872,11 +1223,12 @@ function runDomains(domains, verbose, format) {
872
1223
  // ── Programmatic API ─────────────────────────────────────────────────────────
873
1224
 
874
1225
  /**
875
- * Runs all validation domains against the given project root.
1226
+ * Runs validation domains against the given project root.
1227
+ * Pass `filters` to validate only specific domains; omit to validate all.
876
1228
  * Prints results in slim mode and returns true if everything passes.
877
1229
  */
878
- export function runValidation(root) {
879
- const domains = selectDomains(root);
1230
+ export function runValidation(root, filters) {
1231
+ const domains = selectDomains(root, filters);
880
1232
  const {
881
1233
  ok
882
1234
  } = runDomains(domains, false, 'text');
@@ -886,14 +1238,16 @@ export function runValidation(root) {
886
1238
  // ── Watch mode ────────────────────────────────────────────────────────────────
887
1239
 
888
1240
  function startWatch(root, run) {
889
- const watchDirs = [path.join(root, 'src', 'configuration'), path.join(root, 'src', 'actions')].filter(d => existsSync(d));
1241
+ const watchDirs = [path.join(root, 'src', 'configuration'), path.join(root, 'src', 'actions'), path.join(root, 'src', 'components', 'sections'), path.join(root, 'src', 'createRegistry.ts')].filter(d => existsSync(d));
890
1242
  if (watchDirs.length === 0) {
891
1243
  error('No directories to watch found.');
892
1244
  return;
893
1245
  }
894
1246
  let debounce = null;
895
1247
  const trigger = () => {
896
- if (debounce) clearTimeout(debounce);
1248
+ if (debounce) {
1249
+ clearTimeout(debounce);
1250
+ }
897
1251
  debounce = setTimeout(() => {
898
1252
  console.clear();
899
1253
  info(`[${new Date().toLocaleTimeString()}] Re-validating...`);
@@ -913,7 +1267,7 @@ function startWatch(root, run) {
913
1267
 
914
1268
  // ── Command ───────────────────────────────────────────────────────────────────
915
1269
 
916
- export const validateCommand = new Command('validate').description('Validate project config files against their schemas').option('--verbose', 'Show all checks, including passing ones').option('--domain <domain>', 'Only validate one domain: ai, cms, actions, or scripts').option('--format <format>', 'Output format: text (default) or json', 'text').option('--watch', 'Re-validate on file changes (TTY only)').option('--strict', 'Exit with error even on warnings').option('--project <path>', 'Explicit project root (overrides cwd auto-detection)').action(opts => {
1270
+ export const validateCommand = new Command('validate').description('Validate project config files against their schemas').option('--verbose', 'Show all checks, including passing ones').option('--domain <domain>', 'Only validate one domain: ai, cms, actions, scripts, or sections').option('--format <format>', 'Output format: text (default) or json', 'text').option('--watch', 'Re-validate on file changes (TTY only)').option('--strict', 'Exit with error even on warnings').option('--project <path>', 'Explicit project root (overrides cwd auto-detection)').action(opts => {
917
1271
  const verbose = Boolean(opts.verbose);
918
1272
  const format = opts.format ?? 'text';
919
1273
  const domainFilter = opts.domain;