aem-eds-cli 1.0.0 → 1.2.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 (2) hide show
  1. package/bin/aem-eds.js +76 -41
  2. package/package.json +1 -1
package/bin/aem-eds.js CHANGED
@@ -68,7 +68,6 @@ const FIELD_TYPES = {
68
68
  'radio-group': { label: 'Radio buttons — single value', valueType: 'string', multi: false },
69
69
  'checkbox-group': { label: 'Checkboxes — multiple values', valueType: 'string[]', multi: false },
70
70
  number: { label: 'Number input', valueType: 'number', multi: false },
71
- 'date-time': { label: 'Date / time picker', valueType: 'date', multi: false },
72
71
  'aem-tag': { label: 'AEM Tag picker (cq:tags)', valueType: 'string', multi: false },
73
72
  container: { label: 'Grouped / repeating field set', valueType: 'string', multi: false },
74
73
  tab: { label: 'Tab — organises fields in panel', valueType: null, multi: false },
@@ -78,7 +77,7 @@ const FIELD_GROUPS = [
78
77
  { title: 'Text inputs', keys: ['text', 'textarea', 'richtext'] },
79
78
  { title: 'Media & references', keys: ['reference', 'aem-content', 'aem-content-fragment', 'aem-experience-fragment'] },
80
79
  { title: 'Selection & toggles', keys: ['boolean', 'select', 'multiselect', 'radio-group', 'checkbox-group'] },
81
- { title: 'Specialised', keys: ['number', 'date-time', 'aem-tag'] },
80
+ { title: 'Specialised', keys: ['number', 'aem-tag'] },
82
81
  { title: 'Layout (no value)', keys: ['container', 'tab'] },
83
82
  ];
84
83
 
@@ -114,7 +113,6 @@ const VALIDATIONS = {
114
113
  'radio-group': ['required', 'description', 'readOnly', 'hidden'],
115
114
  'checkbox-group': ['required', 'description', 'readOnly', 'hidden'],
116
115
  number: ['required', 'description', 'readOnly', 'hidden', 'min', 'max', 'step'],
117
- 'date-time': ['required', 'description', 'readOnly', 'hidden', 'min', 'max'],
118
116
  'aem-tag': ['required', 'description', 'readOnly', 'hidden'],
119
117
  container: ['description', 'readOnly', 'hidden'],
120
118
  tab: [],
@@ -148,7 +146,7 @@ function buildFieldSchema(fields) {
148
146
  : f.defaultValue;
149
147
  }
150
148
 
151
- if (f.type === 'date-time') { base.displayFormat = 'YYYY-MM-DD'; base.valueFormat = 'YYYY-MM-DD'; }
149
+ // number / date-time constraints go TOP-LEVEL not inside validation.
152
150
  if (f.type === 'aem-content-fragment') base.variationName = '';
153
151
 
154
152
  const validation = {};
@@ -181,17 +179,6 @@ function genSimpleBlockJS(blockName) {
181
179
  `;
182
180
  }
183
181
 
184
- // ─── JS template — multi-row block ───────────────────────────────────────────
185
- function genMultiRowBlockJS(blockName) {
186
- return `export default function decorate(block) {
187
- const rows = [...block.children];
188
- rows.forEach((row) => {
189
- // TODO: implement ${blockName} row decoration
190
- });
191
- }
192
- `;
193
- }
194
-
195
182
  // ─── JS template — container block ───────────────────────────────────────────
196
183
  function genContainerBlockJS(blockName, childName) {
197
184
  return `export default function decorate(block) {
@@ -430,7 +417,7 @@ async function collectOptions(rl, fieldName, ind = '') {
430
417
  const DEFAULT_VALUE_TYPES = new Set([
431
418
  'text', 'textarea', 'richtext', 'number', 'boolean',
432
419
  'select', 'multiselect', 'radio-group', 'checkbox-group',
433
- 'date-time', 'aem-tag',
420
+ 'aem-tag',
434
421
  ]);
435
422
 
436
423
  async function collectDefaultValue(rl, field, ind = '') {
@@ -463,9 +450,6 @@ async function collectDefaultValue(rl, field, ind = '') {
463
450
  question = `${ind} Default selected values for "${field.name}" (comma-separated, Enter to skip): `;
464
451
  break;
465
452
  }
466
- case 'date-time':
467
- question = `${ind} Default date for "${field.name}" (YYYY-MM-DD, Enter to skip): `;
468
- break;
469
453
  default:
470
454
  question = `${ind} Default value for "${field.name}" (Enter to skip): `;
471
455
  }
@@ -534,14 +518,51 @@ async function collectValidation(rl, field, indent = '') {
534
518
  if (available.includes('description')) { const d = (await prompt(rl, `${indent} Helper text (Enter to skip): `)).trim(); if (d) field.description = d; }
535
519
  if (available.includes('minLength')) { const v = (await prompt(rl, `${indent} Min length (Enter to skip): `)).trim(); if (v && !isNaN(v)) field.minLength = +v; }
536
520
  if (available.includes('maxLength')) { const v = (await prompt(rl, `${indent} Max length (Enter to skip): `)).trim(); if (v && !isNaN(v)) field.maxLength = +v; }
537
- if (available.includes('min')) { const v = (await prompt(rl, `${indent} Min${field.type === 'date-time' ? ' (YYYY-MM-DD)' : ''} (Enter to skip): `)).trim(); if (v) field.min = field.type === 'number' ? +v : v; }
538
- if (available.includes('max')) { const v = (await prompt(rl, `${indent} Max${field.type === 'date-time' ? ' (YYYY-MM-DD)' : ''} (Enter to skip): `)).trim(); if (v) field.max = field.type === 'number' ? +v : v; }
521
+ if (available.includes('min')) { const v = (await prompt(rl, `${indent} Min (Enter to skip): `)).trim(); if (v) field.min = field.type === 'number' ? +v : v; }
522
+ if (available.includes('max')) { const v = (await prompt(rl, `${indent} Max (Enter to skip): `)).trim(); if (v) field.max = field.type === 'number' ? +v : v; }
539
523
  if (available.includes('step')) { const v = (await prompt(rl, `${indent} Step (Enter to skip): `)).trim(); if (v && !isNaN(v)) field.step = +v; }
540
524
  if (available.includes('rootPath')) { const v = (await prompt(rl, `${indent} Root path (Enter to skip): `)).trim(); if (v) field.rootPath = v; }
541
525
  if (available.includes('customErrorMsg')) { const v = (await prompt(rl, `${indent} Custom error message (Enter to skip): `)).trim(); if (v) field.customErrorMsg = v; }
542
526
  log.blank();
543
527
  }
544
528
 
529
+ // ─── Field collector with tab grouping ───────────────────────────────────────
530
+ async function collectFieldsWithTabs(rl, context) {
531
+ log.info(`Define tab groups for ${c.bold}${context}${c.reset}. Each tab appears as a panel section in UE.`);
532
+ log.blank();
533
+
534
+ const allFields = [];
535
+ let tabCount = 0;
536
+
537
+ while (true) {
538
+ tabCount++;
539
+ log.divider();
540
+
541
+ const tabLabelIn = (await prompt(rl, `Tab ${tabCount} name (e.g. Media, Links, Body): `)).trim();
542
+ if (!tabLabelIn) { log.warn('Tab name cannot be blank.'); tabCount--; continue; }
543
+
544
+ const tabName = `tab${tabCount}`;
545
+ const tabLabel = tabLabelIn;
546
+
547
+ allFields.push({ name: tabName, label: tabLabel, type: 'tab' });
548
+ log.success(`Tab "${tabLabel}" created — define its fields:`);
549
+ log.blank();
550
+
551
+ const tabFields = await collectFields(rl, `"${tabLabel}" tab`);
552
+ allFields.push(...tabFields);
553
+
554
+ log.blank();
555
+ const addMore = await confirm(rl, `Add another tab?`, false);
556
+ if (!addMore) break;
557
+ log.blank();
558
+ }
559
+
560
+ log.blank();
561
+ log.info(`${tabCount} tab(s) defined with ${allFields.filter(f => f.type !== 'tab').length} total fields.`);
562
+ log.blank();
563
+ return allFields;
564
+ }
565
+
545
566
  // ─── Field collector ──────────────────────────────────────────────────────────
546
567
  async function collectFields(rl, context, depth = 0) {
547
568
  const fields = [];
@@ -636,7 +657,6 @@ function genBlockJSON(blockName, isContainer, parentFields, childName, childFiel
636
657
  const models = [];
637
658
  const parentSchema = buildFieldSchema(parentFields);
638
659
 
639
- // classes field only added when author explicitly opted in during scaffolding
640
660
  if (needsVariants) {
641
661
  parentSchema.push({
642
662
  component: 'text',
@@ -653,13 +673,15 @@ function genBlockJSON(blockName, isContainer, parentFields, childName, childFiel
653
673
  }
654
674
 
655
675
  // ── definitions ──────────────────────────────────────────────────────────────
676
+ // Must use groups.components format to match boilerplate's component-definition.json
677
+ // structure. merge-json-cli deep-merges this correctly into the global file.
678
+ // Using a flat "definitions" key would create a separate key that UE ignores.
656
679
  const template = { name: toTitleCase(blockName), model: blockName };
657
680
  if (isContainer) {
658
681
  template.filter = blockName;
659
- // No :items — pre-population causes 409 JCR conflict on insert
660
682
  }
661
683
 
662
- const definitions = [{
684
+ const components = [{
663
685
  title: toTitleCase(blockName),
664
686
  id: blockName,
665
687
  plugins: {
@@ -672,11 +694,9 @@ function genBlockJSON(blockName, isContainer, parentFields, childName, childFiel
672
694
  },
673
695
  }];
674
696
 
675
- // Container blocks need a SECOND definition for the child item
676
- // with resourceType block/item — this is what UE uses when author
677
- // clicks + inside the block to add a child
697
+ // Child item definition block/item resourceType required for UE + button
678
698
  if (isContainer) {
679
- definitions.push({
699
+ components.push({
680
700
  title: toTitleCase(childName),
681
701
  id: childName,
682
702
  plugins: {
@@ -693,6 +713,14 @@ function genBlockJSON(blockName, isContainer, parentFields, childName, childFiel
693
713
  });
694
714
  }
695
715
 
716
+ const definitions = {
717
+ groups: [{
718
+ title: 'Blocks',
719
+ id: 'blocks',
720
+ components,
721
+ }],
722
+ };
723
+
696
724
  // ── filters ──────────────────────────────────────────────────────────────────
697
725
  // Section filter is NOT generated here — it is a project-level concern
698
726
  // maintained in the boilerplate's central component-filters.json.
@@ -747,18 +775,23 @@ async function cmdCreate(rl, cwd, argName, dryRun) {
747
775
  // Block type
748
776
  log.blank();
749
777
  console.log(` ${c.bold}Block type:${c.reset}`);
750
- console.log(` ${c.dim}1.${c.reset} ${c.cyan}Simple${c.reset} ${c.dim}Fixed fields — hero, banner, teaser, quote${c.reset}`);
751
- console.log(` ${c.dim}2.${c.reset} ${c.cyan}Multi-row${c.reset} ${c.dim}Same fields repeating slider, cards, table rows${c.reset}`);
752
- console.log(` ${c.dim}3.${c.reset} ${c.cyan}Container${c.reset} ${c.dim}Children with different fields — accordion, tabs${c.reset}`);
778
+ console.log(` ${c.dim}1.${c.reset} ${c.cyan}Simple${c.reset} ${c.dim}Fixed fields — hero, banner, teaser${c.reset}`);
779
+ console.log(` ${c.dim}2.${c.reset} ${c.cyan}Simple with tabs${c.reset} ${c.dim}Fixed fields grouped into UE panel tabs${c.reset}`);
780
+ console.log(` ${c.dim}3.${c.reset} ${c.cyan}Container${c.reset} ${c.dim}Children with different fields — accordion, tabs${c.reset}`);
781
+ console.log(` ${c.dim}4.${c.reset} ${c.cyan}Container with tabs${c.reset} ${c.dim}Container where parent config has tab groups${c.reset}`);
753
782
  log.blank();
754
- const typeIn = (await prompt(rl, `Type [1]: `)).trim();
755
- const isContainer = typeIn === '3' || typeIn.toLowerCase() === 'container';
756
- const isMultiRow = typeIn === '2' || typeIn.toLowerCase() === 'multi-row';
783
+ const typeIn = (await prompt(rl, `Type [1]: `)).trim();
784
+ const isContainer = typeIn === '3' || typeIn === '4' || typeIn.toLowerCase().startsWith('container');
785
+ const useTabs = typeIn === '2' || typeIn === '4'
786
+ || typeIn.toLowerCase() === 'simple with tabs'
787
+ || typeIn.toLowerCase() === 'container with tabs';
757
788
  log.blank();
758
789
 
759
- // Parent fields
790
+ // Parent fields — with or without tab grouping based on block type
760
791
  log.section(isContainer ? `Phase 1 — Parent config fields` : `Define block fields`);
761
- const parentFields = await collectFields(rl, isContainer ? `${blockName} config` : blockName);
792
+ const parentFields = useTabs
793
+ ? await collectFieldsWithTabs(rl, isContainer ? `${blockName} config` : blockName, true)
794
+ : await collectFields(rl, isContainer ? `${blockName} config` : blockName);
762
795
 
763
796
  // Container: child setup
764
797
  let childName = '', childFields = [];
@@ -792,10 +825,9 @@ async function cmdCreate(rl, cwd, argName, dryRun) {
792
825
  const dir = path.join(cwd, 'blocks', blockName);
793
826
 
794
827
  // Minimal lint-free JS based on block type
795
- let jsContent;
796
- if (isContainer) jsContent = genContainerBlockJS(blockName, childName);
797
- else if (isMultiRow) jsContent = genMultiRowBlockJS(blockName);
798
- else jsContent = genSimpleBlockJS(blockName);
828
+ const jsContent = isContainer
829
+ ? genContainerBlockJS(blockName, childName)
830
+ : genSimpleBlockJS(blockName);
799
831
 
800
832
  writeFile(path.join(dir, `${blockName}.js`), jsContent, `blocks/${blockName}/${blockName}.js`, dryRun);
801
833
  writeFile(path.join(dir, `${blockName}.css`), genBlockCSS(blockName), `blocks/${blockName}/${blockName}.css`, dryRun);
@@ -807,7 +839,10 @@ async function cmdCreate(rl, cwd, argName, dryRun) {
807
839
  // Summary
808
840
  log.blank(); log.divider();
809
841
  const prefix = dryRun ? `${c.yellow}~ DRY RUN${c.reset} ` : `${c.green}✔${c.reset} `;
810
- const typeLabel = isContainer ? `container → ${childName}` : isMultiRow ? 'multi-row' : 'simple';
842
+ const typeLabel = isContainer && useTabs ? `container with tabs → ${childName}`
843
+ : isContainer ? `container → ${childName}`
844
+ : useTabs ? 'simple with tabs'
845
+ : 'simple';
811
846
  console.log(`\n ${prefix}${c.bold}"${blockName}" ${dryRun ? 'would be' : 'scaffolded!'} (${typeLabel})${c.reset}\n`);
812
847
  if (!dryRun) {
813
848
  console.log(` ${c.dim}Files created:${c.reset}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aem-eds-cli",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "description": "CLI scaffolding tool for AEM Edge delivery services blocks with Universal editor authoring support",
5
5
  "bin": {
6
6
  "aem-eds-cli": "./bin/aem-eds.js"