aem-eds-kit 0.4.16 → 0.4.22

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.
package/dist/bin/eds.js CHANGED
@@ -273,6 +273,178 @@ ${logoArt()}
273
273
  }
274
274
  });
275
275
 
276
+ // src/lib/sandbox/api.ts
277
+ function headers(token) {
278
+ return {
279
+ Authorization: `Bearer ${token}`,
280
+ Accept: "application/vnd.github+json",
281
+ "X-GitHub-Api-Version": "2022-11-28",
282
+ "User-Agent": "aem-eds-kit",
283
+ "Content-Type": "application/json"
284
+ };
285
+ }
286
+ function generateEndpoint(template2) {
287
+ return `${API}/repos/${template2}/generate`;
288
+ }
289
+ async function getUser(token) {
290
+ const res = await fetch(`${API}/user`, { headers: headers(token) });
291
+ if (!res.ok) throw new Error(`GitHub auth failed: ${res.status}`);
292
+ return (await res.json()).login;
293
+ }
294
+ async function repoExists(token, owner, repo) {
295
+ const res = await fetch(`${API}/repos/${owner}/${repo}`, { headers: headers(token) });
296
+ if (res.status === 404) return false;
297
+ if (res.ok) return true;
298
+ throw new Error(`existence check failed: ${res.status} ${await res.text()}`);
299
+ }
300
+ async function createFromTemplate(token, template2, opts) {
301
+ const res = await fetch(generateEndpoint(template2), {
302
+ method: "POST",
303
+ headers: headers(token),
304
+ body: JSON.stringify({
305
+ owner: opts.owner,
306
+ name: opts.name,
307
+ private: !!opts.private,
308
+ include_all_branches: false
309
+ })
310
+ });
311
+ if (!res.ok) throw new Error(`create failed: ${res.status} ${await res.text()}`);
312
+ const r = await res.json();
313
+ return { fullName: r.full_name, htmlUrl: r.html_url, cloneUrl: r.clone_url };
314
+ }
315
+ async function addTopics(token, owner, repo, topics) {
316
+ await fetch(`${API}/repos/${owner}/${repo}/topics`, {
317
+ method: "PUT",
318
+ headers: headers(token),
319
+ body: JSON.stringify({ names: topics })
320
+ });
321
+ }
322
+ async function deleteRepo(token, owner, repo) {
323
+ const res = await fetch(`${API}/repos/${owner}/${repo}`, {
324
+ method: "DELETE",
325
+ headers: headers(token)
326
+ });
327
+ if (!res.ok && res.status !== 204) {
328
+ throw new Error(`delete failed: ${res.status} ${await res.text()}`);
329
+ }
330
+ }
331
+ async function searchByTopic(token, login, topic) {
332
+ const q = encodeURIComponent(`user:${login} topic:${topic} fork:true`);
333
+ const res = await fetch(`${API}/search/repositories?q=${q}&per_page=100`, {
334
+ headers: headers(token)
335
+ });
336
+ if (!res.ok) throw new Error(`search failed: ${res.status}`);
337
+ const data = await res.json();
338
+ return data.items.map((i) => i.full_name);
339
+ }
340
+ var API;
341
+ var init_api = __esm({
342
+ "src/lib/sandbox/api.ts"() {
343
+ "use strict";
344
+ init_esm_shims();
345
+ API = "https://api.github.com";
346
+ }
347
+ });
348
+
349
+ // src/lib/sandbox/auth.ts
350
+ import { existsSync } from "fs";
351
+ import { mkdir, readFile, rm, writeFile } from "fs/promises";
352
+ import os from "os";
353
+ import path2 from "path";
354
+ async function getToken() {
355
+ const env = process.env.GITHUB_TOKEN || process.env.GH_TOKEN;
356
+ if (env) return env;
357
+ if (existsSync(TOKEN_FILE)) {
358
+ try {
359
+ return JSON.parse(await readFile(TOKEN_FILE, "utf-8")).token ?? null;
360
+ } catch {
361
+ return null;
362
+ }
363
+ }
364
+ return null;
365
+ }
366
+ async function saveToken(token) {
367
+ await mkdir(CONFIG_DIR, { recursive: true, mode: 448 });
368
+ await writeFile(TOKEN_FILE, `${JSON.stringify({ token }, null, 2)}
369
+ `, { mode: 384 });
370
+ }
371
+ async function clearToken() {
372
+ if (existsSync(TOKEN_FILE)) await rm(TOKEN_FILE);
373
+ }
374
+ async function startDeviceFlow(scopes) {
375
+ const res = await fetch("https://github.com/login/device/code", {
376
+ method: "POST",
377
+ headers: { Accept: "application/json", "Content-Type": "application/json" },
378
+ body: JSON.stringify({ client_id: CLIENT_ID, scope: scopes.join(" ") })
379
+ });
380
+ if (!res.ok) throw new Error(`device code request failed: ${res.status}`);
381
+ return await res.json();
382
+ }
383
+ async function pollDeviceToken(deviceCode, interval, expiresIn) {
384
+ let wait = Math.max(interval, 1);
385
+ const deadline = Date.now() + expiresIn * 1e3;
386
+ while (Date.now() < deadline) {
387
+ await new Promise((r) => setTimeout(r, wait * 1e3));
388
+ const res = await fetch("https://github.com/login/oauth/access_token", {
389
+ method: "POST",
390
+ headers: { Accept: "application/json", "Content-Type": "application/json" },
391
+ body: JSON.stringify({
392
+ client_id: CLIENT_ID,
393
+ device_code: deviceCode,
394
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code"
395
+ })
396
+ });
397
+ const data = await res.json();
398
+ if (data.access_token) return data.access_token;
399
+ if (data.error === "slow_down") wait += 5;
400
+ else if (data.error !== "authorization_pending") {
401
+ throw new Error(data.error ?? "device flow failed");
402
+ }
403
+ }
404
+ throw new Error("device flow timed out");
405
+ }
406
+ var CONFIG_DIR, TOKEN_FILE, CLIENT_ID;
407
+ var init_auth = __esm({
408
+ "src/lib/sandbox/auth.ts"() {
409
+ "use strict";
410
+ init_esm_shims();
411
+ CONFIG_DIR = path2.join(os.homedir(), ".eds");
412
+ TOKEN_FILE = path2.join(CONFIG_DIR, "github.json");
413
+ CLIENT_ID = process.env.EDS_GITHUB_CLIENT_ID ?? "";
414
+ }
415
+ });
416
+
417
+ // src/lib/sandbox/github.ts
418
+ function previewUrl(owner, repo, ref = "main") {
419
+ return `https://${ref}--${repo}--${owner}.aem.page/`;
420
+ }
421
+ function codeSyncInstallUrl() {
422
+ return "https://github.com/apps/aem-code-sync/installations/new";
423
+ }
424
+ function createArgs(repoRef, opts) {
425
+ return [
426
+ "repo",
427
+ "create",
428
+ repoRef,
429
+ "--template",
430
+ opts.template,
431
+ opts.private ? "--private" : "--public",
432
+ ...opts.clone ? ["--clone"] : []
433
+ ];
434
+ }
435
+ var SANDBOX_TOPIC, BOILERPLATES;
436
+ var init_github = __esm({
437
+ "src/lib/sandbox/github.ts"() {
438
+ "use strict";
439
+ init_esm_shims();
440
+ SANDBOX_TOPIC = "eds-sandbox";
441
+ BOILERPLATES = {
442
+ document: "adobe/aem-boilerplate",
443
+ ue: "adobe/aem-boilerplate-xwalk"
444
+ };
445
+ }
446
+ });
447
+
276
448
  // src/lib/schemas.ts
277
449
  import { z } from "zod";
278
450
  var figmaUrlSchema, edsConfigSchema, edsProjectSchema, edsMetaSchema, kebabCaseRegex;
@@ -315,26 +487,26 @@ var init_schemas = __esm({
315
487
  });
316
488
 
317
489
  // src/lib/project.ts
318
- import { existsSync } from "fs";
319
- import path2 from "path";
490
+ import { existsSync as existsSync2 } from "fs";
491
+ import path3 from "path";
320
492
  function findProjectRoot(startDir = process.cwd()) {
321
- let dir = path2.resolve(startDir);
322
- const { root } = path2.parse(dir);
493
+ let dir = path3.resolve(startDir);
494
+ const { root } = path3.parse(dir);
323
495
  while (dir !== root) {
324
- if (ROOT_MARKERS.some((m) => existsSync(path2.join(dir, m)))) {
496
+ if (ROOT_MARKERS.some((m) => existsSync2(path3.join(dir, m)))) {
325
497
  return dir;
326
498
  }
327
- dir = path2.dirname(dir);
499
+ dir = path3.dirname(dir);
328
500
  }
329
- if (ROOT_MARKERS.some((m) => existsSync(path2.join(root, m)))) return root;
501
+ if (ROOT_MARKERS.some((m) => existsSync2(path3.join(root, m)))) return root;
330
502
  return void 0;
331
503
  }
332
504
  function detectProject(rootDir) {
333
505
  return edsProjectSchema.parse({
334
506
  root: rootDir,
335
- hasFstab: existsSync(path2.join(rootDir, "fstab.yaml")),
336
- hasBlocks: existsSync(path2.join(rootDir, "blocks")),
337
- hasHeadHtml: existsSync(path2.join(rootDir, "head.html"))
507
+ hasFstab: existsSync2(path3.join(rootDir, "fstab.yaml")),
508
+ hasBlocks: existsSync2(path3.join(rootDir, "blocks")),
509
+ hasHeadHtml: existsSync2(path3.join(rootDir, "head.html"))
338
510
  });
339
511
  }
340
512
  var ROOT_MARKERS;
@@ -355,6 +527,206 @@ var init_project = __esm({
355
527
  }
356
528
  });
357
529
 
530
+ // src/lib/scaffold/ci.ts
531
+ import { existsSync as existsSync3 } from "fs";
532
+ import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
533
+ import path4 from "path";
534
+ async function writeCiWorkflow(projectRoot) {
535
+ const file = path4.join(projectRoot, ".github", "workflows", "eds.yml");
536
+ if (existsSync3(file)) return { path: CI_WORKFLOW_PATH, created: false };
537
+ await mkdir2(path4.dirname(file), { recursive: true });
538
+ await writeFile2(file, CI_WORKFLOW);
539
+ return { path: CI_WORKFLOW_PATH, created: true };
540
+ }
541
+ var CI_WORKFLOW_PATH, CI_WORKFLOW;
542
+ var init_ci = __esm({
543
+ "src/lib/scaffold/ci.ts"() {
544
+ "use strict";
545
+ init_esm_shims();
546
+ CI_WORKFLOW_PATH = ".github/workflows/eds.yml";
547
+ CI_WORKFLOW = `name: EDS checks
548
+
549
+ # Generated by aem-eds-kit (\`eds scaffold ci\`). Runs the project's own
550
+ # lint/build plus the eds audits as a gatekeeper on every PR and push to main.
551
+ on:
552
+ pull_request:
553
+ push:
554
+ branches: [main]
555
+
556
+ jobs:
557
+ checks:
558
+ runs-on: ubuntu-latest
559
+ steps:
560
+ - uses: actions/checkout@v4
561
+ - uses: actions/setup-node@v4
562
+ with:
563
+ node-version: 20
564
+
565
+ # Install project dependencies when the repo has them.
566
+ - name: Install dependencies
567
+ run: |
568
+ if [ -f package-lock.json ]; then
569
+ npm ci
570
+ elif [ -f package.json ]; then
571
+ npm install
572
+ fi
573
+
574
+ # The project's own scripts, if defined (skipped otherwise).
575
+ - name: Lint
576
+ if: hashFiles('package.json') != ''
577
+ run: npm run lint --if-present
578
+ - name: Build
579
+ if: hashFiles('package.json') != ''
580
+ run: npm run build --if-present
581
+
582
+ # aem-eds-kit audits - run via npx, no install needed. Each exits
583
+ # non-zero on failures, so a bad PR fails the check.
584
+ - name: Project health (doctor)
585
+ run: npx --yes aem-eds-kit@latest doctor
586
+ - name: Loading & LCP budget
587
+ run: npx --yes aem-eds-kit@latest audit loading
588
+ - name: Security & exploit scan
589
+ run: npx --yes aem-eds-kit@latest audit security
590
+ `;
591
+ }
592
+ });
593
+
594
+ // src/lib/scaffold/env.ts
595
+ import { existsSync as existsSync4 } from "fs";
596
+ import { readFile as readFile2, writeFile as writeFile3 } from "fs/promises";
597
+ import path5 from "path";
598
+ function buildEnv(opts = {}) {
599
+ const open = opts.open ?? "/";
600
+ const port = opts.port ?? 3007;
601
+ const pagesUrl = opts.pagesUrl ?? "";
602
+ return `AEM_OPEN=${open}
603
+ AEM_PORT=${port}
604
+ AEM_PAGES_URL=${pagesUrl}
605
+ `;
606
+ }
607
+ async function ensureEnvIgnored(projectRoot) {
608
+ const file = path5.join(projectRoot, ".gitignore");
609
+ let current = "";
610
+ if (existsSync4(file)) current = await readFile2(file, "utf-8");
611
+ if (current.split(/\r?\n/).some((l) => l.trim() === ".env")) return false;
612
+ const prefix = current && !current.endsWith("\n") ? "\n" : "";
613
+ await writeFile3(file, `${current}${prefix}.env
614
+ `);
615
+ return true;
616
+ }
617
+ async function writeEnv(projectRoot, opts = {}) {
618
+ const file = path5.join(projectRoot, ".env");
619
+ const created = !existsSync4(file);
620
+ if (created) await writeFile3(file, buildEnv(opts));
621
+ const gitignoreUpdated = await ensureEnvIgnored(projectRoot);
622
+ return { path: ".env", created, gitignoreUpdated };
623
+ }
624
+ var init_env = __esm({
625
+ "src/lib/scaffold/env.ts"() {
626
+ "use strict";
627
+ init_esm_shims();
628
+ }
629
+ });
630
+
631
+ // src/lib/scaffold/fstab.ts
632
+ import { writeFile as writeFile4 } from "fs/promises";
633
+ import path6 from "path";
634
+ function needsFstab(a) {
635
+ return a !== "ue";
636
+ }
637
+ function daMountpoint(org, site) {
638
+ return `https://content.da.live/${org}/${site}/`;
639
+ }
640
+ function fstabYaml(mountpoint) {
641
+ return `mountpoints:
642
+ /: ${mountpoint}
643
+ `;
644
+ }
645
+ async function writeFstab(projectRoot, mountpoint) {
646
+ await writeFile4(path6.join(projectRoot, "fstab.yaml"), fstabYaml(mountpoint));
647
+ return "fstab.yaml";
648
+ }
649
+ function pathsJson(site) {
650
+ return `${JSON.stringify(
651
+ {
652
+ mappings: [`/content/${site}/:/`],
653
+ includes: [`/content/${site}/`]
654
+ },
655
+ null,
656
+ 2
657
+ )}
658
+ `;
659
+ }
660
+ async function writePaths(projectRoot, site) {
661
+ await writeFile4(path6.join(projectRoot, "paths.json"), pathsJson(site));
662
+ return "paths.json";
663
+ }
664
+ var AUTHORING_NAMES;
665
+ var init_fstab = __esm({
666
+ "src/lib/scaffold/fstab.ts"() {
667
+ "use strict";
668
+ init_esm_shims();
669
+ AUTHORING_NAMES = {
670
+ ue: "Universal Editor (crosswalk)",
671
+ da: "Document Authoring (da.live)",
672
+ gdrive: "Document Authoring (Google Drive)",
673
+ sharepoint: "Document Authoring (SharePoint)"
674
+ };
675
+ }
676
+ });
677
+
678
+ // src/lib/scaffold/xwalk.ts
679
+ import { existsSync as existsSync5 } from "fs";
680
+ import { writeFile as writeFile5 } from "fs/promises";
681
+ import path7 from "path";
682
+ function buildXwalk() {
683
+ return `${JSON.stringify(XWALK_CONFIG, null, 2)}
684
+ `;
685
+ }
686
+ async function writeXwalk(projectRoot) {
687
+ const file = path7.join(projectRoot, "xwalk.json");
688
+ if (existsSync5(file)) return { path: "xwalk.json", created: false };
689
+ await writeFile5(file, buildXwalk());
690
+ return { path: "xwalk.json", created: true };
691
+ }
692
+ var XWALK_CONFIG;
693
+ var init_xwalk = __esm({
694
+ "src/lib/scaffold/xwalk.ts"() {
695
+ "use strict";
696
+ init_esm_shims();
697
+ XWALK_CONFIG = {
698
+ public: {
699
+ xwalk: {
700
+ "multi-field": {
701
+ enabled: true
702
+ }
703
+ }
704
+ }
705
+ };
706
+ }
707
+ });
708
+
709
+ // src/lib/ue/link.ts
710
+ function parseRemote(url) {
711
+ const m = url.trim().match(/github\.com[:/]([^/]+)\/(.+?)(?:\.git)?\/?$/i);
712
+ return m ? { owner: m[1], repo: m[2] } : null;
713
+ }
714
+ function previewHost(owner, repo, ref = "main") {
715
+ return `${ref}--${repo}--${owner}.aem.page`;
716
+ }
717
+ function editorLink({ host, path: path36 = "/", org }) {
718
+ const cleanHost = host.replace(/^https?:\/\//, "").replace(/\/+$/, "");
719
+ const p = path36.startsWith("/") ? path36 : `/${path36}`;
720
+ const orgSeg = org ? `@${org}/` : "";
721
+ return `https://experience.adobe.com/#/${orgSeg}aem/editor/canvas/${cleanHost}${p}`;
722
+ }
723
+ var init_link = __esm({
724
+ "src/lib/ue/link.ts"() {
725
+ "use strict";
726
+ init_esm_shims();
727
+ }
728
+ });
729
+
358
730
  // src/lib/scaffold/blocks.ts
359
731
  function json(value) {
360
732
  return `${JSON.stringify(value, null, 2)}
@@ -676,65 +1048,263 @@ export default function decorate(block) {
676
1048
  }
677
1049
  });
678
1050
 
679
- // src/lib/scaffold/ue.ts
680
- function fieldReferenceFields() {
681
- return [
682
- { component: "tab", name: "basics", label: "Basics" },
683
- { component: "text", valueType: "string", name: "text", label: "Text (single line)" },
684
- {
685
- component: "textarea",
686
- valueType: "string",
687
- name: "textarea",
688
- label: "Textarea (multi-line)"
689
- },
690
- { component: "richtext", valueType: "string", name: "richtext", value: "", label: "Rich text" },
691
- { component: "number", valueType: "number", name: "number", label: "Number" },
692
- { component: "boolean", valueType: "boolean", name: "boolean", value: false, label: "Boolean" },
693
- { component: "date-time", valueType: "date", name: "date", label: "Date / time" },
694
- { component: "tab", name: "selection", label: "Selection" },
695
- {
696
- component: "select",
697
- valueType: "string",
698
- name: "select",
699
- label: "Select",
700
- value: "a",
701
- options: [
702
- { name: "Option A", value: "a" },
703
- { name: "Option B", value: "b" }
704
- ]
705
- },
706
- {
707
- component: "radio-group",
708
- valueType: "string",
709
- name: "radio",
710
- label: "Radio group",
711
- value: "x",
712
- options: [
713
- { name: "X", value: "x" },
714
- { name: "Y", value: "y" }
715
- ]
716
- },
717
- {
718
- component: "multiselect",
719
- valueType: "string",
720
- name: "classes",
721
- label: "Multiselect (CSS classes)",
722
- options: [
723
- { name: "Highlight", value: "highlight" },
724
- { name: "Dark", value: "dark" }
725
- ]
726
- },
727
- {
728
- component: "checkbox-group",
729
- valueType: "string[]",
730
- name: "toggles",
731
- label: "Checkbox group",
732
- options: [
733
- { name: "Show title", value: "show-title" },
734
- { name: "Show image", value: "show-image" }
735
- ]
736
- },
737
- { component: "tab", name: "media", label: "Media & content" },
1051
+ // src/lib/scaffold/helpers.ts
1052
+ import { existsSync as existsSync6 } from "fs";
1053
+ import { mkdir as mkdir3, writeFile as writeFile6 } from "fs/promises";
1054
+ import path8 from "path";
1055
+ function helperById(id) {
1056
+ return HELPERS.find((h) => h.id === id);
1057
+ }
1058
+ function resolveHelpers(ids) {
1059
+ const want = /* @__PURE__ */ new Set();
1060
+ const add = (id) => {
1061
+ if (want.has(id)) return;
1062
+ const h = helperById(id);
1063
+ if (!h) return;
1064
+ for (const dep of h.deps ?? []) add(dep);
1065
+ want.add(id);
1066
+ };
1067
+ for (const id of ids) add(id);
1068
+ return HELPERS.filter((h) => want.has(h.id)).map((h) => h.id);
1069
+ }
1070
+ function buildHelpers(ids) {
1071
+ const resolved = resolveHelpers(ids);
1072
+ const body = resolved.map((id) => helperById(id)).filter((h) => !!h).map((h) => h.code).join("\n\n");
1073
+ return `// scripts/utils.js - common EDS helpers (generated by \`eds scaffold helpers\`).
1074
+
1075
+ ${body}
1076
+ `;
1077
+ }
1078
+ async function writeHelpers(projectRoot, ids = ALL_HELPER_IDS) {
1079
+ const resolved = resolveHelpers(ids);
1080
+ const file = path8.join(projectRoot, "scripts", "utils.js");
1081
+ if (existsSync6(file)) return { path: HELPERS_PATH, created: false, helpers: resolved };
1082
+ await mkdir3(path8.dirname(file), { recursive: true });
1083
+ await writeFile6(file, buildHelpers(ids));
1084
+ return { path: HELPERS_PATH, created: true, helpers: resolved };
1085
+ }
1086
+ var HELPERS_PATH, HELPERS, ALL_HELPER_IDS;
1087
+ var init_helpers = __esm({
1088
+ "src/lib/scaffold/helpers.ts"() {
1089
+ "use strict";
1090
+ init_esm_shims();
1091
+ HELPERS_PATH = "scripts/utils.js";
1092
+ HELPERS = [
1093
+ {
1094
+ id: "getMetadata",
1095
+ label: "getMetadata (read a page meta value)",
1096
+ code: `/**
1097
+ * Reads a page metadata value by name (meta[name] or meta[property]).
1098
+ * @param {string} name The metadata name.
1099
+ * @returns {string} The value, or an empty string.
1100
+ */
1101
+ export function getMetadata(name) {
1102
+ const meta =
1103
+ document.querySelector(\`meta[name="\${name}"]\`) ||
1104
+ document.querySelector(\`meta[property="\${name}"]\`);
1105
+ return meta ? meta.content : '';
1106
+ }`
1107
+ },
1108
+ {
1109
+ id: "toCamelCase",
1110
+ label: "toCamelCase (dash/underscore/space -> camelCase)",
1111
+ code: `/**
1112
+ * Converts a string to camelCase, handling dashes, underscores and spaces.
1113
+ * @param {string} value The string to convert.
1114
+ * @returns {string} The camelCased string.
1115
+ */
1116
+ export function toCamelCase(value) {
1117
+ return String(value || '')
1118
+ .trim()
1119
+ .toLowerCase()
1120
+ .replace(/[-_\\s]+(.)/g, (_, char) => char.toUpperCase());
1121
+ }`
1122
+ },
1123
+ {
1124
+ id: "getLanguageRootPath",
1125
+ label: "getLanguageRootPath (e.g. /en, /en-us)",
1126
+ code: `/**
1127
+ * Returns the language root of a path, e.g. '/en' or '/en-us' ('' if none).
1128
+ * @param {string} path The page path.
1129
+ * @returns {string} The language root prefix.
1130
+ */
1131
+ export function getLanguageRootPath(path) {
1132
+ const match = String(path || '').match(/^\\/[a-z]{2}(?:-[a-z]{2})?(?=\\/|$)/i);
1133
+ return match ? match[0] : '';
1134
+ }`
1135
+ },
1136
+ {
1137
+ id: "getPagePath",
1138
+ label: "getPagePath (path without content root)",
1139
+ deps: ["getLanguageRootPath"],
1140
+ code: `// Content root stripped from page paths. Set this if your site is mounted under
1141
+ // a sub-path (e.g. '/content/my-site'); leave empty for a root-mounted site.
1142
+ const ROOT_PATH = '';
1143
+
1144
+ /**
1145
+ * Returns the page path without the content root; \`clean\` drops the language root.
1146
+ * @param {boolean} [clean=false] Also strip the language root.
1147
+ * @returns {string} The page path or 'NA' for the language root.
1148
+ */
1149
+ export function getPagePath({ clean = false } = {}) {
1150
+ const path = window.location.pathname.replace(ROOT_PATH, '').replace(/\\.html$/i, '');
1151
+
1152
+ if (clean) {
1153
+ const langRoot = getLanguageRootPath(path);
1154
+ const cleaned = path.startsWith(langRoot) ? path.slice(langRoot.length) : path;
1155
+ return cleaned || 'NA';
1156
+ }
1157
+
1158
+ return path || 'NA';
1159
+ }`
1160
+ },
1161
+ {
1162
+ id: "getSiteArea",
1163
+ label: "getSiteArea (blog / products / home)",
1164
+ deps: ["getPagePath"],
1165
+ code: `/**
1166
+ * Returns the top-level area of the site from the current path - e.g. 'blog' or
1167
+ * 'products', and 'home' at the site root. Uses the language-cleaned path, so you
1168
+ * can tell whether you're on the blog or somewhere else.
1169
+ * @returns {string} The site area.
1170
+ */
1171
+ export function getSiteArea() {
1172
+ const path = getPagePath({ clean: true });
1173
+ if (!path || path === 'NA') return 'home';
1174
+ return path.split('/').filter(Boolean)[0] || 'home';
1175
+ }`
1176
+ },
1177
+ {
1178
+ id: "getEnvironment",
1179
+ label: "getEnvironment (dev / stage / prod)",
1180
+ code: `/**
1181
+ * Detects the current environment from the hostname.
1182
+ * @returns {'NA'|'dev'|'stage'|'prod'} The environment name.
1183
+ */
1184
+ export function getEnvironment() {
1185
+ const { hostname } = window.location;
1186
+ if (hostname.includes('author')) return 'NA';
1187
+ if (hostname === 'localhost') return 'dev';
1188
+ const repo = hostname.match(/^[^.]+--([^.]+)--[^.]+\\.aem\\.(?:page|live)$/)?.[1] || '';
1189
+ if (repo.includes('dev')) return 'dev';
1190
+ if (repo.includes('stage')) return 'stage';
1191
+ return 'prod';
1192
+ }`
1193
+ },
1194
+ {
1195
+ id: "getContentTopic",
1196
+ label: "getContentTopic (cq-tags content-topic values)",
1197
+ deps: ["getMetadata"],
1198
+ code: `/**
1199
+ * Reads all content-topic values from the page's cq-tags metadata.
1200
+ * @returns {string[]} The topics (part after \`content-topic/\`), empty if none.
1201
+ */
1202
+ export function getContentTopic() {
1203
+ return getMetadata('cq-tags')
1204
+ .split(',')
1205
+ .map((t) => t.trim())
1206
+ .filter((t) => t.includes('content-topic/'))
1207
+ .map((t) => t.split('content-topic/')[1]);
1208
+ }`
1209
+ },
1210
+ {
1211
+ id: "isUEEdit",
1212
+ label: "isUEEdit (Universal Editor edit mode)",
1213
+ code: `/**
1214
+ * Checks whether the current page is opened in the Universal Editor Edit mode.
1215
+ * @returns {boolean} True in Universal Editor Edit mode.
1216
+ */
1217
+ export function isUEEdit() {
1218
+ return document.documentElement.classList.contains('adobe-ue-edit');
1219
+ }`
1220
+ },
1221
+ {
1222
+ id: "isUEPreview",
1223
+ label: "isUEPreview (Universal Editor preview mode)",
1224
+ code: `/**
1225
+ * Checks whether the current page is opened in the Universal Editor Preview mode.
1226
+ * @returns {boolean} True in Universal Editor Preview mode.
1227
+ */
1228
+ export function isUEPreview() {
1229
+ return document.documentElement.classList.contains('adobe-ue-preview');
1230
+ }`
1231
+ },
1232
+ {
1233
+ id: "isUE",
1234
+ label: "isUE (Universal Editor, any mode)",
1235
+ deps: ["isUEEdit", "isUEPreview"],
1236
+ code: `/**
1237
+ * Checks whether the current page is opened in the Universal Editor (any mode).
1238
+ * @returns {boolean} True in the Universal Editor.
1239
+ */
1240
+ export function isUE() {
1241
+ return isUEEdit() || isUEPreview();
1242
+ }`
1243
+ }
1244
+ ];
1245
+ ALL_HELPER_IDS = HELPERS.map((h) => h.id);
1246
+ }
1247
+ });
1248
+
1249
+ // src/lib/scaffold/ue.ts
1250
+ function fieldReferenceFields() {
1251
+ return [
1252
+ { component: "tab", name: "basics", label: "Basics" },
1253
+ { component: "text", valueType: "string", name: "text", label: "Text (single line)" },
1254
+ {
1255
+ component: "textarea",
1256
+ valueType: "string",
1257
+ name: "textarea",
1258
+ label: "Textarea (multi-line)"
1259
+ },
1260
+ { component: "richtext", valueType: "string", name: "richtext", value: "", label: "Rich text" },
1261
+ { component: "number", valueType: "number", name: "number", label: "Number" },
1262
+ { component: "boolean", valueType: "boolean", name: "boolean", value: false, label: "Boolean" },
1263
+ { component: "date-time", valueType: "date", name: "date", label: "Date / time" },
1264
+ { component: "tab", name: "selection", label: "Selection" },
1265
+ {
1266
+ component: "select",
1267
+ valueType: "string",
1268
+ name: "select",
1269
+ label: "Select",
1270
+ value: "a",
1271
+ options: [
1272
+ { name: "Option A", value: "a" },
1273
+ { name: "Option B", value: "b" }
1274
+ ]
1275
+ },
1276
+ {
1277
+ component: "radio-group",
1278
+ valueType: "string",
1279
+ name: "radio",
1280
+ label: "Radio group",
1281
+ value: "x",
1282
+ options: [
1283
+ { name: "X", value: "x" },
1284
+ { name: "Y", value: "y" }
1285
+ ]
1286
+ },
1287
+ {
1288
+ component: "multiselect",
1289
+ valueType: "string",
1290
+ name: "classes",
1291
+ label: "Multiselect (CSS classes)",
1292
+ options: [
1293
+ { name: "Highlight", value: "highlight" },
1294
+ { name: "Dark", value: "dark" }
1295
+ ]
1296
+ },
1297
+ {
1298
+ component: "checkbox-group",
1299
+ valueType: "string[]",
1300
+ name: "toggles",
1301
+ label: "Checkbox group",
1302
+ options: [
1303
+ { name: "Show title", value: "show-title" },
1304
+ { name: "Show image", value: "show-image" }
1305
+ ]
1306
+ },
1307
+ { component: "tab", name: "media", label: "Media & content" },
738
1308
  { component: "reference", valueType: "string", name: "image", label: "Image (DAM asset)" },
739
1309
  { component: "text", valueType: "string", name: "imageAlt", label: "Image alt" },
740
1310
  { component: "aem-content", name: "link", label: "Link (page / URL)" },
@@ -933,1025 +1503,1158 @@ export default function decorate(block) {
933
1503
  }
934
1504
  });
935
1505
 
936
- // src/lib/template/authoring.ts
937
- import { existsSync as existsSync2 } from "fs";
938
- import { readFile } from "fs/promises";
939
- import path3 from "path";
940
- import { parse as parseYaml } from "yaml";
941
- async function detectAuthoring(projectRoot) {
942
- const fstab = path3.join(projectRoot, "fstab.yaml");
943
- if (existsSync2(fstab)) {
944
- try {
945
- const parsed = parseYaml(await readFile(fstab, "utf-8")) ?? {};
946
- const mp = parsed.mountpoints?.["/"];
947
- const url = typeof mp === "string" ? mp : mp?.url ?? "";
948
- const da = url.match(/content\.da\.live\/([^/]+)\/([^/]+)/);
949
- if (da) return { model: "da", org: da[1], site: da[2], mountpoint: url };
950
- if (/drive\.google\.com/.test(url)) return { model: "gdrive", mountpoint: url };
951
- if (/sharepoint\.com|-my\.sharepoint/.test(url))
952
- return { model: "sharepoint", mountpoint: url };
953
- return { model: "document", mountpoint: url };
954
- } catch {
955
- }
956
- }
957
- return { model: "ue" };
958
- }
959
- var init_authoring = __esm({
960
- "src/lib/template/authoring.ts"() {
961
- "use strict";
962
- init_esm_shims();
963
- }
964
- });
965
-
966
- // src/lib/template/da.ts
967
- function daSourceUrl(org, site, path30) {
968
- const clean = path30.replace(/^\/+/, "").replace(/\.html$/i, "");
969
- return `https://admin.da.live/source/${org}/${site}/${clean}.html`;
970
- }
971
- function daEditUrl(org, site, path30) {
972
- const clean = path30.replace(/^\/+/, "").replace(/\.html$/i, "");
973
- return `https://da.live/edit#/${org}/${site}/${clean}`;
974
- }
975
- async function pushToDa(org, site, path30, html, token) {
976
- const url = daSourceUrl(org, site, path30);
977
- const form = new FormData();
978
- form.append("data", new Blob([html], { type: "text/html" }), "content.html");
979
- const headers = {};
980
- if (token) headers.Authorization = `Bearer ${token}`;
981
- const res = await fetch(url, { method: "POST", body: form, headers });
982
- if (!res.ok) {
983
- throw new Error(`DA push failed: ${res.status} ${res.statusText}`);
984
- }
985
- return url;
986
- }
987
- var init_da = __esm({
988
- "src/lib/template/da.ts"() {
989
- "use strict";
990
- init_esm_shims();
991
- }
992
- });
993
-
994
- // src/lib/template/page.ts
995
- function blockDiv(name) {
996
- return ` <div class="${name}">
997
- <div>
998
- <div>${name} content</div>
999
- </div>
1000
- </div>`;
1001
- }
1002
- function metadataBlock(opts) {
1003
- const rows = [
1004
- ` <div><div>Title</div><div>${opts.title}</div></div>`,
1005
- ` <div><div>Description</div><div>${opts.description ?? ""}</div></div>`
1006
- ];
1007
- if (opts.area) rows.push(` <div><div>Template</div><div>${opts.area}</div></div>`);
1008
- return ` <div class="metadata">
1009
- ${rows.join("\n")}
1010
- </div>`;
1011
- }
1012
- function buildPageHtml(opts) {
1013
- const blocks = opts.blocks ?? [];
1014
- const intro = ` <div>
1015
- <h1>${opts.title}</h1>
1016
- <p>${opts.description ?? "Intro copy goes here."}</p>
1017
- </div>`;
1018
- const blockSections = blocks.map((b) => ` <div>
1019
- ${blockDiv(b)}
1020
- </div>`);
1021
- const metaSection = ` <div>
1022
- ${metadataBlock(opts)}
1023
- </div>`;
1024
- return `<body>
1025
- <header></header>
1026
- <main>
1027
- ${[intro, ...blockSections, metaSection].join("\n")}
1028
- </main>
1029
- <footer></footer>
1030
- </body>
1031
- `;
1032
- }
1033
- var init_page = __esm({
1034
- "src/lib/template/page.ts"() {
1035
- "use strict";
1036
- init_esm_shims();
1037
- }
1038
- });
1039
-
1040
- // src/commands/template.ts
1041
- var template_exports = {};
1042
- __export(template_exports, {
1043
- templateNew: () => templateNew
1506
+ // src/commands/scaffold.ts
1507
+ var scaffold_exports = {};
1508
+ __export(scaffold_exports, {
1509
+ scaffoldBlocks: () => scaffoldBlocks,
1510
+ scaffoldCi: () => scaffoldCi,
1511
+ scaffoldEnv: () => scaffoldEnv,
1512
+ scaffoldHelpers: () => scaffoldHelpers,
1513
+ scaffoldInteractive: () => scaffoldInteractive,
1514
+ scaffoldUe: () => scaffoldUe,
1515
+ scaffoldXwalk: () => scaffoldXwalk
1044
1516
  });
1045
- import { existsSync as existsSync3 } from "fs";
1046
- import { mkdir, readFile as readFile2, readdir, writeFile } from "fs/promises";
1047
- import path4 from "path";
1048
- import chalk3 from "chalk";
1049
- async function projectBlocks(projectRoot) {
1050
- const dir = path4.join(projectRoot, "blocks");
1051
- if (!existsSync3(dir)) return [];
1052
- return (await readdir(dir, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name);
1053
- }
1054
- async function readEdsrcAdmin(projectRoot) {
1055
- const rc = path4.join(projectRoot, ".edsrc.json");
1056
- if (!existsSync3(rc)) return {};
1517
+ import { existsSync as existsSync7 } from "fs";
1518
+ import { mkdir as mkdir4, readFile as readFile3, writeFile as writeFile7 } from "fs/promises";
1519
+ import path9 from "path";
1520
+ async function readJson(file) {
1521
+ if (!existsSync7(file)) return null;
1057
1522
  try {
1058
- return JSON.parse(await readFile2(rc, "utf-8")).admin ?? {};
1523
+ return JSON.parse(await readFile3(file, "utf-8"));
1059
1524
  } catch {
1060
- return {};
1525
+ return null;
1061
1526
  }
1062
1527
  }
1063
- async function registerBlocks(projectRoot, blocks) {
1064
- if (!blocks.length) return false;
1065
- const file = path4.join(projectRoot, "component-filters.json");
1066
- let existing = null;
1067
- if (existsSync3(file)) {
1068
- try {
1069
- existing = JSON.parse(await readFile2(file, "utf-8"));
1070
- } catch {
1071
- existing = null;
1072
- }
1073
- }
1074
- const filters = mergeSectionFilter(existing, blocks);
1075
- await writeFile(file, `${JSON.stringify(filters, null, 2)}
1528
+ async function writeJson(file, data) {
1529
+ await writeFile7(file, `${JSON.stringify(data, null, 2)}
1076
1530
  `);
1077
- return true;
1078
1531
  }
1079
- async function templateNew(nameArg, options = {}) {
1532
+ async function scaffoldUe() {
1080
1533
  const projectRoot = findProjectRoot();
1081
1534
  if (!projectRoot) {
1082
1535
  logger.error("Not inside an EDS project (no EDS project markers found).");
1083
1536
  process.exitCode = 1;
1084
1537
  return;
1085
1538
  }
1086
- await logger.logoOnceAnimated("Page template");
1087
- const { input, checkbox, confirm } = await import("@inquirer/prompts");
1088
- const interactive = !options.yes;
1089
- const detected = await detectAuthoring(projectRoot);
1090
- const model2 = options.authoring ?? detected.model;
1091
- const name = slug(nameArg);
1092
- const title = options.title ?? (interactive ? await input({ message: "Page title", default: nameArg }) : nameArg);
1093
- const description = options.description ?? (interactive ? await input({ message: "Description (optional)" }) : "");
1094
- const pagePath = options.path ?? `/templates/${name}`;
1095
- let blocks = options.blocks;
1096
- if (!blocks && interactive) {
1097
- const available = await projectBlocks(projectRoot);
1098
- const choices = (available.length ? available : STANDARD_BLOCKS.map((b) => b.id)).map((b) => ({
1099
- name: b,
1100
- value: b
1101
- }));
1102
- blocks = await checkbox({ message: "Which blocks to seed into the page?", choices });
1103
- }
1104
- const seeded = blocks ?? [];
1105
- const html = buildPageHtml({
1106
- title,
1107
- description: description || void 0,
1108
- blocks: seeded,
1109
- area: options.area
1110
- });
1111
- const localRel = path4.join("templates", `${name}.html`);
1112
- await mkdir(path4.join(projectRoot, "templates"), { recursive: true });
1113
- await writeFile(path4.join(projectRoot, localRel), html);
1114
- logger.info(heading("Page template", `${pagePath} \xB7 ${model2}`));
1115
- logger.info(accentLine(localRel, `${seeded.length} block(s) + metadata`));
1116
- if (model2 === "da") {
1117
- const admin = await readEdsrcAdmin(projectRoot);
1118
- const org = options.org ?? admin.org ?? detected.org;
1119
- const site = options.site ?? admin.site ?? detected.site;
1120
- const wantPush = options.push ?? (interactive ? await confirm({ message: "Push to DA (da.live)?", default: true }) : true);
1121
- if (wantPush) {
1122
- if (!org || !site) {
1123
- logger.warn("No DA org/site. Pass --org/--site or set admin in .edsrc.json.");
1124
- } else {
1125
- try {
1126
- await pushToDa(org, site, pagePath, html, process.env.DA_TOKEN);
1127
- logger.info(accentLine("pushed to DA", daEditUrl(org, site, pagePath)));
1128
- } catch (err) {
1129
- logger.error(`DA push failed: ${err.message}`);
1130
- logger.info(chalk3.dim(" Open projects allow anonymous writes; others need DA_TOKEN."));
1539
+ logger.logoOnce(logo("Scaffold - Universal Editor"));
1540
+ const created = [];
1541
+ const blockDir = path9.join(projectRoot, "blocks", "field-reference");
1542
+ await mkdir4(blockDir, { recursive: true });
1543
+ await writeFile7(path9.join(blockDir, "field-reference.js"), FIELD_REFERENCE_JS);
1544
+ await writeFile7(path9.join(blockDir, "field-reference.css"), FIELD_REFERENCE_CSS);
1545
+ await writeFile7(path9.join(blockDir, "_field-reference.json"), fieldReferenceModelFile());
1546
+ created.push("blocks/field-reference/ (all 17 fields + multifield)");
1547
+ const defFile = path9.join(projectRoot, "component-definition.json");
1548
+ const modelsFile = path9.join(projectRoot, "component-models.json");
1549
+ const filtersFile = path9.join(projectRoot, "component-filters.json");
1550
+ const blockDefs = [
1551
+ {
1552
+ title: "Field Reference",
1553
+ id: "field-reference",
1554
+ plugins: {
1555
+ xwalk: {
1556
+ page: {
1557
+ resourceType: "core/franklin/components/block/v1/block",
1558
+ template: { name: "Field Reference", model: "field-reference" }
1559
+ }
1131
1560
  }
1132
1561
  }
1133
1562
  }
1134
- } else if (model2 === "gdrive" || model2 === "sharepoint") {
1135
- const where = model2 === "gdrive" ? "a Google Doc" : "a Word doc";
1136
- logger.info("");
1137
- logger.info(
1138
- chalk3.dim(` Document model: import ${localRel} into ${where} in your mounted folder,`)
1139
- );
1140
- logger.info(
1141
- chalk3.dim(" then preview/publish. (Share the folder + install aem-code-sync first.)")
1142
- );
1143
- } else if (model2 === "ue") {
1144
- const registered = await registerBlocks(projectRoot, seeded);
1145
- if (registered) {
1146
- logger.info(
1147
- accentLine("component-filters.json", "seeded blocks allowed in Universal Editor")
1563
+ ];
1564
+ const definitions = mergeDefinitions(
1565
+ await readJson(defFile),
1566
+ DEFAULT_CONTENT_DEFINITIONS,
1567
+ blockDefs
1568
+ );
1569
+ await writeJson(defFile, definitions);
1570
+ created.push("component-definition.json (default content + field-reference)");
1571
+ const existingModels = await readJson(modelsFile) ?? [];
1572
+ const fieldRefModel = JSON.parse(fieldReferenceModelFile()).models[0];
1573
+ const models = mergeById(existingModels, [...DEFAULT_CONTENT_MODELS, fieldRefModel]);
1574
+ await writeJson(modelsFile, models);
1575
+ created.push("component-models.json (title / image / button / field-reference)");
1576
+ const filters = mergeSectionFilter(await readJson(filtersFile), ["field-reference"]);
1577
+ await writeJson(filtersFile, filters);
1578
+ created.push("component-filters.json (section filter)");
1579
+ logger.info(heading("Created / merged"));
1580
+ const names = created.map((c) => c.split(" ")[0]);
1581
+ const w = columnWidth(names);
1582
+ for (const c of created) {
1583
+ const [name, ...rest] = c.split(" ");
1584
+ logger.info(accentLine(name, rest.join(" ").replace(/^\((.*)\)$/, "$1"), w));
1585
+ }
1586
+ logger.info("");
1587
+ logger.info(box(["Universal Editor scaffold ready"]));
1588
+ }
1589
+ async function scaffoldBlocks(names) {
1590
+ const projectRoot = findProjectRoot();
1591
+ if (!projectRoot) {
1592
+ logger.error("Not inside an EDS project (no EDS project markers found).");
1593
+ process.exitCode = 1;
1594
+ return;
1595
+ }
1596
+ logger.logoOnce(logo("Scaffold \u2014 standard blocks"));
1597
+ let selected;
1598
+ if (names?.length) {
1599
+ selected = names.map((n2) => blockById(n2.trim())).filter((b) => !!b);
1600
+ const unknown = names.filter((n2) => !blockById(n2.trim()));
1601
+ if (unknown.length) {
1602
+ logger.warn(
1603
+ `Unknown block(s): ${unknown.join(", ")}. Available: ${STANDARD_BLOCKS.map((b) => b.id).join(", ")}`
1148
1604
  );
1149
1605
  }
1150
- logger.info("");
1151
- logger.info(
1152
- chalk3.dim(" Universal Editor: create the page's editable template in AEM and author it")
1153
- );
1154
- logger.info(chalk3.dim(` in the Universal Editor; ${localRel} is the reference structure.`));
1606
+ } else {
1607
+ selected = STANDARD_BLOCKS;
1608
+ }
1609
+ const createdIds = [];
1610
+ const skipped = [];
1611
+ for (const b of selected) {
1612
+ const dir = path9.join(projectRoot, "blocks", b.id);
1613
+ if (existsSync7(dir)) {
1614
+ skipped.push(b.id);
1615
+ continue;
1616
+ }
1617
+ await mkdir4(dir, { recursive: true });
1618
+ await writeFile7(path9.join(dir, `${b.id}.js`), b.js);
1619
+ await writeFile7(path9.join(dir, `${b.id}.css`), b.css);
1620
+ await writeFile7(path9.join(dir, `_${b.id}.json`), b.model);
1621
+ createdIds.push(b.id);
1155
1622
  }
1623
+ const filtersFile = path9.join(projectRoot, "component-filters.json");
1624
+ const filters = mergeSectionFilter(
1625
+ await readJson(filtersFile),
1626
+ selected.map((b) => b.id)
1627
+ );
1628
+ await writeJson(filtersFile, filters);
1629
+ logger.info(heading("Blocks"));
1630
+ const w = columnWidth([...createdIds, ...skipped, "component-filters"]);
1631
+ for (const id of createdIds) logger.info(accentLine(id, "created", w));
1632
+ for (const id of skipped) logger.info(statusLine("warn", id, "already exists \u2014 skipped", w));
1156
1633
  logger.info("");
1157
- logger.info(box([`"${name}" template (${model2}) - preview with \`eds preview ${pagePath}\``]));
1634
+ logger.info(box([`${createdIds.length} block(s) scaffolded + registered`]));
1158
1635
  }
1159
- var slug;
1160
- var init_template = __esm({
1161
- "src/commands/template.ts"() {
1162
- "use strict";
1163
- init_esm_shims();
1164
- init_logger();
1165
- init_project();
1166
- init_blocks();
1167
- init_ue();
1168
- init_authoring();
1169
- init_da();
1170
- init_page();
1171
- init_ui();
1172
- slug = (s) => s.trim().toLowerCase().replace(/[^a-z0-9-/]+/g, "-").replace(/^-+|-+$/g, "");
1636
+ async function scaffoldCi() {
1637
+ const projectRoot = findProjectRoot();
1638
+ if (!projectRoot) {
1639
+ logger.error("Not inside an EDS project (no EDS project markers found).");
1640
+ process.exitCode = 1;
1641
+ return;
1173
1642
  }
1174
- });
1175
-
1176
- // src/lib/scaffold/ci.ts
1177
- import { existsSync as existsSync4 } from "fs";
1178
- import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
1179
- import path5 from "path";
1180
- async function writeCiWorkflow(projectRoot) {
1181
- const file = path5.join(projectRoot, ".github", "workflows", "eds.yml");
1182
- if (existsSync4(file)) return { path: CI_WORKFLOW_PATH, created: false };
1183
- await mkdir2(path5.dirname(file), { recursive: true });
1184
- await writeFile2(file, CI_WORKFLOW);
1185
- return { path: CI_WORKFLOW_PATH, created: true };
1186
- }
1187
- var CI_WORKFLOW_PATH, CI_WORKFLOW;
1188
- var init_ci = __esm({
1189
- "src/lib/scaffold/ci.ts"() {
1190
- "use strict";
1191
- init_esm_shims();
1192
- CI_WORKFLOW_PATH = ".github/workflows/eds.yml";
1193
- CI_WORKFLOW = `name: EDS checks
1194
-
1195
- # Generated by aem-eds-kit (\`eds scaffold ci\`). Runs the project's own
1196
- # lint/build plus the eds audits as a gatekeeper on every PR and push to main.
1197
- on:
1198
- pull_request:
1199
- push:
1200
- branches: [main]
1201
-
1202
- jobs:
1203
- checks:
1204
- runs-on: ubuntu-latest
1205
- steps:
1206
- - uses: actions/checkout@v4
1207
- - uses: actions/setup-node@v4
1208
- with:
1209
- node-version: 20
1210
-
1211
- # Install project dependencies when the repo has them.
1212
- - name: Install dependencies
1213
- run: |
1214
- if [ -f package-lock.json ]; then
1215
- npm ci
1216
- elif [ -f package.json ]; then
1217
- npm install
1218
- fi
1219
-
1220
- # The project's own scripts, if defined (skipped otherwise).
1221
- - name: Lint
1222
- if: hashFiles('package.json') != ''
1223
- run: npm run lint --if-present
1224
- - name: Build
1225
- if: hashFiles('package.json') != ''
1226
- run: npm run build --if-present
1227
-
1228
- # aem-eds-kit audits - run via npx, no install needed. Each exits
1229
- # non-zero on failures, so a bad PR fails the check.
1230
- - name: Project health (doctor)
1231
- run: npx --yes aem-eds-kit@latest doctor
1232
- - name: Loading & LCP budget
1233
- run: npx --yes aem-eds-kit@latest audit loading
1234
- - name: Security & exploit scan
1235
- run: npx --yes aem-eds-kit@latest audit security
1236
- `;
1643
+ logger.logoOnce(logo("Scaffold - CI workflow"));
1644
+ const res = await writeCiWorkflow(projectRoot);
1645
+ logger.info(heading("GitHub Actions"));
1646
+ if (res.created) {
1647
+ logger.info(accentLine(res.path, "doctor + audit loading + audit security + lint/build"));
1648
+ } else {
1649
+ logger.info(statusLine("warn", res.path, "already exists - skipped"));
1237
1650
  }
1238
- });
1239
-
1240
- // src/lib/scaffold/fstab.ts
1241
- import { writeFile as writeFile3 } from "fs/promises";
1242
- import path6 from "path";
1243
- function needsFstab(a) {
1244
- return a !== "ue";
1245
- }
1246
- function daMountpoint(org, site) {
1247
- return `https://content.da.live/${org}/${site}/`;
1651
+ logger.info("");
1652
+ logger.info(
1653
+ box([
1654
+ res.created ? "CI workflow scaffolded - commit it and it runs on every PR" : "CI workflow left untouched"
1655
+ ])
1656
+ );
1248
1657
  }
1249
- function fstabYaml(mountpoint) {
1250
- return `mountpoints:
1251
- /: ${mountpoint}
1252
- `;
1658
+ async function scaffoldHelpers(ids) {
1659
+ const projectRoot = findProjectRoot();
1660
+ if (!projectRoot) {
1661
+ logger.error("Not inside an EDS project (no EDS project markers found).");
1662
+ process.exitCode = 1;
1663
+ return;
1664
+ }
1665
+ logger.logoOnce(logo("Scaffold - helpers"));
1666
+ let selected = ids?.length ? ids : void 0;
1667
+ if (!selected) {
1668
+ if (process.stdout.isTTY) {
1669
+ const { checkbox } = await import("@inquirer/prompts");
1670
+ selected = await checkbox({
1671
+ message: "Which helpers?",
1672
+ choices: HELPERS.map((h) => ({ name: h.label, value: h.id, checked: true }))
1673
+ });
1674
+ } else {
1675
+ selected = ALL_HELPER_IDS;
1676
+ }
1677
+ }
1678
+ if (!selected.length) {
1679
+ logger.info("Nothing selected.");
1680
+ return;
1681
+ }
1682
+ const res = await writeHelpers(projectRoot, selected);
1683
+ logger.info(heading("Helpers"));
1684
+ if (res.created) {
1685
+ logger.info(accentLine(res.path, res.helpers.join(", ")));
1686
+ } else {
1687
+ logger.info(statusLine("warn", res.path, "already exists - skipped"));
1688
+ }
1689
+ logger.info("");
1690
+ logger.info(
1691
+ box([
1692
+ res.created ? `${res.helpers.length} helper(s) -> scripts/utils.js` : "Helpers left untouched"
1693
+ ])
1694
+ );
1253
1695
  }
1254
- async function writeFstab(projectRoot, mountpoint) {
1255
- await writeFile3(path6.join(projectRoot, "fstab.yaml"), fstabYaml(mountpoint));
1256
- return "fstab.yaml";
1696
+ async function scaffoldEnv(options = {}) {
1697
+ const projectRoot = findProjectRoot();
1698
+ if (!projectRoot) {
1699
+ logger.error("Not inside an EDS project (no EDS project markers found).");
1700
+ process.exitCode = 1;
1701
+ return;
1702
+ }
1703
+ logger.logoOnce(logo("Scaffold - .env"));
1704
+ let pagesUrl = options.pagesUrl;
1705
+ if (pagesUrl === void 0) {
1706
+ try {
1707
+ const { execa } = await import("execa");
1708
+ const res2 = await execa("git", ["remote", "get-url", "origin"], { cwd: projectRoot });
1709
+ const parsed = parseRemote(res2.stdout.trim());
1710
+ pagesUrl = parsed ? `https://${previewHost(parsed.owner, parsed.repo)}` : "";
1711
+ } catch {
1712
+ pagesUrl = "";
1713
+ }
1714
+ }
1715
+ let open = options.open ?? "/";
1716
+ let port = options.port ?? "3007";
1717
+ if (!options.yes && process.stdout.isTTY) {
1718
+ const { input } = await import("@inquirer/prompts");
1719
+ open = await input({ message: "AEM_OPEN (path opened on start)", default: open });
1720
+ port = await input({ message: "AEM_PORT", default: String(port) });
1721
+ pagesUrl = await input({ message: "AEM_PAGES_URL (content origin)", default: pagesUrl });
1722
+ }
1723
+ const res = await writeEnv(projectRoot, { open, port, pagesUrl });
1724
+ logger.info(heading(".env"));
1725
+ if (res.created) logger.info(accentLine(res.path, `AEM_OPEN=${open} AEM_PORT=${port}`));
1726
+ else logger.info(statusLine("warn", res.path, "already exists - left untouched"));
1727
+ if (res.gitignoreUpdated) logger.info(accentLine(".gitignore", "added .env"));
1728
+ logger.info("");
1729
+ logger.info(box([res.created ? ".env ready for `aem up`" : ".env left untouched"]));
1257
1730
  }
1258
- function pathsJson(site) {
1259
- return `${JSON.stringify(
1260
- {
1261
- mappings: [`/content/${site}/:/`],
1262
- includes: [`/content/${site}/`]
1263
- },
1264
- null,
1265
- 2
1266
- )}
1267
- `;
1731
+ async function scaffoldXwalk() {
1732
+ const projectRoot = findProjectRoot();
1733
+ if (!projectRoot) {
1734
+ logger.error("Not inside an EDS project (no EDS project markers found).");
1735
+ process.exitCode = 1;
1736
+ return;
1737
+ }
1738
+ logger.logoOnce(logo("Scaffold - xwalk.json"));
1739
+ const res = await writeXwalk(projectRoot);
1740
+ logger.info(heading("xwalk.json"));
1741
+ if (res.created) logger.info(accentLine(res.path, "multi-field enabled"));
1742
+ else logger.info(statusLine("warn", res.path, "already exists - left untouched"));
1743
+ logger.info("");
1744
+ logger.info(
1745
+ box([res.created ? "xwalk.json ready (UE multi-field)" : "xwalk.json left untouched"])
1746
+ );
1268
1747
  }
1269
- async function writePaths(projectRoot, site) {
1270
- await writeFile3(path6.join(projectRoot, "paths.json"), pathsJson(site));
1271
- return "paths.json";
1748
+ async function scaffoldInteractive() {
1749
+ const { select, checkbox } = await import("@inquirer/prompts");
1750
+ logger.logoOnce(logo("Scaffold"));
1751
+ const what = await select({
1752
+ message: "What do you want to scaffold?",
1753
+ choices: [
1754
+ { name: "Universal Editor config + field-reference (all 17 field types)", value: "ue" },
1755
+ { name: "Standard blocks (hero, cards, columns, accordion, embed)", value: "blocks" },
1756
+ { name: "GitHub Actions CI (doctor + audits + lint/build)", value: "ci" },
1757
+ { name: "Helper functions (scripts/utils.js)", value: "helpers" },
1758
+ { name: "Local-dev .env (aem up: port / open / pages URL)", value: "env" },
1759
+ { name: "xwalk.json (enable Universal Editor multi-field)", value: "xwalk" },
1760
+ { name: "UE config + standard blocks", value: "both" }
1761
+ ]
1762
+ });
1763
+ if (what === "env") return scaffoldEnv();
1764
+ if (what === "xwalk") return scaffoldXwalk();
1765
+ if (what === "ue" || what === "both") await scaffoldUe();
1766
+ if (what === "blocks" || what === "both") {
1767
+ const picked = await checkbox({
1768
+ message: "Which blocks?",
1769
+ choices: STANDARD_BLOCKS.map((b) => ({ name: b.title, value: b.id, checked: true }))
1770
+ });
1771
+ await scaffoldBlocks(picked);
1772
+ }
1773
+ if (what === "ci") await scaffoldCi();
1774
+ if (what === "helpers") await scaffoldHelpers();
1272
1775
  }
1273
- var AUTHORING_NAMES;
1274
- var init_fstab = __esm({
1275
- "src/lib/scaffold/fstab.ts"() {
1776
+ var init_scaffold = __esm({
1777
+ "src/commands/scaffold.ts"() {
1276
1778
  "use strict";
1277
1779
  init_esm_shims();
1278
- AUTHORING_NAMES = {
1279
- ue: "Universal Editor (crosswalk)",
1280
- da: "Document Authoring (da.live)",
1281
- gdrive: "Document Authoring (Google Drive)",
1282
- sharepoint: "Document Authoring (SharePoint)"
1283
- };
1780
+ init_logger();
1781
+ init_project();
1782
+ init_blocks();
1783
+ init_ci();
1784
+ init_env();
1785
+ init_helpers();
1786
+ init_ue();
1787
+ init_xwalk();
1788
+ init_link();
1789
+ init_ui();
1284
1790
  }
1285
1791
  });
1286
1792
 
1287
- // src/lib/scaffold/helpers.ts
1288
- import { existsSync as existsSync5 } from "fs";
1289
- import { mkdir as mkdir3, writeFile as writeFile4 } from "fs/promises";
1290
- import path7 from "path";
1291
- function helperById(id) {
1292
- return HELPERS.find((h) => h.id === id);
1793
+ // src/commands/init.ts
1794
+ var init_exports = {};
1795
+ __export(init_exports, {
1796
+ initProject: () => initProject
1797
+ });
1798
+ import { existsSync as existsSync8 } from "fs";
1799
+ import { readFile as readFile4, writeFile as writeFile8 } from "fs/promises";
1800
+ import path10 from "path";
1801
+ import chalk3 from "chalk";
1802
+ async function derivePagesUrl(root) {
1803
+ try {
1804
+ const { execa } = await import("execa");
1805
+ const res = await execa("git", ["remote", "get-url", "origin"], { cwd: root });
1806
+ const parsed = parseRemote(res.stdout.trim());
1807
+ return parsed ? `https://${previewHost(parsed.owner, parsed.repo)}` : "";
1808
+ } catch {
1809
+ return "";
1810
+ }
1293
1811
  }
1294
- function resolveHelpers(ids) {
1295
- const want = /* @__PURE__ */ new Set();
1296
- const add = (id) => {
1297
- if (want.has(id)) return;
1298
- const h = helperById(id);
1299
- if (!h) return;
1300
- for (const dep of h.deps ?? []) add(dep);
1301
- want.add(id);
1302
- };
1303
- for (const id of ids) add(id);
1304
- return HELPERS.filter((h) => want.has(h.id)).map((h) => h.id);
1812
+ async function setPackageName(root, name) {
1813
+ const file = path10.join(root, "package.json");
1814
+ if (!existsSync8(file)) return false;
1815
+ try {
1816
+ const pkg = JSON.parse(await readFile4(file, "utf-8"));
1817
+ pkg.name = name;
1818
+ await writeFile8(file, `${JSON.stringify(pkg, null, 2)}
1819
+ `);
1820
+ return true;
1821
+ } catch {
1822
+ return false;
1823
+ }
1305
1824
  }
1306
- function buildHelpers(ids) {
1307
- const resolved = resolveHelpers(ids);
1308
- const body = resolved.map((id) => helperById(id)).filter((h) => !!h).map((h) => h.code).join("\n\n");
1309
- return `// scripts/utils.js - common EDS helpers (generated by \`eds scaffold helpers\`).
1310
-
1311
- ${body}
1312
- `;
1825
+ async function replacePlaceholders(root, name) {
1826
+ const touched = [];
1827
+ for (const rel of ["README.md", "index.html", "head.html"]) {
1828
+ const file = path10.join(root, rel);
1829
+ if (!existsSync8(file)) continue;
1830
+ try {
1831
+ const before = await readFile4(file, "utf-8");
1832
+ const after = before.replace(/aem-boilerplate/gi, name);
1833
+ if (after !== before) {
1834
+ await writeFile8(file, after);
1835
+ touched.push(rel);
1836
+ }
1837
+ } catch {
1838
+ }
1839
+ }
1840
+ return touched;
1313
1841
  }
1314
- async function writeHelpers(projectRoot, ids = ALL_HELPER_IDS) {
1315
- const resolved = resolveHelpers(ids);
1316
- const file = path7.join(projectRoot, "scripts", "utils.js");
1317
- if (existsSync5(file)) return { path: HELPERS_PATH, created: false, helpers: resolved };
1318
- await mkdir3(path7.dirname(file), { recursive: true });
1319
- await writeFile4(file, buildHelpers(ids));
1320
- return { path: HELPERS_PATH, created: true, helpers: resolved };
1842
+ async function externalSteps(authoring) {
1843
+ const { confirm } = await import("@inquirer/prompts");
1844
+ const steps = [
1845
+ {
1846
+ title: "Install the AEM Code Sync GitHub app",
1847
+ url: "https://github.com/apps/aem-code-sync/installations/new",
1848
+ note: "Connects your repo to the Edge Delivery pipeline (preview/publish)."
1849
+ }
1850
+ ];
1851
+ if (authoring === "gdrive") {
1852
+ steps.push({
1853
+ title: "Share your content folder",
1854
+ note: "Share the Google Drive folder with the AEM service and set it as the mountpoint."
1855
+ });
1856
+ }
1857
+ if (authoring === "sharepoint") {
1858
+ steps.push({
1859
+ title: "Share your content folder",
1860
+ note: "Grant the SharePoint/OneDrive folder to the AEM service account."
1861
+ });
1862
+ }
1863
+ if (authoring === "da") {
1864
+ steps.push({
1865
+ title: "Author in DA",
1866
+ url: "https://da.live",
1867
+ note: "Create documents at da.live."
1868
+ });
1869
+ }
1870
+ if (authoring === "ue") {
1871
+ steps.push({
1872
+ title: "Open the Universal Editor",
1873
+ url: "https://experience.adobe.com",
1874
+ note: "Author in-context once the repo is connected to AEM."
1875
+ });
1876
+ }
1877
+ steps.push({
1878
+ title: "Install the AEM Sidekick extension",
1879
+ url: "https://chromewebstore.google.com/detail/aem-sidekick/igkmdomcgoebiipaifhmpfjhbjccggml",
1880
+ note: "Preview and publish from the browser."
1881
+ });
1882
+ steps.push({
1883
+ title: "Run the local dev server",
1884
+ note: "npm install, then `aem up` (from @adobe/aem-cli)."
1885
+ });
1886
+ logger.info(heading("Next steps (outside the repo)"));
1887
+ for (let i = 0; i < steps.length; i++) {
1888
+ const s = steps[i];
1889
+ logger.info(` ${brand.accent(String(i + 1))} ${chalk3.bold(s.title)}`);
1890
+ if (s.url) logger.info(` ${chalk3.cyan(s.url)}`);
1891
+ logger.info(` ${chalk3.dim(s.note)}`);
1892
+ await confirm({ message: " Done / skip?", default: true });
1893
+ }
1321
1894
  }
1322
- var HELPERS_PATH, HELPERS, ALL_HELPER_IDS;
1323
- var init_helpers = __esm({
1324
- "src/lib/scaffold/helpers.ts"() {
1895
+ async function initProject(options = {}) {
1896
+ const projectRoot = findProjectRoot();
1897
+ if (!projectRoot) {
1898
+ logger.error(
1899
+ "Not inside an EDS project (no EDS project markers found). Clone an EDS boilerplate first, then run this from its root."
1900
+ );
1901
+ process.exitCode = 1;
1902
+ return;
1903
+ }
1904
+ await logger.logoOnceAnimated("Project setup");
1905
+ const { input, select, confirm } = await import("@inquirer/prompts");
1906
+ const interactive = !options.yes;
1907
+ let defaultName = path10.basename(projectRoot);
1908
+ const pkgFile = path10.join(projectRoot, "package.json");
1909
+ if (existsSync8(pkgFile)) {
1910
+ try {
1911
+ defaultName = JSON.parse(await readFile4(pkgFile, "utf-8")).name || defaultName;
1912
+ } catch {
1913
+ }
1914
+ }
1915
+ const name = options.name && slug(options.name) || slug(
1916
+ await input({
1917
+ message: "1/4 Project name",
1918
+ default: slug(defaultName),
1919
+ validate: (v) => !!slug(v) || "Use letters, numbers and dashes"
1920
+ })
1921
+ );
1922
+ const authoring = options.authoring ?? await select({
1923
+ message: "2/4 Authoring model",
1924
+ choices: [
1925
+ { name: AUTHORING_NAMES.ue, value: "ue" },
1926
+ { name: AUTHORING_NAMES.da, value: "da" },
1927
+ { name: AUTHORING_NAMES.gdrive, value: "gdrive" },
1928
+ { name: AUTHORING_NAMES.sharepoint, value: "sharepoint" }
1929
+ ]
1930
+ });
1931
+ let mountpoint = options.mountpoint;
1932
+ if (needsFstab(authoring) && !mountpoint) {
1933
+ if (authoring === "da" && (options.org || interactive)) {
1934
+ const org = options.org ?? await input({ message: " DA org", validate: (v) => !!v.trim() || "Required" });
1935
+ const site = options.site ?? (interactive ? await input({ message: " DA site", default: name }) : name);
1936
+ mountpoint = daMountpoint(org.trim(), slug(site));
1937
+ } else if (authoring !== "da" && interactive) {
1938
+ const label = authoring === "gdrive" ? "Google Drive folder URL" : "SharePoint folder URL";
1939
+ mountpoint = (await input({
1940
+ message: ` ${label}`,
1941
+ validate: (v) => /^https?:\/\//.test(v.trim()) || "Paste the folder URL"
1942
+ })).trim();
1943
+ }
1944
+ }
1945
+ const withCi = options.ci ?? (interactive ? await confirm({ message: "4/4 Add GitHub Actions CI (audits gatekeeper)?", default: true }) : true);
1946
+ logger.info(heading("Summary"));
1947
+ const w = columnWidth(["name", "authoring", "mountpoint", "ci"]);
1948
+ logger.info(accentLine("name", name, w));
1949
+ logger.info(accentLine("authoring", AUTHORING_NAMES[authoring], w));
1950
+ if (mountpoint) logger.info(accentLine("mountpoint", mountpoint, w));
1951
+ else if (needsFstab(authoring))
1952
+ logger.info(accentLine("mountpoint", "(skipped - set it in fstab.yaml later)", w));
1953
+ logger.info(accentLine("ci", withCi ? "yes" : "no", w));
1954
+ logger.info("");
1955
+ if (interactive) {
1956
+ const go = await confirm({ message: "Apply this setup?", default: true });
1957
+ if (!go) {
1958
+ logger.info("Cancelled.");
1959
+ return;
1960
+ }
1961
+ }
1962
+ const done = [];
1963
+ if (await setPackageName(projectRoot, name)) done.push("package.json (name)");
1964
+ for (const t of await replacePlaceholders(projectRoot, name)) done.push(`${t} (renamed)`);
1965
+ if (needsFstab(authoring) && mountpoint) done.push(await writeFstab(projectRoot, mountpoint));
1966
+ if (authoring === "ue") {
1967
+ await scaffoldUe();
1968
+ done.push(await writePaths(projectRoot, name));
1969
+ }
1970
+ if (withCi) {
1971
+ const ci = await writeCiWorkflow(projectRoot);
1972
+ done.push(ci.created ? ci.path : `${ci.path} (exists, skipped)`);
1973
+ }
1974
+ if (options.env !== false) {
1975
+ const res = await writeEnv(projectRoot, { pagesUrl: await derivePagesUrl(projectRoot) });
1976
+ done.push(res.created ? ".env (aem up config)" : ".env (exists, skipped)");
1977
+ }
1978
+ if (options.xwalk !== false && authoring === "ue") {
1979
+ const res = await writeXwalk(projectRoot);
1980
+ done.push(res.created ? `${res.path} (UE multi-field)` : `${res.path} (exists, skipped)`);
1981
+ }
1982
+ logger.info(heading("Configured"));
1983
+ const dw = columnWidth(done.map((d) => d.split(" ")[0]));
1984
+ for (const d of done) {
1985
+ const [n2, ...rest] = d.split(" ");
1986
+ logger.info(accentLine(n2, rest.join(" ").replace(/^\((.*)\)$/, "$1"), dw));
1987
+ }
1988
+ logger.info("");
1989
+ if (interactive) await externalSteps(authoring);
1990
+ logger.info(box([`"${name}" set up for ${AUTHORING_NAMES[authoring]}`]));
1991
+ }
1992
+ var slug;
1993
+ var init_init = __esm({
1994
+ "src/commands/init.ts"() {
1325
1995
  "use strict";
1326
1996
  init_esm_shims();
1327
- HELPERS_PATH = "scripts/utils.js";
1328
- HELPERS = [
1329
- {
1330
- id: "getMetadata",
1331
- label: "getMetadata (read a page meta value)",
1332
- code: `/**
1333
- * Reads a page metadata value by name (meta[name] or meta[property]).
1334
- * @param {string} name The metadata name.
1335
- * @returns {string} The value, or an empty string.
1336
- */
1337
- export function getMetadata(name) {
1338
- const meta =
1339
- document.querySelector(\`meta[name="\${name}"]\`) ||
1340
- document.querySelector(\`meta[property="\${name}"]\`);
1341
- return meta ? meta.content : '';
1342
- }`
1343
- },
1344
- {
1345
- id: "toCamelCase",
1346
- label: "toCamelCase (dash/underscore/space -> camelCase)",
1347
- code: `/**
1348
- * Converts a string to camelCase, handling dashes, underscores and spaces.
1349
- * @param {string} value The string to convert.
1350
- * @returns {string} The camelCased string.
1351
- */
1352
- export function toCamelCase(value) {
1353
- return String(value || '')
1354
- .trim()
1355
- .toLowerCase()
1356
- .replace(/[-_\\s]+(.)/g, (_, char) => char.toUpperCase());
1357
- }`
1358
- },
1359
- {
1360
- id: "getLanguageRootPath",
1361
- label: "getLanguageRootPath (e.g. /en, /en-us)",
1362
- code: `/**
1363
- * Returns the language root of a path, e.g. '/en' or '/en-us' ('' if none).
1364
- * @param {string} path The page path.
1365
- * @returns {string} The language root prefix.
1366
- */
1367
- export function getLanguageRootPath(path) {
1368
- const match = String(path || '').match(/^\\/[a-z]{2}(?:-[a-z]{2})?(?=\\/|$)/i);
1369
- return match ? match[0] : '';
1370
- }`
1371
- },
1372
- {
1373
- id: "getPagePath",
1374
- label: "getPagePath (path without content root)",
1375
- deps: ["getLanguageRootPath"],
1376
- code: `// Content root stripped from page paths. Set this if your site is mounted under
1377
- // a sub-path (e.g. '/content/my-site'); leave empty for a root-mounted site.
1378
- const ROOT_PATH = '';
1379
-
1380
- /**
1381
- * Returns the page path without the content root; \`clean\` drops the language root.
1382
- * @param {boolean} [clean=false] Also strip the language root.
1383
- * @returns {string} The page path or 'NA' for the language root.
1384
- */
1385
- export function getPagePath({ clean = false } = {}) {
1386
- const path = window.location.pathname.replace(ROOT_PATH, '').replace(/\\.html$/i, '');
1387
-
1388
- if (clean) {
1389
- const langRoot = getLanguageRootPath(path);
1390
- const cleaned = path.startsWith(langRoot) ? path.slice(langRoot.length) : path;
1391
- return cleaned || 'NA';
1997
+ init_logger();
1998
+ init_project();
1999
+ init_ci();
2000
+ init_env();
2001
+ init_fstab();
2002
+ init_xwalk();
2003
+ init_link();
2004
+ init_ui();
2005
+ init_scaffold();
2006
+ slug = (s) => s.trim().toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "");
1392
2007
  }
2008
+ });
1393
2009
 
1394
- return path || 'NA';
1395
- }`
1396
- },
1397
- {
1398
- id: "getSiteArea",
1399
- label: "getSiteArea (blog / products / home)",
1400
- deps: ["getPagePath"],
1401
- code: `/**
1402
- * Returns the top-level area of the site from the current path - e.g. 'blog' or
1403
- * 'products', and 'home' at the site root. Uses the language-cleaned path, so you
1404
- * can tell whether you're on the blog or somewhere else.
1405
- * @returns {string} The site area.
1406
- */
1407
- export function getSiteArea() {
1408
- const path = getPagePath({ clean: true });
1409
- if (!path || path === 'NA') return 'home';
1410
- return path.split('/').filter(Boolean)[0] || 'home';
1411
- }`
1412
- },
1413
- {
1414
- id: "getEnvironment",
1415
- label: "getEnvironment (dev / stage / prod)",
1416
- code: `/**
1417
- * Detects the current environment from the hostname.
1418
- * @returns {'NA'|'dev'|'stage'|'prod'} The environment name.
1419
- */
1420
- export function getEnvironment() {
1421
- const { hostname } = window.location;
1422
- if (hostname.includes('author')) return 'NA';
1423
- if (hostname === 'localhost') return 'dev';
1424
- const repo = hostname.match(/^[^.]+--([^.]+)--[^.]+\\.aem\\.(?:page|live)$/)?.[1] || '';
1425
- if (repo.includes('dev')) return 'dev';
1426
- if (repo.includes('stage')) return 'stage';
1427
- return 'prod';
1428
- }`
1429
- },
1430
- {
1431
- id: "getContentTopic",
1432
- label: "getContentTopic (cq-tags content-topic values)",
1433
- deps: ["getMetadata"],
1434
- code: `/**
1435
- * Reads all content-topic values from the page's cq-tags metadata.
1436
- * @returns {string[]} The topics (part after \`content-topic/\`), empty if none.
1437
- */
1438
- export function getContentTopic() {
1439
- return getMetadata('cq-tags')
1440
- .split(',')
1441
- .map((t) => t.trim())
1442
- .filter((t) => t.includes('content-topic/'))
1443
- .map((t) => t.split('content-topic/')[1]);
1444
- }`
1445
- },
1446
- {
1447
- id: "isUEEdit",
1448
- label: "isUEEdit (Universal Editor edit mode)",
1449
- code: `/**
1450
- * Checks whether the current page is opened in the Universal Editor Edit mode.
1451
- * @returns {boolean} True in Universal Editor Edit mode.
1452
- */
1453
- export function isUEEdit() {
1454
- return document.documentElement.classList.contains('adobe-ue-edit');
1455
- }`
1456
- },
1457
- {
1458
- id: "isUEPreview",
1459
- label: "isUEPreview (Universal Editor preview mode)",
1460
- code: `/**
1461
- * Checks whether the current page is opened in the Universal Editor Preview mode.
1462
- * @returns {boolean} True in Universal Editor Preview mode.
1463
- */
1464
- export function isUEPreview() {
1465
- return document.documentElement.classList.contains('adobe-ue-preview');
1466
- }`
1467
- },
1468
- {
1469
- id: "isUE",
1470
- label: "isUE (Universal Editor, any mode)",
1471
- deps: ["isUEEdit", "isUEPreview"],
1472
- code: `/**
1473
- * Checks whether the current page is opened in the Universal Editor (any mode).
1474
- * @returns {boolean} True in the Universal Editor.
1475
- */
1476
- export function isUE() {
1477
- return isUEEdit() || isUEPreview();
1478
- }`
1479
- }
1480
- ];
1481
- ALL_HELPER_IDS = HELPERS.map((h) => h.id);
2010
+ // src/commands/sandbox.ts
2011
+ var sandbox_exports = {};
2012
+ __export(sandbox_exports, {
2013
+ sandboxList: () => sandboxList,
2014
+ sandboxLogin: () => sandboxLogin,
2015
+ sandboxLogout: () => sandboxLogout,
2016
+ sandboxNew: () => sandboxNew,
2017
+ sandboxRm: () => sandboxRm
2018
+ });
2019
+ import path11 from "path";
2020
+ import chalk4 from "chalk";
2021
+ async function run(cmd, args, opts = {}) {
2022
+ const { execa } = await import("execa");
2023
+ const res = await execa(cmd, args, opts);
2024
+ return (res.stdout ?? "").toString().trim();
2025
+ }
2026
+ async function openUrl(url) {
2027
+ const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
2028
+ try {
2029
+ await run(cmd, [url], { stdio: "ignore" });
2030
+ } catch {
2031
+ }
2032
+ }
2033
+ async function ghLogin() {
2034
+ try {
2035
+ await run("gh", ["auth", "status"]);
2036
+ return await run("gh", ["api", "user", "-q", ".login"]);
2037
+ } catch {
2038
+ return null;
2039
+ }
2040
+ }
2041
+ async function resolveAuth() {
2042
+ const token = await getToken();
2043
+ if (token) {
2044
+ try {
2045
+ return { mode: "api", token, login: await getUser(token) };
2046
+ } catch {
2047
+ }
2048
+ }
2049
+ const login = await ghLogin();
2050
+ if (login) return { mode: "gh", login };
2051
+ return null;
2052
+ }
2053
+ async function exists(auth, owner, name) {
2054
+ if (auth.mode === "api") return repoExists(auth.token, owner, name);
2055
+ try {
2056
+ await run("gh", ["repo", "view", `${owner}/${name}`, "--json", "name"]);
2057
+ return true;
2058
+ } catch {
2059
+ return false;
2060
+ }
2061
+ }
2062
+ function noAuth() {
2063
+ logger.error("Not authenticated with GitHub.");
2064
+ logger.info(" Run `eds sandbox login` to authorize GitHub, or install + `gh auth login`.");
2065
+ process.exitCode = 1;
2066
+ }
2067
+ async function sandboxLogin() {
2068
+ await logger.logoOnceAnimated("Sandbox - GitHub login");
2069
+ const { input } = await import("@inquirer/prompts");
2070
+ if (CLIENT_ID) {
2071
+ const code = await startDeviceFlow(["repo", "delete_repo"]);
2072
+ logger.info(heading("Authorize GitHub"));
2073
+ logger.info(accentLine("code", code.user_code));
2074
+ logger.info(accentLine("open", code.verification_uri));
2075
+ logger.info("");
2076
+ await openUrl(code.verification_uri);
2077
+ logger.info(chalk4.dim(" Waiting for you to authorize in the browser..."));
2078
+ try {
2079
+ const token2 = await pollDeviceToken(code.device_code, code.interval, code.expires_in);
2080
+ await saveToken(token2);
2081
+ logger.success(`Authorized as ${await getUser(token2)}.`);
2082
+ } catch (err) {
2083
+ logger.error(`Device flow failed: ${err.message}`);
2084
+ process.exitCode = 1;
2085
+ }
2086
+ return;
2087
+ }
2088
+ logger.info(heading("Authorize GitHub (least-privilege token)"));
2089
+ logger.info(" Create a fine-grained token limited to your sandboxes only:");
2090
+ logger.info(` ${chalk4.cyan("https://github.com/settings/personal-access-tokens/new")}`);
2091
+ logger.info(
2092
+ chalk4.dim(" - Resource owner: a dedicated sandbox org (recommended) or your account")
2093
+ );
2094
+ logger.info(
2095
+ chalk4.dim(" - Repository access: All repositories in that org (to create new sandboxes),")
2096
+ );
2097
+ logger.info(chalk4.dim(" or Only select repositories to manage existing ones"));
2098
+ logger.info(
2099
+ chalk4.dim(" - Permissions: Administration = Read and write, Contents = Read and write,")
2100
+ );
2101
+ logger.info(chalk4.dim(" Metadata = Read. Set a short expiry."));
2102
+ logger.info(
2103
+ chalk4.dim(
2104
+ " Tip: `eds sandbox new --org <sandbox-org>` so nothing in your personal account is touched."
2105
+ )
2106
+ );
2107
+ logger.info("");
2108
+ if (await (await import("@inquirer/prompts")).confirm({
2109
+ message: "Open the token page?",
2110
+ default: true
2111
+ })) {
2112
+ await openUrl("https://github.com/settings/personal-access-tokens/new");
1482
2113
  }
1483
- });
1484
-
1485
- // src/commands/scaffold.ts
1486
- var scaffold_exports = {};
1487
- __export(scaffold_exports, {
1488
- scaffoldBlocks: () => scaffoldBlocks,
1489
- scaffoldCi: () => scaffoldCi,
1490
- scaffoldHelpers: () => scaffoldHelpers,
1491
- scaffoldInteractive: () => scaffoldInteractive,
1492
- scaffoldUe: () => scaffoldUe
1493
- });
1494
- import { existsSync as existsSync6 } from "fs";
1495
- import { mkdir as mkdir4, readFile as readFile3, writeFile as writeFile5 } from "fs/promises";
1496
- import path8 from "path";
1497
- async function readJson(file) {
1498
- if (!existsSync6(file)) return null;
2114
+ const token = (await input({ message: "Paste the token", validate: (v) => !!v.trim() || "Required" })).trim();
1499
2115
  try {
1500
- return JSON.parse(await readFile3(file, "utf-8"));
2116
+ const login = await getUser(token);
2117
+ await saveToken(token);
2118
+ logger.success(`Authorized as ${login}.`);
1501
2119
  } catch {
1502
- return null;
2120
+ logger.error("That token didn't work (check the scopes).");
2121
+ process.exitCode = 1;
1503
2122
  }
1504
2123
  }
1505
- async function writeJson(file, data) {
1506
- await writeFile5(file, `${JSON.stringify(data, null, 2)}
1507
- `);
2124
+ async function sandboxLogout() {
2125
+ await clearToken();
2126
+ logger.success("Signed out (stored GitHub token removed).");
1508
2127
  }
1509
- async function scaffoldUe() {
1510
- const projectRoot = findProjectRoot();
1511
- if (!projectRoot) {
1512
- logger.error("Not inside an EDS project (no EDS project markers found).");
2128
+ async function sandboxNew(name, options = {}) {
2129
+ const auth = await resolveAuth();
2130
+ if (!auth) return noAuth();
2131
+ await logger.logoOnceAnimated("Sandbox");
2132
+ const interactive = !options.yes;
2133
+ const { select, confirm } = await import("@inquirer/prompts");
2134
+ let template2 = options.boilerplate;
2135
+ if (!template2) {
2136
+ if (options.ue) template2 = BOILERPLATES.ue;
2137
+ else if (interactive) {
2138
+ template2 = await select({
2139
+ message: "Which boilerplate?",
2140
+ choices: [
2141
+ { name: "Document Authoring (adobe/aem-boilerplate)", value: BOILERPLATES.document },
2142
+ {
2143
+ name: "Universal Editor / crosswalk (adobe/aem-boilerplate-xwalk)",
2144
+ value: BOILERPLATES.ue
2145
+ }
2146
+ ]
2147
+ });
2148
+ } else template2 = BOILERPLATES.document;
2149
+ }
2150
+ let priv = options.private ?? false;
2151
+ if (options.private === void 0 && interactive) {
2152
+ priv = await select({
2153
+ message: "Visibility?",
2154
+ choices: [
2155
+ { name: "Public", value: false },
2156
+ { name: "Private", value: true }
2157
+ ],
2158
+ default: false
2159
+ });
2160
+ }
2161
+ const owner = options.org ?? auth.login;
2162
+ if (await exists(auth, owner, name)) {
2163
+ logger.error(`A repository ${owner}/${name} already exists - refusing to touch it.`);
2164
+ logger.info(
2165
+ chalk4.dim(" Pick another name, or remove it first (e.g. `eds sandbox rm` for a sandbox).")
2166
+ );
1513
2167
  process.exitCode = 1;
1514
2168
  return;
1515
2169
  }
1516
- logger.logoOnce(logo("Scaffold - Universal Editor"));
1517
- const created = [];
1518
- const blockDir = path8.join(projectRoot, "blocks", "field-reference");
1519
- await mkdir4(blockDir, { recursive: true });
1520
- await writeFile5(path8.join(blockDir, "field-reference.js"), FIELD_REFERENCE_JS);
1521
- await writeFile5(path8.join(blockDir, "field-reference.css"), FIELD_REFERENCE_CSS);
1522
- await writeFile5(path8.join(blockDir, "_field-reference.json"), fieldReferenceModelFile());
1523
- created.push("blocks/field-reference/ (all 17 fields + multifield)");
1524
- const defFile = path8.join(projectRoot, "component-definition.json");
1525
- const modelsFile = path8.join(projectRoot, "component-models.json");
1526
- const filtersFile = path8.join(projectRoot, "component-filters.json");
1527
- const blockDefs = [
1528
- {
1529
- title: "Field Reference",
1530
- id: "field-reference",
1531
- plugins: {
1532
- xwalk: {
1533
- page: {
1534
- resourceType: "core/franklin/components/block/v1/block",
1535
- template: { name: "Field Reference", model: "field-reference" }
1536
- }
1537
- }
2170
+ logger.info(heading("Creating sandbox", `${owner}/${name} \xB7 ${template2}`));
2171
+ try {
2172
+ if (auth.mode === "api") {
2173
+ const repo = await createFromTemplate(auth.token, template2, { owner, name, private: priv });
2174
+ await addTopics(auth.token, owner, name, [SANDBOX_TOPIC]);
2175
+ logger.info(chalk4.dim(" Cloning..."));
2176
+ await run("git", ["clone", repo.cloneUrl, name], { stdio: "inherit" });
2177
+ } else {
2178
+ const repoRef = options.org ? `${options.org}/${name}` : name;
2179
+ await run("gh", createArgs(repoRef, { template: template2, private: priv, clone: true }), {
2180
+ stdio: "inherit"
2181
+ });
2182
+ try {
2183
+ await run("gh", ["repo", "edit", `${owner}/${name}`, "--add-topic", SANDBOX_TOPIC]);
2184
+ } catch {
1538
2185
  }
1539
2186
  }
1540
- ];
1541
- const definitions = mergeDefinitions(
1542
- await readJson(defFile),
1543
- DEFAULT_CONTENT_DEFINITIONS,
1544
- blockDefs
1545
- );
1546
- await writeJson(defFile, definitions);
1547
- created.push("component-definition.json (default content + field-reference)");
1548
- const existingModels = await readJson(modelsFile) ?? [];
1549
- const fieldRefModel = JSON.parse(fieldReferenceModelFile()).models[0];
1550
- const models = mergeById(existingModels, [...DEFAULT_CONTENT_MODELS, fieldRefModel]);
1551
- await writeJson(modelsFile, models);
1552
- created.push("component-models.json (title / image / button / field-reference)");
1553
- const filters = mergeSectionFilter(await readJson(filtersFile), ["field-reference"]);
1554
- await writeJson(filtersFile, filters);
1555
- created.push("component-filters.json (section filter)");
1556
- logger.info(heading("Created / merged"));
1557
- const names = created.map((c) => c.split(" ")[0]);
1558
- const w = columnWidth(names);
1559
- for (const c of created) {
1560
- const [name, ...rest] = c.split(" ");
1561
- logger.info(accentLine(name, rest.join(" ").replace(/^\((.*)\)$/, "$1"), w));
1562
- }
1563
- logger.info("");
1564
- logger.info(box(["Universal Editor scaffold ready"]));
1565
- }
1566
- async function scaffoldBlocks(names) {
1567
- const projectRoot = findProjectRoot();
1568
- if (!projectRoot) {
1569
- logger.error("Not inside an EDS project (no EDS project markers found).");
2187
+ } catch (err) {
2188
+ logger.error(`Create failed: ${err.message}`);
1570
2189
  process.exitCode = 1;
1571
2190
  return;
1572
2191
  }
1573
- logger.logoOnce(logo("Scaffold \u2014 standard blocks"));
1574
- let selected;
1575
- if (names?.length) {
1576
- selected = names.map((n2) => blockById(n2.trim())).filter((b) => !!b);
1577
- const unknown = names.filter((n2) => !blockById(n2.trim()));
1578
- if (unknown.length) {
1579
- logger.warn(
1580
- `Unknown block(s): ${unknown.join(", ")}. Available: ${STANDARD_BLOCKS.map((b) => b.id).join(", ")}`
1581
- );
2192
+ logger.info("");
2193
+ logger.info(heading("Sandbox ready"));
2194
+ logger.info(accentLine("repo", `https://github.com/${owner}/${name}`));
2195
+ logger.info(accentLine("preview", previewUrl(owner, name)));
2196
+ logger.info("");
2197
+ if (interactive) {
2198
+ if (await confirm({ message: "Open the AEM Code Sync install page now?", default: true })) {
2199
+ await openUrl(codeSyncInstallUrl());
2200
+ }
2201
+ if (await confirm({ message: `Configure authoring now (eds init in ./${name})?`, default: true })) {
2202
+ const prevCwd = process.cwd();
2203
+ try {
2204
+ process.chdir(path11.join(prevCwd, name));
2205
+ const { initProject: initProject2 } = await Promise.resolve().then(() => (init_init(), init_exports));
2206
+ await initProject2({});
2207
+ } finally {
2208
+ process.chdir(prevCwd);
2209
+ }
1582
2210
  }
1583
2211
  } else {
1584
- selected = STANDARD_BLOCKS;
2212
+ logger.info(chalk4.dim(` Install AEM Code Sync: ${codeSyncInstallUrl()}`));
2213
+ logger.info(chalk4.dim(` Configure authoring: cd ${name} && eds init`));
1585
2214
  }
1586
- const createdIds = [];
1587
- const skipped = [];
1588
- for (const b of selected) {
1589
- const dir = path8.join(projectRoot, "blocks", b.id);
1590
- if (existsSync6(dir)) {
1591
- skipped.push(b.id);
1592
- continue;
2215
+ logger.info("");
2216
+ logger.info(box([`Sandbox "${name}" ready - tear down with \`eds sandbox rm ${name}\``]));
2217
+ }
2218
+ async function sandboxList() {
2219
+ const auth = await resolveAuth();
2220
+ if (!auth) return noAuth();
2221
+ let names = [];
2222
+ try {
2223
+ if (auth.mode === "api") {
2224
+ names = await searchByTopic(auth.token, auth.login, SANDBOX_TOPIC);
2225
+ } else {
2226
+ const out = await run("gh", [
2227
+ "repo",
2228
+ "list",
2229
+ "--topic",
2230
+ SANDBOX_TOPIC,
2231
+ "--limit",
2232
+ "100",
2233
+ "--json",
2234
+ "nameWithOwner",
2235
+ "-q",
2236
+ ".[].nameWithOwner"
2237
+ ]);
2238
+ names = out ? out.split("\n").filter(Boolean) : [];
1593
2239
  }
1594
- await mkdir4(dir, { recursive: true });
1595
- await writeFile5(path8.join(dir, `${b.id}.js`), b.js);
1596
- await writeFile5(path8.join(dir, `${b.id}.css`), b.css);
1597
- await writeFile5(path8.join(dir, `_${b.id}.json`), b.model);
1598
- createdIds.push(b.id);
2240
+ } catch (err) {
2241
+ logger.error(`List failed: ${err.message}`);
2242
+ process.exitCode = 1;
2243
+ return;
2244
+ }
2245
+ logger.info(heading("Sandboxes", `${names.length} tagged ${SANDBOX_TOPIC}`));
2246
+ if (names.length === 0) {
2247
+ logger.info(chalk4.dim(" None yet. Create one with `eds sandbox new <name>`."));
2248
+ return;
2249
+ }
2250
+ const w = columnWidth(names);
2251
+ for (const full of names) {
2252
+ const [o, n2] = full.split("/");
2253
+ logger.info(accentLine(full, previewUrl(o, n2), w));
1599
2254
  }
1600
- const filtersFile = path8.join(projectRoot, "component-filters.json");
1601
- const filters = mergeSectionFilter(
1602
- await readJson(filtersFile),
1603
- selected.map((b) => b.id)
1604
- );
1605
- await writeJson(filtersFile, filters);
1606
- logger.info(heading("Blocks"));
1607
- const w = columnWidth([...createdIds, ...skipped, "component-filters"]);
1608
- for (const id of createdIds) logger.info(accentLine(id, "created", w));
1609
- for (const id of skipped) logger.info(statusLine("warn", id, "already exists \u2014 skipped", w));
1610
- logger.info("");
1611
- logger.info(box([`${createdIds.length} block(s) scaffolded + registered`]));
1612
2255
  }
1613
- async function scaffoldCi() {
1614
- const projectRoot = findProjectRoot();
1615
- if (!projectRoot) {
1616
- logger.error("Not inside an EDS project (no EDS project markers found).");
2256
+ async function sandboxRm(name, options = {}) {
2257
+ const auth = await resolveAuth();
2258
+ if (!auth) return noAuth();
2259
+ const owner = options.org ?? auth.login;
2260
+ const repoRef = `${owner}/${name}`;
2261
+ if (!options.yes) {
2262
+ const { confirm } = await import("@inquirer/prompts");
2263
+ const go = await confirm({
2264
+ message: `Permanently delete ${repoRef}? This cannot be undone.`,
2265
+ default: false
2266
+ });
2267
+ if (!go) {
2268
+ logger.info("Cancelled.");
2269
+ return;
2270
+ }
2271
+ }
2272
+ try {
2273
+ if (auth.mode === "api") await deleteRepo(auth.token, owner, name);
2274
+ else await run("gh", ["repo", "delete", repoRef, "--yes"]);
2275
+ } catch (err) {
2276
+ logger.error(`Delete failed: ${err.message}`);
2277
+ logger.info(
2278
+ chalk4.dim(
2279
+ " Deleting needs the delete_repo scope (re-run `eds sandbox login`, or `gh auth refresh -s delete_repo`)."
2280
+ )
2281
+ );
1617
2282
  process.exitCode = 1;
1618
2283
  return;
1619
2284
  }
1620
- logger.logoOnce(logo("Scaffold - CI workflow"));
1621
- const res = await writeCiWorkflow(projectRoot);
1622
- logger.info(heading("GitHub Actions"));
1623
- if (res.created) {
1624
- logger.info(accentLine(res.path, "doctor + audit loading + audit security + lint/build"));
1625
- } else {
1626
- logger.info(statusLine("warn", res.path, "already exists - skipped"));
2285
+ logger.info(heading("Deleted"));
2286
+ logger.info(accentLine(repoRef, "removed"));
2287
+ }
2288
+ var init_sandbox = __esm({
2289
+ "src/commands/sandbox.ts"() {
2290
+ "use strict";
2291
+ init_esm_shims();
2292
+ init_logger();
2293
+ init_api();
2294
+ init_auth();
2295
+ init_github();
2296
+ init_ui();
1627
2297
  }
1628
- logger.info("");
1629
- logger.info(
1630
- box([
1631
- res.created ? "CI workflow scaffolded - commit it and it runs on every PR" : "CI workflow left untouched"
1632
- ])
1633
- );
2298
+ });
2299
+
2300
+ // src/lib/template/authoring.ts
2301
+ import { existsSync as existsSync9 } from "fs";
2302
+ import { readFile as readFile5 } from "fs/promises";
2303
+ import path12 from "path";
2304
+ import { parse as parseYaml } from "yaml";
2305
+ async function detectAuthoring(projectRoot) {
2306
+ const fstab = path12.join(projectRoot, "fstab.yaml");
2307
+ if (existsSync9(fstab)) {
2308
+ try {
2309
+ const parsed = parseYaml(await readFile5(fstab, "utf-8")) ?? {};
2310
+ const mp = parsed.mountpoints?.["/"];
2311
+ const url = typeof mp === "string" ? mp : mp?.url ?? "";
2312
+ const da = url.match(/content\.da\.live\/([^/]+)\/([^/]+)/);
2313
+ if (da) return { model: "da", org: da[1], site: da[2], mountpoint: url };
2314
+ if (/drive\.google\.com/.test(url)) return { model: "gdrive", mountpoint: url };
2315
+ if (/sharepoint\.com|-my\.sharepoint/.test(url))
2316
+ return { model: "sharepoint", mountpoint: url };
2317
+ return { model: "document", mountpoint: url };
2318
+ } catch {
2319
+ }
2320
+ }
2321
+ return { model: "ue" };
2322
+ }
2323
+ var init_authoring = __esm({
2324
+ "src/lib/template/authoring.ts"() {
2325
+ "use strict";
2326
+ init_esm_shims();
2327
+ }
2328
+ });
2329
+
2330
+ // src/lib/template/da.ts
2331
+ function daSourceUrl(org, site, path36) {
2332
+ const clean = path36.replace(/^\/+/, "").replace(/\.html$/i, "");
2333
+ return `https://admin.da.live/source/${org}/${site}/${clean}.html`;
2334
+ }
2335
+ function daEditUrl(org, site, path36) {
2336
+ const clean = path36.replace(/^\/+/, "").replace(/\.html$/i, "");
2337
+ return `https://da.live/edit#/${org}/${site}/${clean}`;
2338
+ }
2339
+ async function pushToDa(org, site, path36, html, token) {
2340
+ const url = daSourceUrl(org, site, path36);
2341
+ const form = new FormData();
2342
+ form.append("data", new Blob([html], { type: "text/html" }), "content.html");
2343
+ const headers2 = {};
2344
+ if (token) headers2.Authorization = `Bearer ${token}`;
2345
+ const res = await fetch(url, { method: "POST", body: form, headers: headers2 });
2346
+ if (!res.ok) {
2347
+ throw new Error(`DA push failed: ${res.status} ${res.statusText}`);
2348
+ }
2349
+ return url;
2350
+ }
2351
+ var init_da = __esm({
2352
+ "src/lib/template/da.ts"() {
2353
+ "use strict";
2354
+ init_esm_shims();
2355
+ }
2356
+ });
2357
+
2358
+ // src/lib/template/page.ts
2359
+ function blockDiv(name) {
2360
+ return ` <div class="${name}">
2361
+ <div>
2362
+ <div>${name} content</div>
2363
+ </div>
2364
+ </div>`;
2365
+ }
2366
+ function metadataBlock(opts) {
2367
+ const rows = [
2368
+ ` <div><div>Title</div><div>${opts.title}</div></div>`,
2369
+ ` <div><div>Description</div><div>${opts.description ?? ""}</div></div>`
2370
+ ];
2371
+ if (opts.area) rows.push(` <div><div>Template</div><div>${opts.area}</div></div>`);
2372
+ return ` <div class="metadata">
2373
+ ${rows.join("\n")}
2374
+ </div>`;
2375
+ }
2376
+ function buildPageHtml(opts) {
2377
+ const blocks = opts.blocks ?? [];
2378
+ const intro = ` <div>
2379
+ <h1>${opts.title}</h1>
2380
+ <p>${opts.description ?? "Intro copy goes here."}</p>
2381
+ </div>`;
2382
+ const blockSections = blocks.map((b) => ` <div>
2383
+ ${blockDiv(b)}
2384
+ </div>`);
2385
+ const metaSection = ` <div>
2386
+ ${metadataBlock(opts)}
2387
+ </div>`;
2388
+ return `<body>
2389
+ <header></header>
2390
+ <main>
2391
+ ${[intro, ...blockSections, metaSection].join("\n")}
2392
+ </main>
2393
+ <footer></footer>
2394
+ </body>
2395
+ `;
1634
2396
  }
1635
- async function scaffoldHelpers(ids) {
1636
- const projectRoot = findProjectRoot();
1637
- if (!projectRoot) {
1638
- logger.error("Not inside an EDS project (no EDS project markers found).");
1639
- process.exitCode = 1;
1640
- return;
1641
- }
1642
- logger.logoOnce(logo("Scaffold - helpers"));
1643
- let selected = ids?.length ? ids : void 0;
1644
- if (!selected) {
1645
- if (process.stdout.isTTY) {
1646
- const { checkbox } = await import("@inquirer/prompts");
1647
- selected = await checkbox({
1648
- message: "Which helpers?",
1649
- choices: HELPERS.map((h) => ({ name: h.label, value: h.id, checked: true }))
1650
- });
1651
- } else {
1652
- selected = ALL_HELPER_IDS;
1653
- }
2397
+ var init_page = __esm({
2398
+ "src/lib/template/page.ts"() {
2399
+ "use strict";
2400
+ init_esm_shims();
1654
2401
  }
1655
- if (!selected.length) {
1656
- logger.info("Nothing selected.");
1657
- return;
2402
+ });
2403
+
2404
+ // src/lib/template/ue.ts
2405
+ import { existsSync as existsSync10 } from "fs";
2406
+ import { readFile as readFile6 } from "fs/promises";
2407
+ import path13 from "path";
2408
+ function fieldDefault(field) {
2409
+ if (field.value !== void 0) return field.value;
2410
+ switch (field.valueType) {
2411
+ case "boolean":
2412
+ return false;
2413
+ case "number":
2414
+ return 0;
2415
+ case "string[]":
2416
+ return [];
2417
+ default:
2418
+ return "";
1658
2419
  }
1659
- const res = await writeHelpers(projectRoot, selected);
1660
- logger.info(heading("Helpers"));
1661
- if (res.created) {
1662
- logger.info(accentLine(res.path, res.helpers.join(", ")));
1663
- } else {
1664
- logger.info(statusLine("warn", res.path, "already exists - skipped"));
2420
+ }
2421
+ async function blockDefaults(projectRoot, block2) {
2422
+ const file = path13.join(projectRoot, "blocks", block2, `_${block2}.json`);
2423
+ if (!existsSync10(file)) return {};
2424
+ try {
2425
+ const parsed = JSON.parse(await readFile6(file, "utf-8"));
2426
+ const models = Array.isArray(parsed.models) ? parsed.models : [];
2427
+ const model2 = models.find((m) => m.id === block2) ?? models[0];
2428
+ const fields = model2?.fields ?? [];
2429
+ const out = {};
2430
+ for (const f2 of fields) {
2431
+ if (f2.component === "tab") continue;
2432
+ out[f2.name] = f2.component === "container" ? [] : fieldDefault(f2);
2433
+ }
2434
+ return out;
2435
+ } catch {
2436
+ return {};
1665
2437
  }
1666
- logger.info("");
1667
- logger.info(
1668
- box([
1669
- res.created ? `${res.helpers.length} helper(s) -> scripts/utils.js` : "Helpers left untouched"
1670
- ])
1671
- );
1672
2438
  }
1673
- async function scaffoldInteractive() {
1674
- const { select, checkbox } = await import("@inquirer/prompts");
1675
- logger.logoOnce(logo("Scaffold"));
1676
- const what = await select({
1677
- message: "What do you want to scaffold?",
1678
- choices: [
1679
- { name: "Universal Editor config + field-reference (all 17 field types)", value: "ue" },
1680
- { name: "Standard blocks (hero, cards, columns, accordion, embed)", value: "blocks" },
1681
- { name: "GitHub Actions CI (doctor + audits + lint/build)", value: "ci" },
1682
- { name: "Helper functions (scripts/utils.js)", value: "helpers" },
1683
- { name: "UE config + standard blocks", value: "both" }
1684
- ]
1685
- });
1686
- if (what === "ue" || what === "both") await scaffoldUe();
1687
- if (what === "blocks" || what === "both") {
1688
- const picked = await checkbox({
1689
- message: "Which blocks?",
1690
- choices: STANDARD_BLOCKS.map((b) => ({ name: b.title, value: b.id, checked: true }))
1691
- });
1692
- await scaffoldBlocks(picked);
2439
+ async function buildUeTemplate(projectRoot, opts) {
2440
+ const components = [];
2441
+ for (const b of opts.blocks) {
2442
+ components.push({ name: b, model: b, fields: await blockDefaults(projectRoot, b) });
1693
2443
  }
1694
- if (what === "ci") await scaffoldCi();
1695
- if (what === "helpers") await scaffoldHelpers();
2444
+ const template2 = { title: opts.title, template: opts.name, components };
2445
+ return `${JSON.stringify(template2, null, 2)}
2446
+ `;
1696
2447
  }
1697
- var init_scaffold = __esm({
1698
- "src/commands/scaffold.ts"() {
2448
+ var init_ue2 = __esm({
2449
+ "src/lib/template/ue.ts"() {
1699
2450
  "use strict";
1700
2451
  init_esm_shims();
1701
- init_logger();
1702
- init_project();
1703
- init_blocks();
1704
- init_ci();
1705
- init_helpers();
1706
- init_ue();
1707
- init_ui();
1708
2452
  }
1709
2453
  });
1710
2454
 
1711
- // src/commands/init.ts
1712
- var init_exports = {};
1713
- __export(init_exports, {
1714
- initProject: () => initProject
2455
+ // src/commands/template.ts
2456
+ var template_exports = {};
2457
+ __export(template_exports, {
2458
+ templateNew: () => templateNew
1715
2459
  });
1716
- import { existsSync as existsSync7 } from "fs";
1717
- import { readFile as readFile4, writeFile as writeFile6 } from "fs/promises";
1718
- import path9 from "path";
1719
- import chalk4 from "chalk";
1720
- async function setPackageName(root, name) {
1721
- const file = path9.join(root, "package.json");
1722
- if (!existsSync7(file)) return false;
2460
+ import { existsSync as existsSync11 } from "fs";
2461
+ import { mkdir as mkdir5, readFile as readFile7, readdir, writeFile as writeFile9 } from "fs/promises";
2462
+ import path14 from "path";
2463
+ import chalk5 from "chalk";
2464
+ async function projectBlocks(projectRoot) {
2465
+ const dir = path14.join(projectRoot, "blocks");
2466
+ if (!existsSync11(dir)) return [];
2467
+ return (await readdir(dir, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name);
2468
+ }
2469
+ async function readEdsrcAdmin(projectRoot) {
2470
+ const rc = path14.join(projectRoot, ".edsrc.json");
2471
+ if (!existsSync11(rc)) return {};
1723
2472
  try {
1724
- const pkg = JSON.parse(await readFile4(file, "utf-8"));
1725
- pkg.name = name;
1726
- await writeFile6(file, `${JSON.stringify(pkg, null, 2)}
1727
- `);
1728
- return true;
2473
+ return JSON.parse(await readFile7(rc, "utf-8")).admin ?? {};
1729
2474
  } catch {
1730
- return false;
2475
+ return {};
1731
2476
  }
1732
2477
  }
1733
- async function replacePlaceholders(root, name) {
1734
- const touched = [];
1735
- for (const rel of ["README.md", "index.html", "head.html"]) {
1736
- const file = path9.join(root, rel);
1737
- if (!existsSync7(file)) continue;
2478
+ async function registerBlocks(projectRoot, blocks) {
2479
+ if (!blocks.length) return false;
2480
+ const file = path14.join(projectRoot, "component-filters.json");
2481
+ let existing = null;
2482
+ if (existsSync11(file)) {
1738
2483
  try {
1739
- const before = await readFile4(file, "utf-8");
1740
- const after = before.replace(/aem-boilerplate/gi, name);
1741
- if (after !== before) {
1742
- await writeFile6(file, after);
1743
- touched.push(rel);
1744
- }
2484
+ existing = JSON.parse(await readFile7(file, "utf-8"));
1745
2485
  } catch {
2486
+ existing = null;
1746
2487
  }
1747
2488
  }
1748
- return touched;
1749
- }
1750
- async function externalSteps(authoring) {
1751
- const { confirm } = await import("@inquirer/prompts");
1752
- const steps = [
1753
- {
1754
- title: "Install the AEM Code Sync GitHub app",
1755
- url: "https://github.com/apps/aem-code-sync/installations/new",
1756
- note: "Connects your repo to the Edge Delivery pipeline (preview/publish)."
1757
- }
1758
- ];
1759
- if (authoring === "gdrive") {
1760
- steps.push({
1761
- title: "Share your content folder",
1762
- note: "Share the Google Drive folder with the AEM service and set it as the mountpoint."
1763
- });
1764
- }
1765
- if (authoring === "sharepoint") {
1766
- steps.push({
1767
- title: "Share your content folder",
1768
- note: "Grant the SharePoint/OneDrive folder to the AEM service account."
1769
- });
1770
- }
1771
- if (authoring === "da") {
1772
- steps.push({
1773
- title: "Author in DA",
1774
- url: "https://da.live",
1775
- note: "Create documents at da.live."
1776
- });
1777
- }
1778
- if (authoring === "ue") {
1779
- steps.push({
1780
- title: "Open the Universal Editor",
1781
- url: "https://experience.adobe.com",
1782
- note: "Author in-context once the repo is connected to AEM."
1783
- });
1784
- }
1785
- steps.push({
1786
- title: "Install the AEM Sidekick extension",
1787
- url: "https://chromewebstore.google.com/detail/aem-sidekick/igkmdomcgoebiipaifhmpfjhbjccggml",
1788
- note: "Preview and publish from the browser."
1789
- });
1790
- steps.push({
1791
- title: "Run the local dev server",
1792
- note: "npm install, then `aem up` (from @adobe/aem-cli)."
1793
- });
1794
- logger.info(heading("Next steps (outside the repo)"));
1795
- for (let i = 0; i < steps.length; i++) {
1796
- const s = steps[i];
1797
- logger.info(` ${brand.accent(String(i + 1))} ${chalk4.bold(s.title)}`);
1798
- if (s.url) logger.info(` ${chalk4.cyan(s.url)}`);
1799
- logger.info(` ${chalk4.dim(s.note)}`);
1800
- await confirm({ message: " Done / skip?", default: true });
1801
- }
2489
+ const filters = mergeSectionFilter(existing, blocks);
2490
+ await writeFile9(file, `${JSON.stringify(filters, null, 2)}
2491
+ `);
2492
+ return true;
1802
2493
  }
1803
- async function initProject(options = {}) {
2494
+ async function templateNew(nameArg, options = {}) {
1804
2495
  const projectRoot = findProjectRoot();
1805
2496
  if (!projectRoot) {
1806
- logger.error(
1807
- "Not inside an EDS project (no EDS project markers found). Clone an EDS boilerplate first, then run this from its root."
1808
- );
2497
+ logger.error("Not inside an EDS project (no EDS project markers found).");
1809
2498
  process.exitCode = 1;
1810
2499
  return;
1811
2500
  }
1812
- await logger.logoOnceAnimated("Project setup");
2501
+ await logger.logoOnceAnimated("Page template");
1813
2502
  const { input, select, confirm } = await import("@inquirer/prompts");
1814
2503
  const interactive = !options.yes;
1815
- let defaultName = path9.basename(projectRoot);
1816
- const pkgFile = path9.join(projectRoot, "package.json");
1817
- if (existsSync7(pkgFile)) {
1818
- try {
1819
- defaultName = JSON.parse(await readFile4(pkgFile, "utf-8")).name || defaultName;
1820
- } catch {
2504
+ const detected = await detectAuthoring(projectRoot);
2505
+ const model2 = options.authoring ?? detected.model;
2506
+ const name = slug2(nameArg);
2507
+ const title = options.title ?? (interactive ? await input({ message: "Page title", default: nameArg }) : nameArg);
2508
+ const description = options.description ?? (interactive ? await input({ message: "Description (optional)" }) : "");
2509
+ const pagePath = options.path ?? `/templates/${name}`;
2510
+ let blocks = options.blocks;
2511
+ if (!blocks && interactive) {
2512
+ const available = await projectBlocks(projectRoot);
2513
+ const pool = available.length ? available : STANDARD_BLOCKS.map((b) => b.id);
2514
+ const ordered = [];
2515
+ let adding = true;
2516
+ while (adding) {
2517
+ const pick = await select({
2518
+ message: `Add a block in order (${ordered.length} so far)`,
2519
+ choices: [
2520
+ ...pool.map((b) => ({ name: b, value: b })),
2521
+ { name: ordered.length ? "- done -" : "- done (empty page) -", value: "__done" }
2522
+ ]
2523
+ });
2524
+ if (pick === "__done") adding = false;
2525
+ else ordered.push(pick);
1821
2526
  }
2527
+ blocks = ordered;
1822
2528
  }
1823
- const name = options.name && slug2(options.name) || slug2(
1824
- await input({
1825
- message: "1/4 Project name",
1826
- default: slug2(defaultName),
1827
- validate: (v) => !!slug2(v) || "Use letters, numbers and dashes"
1828
- })
1829
- );
1830
- const authoring = options.authoring ?? await select({
1831
- message: "2/4 Authoring model",
1832
- choices: [
1833
- { name: AUTHORING_NAMES.ue, value: "ue" },
1834
- { name: AUTHORING_NAMES.da, value: "da" },
1835
- { name: AUTHORING_NAMES.gdrive, value: "gdrive" },
1836
- { name: AUTHORING_NAMES.sharepoint, value: "sharepoint" }
1837
- ]
2529
+ const seeded = blocks ?? [];
2530
+ const html = buildPageHtml({
2531
+ title,
2532
+ description: description || void 0,
2533
+ blocks: seeded,
2534
+ area: options.area
1838
2535
  });
1839
- let mountpoint = options.mountpoint;
1840
- if (needsFstab(authoring) && !mountpoint) {
1841
- if (authoring === "da" && (options.org || interactive)) {
1842
- const org = options.org ?? await input({ message: " DA org", validate: (v) => !!v.trim() || "Required" });
1843
- const site = options.site ?? (interactive ? await input({ message: " DA site", default: name }) : name);
1844
- mountpoint = daMountpoint(org.trim(), slug2(site));
1845
- } else if (authoring !== "da" && interactive) {
1846
- const label = authoring === "gdrive" ? "Google Drive folder URL" : "SharePoint folder URL";
1847
- mountpoint = (await input({
1848
- message: ` ${label}`,
1849
- validate: (v) => /^https?:\/\//.test(v.trim()) || "Paste the folder URL"
1850
- })).trim();
1851
- }
1852
- }
1853
- const withCi = options.ci ?? (interactive ? await confirm({ message: "4/4 Add GitHub Actions CI (audits gatekeeper)?", default: true }) : true);
1854
- logger.info(heading("Summary"));
1855
- const w = columnWidth(["name", "authoring", "mountpoint", "ci"]);
1856
- logger.info(accentLine("name", name, w));
1857
- logger.info(accentLine("authoring", AUTHORING_NAMES[authoring], w));
1858
- if (mountpoint) logger.info(accentLine("mountpoint", mountpoint, w));
1859
- else if (needsFstab(authoring))
1860
- logger.info(accentLine("mountpoint", "(skipped - set it in fstab.yaml later)", w));
1861
- logger.info(accentLine("ci", withCi ? "yes" : "no", w));
1862
- logger.info("");
1863
- if (interactive) {
1864
- const go = await confirm({ message: "Apply this setup?", default: true });
1865
- if (!go) {
1866
- logger.info("Cancelled.");
1867
- return;
2536
+ const localRel = path14.join("templates", `${name}.html`);
2537
+ await mkdir5(path14.join(projectRoot, "templates"), { recursive: true });
2538
+ await writeFile9(path14.join(projectRoot, localRel), html);
2539
+ logger.info(heading("Page template", `${pagePath} \xB7 ${model2}`));
2540
+ logger.info(accentLine(localRel, `${seeded.length} block(s) + metadata`));
2541
+ if (model2 === "da") {
2542
+ const admin = await readEdsrcAdmin(projectRoot);
2543
+ const org = options.org ?? admin.org ?? detected.org;
2544
+ const site = options.site ?? admin.site ?? detected.site;
2545
+ const wantPush = options.push ?? (interactive ? await confirm({ message: "Push to DA (da.live)?", default: true }) : true);
2546
+ if (wantPush) {
2547
+ if (!org || !site) {
2548
+ logger.warn("No DA org/site. Pass --org/--site or set admin in .edsrc.json.");
2549
+ } else {
2550
+ try {
2551
+ await pushToDa(org, site, pagePath, html, process.env.DA_TOKEN);
2552
+ logger.info(accentLine("pushed to DA", daEditUrl(org, site, pagePath)));
2553
+ } catch (err) {
2554
+ logger.error(`DA push failed: ${err.message}`);
2555
+ logger.info(chalk5.dim(" Open projects allow anonymous writes; others need DA_TOKEN."));
2556
+ }
2557
+ }
1868
2558
  }
1869
- }
1870
- const done = [];
1871
- if (await setPackageName(projectRoot, name)) done.push("package.json (name)");
1872
- for (const t of await replacePlaceholders(projectRoot, name)) done.push(`${t} (renamed)`);
1873
- if (needsFstab(authoring) && mountpoint) done.push(await writeFstab(projectRoot, mountpoint));
1874
- if (authoring === "ue") {
1875
- await scaffoldUe();
1876
- done.push(await writePaths(projectRoot, name));
1877
- }
1878
- if (withCi) {
1879
- const ci = await writeCiWorkflow(projectRoot);
1880
- done.push(ci.created ? ci.path : `${ci.path} (exists, skipped)`);
1881
- }
1882
- logger.info(heading("Configured"));
1883
- const dw = columnWidth(done.map((d) => d.split(" ")[0]));
1884
- for (const d of done) {
1885
- const [n2, ...rest] = d.split(" ");
1886
- logger.info(accentLine(n2, rest.join(" ").replace(/^\((.*)\)$/, "$1"), dw));
2559
+ } else if (model2 === "gdrive" || model2 === "sharepoint") {
2560
+ const where = model2 === "gdrive" ? "a Google Doc" : "a Word doc";
2561
+ logger.info("");
2562
+ logger.info(
2563
+ chalk5.dim(` Document model: import ${localRel} into ${where} in your mounted folder,`)
2564
+ );
2565
+ logger.info(
2566
+ chalk5.dim(" then preview/publish. (Share the folder + install aem-code-sync first.)")
2567
+ );
2568
+ } else if (model2 === "ue") {
2569
+ const jsonRel = path14.join("templates", `${name}.json`);
2570
+ await writeFile9(
2571
+ path14.join(projectRoot, jsonRel),
2572
+ await buildUeTemplate(projectRoot, { title, name, blocks: seeded })
2573
+ );
2574
+ logger.info(
2575
+ accentLine(jsonRel, `initial content (${seeded.length} component(s), in order)`)
2576
+ );
2577
+ const registered = await registerBlocks(projectRoot, seeded);
2578
+ if (registered) {
2579
+ logger.info(
2580
+ accentLine("component-filters.json", "seeded blocks allowed in Universal Editor")
2581
+ );
2582
+ }
2583
+ logger.info("");
2584
+ logger.info(
2585
+ chalk5.dim(" Universal Editor: use the template JSON as the page's initial content, then")
2586
+ );
2587
+ logger.info(chalk5.dim(" author it in the Universal Editor (wire the repo to AEM)."));
1887
2588
  }
1888
2589
  logger.info("");
1889
- if (interactive) await externalSteps(authoring);
1890
- logger.info(box([`"${name}" set up for ${AUTHORING_NAMES[authoring]}`]));
2590
+ logger.info(box([`"${name}" template (${model2}) - preview with \`eds preview ${pagePath}\``]));
1891
2591
  }
1892
2592
  var slug2;
1893
- var init_init = __esm({
1894
- "src/commands/init.ts"() {
2593
+ var init_template = __esm({
2594
+ "src/commands/template.ts"() {
1895
2595
  "use strict";
1896
2596
  init_esm_shims();
1897
2597
  init_logger();
1898
2598
  init_project();
1899
- init_ci();
1900
- init_fstab();
2599
+ init_blocks();
2600
+ init_ue();
2601
+ init_authoring();
2602
+ init_da();
2603
+ init_page();
2604
+ init_ue2();
1901
2605
  init_ui();
1902
- init_scaffold();
1903
- slug2 = (s) => s.trim().toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "");
2606
+ slug2 = (s) => s.trim().toLowerCase().replace(/[^a-z0-9-/]+/g, "-").replace(/^-+|-+$/g, "");
1904
2607
  }
1905
2608
  });
1906
2609
 
1907
2610
  // src/lib/migrate/clientlib.ts
1908
- import { existsSync as existsSync8 } from "fs";
1909
- import { readFile as readFile5, readdir as readdir2 } from "fs/promises";
1910
- import path10 from "path";
2611
+ import { existsSync as existsSync12 } from "fs";
2612
+ import { readFile as readFile8, readdir as readdir2 } from "fs/promises";
2613
+ import path15 from "path";
1911
2614
  async function listFiles(dir) {
1912
2615
  const out = [];
1913
2616
  const walk3 = async (d) => {
1914
2617
  for (const e of await readdir2(d, { withFileTypes: true })) {
1915
- const p = path10.join(d, e.name);
2618
+ const p = path15.join(d, e.name);
1916
2619
  if (e.isDirectory()) await walk3(p);
1917
2620
  else out.push(p);
1918
2621
  }
1919
2622
  };
1920
- if (existsSync8(dir)) await walk3(dir);
2623
+ if (existsSync12(dir)) await walk3(dir);
1921
2624
  return out;
1922
2625
  }
1923
2626
  async function findClientlibDir(componentDir) {
1924
- if (!existsSync8(componentDir)) return null;
2627
+ if (!existsSync12(componentDir)) return null;
1925
2628
  const entries = await readdir2(componentDir, { withFileTypes: true });
1926
2629
  const match = entries.find((e) => e.isDirectory() && /clientlib/i.test(e.name));
1927
- if (match) return path10.join(componentDir, match.name);
2630
+ if (match) return path15.join(componentDir, match.name);
1928
2631
  const hasCss = entries.some((e) => e.isFile() && e.name.endsWith(".css"));
1929
2632
  return hasCss ? componentDir : null;
1930
2633
  }
1931
2634
  async function readClientlibCss(clientlibDir) {
1932
2635
  const all = await listFiles(clientlibDir);
1933
- const preprocessed = all.filter((f2) => /\.(less|scss|sass)$/i.test(f2)).map((f2) => path10.relative(clientlibDir, f2));
2636
+ const preprocessed = all.filter((f2) => /\.(less|scss|sass)$/i.test(f2)).map((f2) => path15.relative(clientlibDir, f2));
1934
2637
  let cssFiles;
1935
- const cssTxt = path10.join(clientlibDir, "css.txt");
1936
- if (existsSync8(cssTxt)) {
1937
- const lines = (await readFile5(cssTxt, "utf-8")).split("\n").map((l) => l.trim());
2638
+ const cssTxt = path15.join(clientlibDir, "css.txt");
2639
+ if (existsSync12(cssTxt)) {
2640
+ const lines = (await readFile8(cssTxt, "utf-8")).split("\n").map((l) => l.trim());
1938
2641
  let base = clientlibDir;
1939
2642
  const listed = [];
1940
2643
  for (const l of lines) {
1941
2644
  if (!l) continue;
1942
2645
  const baseMatch = l.match(/^#base\s*=\s*(.+)$/);
1943
- if (baseMatch) base = path10.join(clientlibDir, baseMatch[1].trim());
2646
+ if (baseMatch) base = path15.join(clientlibDir, baseMatch[1].trim());
1944
2647
  else if (!l.startsWith("#")) listed.push(l);
1945
2648
  }
1946
- cssFiles = listed.map((rel) => path10.join(base, rel)).filter((f2) => existsSync8(f2) && f2.endsWith(".css"));
2649
+ cssFiles = listed.map((rel) => path15.join(base, rel)).filter((f2) => existsSync12(f2) && f2.endsWith(".css"));
1947
2650
  } else {
1948
2651
  cssFiles = all.filter((f2) => f2.endsWith(".css"));
1949
2652
  }
1950
2653
  const parts = [];
1951
- for (const f2 of cssFiles) parts.push(await readFile5(f2, "utf-8"));
2654
+ for (const f2 of cssFiles) parts.push(await readFile8(f2, "utf-8"));
1952
2655
  return {
1953
2656
  css: parts.join("\n\n"),
1954
- files: cssFiles.map((f2) => path10.relative(clientlibDir, f2)),
2657
+ files: cssFiles.map((f2) => path15.relative(clientlibDir, f2)),
1955
2658
  preprocessed
1956
2659
  };
1957
2660
  }
@@ -2103,24 +2806,24 @@ var init_dialog = __esm({
2103
2806
  });
2104
2807
 
2105
2808
  // src/lib/migrate/component.ts
2106
- import { existsSync as existsSync9 } from "fs";
2107
- import { readFile as readFile6 } from "fs/promises";
2108
- import path11 from "path";
2809
+ import { existsSync as existsSync13 } from "fs";
2810
+ import { readFile as readFile9 } from "fs/promises";
2811
+ import path16 from "path";
2109
2812
  import { XMLParser as XMLParser2 } from "fast-xml-parser";
2110
2813
  function findDialogFile(componentDir) {
2111
2814
  const candidates = [
2112
- path11.join(componentDir, "_cq_dialog", ".content.xml"),
2113
- path11.join(componentDir, "_cq_dialog.xml"),
2114
- path11.join(componentDir, "cq:dialog", ".content.xml")
2815
+ path16.join(componentDir, "_cq_dialog", ".content.xml"),
2816
+ path16.join(componentDir, "_cq_dialog.xml"),
2817
+ path16.join(componentDir, "cq:dialog", ".content.xml")
2115
2818
  ];
2116
- return candidates.find((c) => existsSync9(c)) ?? null;
2819
+ return candidates.find((c) => existsSync13(c)) ?? null;
2117
2820
  }
2118
2821
  async function readComponentTitle(componentDir) {
2119
- const file = path11.join(componentDir, ".content.xml");
2120
- if (!existsSync9(file)) return void 0;
2822
+ const file = path16.join(componentDir, ".content.xml");
2823
+ if (!existsSync13(file)) return void 0;
2121
2824
  try {
2122
2825
  const parser = new XMLParser2({ ignoreAttributes: false, attributeNamePrefix: "@_" });
2123
- const parsed = parser.parse(await readFile6(file, "utf-8"));
2826
+ const parsed = parser.parse(await readFile9(file, "utf-8"));
2124
2827
  const root = parsed["jcr:root"] ?? {};
2125
2828
  const title = root["@_jcr:title"];
2126
2829
  return typeof title === "string" ? title.replace(/^\{[^}]+\}/, "") : void 0;
@@ -2131,7 +2834,7 @@ async function readComponentTitle(componentDir) {
2131
2834
  async function readDialogFields(componentDir) {
2132
2835
  const dialog = findDialogFile(componentDir);
2133
2836
  if (!dialog) return null;
2134
- return parseDialog(await readFile6(dialog, "utf-8"));
2837
+ return parseDialog(await readFile9(dialog, "utf-8"));
2135
2838
  }
2136
2839
  function migratedModelFile(blockName, title, fields) {
2137
2840
  const model2 = {
@@ -2260,10 +2963,10 @@ var track_exports = {};
2260
2963
  __export(track_exports, {
2261
2964
  trackBlock: () => trackBlock
2262
2965
  });
2263
- import { existsSync as existsSync10 } from "fs";
2264
- import { readFile as readFile7, writeFile as writeFile7 } from "fs/promises";
2265
- import path12 from "path";
2266
- import chalk5 from "chalk";
2966
+ import { existsSync as existsSync14 } from "fs";
2967
+ import { readFile as readFile10, writeFile as writeFile10 } from "fs/promises";
2968
+ import path17 from "path";
2969
+ import chalk6 from "chalk";
2267
2970
  async function trackBlock(blockName, options = {}) {
2268
2971
  const projectRoot = findProjectRoot();
2269
2972
  if (!projectRoot) {
@@ -2272,30 +2975,30 @@ async function trackBlock(blockName, options = {}) {
2272
2975
  return;
2273
2976
  }
2274
2977
  logger.logoOnce(logo("Track block"));
2275
- const jsPath = path12.join(projectRoot, "blocks", blockName, `${blockName}.js`);
2276
- if (!existsSync10(jsPath)) {
2978
+ const jsPath = path17.join(projectRoot, "blocks", blockName, `${blockName}.js`);
2979
+ if (!existsSync14(jsPath)) {
2277
2980
  logger.error(`Block "${blockName}" not found (no blocks/${blockName}/${blockName}.js).`);
2278
2981
  process.exitCode = 1;
2279
2982
  return;
2280
2983
  }
2281
2984
  const events = { click: options.click !== false, submit: options.submit !== false };
2282
- const res = instrumentJs(await readFile7(jsPath, "utf-8"), blockName, events);
2985
+ const res = instrumentJs(await readFile10(jsPath, "utf-8"), blockName, events);
2283
2986
  if (!res.changed) {
2284
2987
  logger.warn(`Not instrumented: ${res.reason}.`);
2285
2988
  return;
2286
2989
  }
2287
- await writeFile7(jsPath, res.js);
2990
+ await writeFile10(jsPath, res.js);
2288
2991
  logger.info(heading("Instrumented"));
2289
2992
  const picked = [events.click ? "block_click" : null, events.submit ? "form_submit" : null].filter(
2290
2993
  Boolean
2291
2994
  );
2292
2995
  logger.info(accentLine(`blocks/${blockName}/${blockName}.js`, picked.join(" + ")));
2293
- if (!existsSync10(path12.join(projectRoot, "scripts", "analytics.js"))) {
2996
+ if (!existsSync14(path17.join(projectRoot, "scripts", "analytics.js"))) {
2294
2997
  logger.info("");
2295
2998
  logger.warn("scripts/analytics.js is missing - run `eds integrate gtm` to create it.");
2296
2999
  }
2297
3000
  logger.info("");
2298
- logger.info(chalk5.dim(" Events push to window.dataLayer via trackEvent - GTM picks them up."));
3001
+ logger.info(chalk6.dim(" Events push to window.dataLayer via trackEvent - GTM picks them up."));
2299
3002
  }
2300
3003
  var init_track2 = __esm({
2301
3004
  "src/commands/track.ts"() {
@@ -2313,9 +3016,9 @@ var migrate_exports = {};
2313
3016
  __export(migrate_exports, {
2314
3017
  migrateComponent: () => migrateComponent
2315
3018
  });
2316
- import { existsSync as existsSync11 } from "fs";
2317
- import { mkdir as mkdir5, writeFile as writeFile8 } from "fs/promises";
2318
- import path13 from "path";
3019
+ import { existsSync as existsSync15 } from "fs";
3020
+ import { mkdir as mkdir6, writeFile as writeFile11 } from "fs/promises";
3021
+ import path18 from "path";
2319
3022
  function clientlibBlockCss(blockName, css, files) {
2320
3023
  return `/* Block: ${blockName} */
2321
3024
  /* Migrated from the component clientlib: ${files.join(", ")}. */
@@ -2363,7 +3066,7 @@ async function migrateComponent(dirArg, options = {}) {
2363
3066
  if (!componentDir && interactive) {
2364
3067
  componentDir = await input({
2365
3068
  message: "Path to the classic component folder",
2366
- validate: (v) => existsSync11(v.trim()) || "Folder not found"
3069
+ validate: (v) => existsSync15(v.trim()) || "Folder not found"
2367
3070
  });
2368
3071
  }
2369
3072
  if (!componentDir) {
@@ -2371,7 +3074,7 @@ async function migrateComponent(dirArg, options = {}) {
2371
3074
  process.exitCode = 1;
2372
3075
  return;
2373
3076
  }
2374
- componentDir = path13.resolve(componentDir.trim());
3077
+ componentDir = path18.resolve(componentDir.trim());
2375
3078
  if (!findDialogFile(componentDir)) {
2376
3079
  logger.error(`No Touch UI dialog (_cq_dialog/.content.xml) found in ${componentDir}.`);
2377
3080
  process.exitCode = 1;
@@ -2385,7 +3088,7 @@ async function migrateComponent(dirArg, options = {}) {
2385
3088
  process.exitCode = 1;
2386
3089
  return;
2387
3090
  }
2388
- const title = await readComponentTitle(componentDir) ?? path13.basename(componentDir);
3091
+ const title = await readComponentTitle(componentDir) ?? path18.basename(componentDir);
2389
3092
  const blockName = options.name && slug3(options.name) || (interactive ? slug3(await input({ message: "Block name", default: slug3(title) })) : slug3(title));
2390
3093
  logger.info(heading("Mapped fields", `${countLeaves(parsed.fields)} from dialog`));
2391
3094
  for (const line of previewFields(parsed.fields)) logger.info(` ${line}`);
@@ -2401,19 +3104,19 @@ async function migrateComponent(dirArg, options = {}) {
2401
3104
  return;
2402
3105
  }
2403
3106
  }
2404
- const blockDir = path13.join(projectRoot, "blocks", blockName);
2405
- if (existsSync11(blockDir)) {
3107
+ const blockDir = path18.join(projectRoot, "blocks", blockName);
3108
+ if (existsSync15(blockDir)) {
2406
3109
  logger.error(
2407
3110
  `Block "${blockName}" already exists at blocks/${blockName}. Use --name to pick another.`
2408
3111
  );
2409
3112
  process.exitCode = 1;
2410
3113
  return;
2411
3114
  }
2412
- const clibDir = options.clientlib ? path13.resolve(options.clientlib.trim()) : await findClientlibDir(componentDir);
3115
+ const clibDir = options.clientlib ? path18.resolve(options.clientlib.trim()) : await findClientlibDir(componentDir);
2413
3116
  let cssContent = migratedBlockCss(blockName);
2414
3117
  let cssMessage = "empty - add styles from clientlib / rendered CSS";
2415
3118
  let preprocessedNote = [];
2416
- if (clibDir && existsSync11(clibDir)) {
3119
+ if (clibDir && existsSync15(clibDir)) {
2417
3120
  const lib = await readClientlibCss(clibDir);
2418
3121
  if (lib.css.trim()) {
2419
3122
  cssContent = clientlibBlockCss(blockName, lib.css, lib.files);
@@ -2421,14 +3124,14 @@ async function migrateComponent(dirArg, options = {}) {
2421
3124
  }
2422
3125
  preprocessedNote = lib.preprocessed;
2423
3126
  }
2424
- await mkdir5(blockDir, { recursive: true });
2425
- await writeFile8(
2426
- path13.join(blockDir, `${blockName}.js`),
3127
+ await mkdir6(blockDir, { recursive: true });
3128
+ await writeFile11(
3129
+ path18.join(blockDir, `${blockName}.js`),
2427
3130
  migratedBlockJs(blockName, parsed.fields)
2428
3131
  );
2429
- await writeFile8(path13.join(blockDir, `${blockName}.css`), cssContent);
2430
- await writeFile8(
2431
- path13.join(blockDir, `_${blockName}.json`),
3132
+ await writeFile11(path18.join(blockDir, `${blockName}.css`), cssContent);
3133
+ await writeFile11(
3134
+ path18.join(blockDir, `_${blockName}.json`),
2432
3135
  migratedModelFile(blockName, title, parsed.fields)
2433
3136
  );
2434
3137
  logger.info(heading("Created"));
@@ -2678,9 +3381,9 @@ var model_exports = {};
2678
3381
  __export(model_exports, {
2679
3382
  modelAdd: () => modelAdd
2680
3383
  });
2681
- import { existsSync as existsSync12 } from "fs";
2682
- import { readFile as readFile8, writeFile as writeFile9 } from "fs/promises";
2683
- import path14 from "path";
3384
+ import { existsSync as existsSync16 } from "fs";
3385
+ import { readFile as readFile11, writeFile as writeFile12 } from "fs/promises";
3386
+ import path19 from "path";
2684
3387
  async function modelAdd(blockArg, partialArgs, options = {}) {
2685
3388
  const projectRoot = findProjectRoot();
2686
3389
  if (!projectRoot) {
@@ -2693,10 +3396,10 @@ async function modelAdd(blockArg, partialArgs, options = {}) {
2693
3396
  const interactive = !options.yes;
2694
3397
  let blockName = blockArg;
2695
3398
  if (!blockName && interactive) {
2696
- const { readdir: readdir8 } = await import("fs/promises");
2697
- const blocksDir = path14.join(projectRoot, "blocks");
2698
- const dirs = existsSync12(blocksDir) ? (await readdir8(blocksDir, { withFileTypes: true })).filter(
2699
- (e) => e.isDirectory() && existsSync12(path14.join(blocksDir, e.name, `_${e.name}.json`))
3399
+ const { readdir: readdir9 } = await import("fs/promises");
3400
+ const blocksDir = path19.join(projectRoot, "blocks");
3401
+ const dirs = existsSync16(blocksDir) ? (await readdir9(blocksDir, { withFileTypes: true })).filter(
3402
+ (e) => e.isDirectory() && existsSync16(path19.join(blocksDir, e.name, `_${e.name}.json`))
2700
3403
  ).map((e) => e.name) : [];
2701
3404
  if (dirs.length === 0) {
2702
3405
  logger.error("No block with a _<name>.json model found. Create one with `eds block create`.");
@@ -2713,8 +3416,8 @@ async function modelAdd(blockArg, partialArgs, options = {}) {
2713
3416
  process.exitCode = 1;
2714
3417
  return;
2715
3418
  }
2716
- const modelFile = path14.join(projectRoot, "blocks", blockName, `_${blockName}.json`);
2717
- if (!existsSync12(modelFile)) {
3419
+ const modelFile = path19.join(projectRoot, "blocks", blockName, `_${blockName}.json`);
3420
+ if (!existsSync16(modelFile)) {
2718
3421
  logger.error(`No model at blocks/${blockName}/_${blockName}.json. Create the block first.`);
2719
3422
  process.exitCode = 1;
2720
3423
  return;
@@ -2739,7 +3442,7 @@ async function modelAdd(blockArg, partialArgs, options = {}) {
2739
3442
  }
2740
3443
  let model2;
2741
3444
  try {
2742
- model2 = JSON.parse(await readFile8(modelFile, "utf-8"));
3445
+ model2 = JSON.parse(await readFile11(modelFile, "utf-8"));
2743
3446
  } catch {
2744
3447
  logger.error(`Could not parse ${modelFile}.`);
2745
3448
  process.exitCode = 1;
@@ -2767,7 +3470,7 @@ async function modelAdd(blockArg, partialArgs, options = {}) {
2767
3470
  added.push(field.name);
2768
3471
  }
2769
3472
  }
2770
- await writeFile9(modelFile, `${JSON.stringify(model2, null, 2)}
3473
+ await writeFile12(modelFile, `${JSON.stringify(model2, null, 2)}
2771
3474
  `);
2772
3475
  logger.info(heading("Model updated", `blocks/${blockName}/_${blockName}.json`));
2773
3476
  logger.info(accentLine("partials", partials.map((p) => p.id).join(", ")));
@@ -2959,10 +3662,10 @@ var schema_exports = {};
2959
3662
  __export(schema_exports, {
2960
3663
  schemaBlock: () => schemaBlock
2961
3664
  });
2962
- import { existsSync as existsSync13 } from "fs";
2963
- import { readFile as readFile9, writeFile as writeFile10 } from "fs/promises";
2964
- import path15 from "path";
2965
- import chalk6 from "chalk";
3665
+ import { existsSync as existsSync17 } from "fs";
3666
+ import { readFile as readFile12, writeFile as writeFile13 } from "fs/promises";
3667
+ import path20 from "path";
3668
+ import chalk7 from "chalk";
2966
3669
  async function schemaBlock(blockName, options = {}) {
2967
3670
  const projectRoot = findProjectRoot();
2968
3671
  if (!projectRoot) {
@@ -2971,8 +3674,8 @@ async function schemaBlock(blockName, options = {}) {
2971
3674
  return;
2972
3675
  }
2973
3676
  logger.logoOnce(logo("Schema - JSON-LD"));
2974
- const jsPath = path15.join(projectRoot, "blocks", blockName, `${blockName}.js`);
2975
- if (!existsSync13(jsPath)) {
3677
+ const jsPath = path20.join(projectRoot, "blocks", blockName, `${blockName}.js`);
3678
+ if (!existsSync17(jsPath)) {
2976
3679
  logger.error(`Block "${blockName}" not found (no blocks/${blockName}/${blockName}.js).`);
2977
3680
  process.exitCode = 1;
2978
3681
  return;
@@ -2993,17 +3696,17 @@ async function schemaBlock(blockName, options = {}) {
2993
3696
  process.exitCode = 1;
2994
3697
  return;
2995
3698
  }
2996
- const res = injectJsonLd(await readFile9(jsPath, "utf-8"), type);
3699
+ const res = injectJsonLd(await readFile12(jsPath, "utf-8"), type);
2997
3700
  if (!res.changed) {
2998
3701
  logger.warn(`Not added: ${res.reason}.`);
2999
3702
  return;
3000
3703
  }
3001
- await writeFile10(jsPath, res.js);
3704
+ await writeFile13(jsPath, res.js);
3002
3705
  logger.info(heading("JSON-LD added"));
3003
3706
  logger.info(accentLine(`blocks/${blockName}/${blockName}.js`, `${type.id} (${type.label})`));
3004
3707
  logger.info("");
3005
3708
  logger.info(
3006
- chalk6.dim(
3709
+ chalk7.dim(
3007
3710
  " Review the generated object - some fields are heuristic or TODO. Validate at search.google.com/test/rich-results."
3008
3711
  )
3009
3712
  );
@@ -3019,16 +3722,198 @@ var init_schema = __esm({
3019
3722
  }
3020
3723
  });
3021
3724
 
3725
+ // src/lib/ue/check.ts
3726
+ function metaContent(html, name) {
3727
+ for (const tag of html.match(/<meta\b[^>]*>/gi) ?? []) {
3728
+ if (tag.match(/\bname=["']([^"']+)["']/i)?.[1] === name) {
3729
+ return tag.match(/\bcontent=["']([^"']*)["']/i)?.[1] ?? "";
3730
+ }
3731
+ }
3732
+ return null;
3733
+ }
3734
+ function hasAemConnection(html) {
3735
+ return metaContent(html, AEM_CONNECTION_META) !== null;
3736
+ }
3737
+ function modelIds(componentModels) {
3738
+ if (!Array.isArray(componentModels)) return [];
3739
+ return componentModels.map((m) => m && typeof m === "object" ? m.id : void 0).filter((id) => typeof id === "string");
3740
+ }
3741
+ function assessReadiness(input) {
3742
+ const isUeProject = !!input.configPresent["component-definition.json"] && !!input.configPresent["component-models.json"];
3743
+ const ids = new Set(input.modelIds);
3744
+ const blocks = input.blocks.map((b) => {
3745
+ const editable = b.hasModelFile || ids.has(b.name);
3746
+ return {
3747
+ name: b.name,
3748
+ editable,
3749
+ reason: editable ? void 0 : "no UE model (_<name>.json or component-models entry)"
3750
+ };
3751
+ });
3752
+ return { isUeProject, blocks };
3753
+ }
3754
+ var UE_CONFIG_FILES, AEM_CONNECTION_META;
3755
+ var init_check = __esm({
3756
+ "src/lib/ue/check.ts"() {
3757
+ "use strict";
3758
+ init_esm_shims();
3759
+ UE_CONFIG_FILES = [
3760
+ "component-definition.json",
3761
+ "component-models.json",
3762
+ "component-filters.json"
3763
+ ];
3764
+ AEM_CONNECTION_META = "urn:adobe:aue:system:aemconnection";
3765
+ }
3766
+ });
3767
+
3768
+ // src/commands/ue.ts
3769
+ var ue_exports = {};
3770
+ __export(ue_exports, {
3771
+ ueCheck: () => ueCheck,
3772
+ ueOpen: () => ueOpen
3773
+ });
3774
+ import { existsSync as existsSync18 } from "fs";
3775
+ import { readFile as readFile13, readdir as readdir3 } from "fs/promises";
3776
+ import path21 from "path";
3777
+ import chalk8 from "chalk";
3778
+ async function readJson2(file) {
3779
+ if (!existsSync18(file)) return null;
3780
+ try {
3781
+ return JSON.parse(await readFile13(file, "utf-8"));
3782
+ } catch {
3783
+ return null;
3784
+ }
3785
+ }
3786
+ async function scanBlocks(root) {
3787
+ const blocksDir = path21.join(root, "blocks");
3788
+ if (!existsSync18(blocksDir)) return [];
3789
+ const entries = await readdir3(blocksDir, { withFileTypes: true });
3790
+ return entries.filter((e) => e.isDirectory()).map((e) => ({
3791
+ name: e.name,
3792
+ hasModelFile: existsSync18(path21.join(blocksDir, e.name, `_${e.name}.json`))
3793
+ }));
3794
+ }
3795
+ async function openUrl2(url) {
3796
+ const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
3797
+ try {
3798
+ const { execa } = await import("execa");
3799
+ await execa(cmd, [url], { stdio: "ignore" });
3800
+ } catch {
3801
+ }
3802
+ }
3803
+ async function gitRemote(root) {
3804
+ try {
3805
+ const { execa } = await import("execa");
3806
+ const res = await execa("git", ["remote", "get-url", "origin"], { cwd: root });
3807
+ return res.stdout.trim() || null;
3808
+ } catch {
3809
+ return null;
3810
+ }
3811
+ }
3812
+ async function ueCheck() {
3813
+ const root = findProjectRoot();
3814
+ if (!root) {
3815
+ logger.error("Not inside an EDS project (no EDS project markers found).");
3816
+ process.exitCode = 1;
3817
+ return;
3818
+ }
3819
+ logger.logoOnce(logo("Universal Editor - readiness"));
3820
+ const configPresent = {};
3821
+ for (const f2 of UE_CONFIG_FILES) configPresent[f2] = existsSync18(path21.join(root, f2));
3822
+ const componentModels = await readJson2(path21.join(root, "component-models.json"));
3823
+ const blocks = await scanBlocks(root);
3824
+ const { isUeProject, blocks: blockReadiness } = assessReadiness({
3825
+ configPresent,
3826
+ blocks,
3827
+ modelIds: modelIds(componentModels)
3828
+ });
3829
+ logger.info(heading("Universal Editor config"));
3830
+ const names = [...UE_CONFIG_FILES];
3831
+ const w = columnWidth(names);
3832
+ for (const f2 of names) {
3833
+ logger.info(statusLine(configPresent[f2] ? "pass" : "fail", f2, void 0, w));
3834
+ }
3835
+ if (!isUeProject) {
3836
+ logger.info("");
3837
+ logger.warn(
3838
+ "This doesn't look like a Universal Editor (crosswalk) project - run `eds scaffold ue` first."
3839
+ );
3840
+ process.exitCode = 1;
3841
+ return;
3842
+ }
3843
+ const headFile = path21.join(root, "head.html");
3844
+ if (existsSync18(headFile)) {
3845
+ const connected = hasAemConnection(await readFile13(headFile, "utf-8"));
3846
+ logger.info(
3847
+ statusLine(
3848
+ connected ? "pass" : "info",
3849
+ "head.html",
3850
+ connected ? "aemconnection present" : "no aemconnection (fine for cloud author)"
3851
+ )
3852
+ );
3853
+ }
3854
+ logger.info(heading("Blocks", `${blockReadiness.length} found`));
3855
+ if (blockReadiness.length === 0) {
3856
+ logger.info(chalk8.dim(" No blocks yet."));
3857
+ } else {
3858
+ const bw = columnWidth(blockReadiness.map((b) => b.name));
3859
+ for (const b of blockReadiness) {
3860
+ logger.info(statusLine(b.editable ? "pass" : "warn", b.name, b.reason, bw));
3861
+ }
3862
+ }
3863
+ const editable = blockReadiness.filter((b) => b.editable).length;
3864
+ const notEditable = blockReadiness.length - editable;
3865
+ logger.info("");
3866
+ logger.info(summary({ pass: editable, warn: notEditable }));
3867
+ logger.info(chalk8.dim(" Open a page in the editor with `eds ue open <path>`."));
3868
+ }
3869
+ async function ueOpen(pagePath, options = {}) {
3870
+ const root = findProjectRoot() ?? process.cwd();
3871
+ const pPath = pagePath ?? options.path ?? "/";
3872
+ let host = options.url;
3873
+ if (!host) {
3874
+ const remote = await gitRemote(root);
3875
+ const parsed = remote ? parseRemote(remote) : null;
3876
+ if (!parsed) {
3877
+ logger.error("Couldn't determine the preview host from git.");
3878
+ logger.info(
3879
+ chalk8.dim(
3880
+ " Pass it explicitly: `eds ue open <path> --url main--<repo>--<owner>.aem.page`"
3881
+ )
3882
+ );
3883
+ process.exitCode = 1;
3884
+ return;
3885
+ }
3886
+ host = previewHost(parsed.owner, parsed.repo, options.ref ?? "main");
3887
+ }
3888
+ const link2 = editorLink({ host, path: pPath, org: options.org });
3889
+ logger.info(heading("Universal Editor"));
3890
+ logger.info(accentLine("page", `${host}${pPath.startsWith("/") ? "" : "/"}${pPath}`));
3891
+ logger.info(accentLine("open", link2));
3892
+ if (options.open !== false) await openUrl2(link2);
3893
+ else logger.info(chalk8.dim(" (use without --no-open to launch the browser)"));
3894
+ }
3895
+ var init_ue3 = __esm({
3896
+ "src/commands/ue.ts"() {
3897
+ "use strict";
3898
+ init_esm_shims();
3899
+ init_logger();
3900
+ init_project();
3901
+ init_check();
3902
+ init_link();
3903
+ init_ui();
3904
+ }
3905
+ });
3906
+
3022
3907
  // src/commands/block/create.ts
3023
3908
  var create_exports = {};
3024
3909
  __export(create_exports, {
3025
3910
  createBlock: () => createBlock,
3026
3911
  ueModelFor: () => ueModelFor
3027
3912
  });
3028
- import { existsSync as existsSync14 } from "fs";
3029
- import { mkdir as mkdir6, writeFile as writeFile11 } from "fs/promises";
3030
- import path16 from "path";
3031
- import chalk7 from "chalk";
3913
+ import { existsSync as existsSync19 } from "fs";
3914
+ import { mkdir as mkdir7, writeFile as writeFile14 } from "fs/promises";
3915
+ import path22 from "path";
3916
+ import chalk9 from "chalk";
3032
3917
  function titleCase(name) {
3033
3918
  return name.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
3034
3919
  }
@@ -3106,13 +3991,13 @@ async function createBlock(name, options = {}) {
3106
3991
  process.exitCode = 1;
3107
3992
  return;
3108
3993
  }
3109
- const blocksDir = path16.join(projectRoot, "blocks", name);
3110
- if (existsSync14(blocksDir)) {
3994
+ const blocksDir = path22.join(projectRoot, "blocks", name);
3995
+ if (existsSync19(blocksDir)) {
3111
3996
  logger.error(`Block "${name}" already exists at ${blocksDir}`);
3112
3997
  process.exitCode = 1;
3113
3998
  return;
3114
3999
  }
3115
- await mkdir6(blocksDir, { recursive: true });
4000
+ await mkdir7(blocksDir, { recursive: true });
3116
4001
  const jsContent = `export default function decorate(block) {
3117
4002
  // TODO: implement ${name} block
3118
4003
  }
@@ -3121,14 +4006,14 @@ async function createBlock(name, options = {}) {
3121
4006
  /* TODO: style ${name} block */
3122
4007
  }
3123
4008
  `;
3124
- await writeFile11(path16.join(blocksDir, `${name}.js`), jsContent);
3125
- await writeFile11(path16.join(blocksDir, `${name}.css`), cssContent);
4009
+ await writeFile14(path22.join(blocksDir, `${name}.js`), jsContent);
4010
+ await writeFile14(path22.join(blocksDir, `${name}.css`), cssContent);
3126
4011
  const withUe = options.ueModel !== false;
3127
4012
  if (withUe) {
3128
- await writeFile11(path16.join(blocksDir, `_${name}.json`), ueModelFor(name));
4013
+ await writeFile14(path22.join(blocksDir, `_${name}.json`), ueModelFor(name));
3129
4014
  }
3130
4015
  logger.success(
3131
- `Block "${name}" created at ${chalk7.cyan(path16.relative(projectRoot, blocksDir))}${withUe ? chalk7.dim(" (+ Universal Editor model)") : ""}`
4016
+ `Block "${name}" created at ${chalk9.cyan(path22.relative(projectRoot, blocksDir))}${withUe ? chalk9.dim(" (+ Universal Editor model)") : ""}`
3132
4017
  );
3133
4018
  }
3134
4019
  var init_create = __esm({
@@ -3142,10 +4027,10 @@ var init_create = __esm({
3142
4027
  });
3143
4028
 
3144
4029
  // src/lib/agents/detect.ts
3145
- import { existsSync as existsSync15 } from "fs";
3146
- import { readFile as readFile10 } from "fs/promises";
4030
+ import { existsSync as existsSync20 } from "fs";
4031
+ import { readFile as readFile14 } from "fs/promises";
3147
4032
  import { homedir } from "os";
3148
- import path17 from "path";
4033
+ import path23 from "path";
3149
4034
  async function whichBinary(name) {
3150
4035
  const { execaCommand } = await import("execa");
3151
4036
  try {
@@ -3173,8 +4058,8 @@ function findFigmaServerKey(node) {
3173
4058
  async function findFigmaMcpServer(configPaths) {
3174
4059
  for (const configPath of configPaths) {
3175
4060
  try {
3176
- if (!existsSync15(configPath)) continue;
3177
- const content = await readFile10(configPath, "utf-8");
4061
+ if (!existsSync20(configPath)) continue;
4062
+ const content = await readFile14(configPath, "utf-8");
3178
4063
  try {
3179
4064
  const key = findFigmaServerKey(JSON.parse(content));
3180
4065
  if (key) return key;
@@ -3196,7 +4081,7 @@ async function detectAgent(preferred) {
3196
4081
  return {
3197
4082
  type: def.type,
3198
4083
  path: binPath,
3199
- configDir: existsSync15(def.configDir) ? def.configDir : void 0,
4084
+ configDir: existsSync20(def.configDir) ? def.configDir : void 0,
3200
4085
  hasFigmaMcp: !!figmaMcpServerName,
3201
4086
  figmaMcpServerName
3202
4087
  };
@@ -3211,7 +4096,7 @@ async function detectAgent(preferred) {
3211
4096
  return {
3212
4097
  type: def.type,
3213
4098
  path: binPath,
3214
- configDir: existsSync15(def.configDir) ? def.configDir : void 0,
4099
+ configDir: existsSync20(def.configDir) ? def.configDir : void 0,
3215
4100
  hasFigmaMcp: !!figmaMcpServerName,
3216
4101
  figmaMcpServerName
3217
4102
  };
@@ -3228,7 +4113,7 @@ async function detectAllAgents() {
3228
4113
  agents.push({
3229
4114
  type: def.type,
3230
4115
  path: binPath,
3231
- configDir: existsSync15(def.configDir) ? def.configDir : void 0,
4116
+ configDir: existsSync20(def.configDir) ? def.configDir : void 0,
3232
4117
  hasFigmaMcp: !!figmaMcpServerName,
3233
4118
  figmaMcpServerName
3234
4119
  });
@@ -3246,22 +4131,22 @@ var init_detect = __esm({
3246
4131
  {
3247
4132
  type: "claude",
3248
4133
  binary: "claude",
3249
- configDir: path17.join(homedir(), ".claude"),
4134
+ configDir: path23.join(homedir(), ".claude"),
3250
4135
  mcpConfigPaths: [
3251
- path17.join(homedir(), ".claude", "plugins.json"),
3252
- path17.join(homedir(), ".claude.json")
4136
+ path23.join(homedir(), ".claude", "plugins.json"),
4137
+ path23.join(homedir(), ".claude.json")
3253
4138
  ]
3254
4139
  },
3255
4140
  {
3256
4141
  type: "cursor",
3257
4142
  binary: "cursor",
3258
- configDir: path17.join(homedir(), ".cursor"),
3259
- mcpConfigPaths: [path17.join(homedir(), ".cursor", "mcp.json")]
4143
+ configDir: path23.join(homedir(), ".cursor"),
4144
+ mcpConfigPaths: [path23.join(homedir(), ".cursor", "mcp.json")]
3260
4145
  },
3261
4146
  {
3262
4147
  type: "codex",
3263
4148
  binary: "codex",
3264
- configDir: path17.join(homedir(), ".codex"),
4149
+ configDir: path23.join(homedir(), ".codex"),
3265
4150
  mcpConfigPaths: []
3266
4151
  }
3267
4152
  ];
@@ -3269,27 +4154,27 @@ var init_detect = __esm({
3269
4154
  });
3270
4155
 
3271
4156
  // src/lib/agents/invoke.ts
3272
- import { mkdir as mkdir7, readFile as readFile11, writeFile as writeFile12 } from "fs/promises";
4157
+ import { mkdir as mkdir8, readFile as readFile15, writeFile as writeFile15 } from "fs/promises";
3273
4158
  import { tmpdir } from "os";
3274
- import path18 from "path";
4159
+ import path24 from "path";
3275
4160
  import readline from "readline";
3276
- import chalk8 from "chalk";
4161
+ import chalk10 from "chalk";
3277
4162
  async function savePromptToFile(prompt, projectRoot) {
3278
- const base = projectRoot ? path18.join(projectRoot, "tmp") : tmpdir();
3279
- const dir = path18.join(base, "eds-cli");
3280
- await mkdir7(dir, { recursive: true });
4163
+ const base = projectRoot ? path24.join(projectRoot, "tmp") : tmpdir();
4164
+ const dir = path24.join(base, "eds-cli");
4165
+ await mkdir8(dir, { recursive: true });
3281
4166
  const filename = `from-design-${Date.now()}.md`;
3282
- const filePath = path18.join(dir, filename);
3283
- await writeFile12(filePath, prompt, "utf-8");
4167
+ const filePath = path24.join(dir, filename);
4168
+ await writeFile15(filePath, prompt, "utf-8");
3284
4169
  return filePath;
3285
4170
  }
3286
4171
  async function invokeAgent(agent, promptFile, cwd) {
3287
4172
  const { execa } = await import("execa");
3288
- const prompt = await readFile11(promptFile, "utf-8");
4173
+ const prompt = await readFile15(promptFile, "utf-8");
3289
4174
  try {
3290
4175
  switch (agent.type) {
3291
4176
  case "claude": {
3292
- logger.info(`Running Claude Code ${chalk8.dim("- live log:")}`);
4177
+ logger.info(`Running Claude Code ${chalk10.dim("- live log:")}`);
3293
4178
  const figmaServers = [
3294
4179
  ...agent.figmaMcpServerName ? [agent.figmaMcpServerName] : [],
3295
4180
  "figma-sse",
@@ -3352,10 +4237,10 @@ async function renderClaudeStream(sub, cwd) {
3352
4237
  const ora4 = (await import("ora")).default;
3353
4238
  const start = Date.now();
3354
4239
  const elapsed = () => `${Math.round((Date.now() - start) / 1e3)}s`;
3355
- const spinner = ora4({ text: `thinking ${chalk8.dim(elapsed())}`, color: "yellow" }).start();
4240
+ const spinner = ora4({ text: `thinking ${chalk10.dim(elapsed())}`, color: "yellow" }).start();
3356
4241
  let activity = "thinking";
3357
4242
  const timer = setInterval(() => {
3358
- spinner.text = `${activity} ${chalk8.dim(elapsed())}`;
4243
+ spinner.text = `${activity} ${chalk10.dim(elapsed())}`;
3359
4244
  }, 300);
3360
4245
  const pending = /* @__PURE__ */ new Map();
3361
4246
  const emit = (line) => {
@@ -3381,7 +4266,7 @@ async function renderClaudeStream(sub, cwd) {
3381
4266
  for (const block2 of content) {
3382
4267
  if (block2.type === "text" && "text" in block2) {
3383
4268
  const first = block2.text.trim().split("\n")[0].slice(0, 100);
3384
- if (first) emit(chalk8.dim(first));
4269
+ if (first) emit(chalk10.dim(first));
3385
4270
  } else if (block2.type === "tool_use" && "name" in block2) {
3386
4271
  const id = "id" in block2 && block2.id ? block2.id : `${Math.random()}`;
3387
4272
  pending.set(id, {
@@ -3397,8 +4282,8 @@ async function renderClaudeStream(sub, cwd) {
3397
4282
  const p = pending.get(block2.tool_use_id);
3398
4283
  if (p) {
3399
4284
  const dur = `${((Date.now() - p.start) / 1e3).toFixed(1)}s`;
3400
- const failed = "is_error" in block2 && block2.is_error ? ` ${chalk8.red("failed")}` : "";
3401
- emit(`${p.label} ${chalk8.dim(dur)}${failed}`);
4285
+ const failed = "is_error" in block2 && block2.is_error ? ` ${chalk10.red("failed")}` : "";
4286
+ emit(`${p.label} ${chalk10.dim(dur)}${failed}`);
3402
4287
  pending.delete(block2.tool_use_id);
3403
4288
  }
3404
4289
  }
@@ -3410,9 +4295,9 @@ async function renderClaudeStream(sub, cwd) {
3410
4295
  }
3411
4296
  }
3412
4297
  }
3413
- for (const p of pending.values()) emit(`${p.label} ${chalk8.dim("\u2026")}`);
4298
+ for (const p of pending.values()) emit(`${p.label} ${chalk10.dim("\u2026")}`);
3414
4299
  clearInterval(timer);
3415
- if (result) spinner.succeed(`${brand.accent("agent finished")} ${chalk8.dim(result)}`);
4300
+ if (result) spinner.succeed(`${brand.accent("agent finished")} ${chalk10.dim(result)}`);
3416
4301
  else spinner.stop();
3417
4302
  } catch (err) {
3418
4303
  clearInterval(timer);
@@ -3425,22 +4310,22 @@ function toolLabel(name, input, cwd) {
3425
4310
  const rel = (key) => {
3426
4311
  const p = input?.[key];
3427
4312
  if (typeof p !== "string") return "";
3428
- return p.startsWith(cwd) ? path18.relative(cwd, p) : path18.basename(p);
4313
+ return p.startsWith(cwd) ? path24.relative(cwd, p) : path24.basename(p);
3429
4314
  };
3430
4315
  if (name.includes("figma")) {
3431
4316
  const short = name.split("__").pop() ?? name;
3432
- return `${icon.figma} ${chalk8.bold(short)} ${chalk8.dim("Figma MCP")}`;
4317
+ return `${icon.figma} ${chalk10.bold(short)} ${chalk10.dim("Figma MCP")}`;
3433
4318
  }
3434
4319
  switch (name) {
3435
4320
  case "Write":
3436
4321
  case "Edit":
3437
4322
  case "NotebookEdit":
3438
- return `${brand.accent("\u270E")} ${chalk8.cyan(rel("file_path"))}`;
4323
+ return `${brand.accent("\u270E")} ${chalk10.cyan(rel("file_path"))}`;
3439
4324
  case "Read":
3440
- return chalk8.dim(`\xB7 read ${rel("file_path")}`);
4325
+ return chalk10.dim(`\xB7 read ${rel("file_path")}`);
3441
4326
  case "Bash": {
3442
4327
  const cmd = typeof input?.command === "string" ? input.command.split("\n")[0].slice(0, 70) : "";
3443
- return chalk8.dim(`$ ${cmd}`);
4328
+ return chalk10.dim(`$ ${cmd}`);
3444
4329
  }
3445
4330
  default:
3446
4331
  return `${brand.blue("\u2192")} ${name}`;
@@ -3456,13 +4341,13 @@ var init_invoke = __esm({
3456
4341
  });
3457
4342
 
3458
4343
  // src/lib/config.ts
3459
- import { existsSync as existsSync16 } from "fs";
3460
- import { readFile as readFile12 } from "fs/promises";
4344
+ import { existsSync as existsSync21 } from "fs";
4345
+ import { readFile as readFile16 } from "fs/promises";
3461
4346
  import { homedir as homedir2 } from "os";
3462
- import path19 from "path";
4347
+ import path25 from "path";
3463
4348
  async function loadJsonFile(filePath) {
3464
4349
  try {
3465
- const content = await readFile12(filePath, "utf-8");
4350
+ const content = await readFile16(filePath, "utf-8");
3466
4351
  return JSON.parse(content);
3467
4352
  } catch {
3468
4353
  return void 0;
@@ -3470,7 +4355,7 @@ async function loadJsonFile(filePath) {
3470
4355
  }
3471
4356
  async function loadConfig(projectRoot) {
3472
4357
  const globalRaw = await loadJsonFile(GLOBAL_CONFIG_PATH);
3473
- const projectRaw = projectRoot ? await loadJsonFile(path19.join(projectRoot, PROJECT_CONFIG_NAME)) : void 0;
4358
+ const projectRaw = projectRoot ? await loadJsonFile(path25.join(projectRoot, PROJECT_CONFIG_NAME)) : void 0;
3474
4359
  const merged = {
3475
4360
  ...typeof globalRaw === "object" && globalRaw !== null ? globalRaw : {},
3476
4361
  ...typeof projectRaw === "object" && projectRaw !== null ? projectRaw : {}
@@ -3489,7 +4374,7 @@ function globalConfigPath() {
3489
4374
  return GLOBAL_CONFIG_PATH;
3490
4375
  }
3491
4376
  function globalConfigExists() {
3492
- return existsSync16(GLOBAL_CONFIG_PATH);
4377
+ return existsSync21(GLOBAL_CONFIG_PATH);
3493
4378
  }
3494
4379
  var GLOBAL_CONFIG_DIR, GLOBAL_CONFIG_PATH, PROJECT_CONFIG_NAME;
3495
4380
  var init_config = __esm({
@@ -3498,8 +4383,8 @@ var init_config = __esm({
3498
4383
  init_esm_shims();
3499
4384
  init_logger();
3500
4385
  init_schemas();
3501
- GLOBAL_CONFIG_DIR = path19.join(homedir2(), ".eds");
3502
- GLOBAL_CONFIG_PATH = path19.join(GLOBAL_CONFIG_DIR, "config.json");
4386
+ GLOBAL_CONFIG_DIR = path25.join(homedir2(), ".eds");
4387
+ GLOBAL_CONFIG_PATH = path25.join(GLOBAL_CONFIG_DIR, "config.json");
3503
4388
  PROJECT_CONFIG_NAME = ".edsrc.json";
3504
4389
  }
3505
4390
  });
@@ -3549,9 +4434,9 @@ var init_node_id = __esm({
3549
4434
  });
3550
4435
 
3551
4436
  // src/lib/figma/prompt-builder.ts
3552
- import { existsSync as existsSync17 } from "fs";
3553
- import { readFile as readFile13 } from "fs/promises";
3554
- import path20 from "path";
4437
+ import { existsSync as existsSync22 } from "fs";
4438
+ import { readFile as readFile17 } from "fs/promises";
4439
+ import path26 from "path";
3555
4440
  function contentSourceGuidance(source, cfHint, blockName) {
3556
4441
  if (!source || source === "document" || source === "ue") return "";
3557
4442
  const q = cfHint ? ` (model / persisted query: \`${cfHint}\`)` : "";
@@ -3766,10 +4651,10 @@ ${tokensHint}
3766
4651
  `;
3767
4652
  }
3768
4653
  async function getTokensHint(projectRoot) {
3769
- const stylesPath = path20.join(projectRoot, "styles", "styles.css");
3770
- if (!existsSync17(stylesPath)) return "";
4654
+ const stylesPath = path26.join(projectRoot, "styles", "styles.css");
4655
+ if (!existsSync22(stylesPath)) return "";
3771
4656
  try {
3772
- const css = await readFile13(stylesPath, "utf-8");
4657
+ const css = await readFile17(stylesPath, "utf-8");
3773
4658
  const rootMatch = css.match(/:root\s*\{([^}]+)\}/);
3774
4659
  if (!rootMatch) return "";
3775
4660
  const props = rootMatch[1].split("\n").filter((line) => line.trim().startsWith("--")).map((line) => line.trim()).join("\n ");
@@ -3911,11 +4796,11 @@ var init_harness = __esm({
3911
4796
  });
3912
4797
 
3913
4798
  // src/lib/preview/server.ts
3914
- import { createReadStream, existsSync as existsSync18, statSync } from "fs";
4799
+ import { createReadStream, existsSync as existsSync23, statSync } from "fs";
3915
4800
  import http from "http";
3916
- import path21 from "path";
4801
+ import path27 from "path";
3917
4802
  function startServer(root, routes, port, host = "127.0.0.1") {
3918
- const resolvedRoot = path21.resolve(root);
4803
+ const resolvedRoot = path27.resolve(root);
3919
4804
  const clients = /* @__PURE__ */ new Set();
3920
4805
  const server = http.createServer((req, res) => {
3921
4806
  const urlPath = decodeURIComponent((req.url ?? "/").split("?")[0]);
@@ -3941,19 +4826,19 @@ function startServer(root, routes, port, host = "127.0.0.1") {
3941
4826
  res.end(route.body);
3942
4827
  return;
3943
4828
  }
3944
- const filePath = path21.join(resolvedRoot, urlPath);
4829
+ const filePath = path27.join(resolvedRoot, urlPath);
3945
4830
  if (!filePath.startsWith(resolvedRoot)) {
3946
4831
  res.writeHead(403);
3947
4832
  res.end("Forbidden");
3948
4833
  return;
3949
4834
  }
3950
- if (!existsSync18(filePath) || !statSync(filePath).isFile()) {
4835
+ if (!existsSync23(filePath) || !statSync(filePath).isFile()) {
3951
4836
  res.writeHead(404, { "content-type": "text/plain" });
3952
4837
  res.end(`Not found: ${urlPath}`);
3953
4838
  return;
3954
4839
  }
3955
4840
  res.writeHead(200, {
3956
- "content-type": MIME[path21.extname(filePath).toLowerCase()] ?? "application/octet-stream"
4841
+ "content-type": MIME[path27.extname(filePath).toLowerCase()] ?? "application/octet-stream"
3957
4842
  });
3958
4843
  createReadStream(filePath).pipe(res);
3959
4844
  });
@@ -4017,10 +4902,10 @@ var preview_exports = {};
4017
4902
  __export(preview_exports, {
4018
4903
  previewBlock: () => previewBlock
4019
4904
  });
4020
- import { existsSync as existsSync19, watch } from "fs";
4021
- import { readFile as readFile14 } from "fs/promises";
4022
- import path22 from "path";
4023
- import chalk9 from "chalk";
4905
+ import { existsSync as existsSync24, watch } from "fs";
4906
+ import { readFile as readFile18 } from "fs/promises";
4907
+ import path28 from "path";
4908
+ import chalk11 from "chalk";
4024
4909
  function labelFor(width) {
4025
4910
  if (width <= 480) return `${width} mobile`;
4026
4911
  if (width <= 1024) return `${width} tablet`;
@@ -4072,30 +4957,30 @@ async function previewBlock(name, options = {}) {
4072
4957
  process.exitCode = 1;
4073
4958
  return;
4074
4959
  }
4075
- const blockDir = path22.join(projectRoot, "blocks", name);
4076
- if (!existsSync19(blockDir)) {
4960
+ const blockDir = path28.join(projectRoot, "blocks", name);
4961
+ if (!existsSync24(blockDir)) {
4077
4962
  logger.error(`Block "${name}" not found at blocks/${name}/.`);
4078
4963
  process.exitCode = 1;
4079
4964
  return;
4080
4965
  }
4081
- if (!existsSync19(path22.join(blockDir, `${name}.css`)) && !existsSync19(path22.join(blockDir, `${name}.js`))) {
4966
+ if (!existsSync24(path28.join(blockDir, `${name}.css`)) && !existsSync24(path28.join(blockDir, `${name}.js`))) {
4082
4967
  logger.error(`Block "${name}" has no ${name}.css / ${name}.js to preview.`);
4083
4968
  process.exitCode = 1;
4084
4969
  return;
4085
4970
  }
4086
4971
  let meta = null;
4087
- const metaPath = path22.join(blockDir, ".eds-meta.json");
4088
- if (existsSync19(metaPath)) {
4972
+ const metaPath = path28.join(blockDir, ".eds-meta.json");
4973
+ if (existsSync24(metaPath)) {
4089
4974
  try {
4090
- meta = JSON.parse(await readFile14(metaPath, "utf-8"));
4975
+ meta = JSON.parse(await readFile18(metaPath, "utf-8"));
4091
4976
  } catch {
4092
4977
  }
4093
4978
  }
4094
- const samplePath = path22.join(blockDir, `_${name}.preview.html`);
4979
+ const samplePath = path28.join(blockDir, `_${name}.preview.html`);
4095
4980
  let sampleContent;
4096
4981
  let placeholder = false;
4097
- if (existsSync19(samplePath)) {
4098
- sampleContent = await readFile14(samplePath, "utf-8");
4982
+ if (existsSync24(samplePath)) {
4983
+ sampleContent = await readFile18(samplePath, "utf-8");
4099
4984
  } else {
4100
4985
  sampleContent = defaultSample(name);
4101
4986
  placeholder = true;
@@ -4145,9 +5030,9 @@ async function previewBlock(name, options = {}) {
4145
5030
  reloadTimer = setTimeout(() => server.reload(), 150);
4146
5031
  });
4147
5032
  logger.info("");
4148
- logger.info(` ${chalk9.bold("\u279C")} ${chalk9.cyan(server.url)}`);
5033
+ logger.info(` ${chalk11.bold("\u279C")} ${chalk11.cyan(server.url)}`);
4149
5034
  logger.info(statusLine("pass", "live reload", "edits to the block refresh the browser"));
4150
- logger.info(chalk9.dim(" Press Ctrl+C to stop.\n"));
5035
+ logger.info(chalk11.dim(" Press Ctrl+C to stop.\n"));
4151
5036
  if (options.open !== false) await openInBrowser(server.url);
4152
5037
  const stop = async () => {
4153
5038
  watcher.close();
@@ -4176,10 +5061,10 @@ var from_design_exports = {};
4176
5061
  __export(from_design_exports, {
4177
5062
  blockFromDesign: () => blockFromDesign
4178
5063
  });
4179
- import { existsSync as existsSync20 } from "fs";
4180
- import { readFile as readFile15, writeFile as writeFile13 } from "fs/promises";
4181
- import path23 from "path";
4182
- import chalk10 from "chalk";
5064
+ import { existsSync as existsSync25 } from "fs";
5065
+ import { readFile as readFile19, writeFile as writeFile16 } from "fs/promises";
5066
+ import path29 from "path";
5067
+ import chalk12 from "chalk";
4183
5068
  import ora from "ora";
4184
5069
  async function previewRunning(port = 8777) {
4185
5070
  try {
@@ -4329,7 +5214,7 @@ async function blockFromDesign(figmaUrlArg, optionsArg) {
4329
5214
  try {
4330
5215
  figma2 = parseFigmaUrl(designUrl);
4331
5216
  spinner.succeed(
4332
- `Parsed Figma URL ${chalk10.dim(figma2.nodeId ? `node ${figma2.nodeId}` : `file ${figma2.fileKey}`)}`
5217
+ `Parsed Figma URL ${chalk12.dim(figma2.nodeId ? `node ${figma2.nodeId}` : `file ${figma2.fileKey}`)}`
4333
5218
  );
4334
5219
  } catch (err) {
4335
5220
  spinner.fail(err.message);
@@ -4337,7 +5222,7 @@ async function blockFromDesign(figmaUrlArg, optionsArg) {
4337
5222
  return;
4338
5223
  }
4339
5224
  } else {
4340
- logger.info(` ${icon.pass} ${providerName} design ${chalk10.dim(designRef)}`);
5225
+ logger.info(` ${icon.pass} ${providerName} design ${chalk12.dim(designRef)}`);
4341
5226
  }
4342
5227
  const blockName = options.name || (figma2 ? inferBlockName(figma2.fileName) : "");
4343
5228
  if (!kebabCaseRegex.test(blockName)) {
@@ -4360,20 +5245,20 @@ async function blockFromDesign(figmaUrlArg, optionsArg) {
4360
5245
  screenshot: options.screenshot !== false
4361
5246
  });
4362
5247
  const promptFile = await savePromptToFile(prompt, projectRoot);
4363
- const hasTokens = existsSync20(path23.join(projectRoot, "styles", "styles.css"));
5248
+ const hasTokens = existsSync25(path29.join(projectRoot, "styles", "styles.css"));
4364
5249
  promptSpinner.succeed(
4365
- hasTokens ? `Loaded design tokens ${chalk10.dim("styles/styles.css")}` : `Built prompt ${chalk10.dim("no project tokens found")}`
5250
+ hasTokens ? `Loaded design tokens ${chalk12.dim("styles/styles.css")}` : `Built prompt ${chalk12.dim("no project tokens found")}`
4366
5251
  );
4367
- logger.info(chalk10.dim(` prompt \u2192 ${promptFile}`));
5252
+ logger.info(chalk12.dim(` prompt \u2192 ${promptFile}`));
4368
5253
  if (options.dryRun) {
4369
5254
  logger.info("");
4370
- logger.info(chalk10.yellow("Dry run mode \u2014 prompt generated but agent not invoked."));
4371
- logger.info(`Prompt file: ${chalk10.cyan(promptFile)}`);
4372
- logger.info(`Block name: ${chalk10.cyan(blockName)}`);
5255
+ logger.info(chalk12.yellow("Dry run mode \u2014 prompt generated but agent not invoked."));
5256
+ logger.info(`Prompt file: ${chalk12.cyan(promptFile)}`);
5257
+ logger.info(`Block name: ${chalk12.cyan(blockName)}`);
4373
5258
  logger.info(`Prompt version: ${PROMPT_VERSION}`);
4374
5259
  logger.info("");
4375
5260
  logger.info("Preview:");
4376
- logger.info(chalk10.dim("\u2500".repeat(60)));
5261
+ logger.info(chalk12.dim("\u2500".repeat(60)));
4377
5262
  console.log(prompt);
4378
5263
  return;
4379
5264
  }
@@ -4381,22 +5266,22 @@ async function blockFromDesign(figmaUrlArg, optionsArg) {
4381
5266
  const preferredAgent = options.agent || config.agent;
4382
5267
  if (options.agent === "none") {
4383
5268
  logger.info("Agent skipped (--agent none). Use the prompt file manually:");
4384
- logger.info(` ${chalk10.cyan(promptFile)}`);
5269
+ logger.info(` ${chalk12.cyan(promptFile)}`);
4385
5270
  return;
4386
5271
  }
4387
5272
  const agentSpinner = ora("Detecting AI agent...").start();
4388
5273
  const agent = await detectAgent(preferredAgent);
4389
5274
  if (!agent) {
4390
5275
  agentSpinner.fail("No AI agent found (claude, cursor, or codex).");
4391
- logger.info(`Install Claude Code: ${chalk10.cyan("npm install -g @anthropic-ai/claude-code")}`);
4392
- logger.info(`Or use the prompt file manually: ${chalk10.cyan(promptFile)}`);
5276
+ logger.info(`Install Claude Code: ${chalk12.cyan("npm install -g @anthropic-ai/claude-code")}`);
5277
+ logger.info(`Or use the prompt file manually: ${chalk12.cyan(promptFile)}`);
4393
5278
  process.exitCode = 1;
4394
5279
  return;
4395
5280
  }
4396
- agentSpinner.succeed(`Agent ready ${chalk10.cyan(agent.type)} ${chalk10.dim(agent.path)}`);
5281
+ agentSpinner.succeed(`Agent ready ${chalk12.cyan(agent.type)} ${chalk12.dim(agent.path)}`);
4397
5282
  if (provider === "figma" && !agent.hasFigmaMcp) {
4398
5283
  logger.warn(`Figma MCP may not be configured for ${agent.type}.`);
4399
- logger.info(`Run ${chalk10.cyan("eds figma setup")} to configure it.`);
5284
+ logger.info(`Run ${chalk12.cyan("eds figma setup")} to configure it.`);
4400
5285
  }
4401
5286
  if (!options.yes) {
4402
5287
  const { confirm } = await import("@inquirer/prompts");
@@ -4405,24 +5290,24 @@ async function blockFromDesign(figmaUrlArg, optionsArg) {
4405
5290
  default: true
4406
5291
  });
4407
5292
  if (!proceed) {
4408
- logger.info(`Prompt saved to: ${chalk10.cyan(promptFile)}`);
5293
+ logger.info(`Prompt saved to: ${chalk12.cyan(promptFile)}`);
4409
5294
  return;
4410
5295
  }
4411
5296
  }
4412
5297
  logger.info(
4413
- ` ${chalk10.dim("\u2192")} Handing off to ${chalk10.cyan(agent.type)} ${chalk10.dim(`via ${providerName} MCP`)}`
5298
+ ` ${chalk12.dim("\u2192")} Handing off to ${chalk12.cyan(agent.type)} ${chalk12.dim(`via ${providerName} MCP`)}`
4414
5299
  );
4415
5300
  if (provider === "figma") {
4416
- logger.info(chalk10.dim(" get_design_context \xB7 get_variable_defs \xB7 get_screenshot"));
5301
+ logger.info(chalk12.dim(" get_design_context \xB7 get_variable_defs \xB7 get_screenshot"));
4417
5302
  }
4418
5303
  const success = await invokeAgent(agent, promptFile, projectRoot);
4419
5304
  if (!success) {
4420
5305
  process.exitCode = 1;
4421
5306
  return;
4422
5307
  }
4423
- const blockDir = path23.join(projectRoot, "blocks", blockName);
5308
+ const blockDir = path29.join(projectRoot, "blocks", blockName);
4424
5309
  const ok = await verifyGeneratedBlock(blockDir, blockName, options.ueModel !== false);
4425
- if (existsSync20(blockDir)) {
5310
+ if (existsSync25(blockDir)) {
4426
5311
  const meta = {
4427
5312
  provider,
4428
5313
  designRef,
@@ -4432,7 +5317,7 @@ async function blockFromDesign(figmaUrlArg, optionsArg) {
4432
5317
  promptVersion: PROMPT_VERSION,
4433
5318
  agentUsed: agent.type
4434
5319
  };
4435
- await writeFile13(path23.join(blockDir, ".eds-meta.json"), `${JSON.stringify(meta, null, 2)}
5320
+ await writeFile16(path29.join(blockDir, ".eds-meta.json"), `${JSON.stringify(meta, null, 2)}
4436
5321
  `);
4437
5322
  }
4438
5323
  if (!ok) {
@@ -4457,7 +5342,7 @@ async function blockFromDesign(figmaUrlArg, optionsArg) {
4457
5342
  if (options.serve !== false) {
4458
5343
  if (await previewRunning()) {
4459
5344
  logger.info(
4460
- chalk10.dim(
5345
+ chalk12.dim(
4461
5346
  " A preview server is already running at http://127.0.0.1:8777 \u2014 your change will refresh there."
4462
5347
  )
4463
5348
  );
@@ -4467,15 +5352,15 @@ async function blockFromDesign(figmaUrlArg, optionsArg) {
4467
5352
  await previewBlock2(blockName, {});
4468
5353
  }
4469
5354
  } else {
4470
- logger.info(` ${chalk10.dim("\u2192")} Next: ${chalk10.cyan(`eds block preview ${blockName}`)}`);
5355
+ logger.info(` ${chalk12.dim("\u2192")} Next: ${chalk12.cyan(`eds block preview ${blockName}`)}`);
4471
5356
  }
4472
5357
  }
4473
5358
  async function verifyGeneratedBlock(blockDir, blockName, withUeModel) {
4474
5359
  const checks = [];
4475
- const jsPath = path23.join(blockDir, `${blockName}.js`);
4476
- const cssPath = path23.join(blockDir, `${blockName}.css`);
4477
- const hasJs = existsSync20(jsPath);
4478
- const hasCss = existsSync20(cssPath);
5360
+ const jsPath = path29.join(blockDir, `${blockName}.js`);
5361
+ const cssPath = path29.join(blockDir, `${blockName}.css`);
5362
+ const hasJs = existsSync25(jsPath);
5363
+ const hasCss = existsSync25(cssPath);
4479
5364
  checks.push({
4480
5365
  status: hasJs ? "pass" : "fail",
4481
5366
  name: `${blockName}.js`,
@@ -4487,7 +5372,7 @@ async function verifyGeneratedBlock(blockDir, blockName, withUeModel) {
4487
5372
  message: hasCss ? "created" : "missing \u2014 agent did not write the CSS file"
4488
5373
  });
4489
5374
  if (hasJs) {
4490
- const js = await readFile15(jsPath, "utf-8");
5375
+ const js = await readFile19(jsPath, "utf-8");
4491
5376
  checks.push({
4492
5377
  status: /export\s+default/.test(js) ? "pass" : "fail",
4493
5378
  name: "decorate export",
@@ -4502,7 +5387,7 @@ async function verifyGeneratedBlock(blockDir, blockName, withUeModel) {
4502
5387
  }
4503
5388
  }
4504
5389
  if (hasCss) {
4505
- const css = await readFile15(cssPath, "utf-8");
5390
+ const css = await readFile19(cssPath, "utf-8");
4506
5391
  checks.push({
4507
5392
  status: css.includes(`.${blockName}`) ? "pass" : "warn",
4508
5393
  name: "scoped CSS",
@@ -4510,12 +5395,12 @@ async function verifyGeneratedBlock(blockDir, blockName, withUeModel) {
4510
5395
  });
4511
5396
  }
4512
5397
  if (withUeModel) {
4513
- const hasPerBlock = existsSync20(path23.join(blockDir, `_${blockName}.json`));
4514
- const modelsFile = path23.join(blockDir, "..", "..", "component-models.json");
5398
+ const hasPerBlock = existsSync25(path29.join(blockDir, `_${blockName}.json`));
5399
+ const modelsFile = path29.join(blockDir, "..", "..", "component-models.json");
4515
5400
  let hasAggregated = false;
4516
- if (!hasPerBlock && existsSync20(modelsFile)) {
5401
+ if (!hasPerBlock && existsSync25(modelsFile)) {
4517
5402
  try {
4518
- hasAggregated = (await readFile15(modelsFile, "utf-8")).includes(`"${blockName}"`);
5403
+ hasAggregated = (await readFile19(modelsFile, "utf-8")).includes(`"${blockName}"`);
4519
5404
  } catch {
4520
5405
  }
4521
5406
  }
@@ -4527,8 +5412,8 @@ async function verifyGeneratedBlock(blockDir, blockName, withUeModel) {
4527
5412
  });
4528
5413
  if (hasPerBlock && hasJs) {
4529
5414
  try {
4530
- const modelRaw = await readFile15(path23.join(blockDir, `_${blockName}.json`), "utf-8");
4531
- const js = await readFile15(jsPath, "utf-8");
5415
+ const modelRaw = await readFile19(path29.join(blockDir, `_${blockName}.json`), "utf-8");
5416
+ const js = await readFile19(jsPath, "utf-8");
4532
5417
  const isRepeatable = /"resourceType"\s*:\s*"[^"]*\/item"/.test(modelRaw) || /"filter"\s*:/.test(modelRaw) || /"multi"\s*:\s*true/.test(modelRaw);
4533
5418
  const iterates = /\.children/.test(js) || /\bfor\s*\(/.test(js) || /\.forEach\s*\(/.test(js);
4534
5419
  if (isRepeatable) {
@@ -4571,10 +5456,10 @@ var list_exports = {};
4571
5456
  __export(list_exports, {
4572
5457
  listBlocks: () => listBlocks
4573
5458
  });
4574
- import { existsSync as existsSync21 } from "fs";
4575
- import { readFile as readFile16, readdir as readdir3 } from "fs/promises";
4576
- import path24 from "path";
4577
- import chalk11 from "chalk";
5459
+ import { existsSync as existsSync26 } from "fs";
5460
+ import { readFile as readFile20, readdir as readdir4 } from "fs/promises";
5461
+ import path30 from "path";
5462
+ import chalk13 from "chalk";
4578
5463
  async function listBlocks(options = {}) {
4579
5464
  const projectRoot = findProjectRoot();
4580
5465
  if (!projectRoot) {
@@ -4582,27 +5467,27 @@ async function listBlocks(options = {}) {
4582
5467
  process.exitCode = 1;
4583
5468
  return;
4584
5469
  }
4585
- const blocksDir = path24.join(projectRoot, "blocks");
4586
- if (!existsSync21(blocksDir)) {
5470
+ const blocksDir = path30.join(projectRoot, "blocks");
5471
+ if (!existsSync26(blocksDir)) {
4587
5472
  logger.info("No blocks/ directory found.");
4588
5473
  return;
4589
5474
  }
4590
- const entries = await readdir3(blocksDir, { withFileTypes: true });
5475
+ const entries = await readdir4(blocksDir, { withFileTypes: true });
4591
5476
  const blocks = [];
4592
5477
  for (const entry of entries) {
4593
5478
  if (!entry.isDirectory()) continue;
4594
5479
  const name = entry.name;
4595
- const blockDir = path24.join(blocksDir, name);
5480
+ const blockDir = path30.join(blocksDir, name);
4596
5481
  const info = {
4597
5482
  name,
4598
- hasJs: existsSync21(path24.join(blockDir, `${name}.js`)),
4599
- hasCss: existsSync21(path24.join(blockDir, `${name}.css`)),
4600
- hasUeModel: existsSync21(path24.join(blockDir, `_${name}.json`))
5483
+ hasJs: existsSync26(path30.join(blockDir, `${name}.js`)),
5484
+ hasCss: existsSync26(path30.join(blockDir, `${name}.css`)),
5485
+ hasUeModel: existsSync26(path30.join(blockDir, `_${name}.json`))
4601
5486
  };
4602
- const metaFile = path24.join(blockDir, ".eds-meta.json");
4603
- if (existsSync21(metaFile)) {
5487
+ const metaFile = path30.join(blockDir, ".eds-meta.json");
5488
+ if (existsSync26(metaFile)) {
4604
5489
  try {
4605
- const meta = JSON.parse(await readFile16(metaFile, "utf-8"));
5490
+ const meta = JSON.parse(await readFile20(metaFile, "utf-8"));
4606
5491
  info.figmaSource = {
4607
5492
  fileKey: meta.figmaFileKey,
4608
5493
  nodeId: meta.figmaNodeId,
@@ -4625,17 +5510,17 @@ async function listBlocks(options = {}) {
4625
5510
  const nameWidth = columnWidth(blocks.map((b) => b.name));
4626
5511
  for (const b of blocks) {
4627
5512
  const badges = [
4628
- b.hasJs ? brand.ok("js") : chalk11.dim.strikethrough("js"),
4629
- b.hasCss ? brand.ok("css") : chalk11.dim.strikethrough("css")
5513
+ b.hasJs ? brand.ok("js") : chalk13.dim.strikethrough("js"),
5514
+ b.hasCss ? brand.ok("css") : chalk13.dim.strikethrough("css")
4630
5515
  ];
4631
5516
  if (b.hasUeModel) badges.push(brand.blue("ue-model"));
4632
5517
  if (b.figmaSource) badges.push(`${icon.figma} ${brand.dotPurple("figma")}`);
4633
- const name = padEndVisible(chalk11.bold(b.name), nameWidth);
4634
- logger.info(` ${name} ${chalk11.dim("[")}${badges.join(" ")}${chalk11.dim("]")}`);
5518
+ const name = padEndVisible(chalk13.bold(b.name), nameWidth);
5519
+ logger.info(` ${name} ${chalk13.dim("[")}${badges.join(" ")}${chalk13.dim("]")}`);
4635
5520
  if (b.figmaSource) {
4636
5521
  const synced = new Date(b.figmaSource.lastSyncedAt).toLocaleDateString();
4637
5522
  logger.info(
4638
- ` ${" ".repeat(nameWidth)} ${icon.arrow} ${chalk11.dim(
5523
+ ` ${" ".repeat(nameWidth)} ${icon.arrow} ${chalk13.dim(
4639
5524
  `figma:${b.figmaSource.fileKey} \xB7 synced ${synced}`
4640
5525
  )}`
4641
5526
  );
@@ -4815,10 +5700,10 @@ var integrate_exports = {};
4815
5700
  __export(integrate_exports, {
4816
5701
  integrate: () => integrate
4817
5702
  });
4818
- import { existsSync as existsSync22 } from "fs";
4819
- import { mkdir as mkdir8, readFile as readFile17, writeFile as writeFile14 } from "fs/promises";
4820
- import path25 from "path";
4821
- import chalk12 from "chalk";
5703
+ import { existsSync as existsSync27 } from "fs";
5704
+ import { mkdir as mkdir9, readFile as readFile21, writeFile as writeFile17 } from "fs/promises";
5705
+ import path31 from "path";
5706
+ import chalk14 from "chalk";
4822
5707
  async function integrate(type) {
4823
5708
  const projectRoot = findProjectRoot();
4824
5709
  if (!projectRoot) {
@@ -4848,9 +5733,9 @@ async function integrate(type) {
4848
5733
  const modulePaths = [];
4849
5734
  if (integration.module) {
4850
5735
  for (const mod of integration.module(values)) {
4851
- const abs = path25.join(projectRoot, mod.file);
4852
- await mkdir8(path25.dirname(abs), { recursive: true });
4853
- await writeFile14(abs, mod.content);
5736
+ const abs = path31.join(projectRoot, mod.file);
5737
+ await mkdir9(path31.dirname(abs), { recursive: true });
5738
+ await writeFile17(abs, mod.content);
4854
5739
  modulePaths.push(mod.file);
4855
5740
  }
4856
5741
  }
@@ -4858,26 +5743,26 @@ async function integrate(type) {
4858
5743
  // ${integration.name} (eds integrate)
4859
5744
  ${integration.code(values)}
4860
5745
  ` : wrapBlock(integration.name, integration.code(values));
4861
- const delayedPath = path25.join(projectRoot, "scripts", "delayed.js");
4862
- await mkdir8(path25.dirname(delayedPath), { recursive: true });
4863
- const existing = existsSync22(delayedPath) ? await readFile17(delayedPath, "utf-8") : DELAYED_HEADER;
4864
- await writeFile14(delayedPath, `${existing.replace(/\s*$/, "")}
5746
+ const delayedPath = path31.join(projectRoot, "scripts", "delayed.js");
5747
+ await mkdir9(path31.dirname(delayedPath), { recursive: true });
5748
+ const existing = existsSync27(delayedPath) ? await readFile21(delayedPath, "utf-8") : DELAYED_HEADER;
5749
+ await writeFile17(delayedPath, `${existing.replace(/\s*$/, "")}
4865
5750
  ${block2}`);
4866
5751
  logger.info(heading("Added"));
4867
5752
  for (const p of modulePaths) logger.info(accentLine(integration.name, `wrote ${p}`));
4868
5753
  logger.info(accentLine(integration.name, "wired into scripts/delayed.js"));
4869
5754
  logger.info("");
4870
5755
  logger.info(
4871
- chalk12.dim(" Loads after LCP (delayed phase) - zero impact on your Core Web Vitals.")
5756
+ chalk14.dim(" Loads after LCP (delayed phase) - zero impact on your Core Web Vitals.")
4872
5757
  );
4873
5758
  if (integration.id === "gtm" || integration.id === "ga4") {
4874
5759
  logger.info(
4875
- chalk12.dim(" Tip: gate analytics behind user consent (add the Cookiebot integration).")
5760
+ chalk14.dim(" Tip: gate analytics behind user consent (add the Cookiebot integration).")
4876
5761
  );
4877
5762
  }
4878
5763
  if (integration.id === "chat") {
4879
5764
  logger.info(
4880
- chalk12.dim(" The widget loads only when the visitor clicks the button (facade pattern).")
5765
+ chalk14.dim(" The widget loads only when the visitor clicks the button (facade pattern).")
4881
5766
  );
4882
5767
  }
4883
5768
  logger.info("");
@@ -4903,9 +5788,9 @@ var setup_exports = {};
4903
5788
  __export(setup_exports, {
4904
5789
  figmaSetup: () => figmaSetup
4905
5790
  });
4906
- import { existsSync as existsSync23 } from "fs";
4907
- import { mkdir as mkdir9, writeFile as writeFile15 } from "fs/promises";
4908
- import chalk13 from "chalk";
5791
+ import { existsSync as existsSync28 } from "fs";
5792
+ import { mkdir as mkdir10, writeFile as writeFile18 } from "fs/promises";
5793
+ import chalk15 from "chalk";
4909
5794
  async function figmaSetup() {
4910
5795
  const { select, confirm } = await import("@inquirer/prompts");
4911
5796
  logger.logoOnce(logo());
@@ -4915,15 +5800,15 @@ async function figmaSetup() {
4915
5800
  let selectedAgent;
4916
5801
  if (agents.length === 0) {
4917
5802
  logger.warn("No AI agents found (claude, cursor, codex).");
4918
- logger.info(`Install Claude Code: ${chalk13.cyan("npm install -g @anthropic-ai/claude-code")}`);
5803
+ logger.info(`Install Claude Code: ${chalk15.cyan("npm install -g @anthropic-ai/claude-code")}`);
4919
5804
  selectedAgent = "none";
4920
5805
  } else {
4921
- logger.success(`Found: ${agents.map((a) => chalk13.cyan(a.type)).join(", ")}`);
5806
+ logger.success(`Found: ${agents.map((a) => chalk15.cyan(a.type)).join(", ")}`);
4922
5807
  selectedAgent = await select({
4923
5808
  message: "Which agent do you want to use with eds-cli?",
4924
5809
  choices: [
4925
5810
  ...agents.map((a) => ({
4926
- name: `${a.type}${a.hasFigmaMcp ? chalk13.green(" (Figma MCP detected)") : ""}`,
5811
+ name: `${a.type}${a.hasFigmaMcp ? chalk15.green(" (Figma MCP detected)") : ""}`,
4927
5812
  value: a.type
4928
5813
  })),
4929
5814
  { name: "None (manual mode)", value: "none" }
@@ -4939,17 +5824,17 @@ async function figmaSetup() {
4939
5824
  switch (selectedAgent) {
4940
5825
  case "claude":
4941
5826
  logger.info(
4942
- ` ${chalk13.cyan("claude mcp add --transport sse figma-sse https://mcp.figma.com/sse")}`
5827
+ ` ${chalk15.cyan("claude mcp add --transport sse figma-sse https://mcp.figma.com/sse")}`
4943
5828
  );
4944
5829
  logger.info(" or for Figma Desktop app:");
4945
5830
  logger.info(
4946
- ` ${chalk13.cyan("claude mcp add --transport http figma-desktop http://127.0.0.1:3845/mcp")}`
5831
+ ` ${chalk15.cyan("claude mcp add --transport http figma-desktop http://127.0.0.1:3845/mcp")}`
4947
5832
  );
4948
5833
  break;
4949
5834
  case "cursor":
4950
- logger.info(` Add to ${chalk13.cyan("~/.cursor/mcp.json")}:`);
5835
+ logger.info(` Add to ${chalk15.cyan("~/.cursor/mcp.json")}:`);
4951
5836
  logger.info(
4952
- chalk13.dim(
5837
+ chalk15.dim(
4953
5838
  JSON.stringify(
4954
5839
  {
4955
5840
  mcpServers: {
@@ -4974,7 +5859,7 @@ async function figmaSetup() {
4974
5859
  }
4975
5860
  }
4976
5861
  logger.info(
4977
- chalk13.dim(
5862
+ chalk15.dim(
4978
5863
  "\nNote: Figma MCP requires a Figma seat (Dev or Full on paid plans for desktop server)."
4979
5864
  )
4980
5865
  );
@@ -4984,22 +5869,22 @@ async function figmaSetup() {
4984
5869
  });
4985
5870
  if (shouldSave) {
4986
5871
  const configDir = globalConfigDir();
4987
- if (!existsSync23(configDir)) {
4988
- await mkdir9(configDir, { recursive: true });
5872
+ if (!existsSync28(configDir)) {
5873
+ await mkdir10(configDir, { recursive: true });
4989
5874
  }
4990
5875
  const config = {};
4991
5876
  if (selectedAgent !== "none") {
4992
5877
  config.agent = selectedAgent;
4993
5878
  }
4994
- await writeFile15(globalConfigPath(), `${JSON.stringify(config, null, 2)}
5879
+ await writeFile18(globalConfigPath(), `${JSON.stringify(config, null, 2)}
4995
5880
  `, {
4996
5881
  mode: 384
4997
5882
  });
4998
- logger.success(`Config saved to ${chalk13.cyan(globalConfigPath())}`);
5883
+ logger.success(`Config saved to ${chalk15.cyan(globalConfigPath())}`);
4999
5884
  }
5000
- logger.info(chalk13.bold("\nSetup complete!"));
5885
+ logger.info(chalk15.bold("\nSetup complete!"));
5001
5886
  if (selectedAgent !== "none") {
5002
- logger.info(`Try: ${chalk13.cyan('eds block from-design "<figma-url>" --dry-run')}`);
5887
+ logger.info(`Try: ${chalk15.cyan('eds block from-design "<figma-url>" --dry-run')}`);
5003
5888
  }
5004
5889
  }
5005
5890
  var init_setup = __esm({
@@ -5018,9 +5903,9 @@ var doctor_exports = {};
5018
5903
  __export(doctor_exports, {
5019
5904
  doctor: () => doctor
5020
5905
  });
5021
- import { existsSync as existsSync24 } from "fs";
5022
- import { readFile as readFile18, readdir as readdir4 } from "fs/promises";
5023
- import path26 from "path";
5906
+ import { existsSync as existsSync29 } from "fs";
5907
+ import { readFile as readFile22, readdir as readdir5 } from "fs/promises";
5908
+ import path32 from "path";
5024
5909
  import { parse as parseYaml2 } from "yaml";
5025
5910
  async function doctor(options = {}) {
5026
5911
  const projectRoot = findProjectRoot();
@@ -5042,7 +5927,7 @@ async function doctor(options = {}) {
5042
5927
  });
5043
5928
  if (project.hasFstab) {
5044
5929
  try {
5045
- const content = await readFile18(path26.join(projectRoot, "fstab.yaml"), "utf-8");
5930
+ const content = await readFile22(path32.join(projectRoot, "fstab.yaml"), "utf-8");
5046
5931
  parseYaml2(content);
5047
5932
  results.push({ name: "fstab.yaml valid YAML", status: "pass", message: "Valid" });
5048
5933
  } catch {
@@ -5064,28 +5949,28 @@ async function doctor(options = {}) {
5064
5949
  message: project.hasBlocks ? "Found" : "Missing - no blocks yet"
5065
5950
  });
5066
5951
  if (project.hasBlocks) {
5067
- const blocksDir = path26.join(projectRoot, "blocks");
5068
- const entries = await readdir4(blocksDir, { withFileTypes: true });
5952
+ const blocksDir = path32.join(projectRoot, "blocks");
5953
+ const entries = await readdir5(blocksDir, { withFileTypes: true });
5069
5954
  let aggregatedModels = "";
5070
- const aggregatedPath = path26.join(projectRoot, "component-models.json");
5071
- if (existsSync24(aggregatedPath)) {
5955
+ const aggregatedPath = path32.join(projectRoot, "component-models.json");
5956
+ if (existsSync29(aggregatedPath)) {
5072
5957
  try {
5073
- aggregatedModels = await readFile18(aggregatedPath, "utf-8");
5958
+ aggregatedModels = await readFile22(aggregatedPath, "utf-8");
5074
5959
  } catch {
5075
5960
  }
5076
5961
  }
5077
5962
  for (const entry of entries) {
5078
5963
  if (!entry.isDirectory()) continue;
5079
5964
  const blockName = entry.name;
5080
- const jsFile = path26.join(blocksDir, blockName, `${blockName}.js`);
5081
- const hasJs = existsSync24(jsFile);
5965
+ const jsFile = path32.join(blocksDir, blockName, `${blockName}.js`);
5966
+ const hasJs = existsSync29(jsFile);
5082
5967
  results.push({
5083
5968
  name: `blocks/${blockName} has JS`,
5084
5969
  status: hasJs ? "pass" : "warn",
5085
5970
  message: hasJs ? "Found" : `Missing ${blockName}.js`
5086
5971
  });
5087
5972
  if (hasJs) {
5088
- const jsContent = await readFile18(jsFile, "utf-8");
5973
+ const jsContent = await readFile22(jsFile, "utf-8");
5089
5974
  if (jsContent.includes("console.log")) {
5090
5975
  results.push({
5091
5976
  name: `blocks/${blockName} no console.log`,
@@ -5094,17 +5979,17 @@ async function doctor(options = {}) {
5094
5979
  });
5095
5980
  }
5096
5981
  }
5097
- const hasPerBlockModel = existsSync24(path26.join(blocksDir, blockName, `_${blockName}.json`));
5982
+ const hasPerBlockModel = existsSync29(path32.join(blocksDir, blockName, `_${blockName}.json`));
5098
5983
  const hasAggregatedModel = aggregatedModels.includes(`"${blockName}"`);
5099
5984
  results.push({
5100
5985
  name: `blocks/${blockName} UE model`,
5101
5986
  status: hasPerBlockModel || hasAggregatedModel ? "pass" : "warn",
5102
5987
  message: hasPerBlockModel || hasAggregatedModel ? hasPerBlockModel ? `_${blockName}.json` : "in component-models.json" : "no Universal Editor model - authors can't edit this block in UE"
5103
5988
  });
5104
- const metaFile = path26.join(blocksDir, blockName, ".eds-meta.json");
5105
- if (existsSync24(metaFile)) {
5989
+ const metaFile = path32.join(blocksDir, blockName, ".eds-meta.json");
5990
+ if (existsSync29(metaFile)) {
5106
5991
  try {
5107
- const meta = JSON.parse(await readFile18(metaFile, "utf-8"));
5992
+ const meta = JSON.parse(await readFile22(metaFile, "utf-8"));
5108
5993
  const syncDate = new Date(meta.lastSyncedAt);
5109
5994
  const daysSinceSync = Math.floor(
5110
5995
  (Date.now() - syncDate.getTime()) / (1e3 * 60 * 60 * 24)
@@ -5124,10 +6009,10 @@ async function doctor(options = {}) {
5124
6009
  }
5125
6010
  }
5126
6011
  }
5127
- const queryFile = path26.join(projectRoot, "helix-query.yaml");
5128
- if (existsSync24(queryFile)) {
6012
+ const queryFile = path32.join(projectRoot, "helix-query.yaml");
6013
+ if (existsSync29(queryFile)) {
5129
6014
  try {
5130
- const content = await readFile18(queryFile, "utf-8");
6015
+ const content = await readFile22(queryFile, "utf-8");
5131
6016
  parseYaml2(content);
5132
6017
  results.push({ name: "helix-query.yaml valid", status: "pass", message: "Valid YAML" });
5133
6018
  } catch {
@@ -5184,10 +6069,10 @@ var audit_loading_exports = {};
5184
6069
  __export(audit_loading_exports, {
5185
6070
  auditLoading: () => auditLoading
5186
6071
  });
5187
- import { existsSync as existsSync25 } from "fs";
5188
- import { readFile as readFile19, readdir as readdir5, stat as stat3 } from "fs/promises";
5189
- import path27 from "path";
5190
- import chalk14 from "chalk";
6072
+ import { existsSync as existsSync30 } from "fs";
6073
+ import { readFile as readFile23, readdir as readdir6, stat as stat3 } from "fs/promises";
6074
+ import path33 from "path";
6075
+ import chalk16 from "chalk";
5191
6076
  async function auditLoading(options = {}) {
5192
6077
  const projectRoot = findProjectRoot();
5193
6078
  if (!projectRoot) {
@@ -5220,8 +6105,8 @@ async function auditLoading(options = {}) {
5220
6105
  printResults(results);
5221
6106
  }
5222
6107
  async function checkHeadHtml(root, results) {
5223
- const filePath = path27.join(root, "head.html");
5224
- if (!existsSync25(filePath)) {
6108
+ const filePath = path33.join(root, "head.html");
6109
+ if (!existsSync30(filePath)) {
5225
6110
  results.push({
5226
6111
  rule: "head.html exists",
5227
6112
  status: "warn",
@@ -5229,7 +6114,7 @@ async function checkHeadHtml(root, results) {
5229
6114
  });
5230
6115
  return;
5231
6116
  }
5232
- const content = await readFile19(filePath, "utf-8");
6117
+ const content = await readFile23(filePath, "utf-8");
5233
6118
  const lines = content.split("\n");
5234
6119
  for (let i = 0; i < lines.length; i++) {
5235
6120
  const line = lines[i];
@@ -5301,8 +6186,8 @@ async function checkHeadHtml(root, results) {
5301
6186
  }
5302
6187
  }
5303
6188
  async function checkScriptsJs(root, results) {
5304
- const filePath = path27.join(root, "scripts", "scripts.js");
5305
- if (!existsSync25(filePath)) {
6189
+ const filePath = path33.join(root, "scripts", "scripts.js");
6190
+ if (!existsSync30(filePath)) {
5306
6191
  results.push({
5307
6192
  rule: "scripts/scripts.js exists",
5308
6193
  status: "warn",
@@ -5310,7 +6195,7 @@ async function checkScriptsJs(root, results) {
5310
6195
  });
5311
6196
  return;
5312
6197
  }
5313
- const content = await readFile19(filePath, "utf-8");
6198
+ const content = await readFile23(filePath, "utf-8");
5314
6199
  const lines = content.split("\n");
5315
6200
  const hasLoadEager = /function\s+loadEager|loadEager\s*=/i.test(content);
5316
6201
  const hasLoadLazy = /function\s+loadLazy|loadLazy\s*=/i.test(content);
@@ -5388,8 +6273,8 @@ async function checkScriptsJs(root, results) {
5388
6273
  }
5389
6274
  }
5390
6275
  async function checkStylesCss(root, results) {
5391
- const filePath = path27.join(root, "styles", "styles.css");
5392
- if (!existsSync25(filePath)) return;
6276
+ const filePath = path33.join(root, "styles", "styles.css");
6277
+ if (!existsSync30(filePath)) return;
5393
6278
  const stats = await stat3(filePath);
5394
6279
  const sizeKb = stats.size / 1024;
5395
6280
  results.push({
@@ -5398,7 +6283,7 @@ async function checkStylesCss(root, results) {
5398
6283
  message: `${sizeKb.toFixed(1)} KB${sizeKb > 40 ? " \u2014 large CSS competes with 100KB LCP budget" : ""}`,
5399
6284
  file: "styles/styles.css"
5400
6285
  });
5401
- const content = await readFile19(filePath, "utf-8");
6286
+ const content = await readFile23(filePath, "utf-8");
5402
6287
  if (/@import\s/.test(content)) {
5403
6288
  results.push({
5404
6289
  rule: "No @import in styles.css",
@@ -5409,8 +6294,8 @@ async function checkStylesCss(root, results) {
5409
6294
  }
5410
6295
  }
5411
6296
  async function checkDelayedJs(root, results) {
5412
- const filePath = path27.join(root, "scripts", "delayed.js");
5413
- if (!existsSync25(filePath)) {
6297
+ const filePath = path33.join(root, "scripts", "delayed.js");
6298
+ if (!existsSync30(filePath)) {
5414
6299
  results.push({
5415
6300
  rule: "scripts/delayed.js exists",
5416
6301
  status: "info",
@@ -5418,7 +6303,7 @@ async function checkDelayedJs(root, results) {
5418
6303
  });
5419
6304
  return;
5420
6305
  }
5421
- const content = await readFile19(filePath, "utf-8");
6306
+ const content = await readFile23(filePath, "utf-8");
5422
6307
  const stripped = content.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "").trim();
5423
6308
  if (!stripped) {
5424
6309
  results.push({
@@ -5430,15 +6315,15 @@ async function checkDelayedJs(root, results) {
5430
6315
  }
5431
6316
  }
5432
6317
  async function checkBlocks(root, results) {
5433
- const blocksDir = path27.join(root, "blocks");
5434
- if (!existsSync25(blocksDir)) return;
5435
- const entries = await readdir5(blocksDir, { withFileTypes: true });
6318
+ const blocksDir = path33.join(root, "blocks");
6319
+ if (!existsSync30(blocksDir)) return;
6320
+ const entries = await readdir6(blocksDir, { withFileTypes: true });
5436
6321
  for (const entry of entries) {
5437
6322
  if (!entry.isDirectory()) continue;
5438
6323
  const blockName = entry.name;
5439
- const jsFile = path27.join(blocksDir, blockName, `${blockName}.js`);
5440
- if (!existsSync25(jsFile)) continue;
5441
- const content = await readFile19(jsFile, "utf-8");
6324
+ const jsFile = path33.join(blocksDir, blockName, `${blockName}.js`);
6325
+ if (!existsSync30(jsFile)) continue;
6326
+ const content = await readFile23(jsFile, "utf-8");
5442
6327
  const lines = content.split("\n");
5443
6328
  const heavyLibs = [
5444
6329
  "jquery",
@@ -5488,9 +6373,9 @@ async function checkBlocks(root, results) {
5488
6373
  for (const entry of entries) {
5489
6374
  if (!entry.isDirectory()) continue;
5490
6375
  const blockName = entry.name;
5491
- const cssFile = path27.join(blocksDir, blockName, `${blockName}.css`);
5492
- if (!existsSync25(cssFile)) continue;
5493
- const content = await readFile19(cssFile, "utf-8");
6376
+ const cssFile = path33.join(blocksDir, blockName, `${blockName}.css`);
6377
+ if (!existsSync30(cssFile)) continue;
6378
+ const content = await readFile23(cssFile, "utf-8");
5494
6379
  const selectorPattern = /^([a-z][a-z0-9-]*)\s*\{/gm;
5495
6380
  for (const selectorMatch of content.matchAll(selectorPattern)) {
5496
6381
  const selector = selectorMatch[1];
@@ -5506,8 +6391,8 @@ async function checkBlocks(root, results) {
5506
6391
  }
5507
6392
  }
5508
6393
  async function checkAemJs(root, results) {
5509
- const filePath = path27.join(root, "scripts", "aem.js");
5510
- if (!existsSync25(filePath)) {
6394
+ const filePath = path33.join(root, "scripts", "aem.js");
6395
+ if (!existsSync30(filePath)) {
5511
6396
  results.push({
5512
6397
  rule: "scripts/aem.js exists",
5513
6398
  status: "warn",
@@ -5545,12 +6430,12 @@ function printResults(results) {
5545
6430
  grouped.set(key, bucket);
5546
6431
  }
5547
6432
  for (const [file, fileResults] of grouped) {
5548
- logger.info(` ${icon.bullet} ${chalk14.underline(file)}`);
6433
+ logger.info(` ${icon.bullet} ${chalk16.underline(file)}`);
5549
6434
  for (const r of fileResults) {
5550
- const loc = r.line ? chalk14.dim(`:${r.line}`) : "";
6435
+ const loc = r.line ? chalk16.dim(`:${r.line}`) : "";
5551
6436
  logger.info(` ${statusIcon(r.status)} ${r.rule}${loc}`);
5552
6437
  if (r.status !== "pass") {
5553
- logger.info(chalk14.dim(` ${r.message}`));
6438
+ logger.info(chalk16.dim(` ${r.message}`));
5554
6439
  }
5555
6440
  }
5556
6441
  logger.info("");
@@ -5579,14 +6464,14 @@ var audit_security_exports = {};
5579
6464
  __export(audit_security_exports, {
5580
6465
  auditSecurity: () => auditSecurity
5581
6466
  });
5582
- import { existsSync as existsSync26 } from "fs";
5583
- import { readFile as readFile20, readdir as readdir6 } from "fs/promises";
5584
- import path28 from "path";
5585
- import chalk15 from "chalk";
6467
+ import { existsSync as existsSync31 } from "fs";
6468
+ import { readFile as readFile24, readdir as readdir7 } from "fs/promises";
6469
+ import path34 from "path";
6470
+ import chalk17 from "chalk";
5586
6471
  async function* walk2(dir) {
5587
- const entries = await readdir6(dir, { withFileTypes: true });
6472
+ const entries = await readdir7(dir, { withFileTypes: true });
5588
6473
  for (const entry of entries) {
5589
- const full = path28.join(dir, entry.name);
6474
+ const full = path34.join(dir, entry.name);
5590
6475
  if (entry.isDirectory()) {
5591
6476
  if (IGNORE_DIRS.has(entry.name)) continue;
5592
6477
  yield* walk2(full);
@@ -5620,11 +6505,11 @@ function scanContent(content, relFile, ext) {
5620
6505
  return findings;
5621
6506
  }
5622
6507
  async function auditDependencies(root) {
5623
- if (!existsSync26(path28.join(root, "package.json"))) {
6508
+ if (!existsSync31(path34.join(root, "package.json"))) {
5624
6509
  return { status: "skipped", note: "no package.json" };
5625
6510
  }
5626
- const hasLock = existsSync26(path28.join(root, "package-lock.json")) || existsSync26(path28.join(root, "npm-shrinkwrap.json"));
5627
- if (!hasLock && !existsSync26(path28.join(root, "node_modules"))) {
6511
+ const hasLock = existsSync31(path34.join(root, "package-lock.json")) || existsSync31(path34.join(root, "npm-shrinkwrap.json"));
6512
+ if (!hasLock && !existsSync31(path34.join(root, "node_modules"))) {
5628
6513
  return { status: "skipped", note: "no package-lock.json / node_modules \u2014 run npm install" };
5629
6514
  }
5630
6515
  try {
@@ -5656,11 +6541,11 @@ async function auditSecurity(options = {}) {
5656
6541
  }
5657
6542
  const findings = [];
5658
6543
  for await (const file of walk2(projectRoot)) {
5659
- const ext = path28.extname(file).slice(1).toLowerCase();
6544
+ const ext = path34.extname(file).slice(1).toLowerCase();
5660
6545
  if (!SCAN_EXT.has(ext)) continue;
5661
- const rel = path28.relative(projectRoot, file);
6546
+ const rel = path34.relative(projectRoot, file);
5662
6547
  if (rel.endsWith("aem.js") || rel.endsWith("lib-franklin.js")) continue;
5663
- const content = await readFile20(file, "utf-8");
6548
+ const content = await readFile24(file, "utf-8");
5664
6549
  findings.push(...scanContent(content, rel, ext));
5665
6550
  }
5666
6551
  const deps = await auditDependencies(projectRoot);
@@ -5708,9 +6593,9 @@ async function auditSecurity(options = {}) {
5708
6593
  );
5709
6594
  for (const f2 of sorted) {
5710
6595
  const meta = SEVERITY_META[f2.severity];
5711
- const loc = chalk15.dim(`${f2.file}:${f2.line}`);
5712
- logger.info(` ${statusIcon(meta.status)} ${chalk15.bold(meta.label)} ${f2.message} ${loc}`);
5713
- logger.info(chalk15.dim(` ${f2.snippet}`));
6596
+ const loc = chalk17.dim(`${f2.file}:${f2.line}`);
6597
+ logger.info(` ${statusIcon(meta.status)} ${chalk17.bold(meta.label)} ${f2.message} ${loc}`);
6598
+ logger.info(chalk17.dim(` ${f2.snippet}`));
5714
6599
  }
5715
6600
  }
5716
6601
  const summaryLine = summary({
@@ -5917,7 +6802,7 @@ __export(preview_exports2, {
5917
6802
  preview: () => preview,
5918
6803
  resolveAdminConfig: () => resolveAdminConfig
5919
6804
  });
5920
- import chalk16 from "chalk";
6805
+ import chalk18 from "chalk";
5921
6806
  import ora2 from "ora";
5922
6807
  async function preview(paths, options) {
5923
6808
  const projectRoot = findProjectRoot();
@@ -5930,7 +6815,7 @@ async function preview(paths, options) {
5930
6815
  let hasErrors = false;
5931
6816
  for (const r of results) {
5932
6817
  if (r.ok) {
5933
- logger.success(`${r.path} \u2192 ${chalk16.cyan(r.previewUrl || "preview updated")}`);
6818
+ logger.success(`${r.path} \u2192 ${chalk18.cyan(r.previewUrl || "preview updated")}`);
5934
6819
  } else {
5935
6820
  logger.error(`${r.path} \u2014 ${r.message}`);
5936
6821
  hasErrors = true;
@@ -5944,7 +6829,7 @@ function resolveAdminConfig(config, options) {
5944
6829
  const ref = options.ref || config.admin?.ref || "main";
5945
6830
  if (!org || !site) {
5946
6831
  logger.error("Missing --org and --site. Set them via flags or in .edsrc.json:");
5947
- logger.info(chalk16.dim(' { "admin": { "org": "my-org", "site": "my-site" } }'));
6832
+ logger.info(chalk18.dim(' { "admin": { "org": "my-org", "site": "my-site" } }'));
5948
6833
  process.exitCode = 1;
5949
6834
  return void 0;
5950
6835
  }
@@ -5966,7 +6851,7 @@ var publish_exports = {};
5966
6851
  __export(publish_exports, {
5967
6852
  publish: () => publish
5968
6853
  });
5969
- import chalk17 from "chalk";
6854
+ import chalk19 from "chalk";
5970
6855
  import ora3 from "ora";
5971
6856
  async function publish(paths, options) {
5972
6857
  const projectRoot = findProjectRoot();
@@ -5979,7 +6864,7 @@ async function publish(paths, options) {
5979
6864
  let hasErrors = false;
5980
6865
  for (const r of results) {
5981
6866
  if (r.ok) {
5982
- logger.success(`${r.path} \u2192 ${chalk17.cyan(r.liveUrl || "published")}`);
6867
+ logger.success(`${r.path} \u2192 ${chalk19.cyan(r.liveUrl || "published")}`);
5983
6868
  } else {
5984
6869
  logger.error(`${r.path} \u2014 ${r.message}`);
5985
6870
  hasErrors = true;
@@ -6078,13 +6963,13 @@ var menu_exports = {};
6078
6963
  __export(menu_exports, {
6079
6964
  mainMenu: () => mainMenu
6080
6965
  });
6081
- import { existsSync as existsSync27 } from "fs";
6082
- import { readdir as readdir7 } from "fs/promises";
6083
- import path29 from "path";
6966
+ import { existsSync as existsSync32 } from "fs";
6967
+ import { readdir as readdir8 } from "fs/promises";
6968
+ import path35 from "path";
6084
6969
  async function listBlockNames(projectRoot) {
6085
- const dir = path29.join(projectRoot, "blocks");
6086
- if (!existsSync27(dir)) return [];
6087
- const entries = await readdir7(dir, { withFileTypes: true });
6970
+ const dir = path35.join(projectRoot, "blocks");
6971
+ if (!existsSync32(dir)) return [];
6972
+ const entries = await readdir8(dir, { withFileTypes: true });
6088
6973
  return entries.filter((e) => e.isDirectory()).map((e) => e.name);
6089
6974
  }
6090
6975
  async function mainMenu() {
@@ -6095,6 +6980,7 @@ async function mainMenu() {
6095
6980
  choices: [
6096
6981
  { name: "Set up this project (authoring model, CI, naming)", value: "init" },
6097
6982
  { name: "Generate a page template (initial content)", value: "template" },
6983
+ { name: "Create a disposable sandbox repo on GitHub", value: "sandbox" },
6098
6984
  {
6099
6985
  name: "Generate a block from a design (Figma/Stitch/Canva/Sketch/Framer)",
6100
6986
  value: "from-design"
@@ -6106,6 +6992,7 @@ async function mainMenu() {
6106
6992
  { name: "Add fields to a block model (partials)", value: "model" },
6107
6993
  { name: "Instrument a block for analytics (dataLayer)", value: "track" },
6108
6994
  { name: "Add JSON-LD structured data to a block", value: "schema" },
6995
+ { name: "Check Universal Editor readiness / open the editor", value: "ue" },
6109
6996
  { name: "Preview a block in the browser", value: "preview-block" },
6110
6997
  { name: "Preview / publish pages (Admin API)", value: "admin" },
6111
6998
  { name: "Audit the project", value: "audit" },
@@ -6127,6 +7014,15 @@ async function mainMenu() {
6127
7014
  await templateNew2(name.trim(), {});
6128
7015
  return;
6129
7016
  }
7017
+ case "sandbox": {
7018
+ const name = await input({
7019
+ message: "Sandbox repo name",
7020
+ validate: (v) => /^[a-z0-9][a-z0-9._-]*$/i.test(v.trim()) || "Use a valid repo name"
7021
+ });
7022
+ const { sandboxNew: sandboxNew2 } = await Promise.resolve().then(() => (init_sandbox(), sandbox_exports));
7023
+ await sandboxNew2(name.trim(), {});
7024
+ return;
7025
+ }
6130
7026
  case "from-design": {
6131
7027
  const { blockFromDesign: blockFromDesign2 } = await Promise.resolve().then(() => (init_from_design(), from_design_exports));
6132
7028
  await blockFromDesign2(void 0, {});
@@ -6218,6 +7114,24 @@ async function mainMenu() {
6218
7114
  await schemaBlock2(name, {});
6219
7115
  return;
6220
7116
  }
7117
+ case "ue": {
7118
+ const which = await select({
7119
+ message: "Universal Editor:",
7120
+ choices: [
7121
+ { name: "Check readiness (which blocks are editable)", value: "check" },
7122
+ { name: "Open a page in the editor", value: "open" }
7123
+ ]
7124
+ });
7125
+ if (which === "check") {
7126
+ const { ueCheck: ueCheck2 } = await Promise.resolve().then(() => (init_ue3(), ue_exports));
7127
+ await ueCheck2();
7128
+ } else {
7129
+ const pagePath = await input({ message: "Page path", default: "/" });
7130
+ const { ueOpen: ueOpen2 } = await Promise.resolve().then(() => (init_ue3(), ue_exports));
7131
+ await ueOpen2(pagePath.trim() || "/", {});
7132
+ }
7133
+ return;
7134
+ }
6221
7135
  case "admin": {
6222
7136
  const { adminWizard: adminWizard2 } = await Promise.resolve().then(() => (init_admin(), admin_exports));
6223
7137
  await adminWizard2();
@@ -6263,13 +7177,13 @@ var init_menu = __esm({
6263
7177
 
6264
7178
  // src/bin/eds.ts
6265
7179
  init_esm_shims();
6266
- import chalk18 from "chalk";
7180
+ import chalk20 from "chalk";
6267
7181
  import { Command } from "commander";
6268
7182
 
6269
7183
  // package.json
6270
7184
  var package_default = {
6271
7185
  name: "aem-eds-kit",
6272
- version: "0.4.16",
7186
+ version: "0.4.22",
6273
7187
  description: "CLI for AEM Edge Delivery Services with Figma MCP integration",
6274
7188
  type: "module",
6275
7189
  engines: {
@@ -6345,9 +7259,30 @@ program.name("eds").description("CLI for AEM Edge Delivery Services with Figma i
6345
7259
  const opts = thisCommand.opts();
6346
7260
  if (opts.verbose) logger.setLevel("debug");
6347
7261
  if (opts.quiet) logger.setQuiet(true);
6348
- if (opts.color === false) chalk18.level = 0;
7262
+ if (opts.color === false) chalk20.level = 0;
6349
7263
  });
6350
7264
  program.addHelpText("beforeAll", () => logo());
7265
+ var sandbox = program.command("sandbox").description("Disposable EDS sandbox repos on GitHub");
7266
+ sandbox.command("new <name>").description("Create a disposable EDS repo from a boilerplate (GitHub auth, no gh required)").option("--org <org>", "Create under a GitHub org (else your account)").option("--boilerplate <repo>", "Template repo (default adobe/aem-boilerplate)").option("--ue", "Use the crosswalk / Universal Editor boilerplate").option("--private", "Create a private repo").option("--yes", "Skip the guided prompts (use flags/defaults)").action(async (name, options) => {
7267
+ const { sandboxNew: sandboxNew2 } = await Promise.resolve().then(() => (init_sandbox(), sandbox_exports));
7268
+ await sandboxNew2(name, options);
7269
+ });
7270
+ sandbox.command("login").description("Authorize GitHub (OAuth device flow or a token) - no gh needed").action(async () => {
7271
+ const { sandboxLogin: sandboxLogin2 } = await Promise.resolve().then(() => (init_sandbox(), sandbox_exports));
7272
+ await sandboxLogin2();
7273
+ });
7274
+ sandbox.command("logout").description("Remove the stored GitHub token").action(async () => {
7275
+ const { sandboxLogout: sandboxLogout2 } = await Promise.resolve().then(() => (init_sandbox(), sandbox_exports));
7276
+ await sandboxLogout2();
7277
+ });
7278
+ sandbox.command("list").description("List your EDS sandboxes (repos tagged eds-sandbox)").action(async () => {
7279
+ const { sandboxList: sandboxList2 } = await Promise.resolve().then(() => (init_sandbox(), sandbox_exports));
7280
+ await sandboxList2();
7281
+ });
7282
+ sandbox.command("rm <name>").description("Delete a sandbox repo (destructive)").option("--org <org>", "Owner org (else your account)").option("--yes", "Skip the confirmation").action(async (name, options) => {
7283
+ const { sandboxRm: sandboxRm2 } = await Promise.resolve().then(() => (init_sandbox(), sandbox_exports));
7284
+ await sandboxRm2(name, options);
7285
+ });
6351
7286
  var template = program.command("template").description("Generate page templates / initial content");
6352
7287
  template.command("new <name>").description("Generate EDS page initial content (sections + blocks + metadata), DA push optional").option("--path <path>", "Content path (default: /templates/<name>)").option("--title <title>", "Page title").option("--description <text>", "Page description").option(
6353
7288
  "--blocks <list>",
@@ -6357,7 +7292,7 @@ template.command("new <name>").description("Generate EDS page initial content (s
6357
7292
  const { templateNew: templateNew2 } = await Promise.resolve().then(() => (init_template(), template_exports));
6358
7293
  await templateNew2(name, options);
6359
7294
  });
6360
- program.command("init").description("Set up a freshly cloned EDS boilerplate for your authoring model (interactive)").option("--name <name>", "Project name (kebab-case)").option("--authoring <model>", "Authoring model: ue | da | gdrive | sharepoint").option("--mountpoint <url>", "Content source URL (document models)").option("--org <org>", "DA org (authoring=da)").option("--site <site>", "DA site (authoring=da)").option("--no-ci", "Skip the GitHub Actions CI workflow").option("--yes", "Skip prompts; apply with flags/defaults").action(async (options) => {
7295
+ program.command("init").description("Set up a freshly cloned EDS boilerplate for your authoring model (interactive)").option("--name <name>", "Project name (kebab-case)").option("--authoring <model>", "Authoring model: ue | da | gdrive | sharepoint").option("--mountpoint <url>", "Content source URL (document models)").option("--org <org>", "DA org (authoring=da)").option("--site <site>", "DA site (authoring=da)").option("--no-ci", "Skip the GitHub Actions CI workflow").option("--no-env", "Skip the local-dev .env (aem up config)").option("--no-xwalk", "Skip xwalk.json (UE multi-field)").option("--yes", "Skip prompts; apply with flags/defaults").action(async (options) => {
6361
7296
  const { initProject: initProject2 } = await Promise.resolve().then(() => (init_init(), init_exports));
6362
7297
  await initProject2(options);
6363
7298
  });
@@ -6381,6 +7316,15 @@ track.command("block <name>").description("Add dataLayer tracking (click + form
6381
7316
  const { trackBlock: trackBlock2 } = await Promise.resolve().then(() => (init_track2(), track_exports));
6382
7317
  await trackBlock2(name, options);
6383
7318
  });
7319
+ var ue = program.command("ue").description("Universal Editor - readiness check + editor deep link");
7320
+ ue.command("check").description("Check whether blocks are Universal-Editor-ready (offline)").action(async () => {
7321
+ const { ueCheck: ueCheck2 } = await Promise.resolve().then(() => (init_ue3(), ue_exports));
7322
+ await ueCheck2();
7323
+ });
7324
+ ue.command("open [path]").description("Open a page in the hosted Universal Editor (preview host from git)").option("--org <slug>", "IMS org slug (skips the org picker)").option("--ref <branch>", "Branch/ref for the preview host (default: main)").option("--url <host>", "Override the delivery host (e.g. main--repo--owner.aem.page)").option("--no-open", "Print the link without opening the browser").action(async (pagePath, options) => {
7325
+ const { ueOpen: ueOpen2 } = await Promise.resolve().then(() => (init_ue3(), ue_exports));
7326
+ await ueOpen2(pagePath, options);
7327
+ });
6384
7328
  var block = program.command("block").description("Manage EDS blocks");
6385
7329
  block.command("create <name>").description("Scaffold a new EDS block (with Universal Editor model by default)").option("--no-ue-model", "Skip the Universal Editor model (generated by default)").action(async (name, options) => {
6386
7330
  const { createBlock: createBlock2 } = await Promise.resolve().then(() => (init_create(), create_exports));
@@ -6420,6 +7364,14 @@ scaffold.command("helpers [names...]").description("Scaffold scripts/utils.js wi
6420
7364
  const { scaffoldHelpers: scaffoldHelpers2 } = await Promise.resolve().then(() => (init_scaffold(), scaffold_exports));
6421
7365
  await scaffoldHelpers2(names);
6422
7366
  });
7367
+ scaffold.command("env").description("Write a local-dev .env for `aem up` (AEM_OPEN / AEM_PORT / AEM_PAGES_URL)").option("--open <path>", "AEM_OPEN - path opened on start (default: /)").option("--port <port>", "AEM_PORT - proxy port (default: 3007)").option("--pages-url <url>", "AEM_PAGES_URL - content origin (default: from git remote)").option("--yes", "Skip prompts; use flags/defaults").action(async (options) => {
7368
+ const { scaffoldEnv: scaffoldEnv2 } = await Promise.resolve().then(() => (init_scaffold(), scaffold_exports));
7369
+ await scaffoldEnv2(options);
7370
+ });
7371
+ scaffold.command("xwalk").description("Write xwalk.json enabling Universal Editor multi-field support").action(async () => {
7372
+ const { scaffoldXwalk: scaffoldXwalk2 } = await Promise.resolve().then(() => (init_scaffold(), scaffold_exports));
7373
+ await scaffoldXwalk2();
7374
+ });
6423
7375
  program.command("integrate [type]").description("Add a third-party service (GTM, GA4, chat, consent) to scripts/delayed.js").action(async (type) => {
6424
7376
  const { integrate: integrate2 } = await Promise.resolve().then(() => (init_integrate(), integrate_exports));
6425
7377
  await integrate2(type);