aem-eds-cli 1.1.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 +40 -111
  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,8 +518,8 @@ 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; }
@@ -668,12 +652,11 @@ async function collectFields(rl, context, depth = 0) {
668
652
  // definitions, models and filters — matching the boilerplate pattern.
669
653
  // `npm run build:json` (merge-json-cli) assembles these into global files.
670
654
 
671
- function genBlockJSON(blockName, isContainer, parentFields, childName, childFields, needsVariants = false, useExistingChild = false) {
655
+ function genBlockJSON(blockName, isContainer, parentFields, childName, childFields, needsVariants = false) {
672
656
  // ── models ───────────────────────────────────────────────────────────────────
673
657
  const models = [];
674
658
  const parentSchema = buildFieldSchema(parentFields);
675
659
 
676
- // classes field only added when author explicitly opted in during scaffolding
677
660
  if (needsVariants) {
678
661
  parentSchema.push({
679
662
  component: 'text',
@@ -685,21 +668,20 @@ function genBlockJSON(blockName, isContainer, parentFields, childName, childFiel
685
668
  }
686
669
 
687
670
  models.push({ id: blockName, fields: parentSchema });
688
-
689
- // Only create child model if NOT using an existing block
690
- // When useExistingChild=true, the child block already has its own model
691
- if (isContainer && !useExistingChild) {
671
+ if (isContainer) {
692
672
  models.push({ id: childName, fields: buildFieldSchema(childFields) });
693
673
  }
694
674
 
695
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.
696
679
  const template = { name: toTitleCase(blockName), model: blockName };
697
680
  if (isContainer) {
698
681
  template.filter = blockName;
699
- // No :items — pre-population causes 409 JCR conflict on insert
700
682
  }
701
683
 
702
- const definitions = [{
684
+ const components = [{
703
685
  title: toTitleCase(blockName),
704
686
  id: blockName,
705
687
  plugins: {
@@ -712,11 +694,9 @@ function genBlockJSON(blockName, isContainer, parentFields, childName, childFiel
712
694
  },
713
695
  }];
714
696
 
715
- // Container blocks need a SECOND definition for the child item
716
- // with resourceType block/item — this is what UE uses when author
717
- // clicks + inside the block to add a child
697
+ // Child item definition block/item resourceType required for UE + button
718
698
  if (isContainer) {
719
- definitions.push({
699
+ components.push({
720
700
  title: toTitleCase(childName),
721
701
  id: childName,
722
702
  plugins: {
@@ -733,6 +713,14 @@ function genBlockJSON(blockName, isContainer, parentFields, childName, childFiel
733
713
  });
734
714
  }
735
715
 
716
+ const definitions = {
717
+ groups: [{
718
+ title: 'Blocks',
719
+ id: 'blocks',
720
+ components,
721
+ }],
722
+ };
723
+
736
724
  // ── filters ──────────────────────────────────────────────────────────────────
737
725
  // Section filter is NOT generated here — it is a project-level concern
738
726
  // maintained in the boilerplate's central component-filters.json.
@@ -789,14 +777,12 @@ async function cmdCreate(rl, cwd, argName, dryRun) {
789
777
  console.log(` ${c.bold}Block type:${c.reset}`);
790
778
  console.log(` ${c.dim}1.${c.reset} ${c.cyan}Simple${c.reset} ${c.dim}Fixed fields — hero, banner, teaser${c.reset}`);
791
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}`);
792
- console.log(` ${c.dim}3.${c.reset} ${c.cyan}Multi-row${c.reset} ${c.dim}Same fields repeating slider, cards${c.reset}`);
793
- console.log(` ${c.dim}4.${c.reset} ${c.cyan}Container${c.reset} ${c.dim}Children with different fields accordion, tabs${c.reset}`);
794
- console.log(` ${c.dim}5.${c.reset} ${c.cyan}Container with tabs${c.reset} ${c.dim}Container where parent config has tab groups${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}`);
795
782
  log.blank();
796
783
  const typeIn = (await prompt(rl, `Type [1]: `)).trim();
797
- const isContainer = typeIn === '4' || typeIn === '5' || typeIn.toLowerCase().startsWith('container');
798
- const isMultiRow = typeIn === '3' || typeIn.toLowerCase() === 'multi-row';
799
- const useTabs = typeIn === '2' || typeIn === '5'
784
+ const isContainer = typeIn === '3' || typeIn === '4' || typeIn.toLowerCase().startsWith('container');
785
+ const useTabs = typeIn === '2' || typeIn === '4'
800
786
  || typeIn.toLowerCase() === 'simple with tabs'
801
787
  || typeIn.toLowerCase() === 'container with tabs';
802
788
  log.blank();
@@ -808,73 +794,18 @@ async function cmdCreate(rl, cwd, argName, dryRun) {
808
794
  : await collectFields(rl, isContainer ? `${blockName} config` : blockName);
809
795
 
810
796
  // Container: child setup
811
- let childName = '', childFields = [], useExistingChild = false;
797
+ let childName = '', childFields = [];
812
798
  if (isContainer) {
813
799
  log.divider();
814
800
  log.section(`Phase 2 — Child item`);
815
-
816
- // Ask if they want to use an existing block as child
817
- useExistingChild = await confirm(rl, `Use an existing block as child item?`, false);
818
-
819
- if (useExistingChild) {
820
- log.blank();
821
-
822
- // Scan blocks/ directory for existing blocks
823
- const blocksDir = path.join(cwd, 'blocks');
824
- const allBlocks = fs.existsSync(blocksDir)
825
- ? fs.readdirSync(blocksDir, { withFileTypes: true })
826
- .filter((d) => d.isDirectory())
827
- .map((d) => d.name)
828
- .filter((name) => fs.existsSync(path.join(blocksDir, name, `${name}.js`)))
829
- .filter((name) => name !== blockName) // exclude current block
830
- : [];
831
-
832
- if (!allBlocks.length) {
833
- log.warn('No existing blocks found in blocks/ — switching to manual entry.');
834
- const existingIn = (await prompt(rl, `Block name: `)).trim().toLowerCase().replace(/\s+/g, '-');
835
- childName = existingIn;
836
- } else {
837
- // Display numbered list
838
- console.log(`\n ${c.bold}Available blocks in this project:${c.reset}\n`);
839
- allBlocks.forEach((name, i) => {
840
- const hasJSON = fs.existsSync(path.join(blocksDir, name, `_${name}.json`));
841
- const tag = hasJSON ? `${c.green}(scaffolded)${c.reset}` : `${c.dim}(manual)${c.reset}`;
842
- console.log(` ${c.dim}${String(i + 1).padStart(2)}.${c.reset} ${c.cyan}${name.padEnd(24)}${c.reset} ${tag}`);
843
- });
844
- log.blank();
845
-
846
- const sel = (await prompt(rl, `Select number or type block name: `)).trim().toLowerCase();
847
- const byNum = allBlocks[parseInt(sel, 10) - 1];
848
- const chosen = byNum || (allBlocks.includes(sel) ? sel : null);
849
-
850
- if (!chosen) {
851
- log.warn(`"${sel}" not found in blocks/ — using as-is.`);
852
- childName = sel.replace(/\s+/g, '-');
853
- } else {
854
- childName = chosen;
855
- log.success(`Selected: ${c.bold}${childName}${c.reset}`);
856
- }
857
- }
858
-
859
- if (!isValidName(childName)) { log.error(`Invalid block name.`); rl.close(); process.exit(1); }
860
-
861
- childFields = [];
862
- log.blank();
863
- log.info(`Child: ${c.bold}${childName}${c.reset} ${c.dim}(existing block — model already defined)${c.reset}`);
864
- log.blank();
865
- log.info(`${c.yellow}Note:${c.reset} A ${c.cyan}block/item${c.reset} definition for "${childName}" will be added`);
866
- log.info(`to ${c.cyan}_${blockName}.json${c.reset} so UE knows it can be dropped inside ${blockName}.`);
867
- log.blank();
868
- } else {
869
- const sugg = `${blockName}-item`;
870
- const childIn = (await prompt(rl, `Child item name [${sugg}]: `)).trim().toLowerCase().replace(/\s+/g, '-');
871
- childName = childIn || sugg;
872
- if (!isValidName(childName)) { log.error(`Invalid child name.`); rl.close(); process.exit(1); }
873
- log.blank();
874
- log.info(`Child: ${c.bold}${childName}${c.reset}`);
875
- log.blank();
876
- childFields = await collectFields(rl, `${childName} fields`);
877
- }
801
+ const sugg = `${blockName}-item`;
802
+ const childIn = (await prompt(rl, `Child item name [${sugg}]: `)).trim().toLowerCase().replace(/\s+/g, '-');
803
+ childName = childIn || sugg;
804
+ if (!isValidName(childName)) { log.error(`Invalid child name.`); rl.close(); process.exit(1); }
805
+ log.blank();
806
+ log.info(`Child: ${c.bold}${childName}${c.reset}`);
807
+ log.blank();
808
+ childFields = await collectFields(rl, `${childName} fields`);
878
809
  }
879
810
 
880
811
  // Variants prompt — asked for all block types
@@ -894,24 +825,22 @@ async function cmdCreate(rl, cwd, argName, dryRun) {
894
825
  const dir = path.join(cwd, 'blocks', blockName);
895
826
 
896
827
  // Minimal lint-free JS based on block type
897
- let jsContent;
898
- if (isContainer) jsContent = genContainerBlockJS(blockName, childName);
899
- else if (isMultiRow) jsContent = genMultiRowBlockJS(blockName);
900
- else jsContent = genSimpleBlockJS(blockName);
828
+ const jsContent = isContainer
829
+ ? genContainerBlockJS(blockName, childName)
830
+ : genSimpleBlockJS(blockName);
901
831
 
902
832
  writeFile(path.join(dir, `${blockName}.js`), jsContent, `blocks/${blockName}/${blockName}.js`, dryRun);
903
833
  writeFile(path.join(dir, `${blockName}.css`), genBlockCSS(blockName), `blocks/${blockName}/${blockName}.css`, dryRun);
904
834
  writeFile(path.join(dir, 'README.md'), genReadme(blockName, isContainer, parentFields, childName, childFields, needsVariants), `blocks/${blockName}/README.md`, dryRun);
905
835
 
906
- const blockJSON = genBlockJSON(blockName, isContainer, parentFields, childName, childFields, needsVariants, useExistingChild);
836
+ const blockJSON = genBlockJSON(blockName, isContainer, parentFields, childName, childFields, needsVariants);
907
837
  writeBlockJSON(dir, blockName, blockJSON, dryRun);
908
838
 
909
839
  // Summary
910
840
  log.blank(); log.divider();
911
841
  const prefix = dryRun ? `${c.yellow}~ DRY RUN${c.reset} ` : `${c.green}✔${c.reset} `;
912
- const typeLabel = isContainer && useTabs ? `container with tabs → ${childName}${useExistingChild ? ' (existing)' : ''}`
913
- : isContainer ? `container → ${childName}${useExistingChild ? ' (existing)' : ''}`
914
- : isMultiRow ? 'multi-row'
842
+ const typeLabel = isContainer && useTabs ? `container with tabs → ${childName}`
843
+ : isContainer ? `container → ${childName}`
915
844
  : useTabs ? 'simple with tabs'
916
845
  : 'simple';
917
846
  console.log(`\n ${prefix}${c.bold}"${blockName}" ${dryRun ? 'would be' : 'scaffolded!'} (${typeLabel})${c.reset}\n`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aem-eds-cli",
3
- "version": "1.1.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"