aem-eds-cli 1.0.0 → 1.1.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 +127 -21
  2. package/package.json +1 -1
package/bin/aem-eds.js CHANGED
@@ -542,6 +542,43 @@ async function collectValidation(rl, field, indent = '') {
542
542
  log.blank();
543
543
  }
544
544
 
545
+ // ─── Field collector with tab grouping ───────────────────────────────────────
546
+ async function collectFieldsWithTabs(rl, context) {
547
+ log.info(`Define tab groups for ${c.bold}${context}${c.reset}. Each tab appears as a panel section in UE.`);
548
+ log.blank();
549
+
550
+ const allFields = [];
551
+ let tabCount = 0;
552
+
553
+ while (true) {
554
+ tabCount++;
555
+ log.divider();
556
+
557
+ const tabLabelIn = (await prompt(rl, `Tab ${tabCount} name (e.g. Media, Links, Body): `)).trim();
558
+ if (!tabLabelIn) { log.warn('Tab name cannot be blank.'); tabCount--; continue; }
559
+
560
+ const tabName = `tab${tabCount}`;
561
+ const tabLabel = tabLabelIn;
562
+
563
+ allFields.push({ name: tabName, label: tabLabel, type: 'tab' });
564
+ log.success(`Tab "${tabLabel}" created — define its fields:`);
565
+ log.blank();
566
+
567
+ const tabFields = await collectFields(rl, `"${tabLabel}" tab`);
568
+ allFields.push(...tabFields);
569
+
570
+ log.blank();
571
+ const addMore = await confirm(rl, `Add another tab?`, false);
572
+ if (!addMore) break;
573
+ log.blank();
574
+ }
575
+
576
+ log.blank();
577
+ log.info(`${tabCount} tab(s) defined with ${allFields.filter(f => f.type !== 'tab').length} total fields.`);
578
+ log.blank();
579
+ return allFields;
580
+ }
581
+
545
582
  // ─── Field collector ──────────────────────────────────────────────────────────
546
583
  async function collectFields(rl, context, depth = 0) {
547
584
  const fields = [];
@@ -631,7 +668,7 @@ async function collectFields(rl, context, depth = 0) {
631
668
  // definitions, models and filters — matching the boilerplate pattern.
632
669
  // `npm run build:json` (merge-json-cli) assembles these into global files.
633
670
 
634
- function genBlockJSON(blockName, isContainer, parentFields, childName, childFields, needsVariants = false) {
671
+ function genBlockJSON(blockName, isContainer, parentFields, childName, childFields, needsVariants = false, useExistingChild = false) {
635
672
  // ── models ───────────────────────────────────────────────────────────────────
636
673
  const models = [];
637
674
  const parentSchema = buildFieldSchema(parentFields);
@@ -648,7 +685,10 @@ function genBlockJSON(blockName, isContainer, parentFields, childName, childFiel
648
685
  }
649
686
 
650
687
  models.push({ id: blockName, fields: parentSchema });
651
- if (isContainer) {
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) {
652
692
  models.push({ id: childName, fields: buildFieldSchema(childFields) });
653
693
  }
654
694
 
@@ -747,32 +787,94 @@ async function cmdCreate(rl, cwd, argName, dryRun) {
747
787
  // Block type
748
788
  log.blank();
749
789
  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}`);
790
+ console.log(` ${c.dim}1.${c.reset} ${c.cyan}Simple${c.reset} ${c.dim}Fixed fields — hero, banner, teaser${c.reset}`);
791
+ 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 repeatingslider, 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}`);
753
795
  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';
796
+ 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'
800
+ || typeIn.toLowerCase() === 'simple with tabs'
801
+ || typeIn.toLowerCase() === 'container with tabs';
757
802
  log.blank();
758
803
 
759
- // Parent fields
804
+ // Parent fields — with or without tab grouping based on block type
760
805
  log.section(isContainer ? `Phase 1 — Parent config fields` : `Define block fields`);
761
- const parentFields = await collectFields(rl, isContainer ? `${blockName} config` : blockName);
806
+ const parentFields = useTabs
807
+ ? await collectFieldsWithTabs(rl, isContainer ? `${blockName} config` : blockName, true)
808
+ : await collectFields(rl, isContainer ? `${blockName} config` : blockName);
762
809
 
763
810
  // Container: child setup
764
- let childName = '', childFields = [];
811
+ let childName = '', childFields = [], useExistingChild = false;
765
812
  if (isContainer) {
766
813
  log.divider();
767
814
  log.section(`Phase 2 — Child item`);
768
- const sugg = `${blockName}-item`;
769
- const childIn = (await prompt(rl, `Child item name [${sugg}]: `)).trim().toLowerCase().replace(/\s+/g, '-');
770
- childName = childIn || sugg;
771
- if (!isValidName(childName)) { log.error(`Invalid child name.`); rl.close(); process.exit(1); }
772
- log.blank();
773
- log.info(`Child: ${c.bold}${childName}${c.reset}`);
774
- log.blank();
775
- childFields = await collectFields(rl, `${childName} fields`);
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
+ }
776
878
  }
777
879
 
778
880
  // Variants prompt — asked for all block types
@@ -801,13 +903,17 @@ async function cmdCreate(rl, cwd, argName, dryRun) {
801
903
  writeFile(path.join(dir, `${blockName}.css`), genBlockCSS(blockName), `blocks/${blockName}/${blockName}.css`, dryRun);
802
904
  writeFile(path.join(dir, 'README.md'), genReadme(blockName, isContainer, parentFields, childName, childFields, needsVariants), `blocks/${blockName}/README.md`, dryRun);
803
905
 
804
- const blockJSON = genBlockJSON(blockName, isContainer, parentFields, childName, childFields, needsVariants);
906
+ const blockJSON = genBlockJSON(blockName, isContainer, parentFields, childName, childFields, needsVariants, useExistingChild);
805
907
  writeBlockJSON(dir, blockName, blockJSON, dryRun);
806
908
 
807
909
  // Summary
808
910
  log.blank(); log.divider();
809
911
  const prefix = dryRun ? `${c.yellow}~ DRY RUN${c.reset} ` : `${c.green}✔${c.reset} `;
810
- const typeLabel = isContainer ? `container → ${childName}` : isMultiRow ? 'multi-row' : 'simple';
912
+ const typeLabel = isContainer && useTabs ? `container with tabs → ${childName}${useExistingChild ? ' (existing)' : ''}`
913
+ : isContainer ? `container → ${childName}${useExistingChild ? ' (existing)' : ''}`
914
+ : isMultiRow ? 'multi-row'
915
+ : useTabs ? 'simple with tabs'
916
+ : 'simple';
811
917
  console.log(`\n ${prefix}${c.bold}"${blockName}" ${dryRun ? 'would be' : 'scaffolded!'} (${typeLabel})${c.reset}\n`);
812
918
  if (!dryRun) {
813
919
  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.1.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"