aem-eds-kit 0.4.9 → 0.4.16
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/CHANGELOG.md +80 -2
- package/README.md +122 -4
- package/dist/bin/eds.js +2420 -1081
- package/dist/bin/eds.js.map +1 -1
- package/package.json +1 -1
package/dist/bin/eds.js
CHANGED
|
@@ -67,7 +67,7 @@ function mix(a, b, t) {
|
|
|
67
67
|
const r = Math.round(ar + (br - ar) * t);
|
|
68
68
|
const g = Math.round(ag + (bg - ag) * t);
|
|
69
69
|
const bl = Math.round(ab + (bb - ab) * t);
|
|
70
|
-
return `#${[r, g, bl].map((
|
|
70
|
+
return `#${[r, g, bl].map((n2) => n2.toString(16).padStart(2, "0")).join("")}`;
|
|
71
71
|
}
|
|
72
72
|
function gradientLine(line, stops, width) {
|
|
73
73
|
const chars = [...line];
|
|
@@ -127,8 +127,8 @@ function summary(counts) {
|
|
|
127
127
|
if (counts.info) parts.push(`${icon.info} ${counts.info} info`);
|
|
128
128
|
return parts.join(chalk.dim(" "));
|
|
129
129
|
}
|
|
130
|
-
function plural(
|
|
131
|
-
return
|
|
130
|
+
function plural(n2, word) {
|
|
131
|
+
return n2 === 1 ? word : `${word}s`;
|
|
132
132
|
}
|
|
133
133
|
var brand, icon, ANSI, DOTS_N, LOGO_ART, LOGO_STOPS;
|
|
134
134
|
var init_ui = __esm({
|
|
@@ -355,117 +355,6 @@ var init_project = __esm({
|
|
|
355
355
|
}
|
|
356
356
|
});
|
|
357
357
|
|
|
358
|
-
// src/lib/scaffold/ci.ts
|
|
359
|
-
import { existsSync as existsSync2 } from "fs";
|
|
360
|
-
import { mkdir, writeFile } from "fs/promises";
|
|
361
|
-
import path3 from "path";
|
|
362
|
-
async function writeCiWorkflow(projectRoot) {
|
|
363
|
-
const file = path3.join(projectRoot, ".github", "workflows", "eds.yml");
|
|
364
|
-
if (existsSync2(file)) return { path: CI_WORKFLOW_PATH, created: false };
|
|
365
|
-
await mkdir(path3.dirname(file), { recursive: true });
|
|
366
|
-
await writeFile(file, CI_WORKFLOW);
|
|
367
|
-
return { path: CI_WORKFLOW_PATH, created: true };
|
|
368
|
-
}
|
|
369
|
-
var CI_WORKFLOW_PATH, CI_WORKFLOW;
|
|
370
|
-
var init_ci = __esm({
|
|
371
|
-
"src/lib/scaffold/ci.ts"() {
|
|
372
|
-
"use strict";
|
|
373
|
-
init_esm_shims();
|
|
374
|
-
CI_WORKFLOW_PATH = ".github/workflows/eds.yml";
|
|
375
|
-
CI_WORKFLOW = `name: EDS checks
|
|
376
|
-
|
|
377
|
-
# Generated by aem-eds-kit (\`eds scaffold ci\`). Runs the project's own
|
|
378
|
-
# lint/build plus the eds audits as a gatekeeper on every PR and push to main.
|
|
379
|
-
on:
|
|
380
|
-
pull_request:
|
|
381
|
-
push:
|
|
382
|
-
branches: [main]
|
|
383
|
-
|
|
384
|
-
jobs:
|
|
385
|
-
checks:
|
|
386
|
-
runs-on: ubuntu-latest
|
|
387
|
-
steps:
|
|
388
|
-
- uses: actions/checkout@v4
|
|
389
|
-
- uses: actions/setup-node@v4
|
|
390
|
-
with:
|
|
391
|
-
node-version: 20
|
|
392
|
-
|
|
393
|
-
# Install project dependencies when the repo has them.
|
|
394
|
-
- name: Install dependencies
|
|
395
|
-
run: |
|
|
396
|
-
if [ -f package-lock.json ]; then
|
|
397
|
-
npm ci
|
|
398
|
-
elif [ -f package.json ]; then
|
|
399
|
-
npm install
|
|
400
|
-
fi
|
|
401
|
-
|
|
402
|
-
# The project's own scripts, if defined (skipped otherwise).
|
|
403
|
-
- name: Lint
|
|
404
|
-
if: hashFiles('package.json') != ''
|
|
405
|
-
run: npm run lint --if-present
|
|
406
|
-
- name: Build
|
|
407
|
-
if: hashFiles('package.json') != ''
|
|
408
|
-
run: npm run build --if-present
|
|
409
|
-
|
|
410
|
-
# aem-eds-kit audits - run via npx, no install needed. Each exits
|
|
411
|
-
# non-zero on failures, so a bad PR fails the check.
|
|
412
|
-
- name: Project health (doctor)
|
|
413
|
-
run: npx --yes aem-eds-kit@latest doctor
|
|
414
|
-
- name: Loading & LCP budget
|
|
415
|
-
run: npx --yes aem-eds-kit@latest audit loading
|
|
416
|
-
- name: Security & exploit scan
|
|
417
|
-
run: npx --yes aem-eds-kit@latest audit security
|
|
418
|
-
`;
|
|
419
|
-
}
|
|
420
|
-
});
|
|
421
|
-
|
|
422
|
-
// src/lib/scaffold/fstab.ts
|
|
423
|
-
import { writeFile as writeFile2 } from "fs/promises";
|
|
424
|
-
import path4 from "path";
|
|
425
|
-
function needsFstab(a) {
|
|
426
|
-
return a !== "ue";
|
|
427
|
-
}
|
|
428
|
-
function daMountpoint(org, site) {
|
|
429
|
-
return `https://content.da.live/${org}/${site}/`;
|
|
430
|
-
}
|
|
431
|
-
function fstabYaml(mountpoint) {
|
|
432
|
-
return `mountpoints:
|
|
433
|
-
/: ${mountpoint}
|
|
434
|
-
`;
|
|
435
|
-
}
|
|
436
|
-
async function writeFstab(projectRoot, mountpoint) {
|
|
437
|
-
await writeFile2(path4.join(projectRoot, "fstab.yaml"), fstabYaml(mountpoint));
|
|
438
|
-
return "fstab.yaml";
|
|
439
|
-
}
|
|
440
|
-
function pathsJson(site) {
|
|
441
|
-
return `${JSON.stringify(
|
|
442
|
-
{
|
|
443
|
-
mappings: [`/content/${site}/:/`],
|
|
444
|
-
includes: [`/content/${site}/`]
|
|
445
|
-
},
|
|
446
|
-
null,
|
|
447
|
-
2
|
|
448
|
-
)}
|
|
449
|
-
`;
|
|
450
|
-
}
|
|
451
|
-
async function writePaths(projectRoot, site) {
|
|
452
|
-
await writeFile2(path4.join(projectRoot, "paths.json"), pathsJson(site));
|
|
453
|
-
return "paths.json";
|
|
454
|
-
}
|
|
455
|
-
var AUTHORING_NAMES;
|
|
456
|
-
var init_fstab = __esm({
|
|
457
|
-
"src/lib/scaffold/fstab.ts"() {
|
|
458
|
-
"use strict";
|
|
459
|
-
init_esm_shims();
|
|
460
|
-
AUTHORING_NAMES = {
|
|
461
|
-
ue: "Universal Editor (crosswalk)",
|
|
462
|
-
da: "Document Authoring (da.live)",
|
|
463
|
-
gdrive: "Document Authoring (Google Drive)",
|
|
464
|
-
sharepoint: "Document Authoring (SharePoint)"
|
|
465
|
-
};
|
|
466
|
-
}
|
|
467
|
-
});
|
|
468
|
-
|
|
469
358
|
// src/lib/scaffold/blocks.ts
|
|
470
359
|
function json(value) {
|
|
471
360
|
return `${JSON.stringify(value, null, 2)}
|
|
@@ -479,14 +368,14 @@ var init_blocks = __esm({
|
|
|
479
368
|
"src/lib/scaffold/blocks.ts"() {
|
|
480
369
|
"use strict";
|
|
481
370
|
init_esm_shims();
|
|
482
|
-
blockDef = (title, id,
|
|
371
|
+
blockDef = (title, id, model2) => ({
|
|
483
372
|
title,
|
|
484
373
|
id,
|
|
485
374
|
plugins: {
|
|
486
375
|
xwalk: {
|
|
487
376
|
page: {
|
|
488
377
|
resourceType: "core/franklin/components/block/v1/block",
|
|
489
|
-
template: { name: title, model }
|
|
378
|
+
template: { name: title, model: model2 }
|
|
490
379
|
}
|
|
491
380
|
}
|
|
492
381
|
}
|
|
@@ -503,14 +392,14 @@ var init_blocks = __esm({
|
|
|
503
392
|
}
|
|
504
393
|
}
|
|
505
394
|
});
|
|
506
|
-
itemDef = (title, id,
|
|
395
|
+
itemDef = (title, id, model2) => ({
|
|
507
396
|
title,
|
|
508
397
|
id,
|
|
509
398
|
plugins: {
|
|
510
399
|
xwalk: {
|
|
511
400
|
page: {
|
|
512
401
|
resourceType: "core/franklin/components/block/v1/block/item",
|
|
513
|
-
template: { name: title, model }
|
|
402
|
+
template: { name: title, model: model2 }
|
|
514
403
|
}
|
|
515
404
|
}
|
|
516
405
|
}
|
|
@@ -872,7 +761,7 @@ function fieldReferenceFields() {
|
|
|
872
761
|
];
|
|
873
762
|
}
|
|
874
763
|
function fieldReferenceModelFile() {
|
|
875
|
-
const
|
|
764
|
+
const model2 = {
|
|
876
765
|
definitions: [
|
|
877
766
|
{
|
|
878
767
|
title: "Field Reference",
|
|
@@ -890,7 +779,7 @@ function fieldReferenceModelFile() {
|
|
|
890
779
|
models: [{ id: "field-reference", fields: fieldReferenceFields() }],
|
|
891
780
|
filters: []
|
|
892
781
|
};
|
|
893
|
-
return `${JSON.stringify(
|
|
782
|
+
return `${JSON.stringify(model2, null, 2)}
|
|
894
783
|
`;
|
|
895
784
|
}
|
|
896
785
|
function mergeById(existing, additions) {
|
|
@@ -913,7 +802,7 @@ function mergeDefinitions(existing, defaultContent, blockDefs) {
|
|
|
913
802
|
}
|
|
914
803
|
function mergeSectionFilter(existing, ids) {
|
|
915
804
|
const filters = existing ? [...existing] : [];
|
|
916
|
-
let section = filters.find((
|
|
805
|
+
let section = filters.find((f2) => f2.id === "section");
|
|
917
806
|
if (!section) {
|
|
918
807
|
section = { id: "section", components: ["text", "image", "button", "title"] };
|
|
919
808
|
filters.push(section);
|
|
@@ -1044,819 +933,2089 @@ export default function decorate(block) {
|
|
|
1044
933
|
}
|
|
1045
934
|
});
|
|
1046
935
|
|
|
1047
|
-
// src/
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
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
|
|
1054
1044
|
});
|
|
1055
1045
|
import { existsSync as existsSync3 } from "fs";
|
|
1056
|
-
import { mkdir as
|
|
1057
|
-
import
|
|
1058
|
-
|
|
1059
|
-
|
|
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 {};
|
|
1060
1057
|
try {
|
|
1061
|
-
return JSON.parse(await
|
|
1058
|
+
return JSON.parse(await readFile2(rc, "utf-8")).admin ?? {};
|
|
1062
1059
|
} catch {
|
|
1063
|
-
return
|
|
1060
|
+
return {};
|
|
1064
1061
|
}
|
|
1065
1062
|
}
|
|
1066
|
-
async function
|
|
1067
|
-
|
|
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)}
|
|
1068
1076
|
`);
|
|
1077
|
+
return true;
|
|
1069
1078
|
}
|
|
1070
|
-
async function
|
|
1079
|
+
async function templateNew(nameArg, options = {}) {
|
|
1071
1080
|
const projectRoot = findProjectRoot();
|
|
1072
1081
|
if (!projectRoot) {
|
|
1073
1082
|
logger.error("Not inside an EDS project (no EDS project markers found).");
|
|
1074
1083
|
process.exitCode = 1;
|
|
1075
1084
|
return;
|
|
1076
1085
|
}
|
|
1077
|
-
logger.
|
|
1078
|
-
const
|
|
1079
|
-
const
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
const
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
{
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
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."));
|
|
1098
1131
|
}
|
|
1099
1132
|
}
|
|
1100
1133
|
}
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
await writeJson(filtersFile, filters);
|
|
1116
|
-
created.push("component-filters.json (section filter)");
|
|
1117
|
-
logger.info(heading("Created / merged"));
|
|
1118
|
-
const names = created.map((c) => c.split(" ")[0]);
|
|
1119
|
-
const w = columnWidth(names);
|
|
1120
|
-
for (const c of created) {
|
|
1121
|
-
const [name, ...rest] = c.split(" ");
|
|
1122
|
-
logger.info(accentLine(name, rest.join(" ").replace(/^\((.*)\)$/, "$1"), w));
|
|
1123
|
-
}
|
|
1124
|
-
logger.info("");
|
|
1125
|
-
logger.info(box(["Universal Editor scaffold ready"]));
|
|
1126
|
-
}
|
|
1127
|
-
async function scaffoldBlocks(names) {
|
|
1128
|
-
const projectRoot = findProjectRoot();
|
|
1129
|
-
if (!projectRoot) {
|
|
1130
|
-
logger.error("Not inside an EDS project (no EDS project markers found).");
|
|
1131
|
-
process.exitCode = 1;
|
|
1132
|
-
return;
|
|
1133
|
-
}
|
|
1134
|
-
logger.logoOnce(logo("Scaffold \u2014 standard blocks"));
|
|
1135
|
-
let selected;
|
|
1136
|
-
if (names?.length) {
|
|
1137
|
-
selected = names.map((n) => blockById(n.trim())).filter((b) => !!b);
|
|
1138
|
-
const unknown = names.filter((n) => !blockById(n.trim()));
|
|
1139
|
-
if (unknown.length) {
|
|
1140
|
-
logger.warn(
|
|
1141
|
-
`Unknown block(s): ${unknown.join(", ")}. Available: ${STANDARD_BLOCKS.map((b) => b.id).join(", ")}`
|
|
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")
|
|
1142
1148
|
);
|
|
1143
1149
|
}
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
for (const b of selected) {
|
|
1150
|
-
const dir = path5.join(projectRoot, "blocks", b.id);
|
|
1151
|
-
if (existsSync3(dir)) {
|
|
1152
|
-
skipped.push(b.id);
|
|
1153
|
-
continue;
|
|
1154
|
-
}
|
|
1155
|
-
await mkdir2(dir, { recursive: true });
|
|
1156
|
-
await writeFile3(path5.join(dir, `${b.id}.js`), b.js);
|
|
1157
|
-
await writeFile3(path5.join(dir, `${b.id}.css`), b.css);
|
|
1158
|
-
await writeFile3(path5.join(dir, `_${b.id}.json`), b.model);
|
|
1159
|
-
createdIds.push(b.id);
|
|
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.`));
|
|
1160
1155
|
}
|
|
1161
|
-
const filtersFile = path5.join(projectRoot, "component-filters.json");
|
|
1162
|
-
const filters = mergeSectionFilter(
|
|
1163
|
-
await readJson(filtersFile),
|
|
1164
|
-
selected.map((b) => b.id)
|
|
1165
|
-
);
|
|
1166
|
-
await writeJson(filtersFile, filters);
|
|
1167
|
-
logger.info(heading("Blocks"));
|
|
1168
|
-
const w = columnWidth([...createdIds, ...skipped, "component-filters"]);
|
|
1169
|
-
for (const id of createdIds) logger.info(accentLine(id, "created", w));
|
|
1170
|
-
for (const id of skipped) logger.info(statusLine("warn", id, "already exists \u2014 skipped", w));
|
|
1171
1156
|
logger.info("");
|
|
1172
|
-
logger.info(box([
|
|
1157
|
+
logger.info(box([`"${name}" template (${model2}) - preview with \`eds preview ${pagePath}\``]));
|
|
1173
1158
|
}
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
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, "");
|
|
1188
1173
|
}
|
|
1189
|
-
|
|
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
|
+
`;
|
|
1237
|
+
}
|
|
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}/`;
|
|
1248
|
+
}
|
|
1249
|
+
function fstabYaml(mountpoint) {
|
|
1250
|
+
return `mountpoints:
|
|
1251
|
+
/: ${mountpoint}
|
|
1252
|
+
`;
|
|
1253
|
+
}
|
|
1254
|
+
async function writeFstab(projectRoot, mountpoint) {
|
|
1255
|
+
await writeFile3(path6.join(projectRoot, "fstab.yaml"), fstabYaml(mountpoint));
|
|
1256
|
+
return "fstab.yaml";
|
|
1257
|
+
}
|
|
1258
|
+
function pathsJson(site) {
|
|
1259
|
+
return `${JSON.stringify(
|
|
1260
|
+
{
|
|
1261
|
+
mappings: [`/content/${site}/:/`],
|
|
1262
|
+
includes: [`/content/${site}/`]
|
|
1263
|
+
},
|
|
1264
|
+
null,
|
|
1265
|
+
2
|
|
1266
|
+
)}
|
|
1267
|
+
`;
|
|
1268
|
+
}
|
|
1269
|
+
async function writePaths(projectRoot, site) {
|
|
1270
|
+
await writeFile3(path6.join(projectRoot, "paths.json"), pathsJson(site));
|
|
1271
|
+
return "paths.json";
|
|
1272
|
+
}
|
|
1273
|
+
var AUTHORING_NAMES;
|
|
1274
|
+
var init_fstab = __esm({
|
|
1275
|
+
"src/lib/scaffold/fstab.ts"() {
|
|
1276
|
+
"use strict";
|
|
1277
|
+
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
|
+
};
|
|
1284
|
+
}
|
|
1285
|
+
});
|
|
1286
|
+
|
|
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);
|
|
1293
|
+
}
|
|
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);
|
|
1305
|
+
}
|
|
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
|
+
`;
|
|
1313
|
+
}
|
|
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 };
|
|
1321
|
+
}
|
|
1322
|
+
var HELPERS_PATH, HELPERS, ALL_HELPER_IDS;
|
|
1323
|
+
var init_helpers = __esm({
|
|
1324
|
+
"src/lib/scaffold/helpers.ts"() {
|
|
1325
|
+
"use strict";
|
|
1326
|
+
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';
|
|
1392
|
+
}
|
|
1393
|
+
|
|
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);
|
|
1482
|
+
}
|
|
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;
|
|
1499
|
+
try {
|
|
1500
|
+
return JSON.parse(await readFile3(file, "utf-8"));
|
|
1501
|
+
} catch {
|
|
1502
|
+
return null;
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
async function writeJson(file, data) {
|
|
1506
|
+
await writeFile5(file, `${JSON.stringify(data, null, 2)}
|
|
1507
|
+
`);
|
|
1508
|
+
}
|
|
1509
|
+
async function scaffoldUe() {
|
|
1510
|
+
const projectRoot = findProjectRoot();
|
|
1511
|
+
if (!projectRoot) {
|
|
1512
|
+
logger.error("Not inside an EDS project (no EDS project markers found).");
|
|
1513
|
+
process.exitCode = 1;
|
|
1514
|
+
return;
|
|
1515
|
+
}
|
|
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
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
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).");
|
|
1570
|
+
process.exitCode = 1;
|
|
1571
|
+
return;
|
|
1572
|
+
}
|
|
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
|
+
);
|
|
1582
|
+
}
|
|
1583
|
+
} else {
|
|
1584
|
+
selected = STANDARD_BLOCKS;
|
|
1585
|
+
}
|
|
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;
|
|
1593
|
+
}
|
|
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);
|
|
1599
|
+
}
|
|
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
|
+
}
|
|
1613
|
+
async function scaffoldCi() {
|
|
1614
|
+
const projectRoot = findProjectRoot();
|
|
1615
|
+
if (!projectRoot) {
|
|
1616
|
+
logger.error("Not inside an EDS project (no EDS project markers found).");
|
|
1617
|
+
process.exitCode = 1;
|
|
1618
|
+
return;
|
|
1619
|
+
}
|
|
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"));
|
|
1627
|
+
}
|
|
1628
|
+
logger.info("");
|
|
1190
1629
|
logger.info(
|
|
1191
1630
|
box([
|
|
1192
1631
|
res.created ? "CI workflow scaffolded - commit it and it runs on every PR" : "CI workflow left untouched"
|
|
1193
1632
|
])
|
|
1194
1633
|
);
|
|
1195
1634
|
}
|
|
1196
|
-
async function
|
|
1197
|
-
const
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
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
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
if (!selected.length) {
|
|
1656
|
+
logger.info("Nothing selected.");
|
|
1657
|
+
return;
|
|
1658
|
+
}
|
|
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"));
|
|
1665
|
+
}
|
|
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
|
+
}
|
|
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);
|
|
1693
|
+
}
|
|
1694
|
+
if (what === "ci") await scaffoldCi();
|
|
1695
|
+
if (what === "helpers") await scaffoldHelpers();
|
|
1696
|
+
}
|
|
1697
|
+
var init_scaffold = __esm({
|
|
1698
|
+
"src/commands/scaffold.ts"() {
|
|
1699
|
+
"use strict";
|
|
1700
|
+
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
|
+
}
|
|
1709
|
+
});
|
|
1710
|
+
|
|
1711
|
+
// src/commands/init.ts
|
|
1712
|
+
var init_exports = {};
|
|
1713
|
+
__export(init_exports, {
|
|
1714
|
+
initProject: () => initProject
|
|
1715
|
+
});
|
|
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;
|
|
1723
|
+
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;
|
|
1729
|
+
} catch {
|
|
1730
|
+
return false;
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
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;
|
|
1738
|
+
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
|
+
}
|
|
1745
|
+
} catch {
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
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
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
async function initProject(options = {}) {
|
|
1804
|
+
const projectRoot = findProjectRoot();
|
|
1805
|
+
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
|
+
);
|
|
1809
|
+
process.exitCode = 1;
|
|
1810
|
+
return;
|
|
1811
|
+
}
|
|
1812
|
+
await logger.logoOnceAnimated("Project setup");
|
|
1813
|
+
const { input, select, confirm } = await import("@inquirer/prompts");
|
|
1814
|
+
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 {
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
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
|
+
]
|
|
1838
|
+
});
|
|
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;
|
|
1868
|
+
}
|
|
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));
|
|
1887
|
+
}
|
|
1888
|
+
logger.info("");
|
|
1889
|
+
if (interactive) await externalSteps(authoring);
|
|
1890
|
+
logger.info(box([`"${name}" set up for ${AUTHORING_NAMES[authoring]}`]));
|
|
1891
|
+
}
|
|
1892
|
+
var slug2;
|
|
1893
|
+
var init_init = __esm({
|
|
1894
|
+
"src/commands/init.ts"() {
|
|
1895
|
+
"use strict";
|
|
1896
|
+
init_esm_shims();
|
|
1897
|
+
init_logger();
|
|
1898
|
+
init_project();
|
|
1899
|
+
init_ci();
|
|
1900
|
+
init_fstab();
|
|
1901
|
+
init_ui();
|
|
1902
|
+
init_scaffold();
|
|
1903
|
+
slug2 = (s) => s.trim().toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
1904
|
+
}
|
|
1905
|
+
});
|
|
1906
|
+
|
|
1907
|
+
// 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";
|
|
1911
|
+
async function listFiles(dir) {
|
|
1912
|
+
const out = [];
|
|
1913
|
+
const walk3 = async (d) => {
|
|
1914
|
+
for (const e of await readdir2(d, { withFileTypes: true })) {
|
|
1915
|
+
const p = path10.join(d, e.name);
|
|
1916
|
+
if (e.isDirectory()) await walk3(p);
|
|
1917
|
+
else out.push(p);
|
|
1918
|
+
}
|
|
1919
|
+
};
|
|
1920
|
+
if (existsSync8(dir)) await walk3(dir);
|
|
1921
|
+
return out;
|
|
1922
|
+
}
|
|
1923
|
+
async function findClientlibDir(componentDir) {
|
|
1924
|
+
if (!existsSync8(componentDir)) return null;
|
|
1925
|
+
const entries = await readdir2(componentDir, { withFileTypes: true });
|
|
1926
|
+
const match = entries.find((e) => e.isDirectory() && /clientlib/i.test(e.name));
|
|
1927
|
+
if (match) return path10.join(componentDir, match.name);
|
|
1928
|
+
const hasCss = entries.some((e) => e.isFile() && e.name.endsWith(".css"));
|
|
1929
|
+
return hasCss ? componentDir : null;
|
|
1930
|
+
}
|
|
1931
|
+
async function readClientlibCss(clientlibDir) {
|
|
1932
|
+
const all = await listFiles(clientlibDir);
|
|
1933
|
+
const preprocessed = all.filter((f2) => /\.(less|scss|sass)$/i.test(f2)).map((f2) => path10.relative(clientlibDir, f2));
|
|
1934
|
+
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());
|
|
1938
|
+
let base = clientlibDir;
|
|
1939
|
+
const listed = [];
|
|
1940
|
+
for (const l of lines) {
|
|
1941
|
+
if (!l) continue;
|
|
1942
|
+
const baseMatch = l.match(/^#base\s*=\s*(.+)$/);
|
|
1943
|
+
if (baseMatch) base = path10.join(clientlibDir, baseMatch[1].trim());
|
|
1944
|
+
else if (!l.startsWith("#")) listed.push(l);
|
|
1945
|
+
}
|
|
1946
|
+
cssFiles = listed.map((rel) => path10.join(base, rel)).filter((f2) => existsSync8(f2) && f2.endsWith(".css"));
|
|
1947
|
+
} else {
|
|
1948
|
+
cssFiles = all.filter((f2) => f2.endsWith(".css"));
|
|
1949
|
+
}
|
|
1950
|
+
const parts = [];
|
|
1951
|
+
for (const f2 of cssFiles) parts.push(await readFile5(f2, "utf-8"));
|
|
1952
|
+
return {
|
|
1953
|
+
css: parts.join("\n\n"),
|
|
1954
|
+
files: cssFiles.map((f2) => path10.relative(clientlibDir, f2)),
|
|
1955
|
+
preprocessed
|
|
1956
|
+
};
|
|
1957
|
+
}
|
|
1958
|
+
var init_clientlib = __esm({
|
|
1959
|
+
"src/lib/migrate/clientlib.ts"() {
|
|
1960
|
+
"use strict";
|
|
1961
|
+
init_esm_shims();
|
|
1962
|
+
}
|
|
1963
|
+
});
|
|
1964
|
+
|
|
1965
|
+
// src/lib/migrate/dialog.ts
|
|
1966
|
+
import { XMLParser } from "fast-xml-parser";
|
|
1967
|
+
function tagOf(n2) {
|
|
1968
|
+
return Object.keys(n2).find((k) => k !== ":@") ?? "";
|
|
1969
|
+
}
|
|
1970
|
+
function childrenOf(n2) {
|
|
1971
|
+
const t = tagOf(n2);
|
|
1972
|
+
const v = n2[t];
|
|
1973
|
+
return Array.isArray(v) ? v : [];
|
|
1974
|
+
}
|
|
1975
|
+
function attrsOf(n2) {
|
|
1976
|
+
return n2[":@"] ?? {};
|
|
1977
|
+
}
|
|
1978
|
+
function stripType(v) {
|
|
1979
|
+
return typeof v === "string" ? v.replace(/^\{[^}]+\}/, "") : v;
|
|
1980
|
+
}
|
|
1981
|
+
function attr(n2, name) {
|
|
1982
|
+
return stripType(attrsOf(n2)[`@_${name}`]);
|
|
1983
|
+
}
|
|
1984
|
+
function leafComponent(rt, n2) {
|
|
1985
|
+
const multiple = attr(n2, "multiple") === "true" || attr(n2, "multiple") === "{Boolean}true";
|
|
1986
|
+
if (rt.endsWith("/form/textfield")) return "text";
|
|
1987
|
+
if (rt.endsWith("/form/textarea")) return "textarea";
|
|
1988
|
+
if (rt.includes("/richtext")) return "richtext";
|
|
1989
|
+
if (rt.endsWith("/form/numberfield")) return "number";
|
|
1990
|
+
if (rt.endsWith("/form/checkbox")) return "boolean";
|
|
1991
|
+
if (rt.endsWith("/form/radiogroup")) return "radio-group";
|
|
1992
|
+
if (rt.endsWith("/form/select")) return multiple ? "multiselect" : "select";
|
|
1993
|
+
if (rt.endsWith("/form/datepicker")) return "date-time";
|
|
1994
|
+
if (rt.endsWith("/form/pathfield") || rt.endsWith("/form/pathbrowser")) return "aem-content";
|
|
1995
|
+
if (rt.includes("/fileupload") || rt.endsWith("/form/fileupload")) return "reference";
|
|
1996
|
+
if (rt.includes("tagfield") || rt.includes("tagpicker") || rt.includes("tagbrowser"))
|
|
1997
|
+
return "aem-tag";
|
|
1998
|
+
if (rt.includes("contentfragment")) return "aem-content-fragment";
|
|
1999
|
+
if (rt.includes("experiencefragment")) return "aem-experience-fragment";
|
|
2000
|
+
return null;
|
|
2001
|
+
}
|
|
2002
|
+
function isWrapper(rt) {
|
|
2003
|
+
return rt.includes("/foundation/container") || rt.includes("/foundation/fixedcolumns") || rt.includes("/foundation/well") || rt.includes("/foundation/tabs") || rt.includes("/foundation/form/fieldset") || rt.includes("/foundation/accordion");
|
|
2004
|
+
}
|
|
2005
|
+
function valueTypeFor(c) {
|
|
2006
|
+
switch (c) {
|
|
2007
|
+
case "number":
|
|
2008
|
+
return "number";
|
|
2009
|
+
case "boolean":
|
|
2010
|
+
return "boolean";
|
|
2011
|
+
case "date-time":
|
|
2012
|
+
return "date";
|
|
2013
|
+
case "checkbox-group":
|
|
2014
|
+
return "string[]";
|
|
2015
|
+
case "aem-content":
|
|
2016
|
+
case "aem-content-fragment":
|
|
2017
|
+
case "aem-experience-fragment":
|
|
2018
|
+
return void 0;
|
|
2019
|
+
default:
|
|
2020
|
+
return "string";
|
|
2021
|
+
}
|
|
2022
|
+
}
|
|
2023
|
+
function cleanName(raw, fallback) {
|
|
2024
|
+
const n2 = (raw ?? "").replace(/^\.\//, "").trim();
|
|
2025
|
+
return n2 || fallback;
|
|
2026
|
+
}
|
|
2027
|
+
function readOptions(fieldNode) {
|
|
2028
|
+
const items = childrenOf(fieldNode).find((c) => tagOf(c) === "items");
|
|
2029
|
+
if (!items) return [];
|
|
2030
|
+
return childrenOf(items).map((opt) => {
|
|
2031
|
+
const value = attr(opt, "value") ?? "";
|
|
2032
|
+
const name = attr(opt, "text") ?? attr(opt, "jcr:title") ?? value;
|
|
2033
|
+
return { name, value };
|
|
1207
2034
|
});
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
2035
|
+
}
|
|
2036
|
+
function walk(nodes, out, unmapped, counter) {
|
|
2037
|
+
for (const node of nodes) {
|
|
2038
|
+
const rt = attr(node, "sling:resourceType") ?? "";
|
|
2039
|
+
if (rt.endsWith("/foundation/tabs")) {
|
|
2040
|
+
const items = childrenOf(node).find((c) => tagOf(c) === "items");
|
|
2041
|
+
for (const tab of items ? childrenOf(items) : []) {
|
|
2042
|
+
const label = attr(tab, "jcr:title");
|
|
2043
|
+
out.push({ component: "tab", name: cleanName(void 0, tagOf(tab)), label });
|
|
2044
|
+
walk(childrenOf(tab), out, unmapped, counter);
|
|
2045
|
+
}
|
|
2046
|
+
continue;
|
|
2047
|
+
}
|
|
2048
|
+
if (rt.endsWith("/form/multifield")) {
|
|
2049
|
+
const sub = [];
|
|
2050
|
+
walk(childrenOf(node), sub, unmapped, counter);
|
|
2051
|
+
const name = cleanName(attr(node, "name"), `items${counter.i++}`);
|
|
2052
|
+
out.push({
|
|
2053
|
+
component: "container",
|
|
2054
|
+
name,
|
|
2055
|
+
label: attr(node, "fieldLabel") ?? "Items",
|
|
2056
|
+
multi: true,
|
|
2057
|
+
collapsible: true,
|
|
2058
|
+
fields: sub
|
|
2059
|
+
});
|
|
2060
|
+
continue;
|
|
2061
|
+
}
|
|
2062
|
+
const comp = rt ? leafComponent(rt, node) : null;
|
|
2063
|
+
if (comp) {
|
|
2064
|
+
const name = cleanName(attr(node, "name"), `field${counter.i++}`);
|
|
2065
|
+
const field = { component: comp, name };
|
|
2066
|
+
const vt = valueTypeFor(comp);
|
|
2067
|
+
if (vt) field.valueType = vt;
|
|
2068
|
+
const label = attr(node, "fieldLabel") ?? attr(node, "text");
|
|
2069
|
+
if (label) field.label = label;
|
|
2070
|
+
if (comp === "select" || comp === "radio-group" || comp === "multiselect") {
|
|
2071
|
+
const options = readOptions(node);
|
|
2072
|
+
if (options.length) field.options = options;
|
|
2073
|
+
}
|
|
2074
|
+
const def = attr(node, "value") ?? (comp === "boolean" ? attr(node, "checked") : void 0);
|
|
2075
|
+
if (def !== void 0 && def !== "") field.value = comp === "boolean" ? def === "true" : def;
|
|
2076
|
+
out.push(field);
|
|
2077
|
+
continue;
|
|
2078
|
+
}
|
|
2079
|
+
if (rt && attr(node, "name") && !isWrapper(rt)) unmapped.add(rt);
|
|
2080
|
+
walk(childrenOf(node), out, unmapped, counter);
|
|
1215
2081
|
}
|
|
1216
|
-
if (what === "ci") await scaffoldCi();
|
|
1217
2082
|
}
|
|
1218
|
-
|
|
1219
|
-
|
|
2083
|
+
function parseDialog(xml) {
|
|
2084
|
+
const parser = new XMLParser({
|
|
2085
|
+
ignoreAttributes: false,
|
|
2086
|
+
attributeNamePrefix: "@_",
|
|
2087
|
+
preserveOrder: true,
|
|
2088
|
+
removeNSPrefix: false,
|
|
2089
|
+
trimValues: true
|
|
2090
|
+
});
|
|
2091
|
+
const tree = parser.parse(xml);
|
|
2092
|
+
const root = tree.find((n2) => tagOf(n2) === "jcr:root");
|
|
2093
|
+
const fields = [];
|
|
2094
|
+
const unmapped = /* @__PURE__ */ new Set();
|
|
2095
|
+
if (root) walk(childrenOf(root), fields, unmapped, { i: 1 });
|
|
2096
|
+
return { fields, unmapped: [...unmapped] };
|
|
2097
|
+
}
|
|
2098
|
+
var init_dialog = __esm({
|
|
2099
|
+
"src/lib/migrate/dialog.ts"() {
|
|
1220
2100
|
"use strict";
|
|
1221
2101
|
init_esm_shims();
|
|
1222
|
-
init_logger();
|
|
1223
|
-
init_project();
|
|
1224
|
-
init_blocks();
|
|
1225
|
-
init_ci();
|
|
1226
|
-
init_ue();
|
|
1227
|
-
init_ui();
|
|
1228
2102
|
}
|
|
1229
2103
|
});
|
|
1230
2104
|
|
|
1231
|
-
// src/
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
}
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
2105
|
+
// 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";
|
|
2109
|
+
import { XMLParser as XMLParser2 } from "fast-xml-parser";
|
|
2110
|
+
function findDialogFile(componentDir) {
|
|
2111
|
+
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")
|
|
2115
|
+
];
|
|
2116
|
+
return candidates.find((c) => existsSync9(c)) ?? null;
|
|
2117
|
+
}
|
|
2118
|
+
async function readComponentTitle(componentDir) {
|
|
2119
|
+
const file = path11.join(componentDir, ".content.xml");
|
|
2120
|
+
if (!existsSync9(file)) return void 0;
|
|
1243
2121
|
try {
|
|
1244
|
-
const
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
return
|
|
2122
|
+
const parser = new XMLParser2({ ignoreAttributes: false, attributeNamePrefix: "@_" });
|
|
2123
|
+
const parsed = parser.parse(await readFile6(file, "utf-8"));
|
|
2124
|
+
const root = parsed["jcr:root"] ?? {};
|
|
2125
|
+
const title = root["@_jcr:title"];
|
|
2126
|
+
return typeof title === "string" ? title.replace(/^\{[^}]+\}/, "") : void 0;
|
|
1249
2127
|
} catch {
|
|
1250
|
-
return
|
|
2128
|
+
return void 0;
|
|
1251
2129
|
}
|
|
1252
2130
|
}
|
|
1253
|
-
async function
|
|
1254
|
-
const
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
2131
|
+
async function readDialogFields(componentDir) {
|
|
2132
|
+
const dialog = findDialogFile(componentDir);
|
|
2133
|
+
if (!dialog) return null;
|
|
2134
|
+
return parseDialog(await readFile6(dialog, "utf-8"));
|
|
2135
|
+
}
|
|
2136
|
+
function migratedModelFile(blockName, title, fields) {
|
|
2137
|
+
const model2 = {
|
|
2138
|
+
definitions: [
|
|
2139
|
+
{
|
|
2140
|
+
title,
|
|
2141
|
+
id: blockName,
|
|
2142
|
+
plugins: {
|
|
2143
|
+
xwalk: {
|
|
2144
|
+
page: {
|
|
2145
|
+
resourceType: "core/franklin/components/block/v1/block",
|
|
2146
|
+
template: { name: title, model: blockName }
|
|
2147
|
+
}
|
|
2148
|
+
}
|
|
2149
|
+
}
|
|
1264
2150
|
}
|
|
1265
|
-
|
|
1266
|
-
}
|
|
2151
|
+
],
|
|
2152
|
+
models: [{ id: blockName, fields }],
|
|
2153
|
+
filters: []
|
|
2154
|
+
};
|
|
2155
|
+
return `${JSON.stringify(model2, null, 2)}
|
|
2156
|
+
`;
|
|
2157
|
+
}
|
|
2158
|
+
function migratedBlockJs(blockName, fields) {
|
|
2159
|
+
const hasContainer = fields.some((f2) => f2.component === "container" && f2.multi);
|
|
2160
|
+
const body = hasContainer ? ` // This block was migrated from a classic AEM component with a multifield.
|
|
2161
|
+
// Each child row is one repeated item - enhance them here.
|
|
2162
|
+
[...block.children].forEach((row) => {
|
|
2163
|
+
row.classList.add('${blockName}-item');
|
|
2164
|
+
});` : ` // Migrated from a classic AEM component. The authored fields arrive as
|
|
2165
|
+
// rows; read and enhance them here (port the old HTL logic as needed).`;
|
|
2166
|
+
return `// Block: ${blockName}
|
|
2167
|
+
// Migrated from a classic AEM component by \`eds migrate component\`.
|
|
2168
|
+
// TODO: port the component's HTL/JS behaviour into this decorator.
|
|
2169
|
+
export default function decorate(block) {
|
|
2170
|
+
block.classList.add('${blockName}');
|
|
2171
|
+
${body}
|
|
2172
|
+
}
|
|
2173
|
+
`;
|
|
2174
|
+
}
|
|
2175
|
+
function migratedBlockCss(blockName) {
|
|
2176
|
+
return `/* Block: ${blockName} */
|
|
2177
|
+
/* Migrated from a classic AEM component. Bring the component's styles here */
|
|
2178
|
+
/* (from its clientlib or the rendered page) and keep them scoped to the block. */
|
|
2179
|
+
.${blockName} {
|
|
2180
|
+
}
|
|
2181
|
+
`;
|
|
2182
|
+
}
|
|
2183
|
+
var init_component = __esm({
|
|
2184
|
+
"src/lib/migrate/component.ts"() {
|
|
2185
|
+
"use strict";
|
|
2186
|
+
init_esm_shims();
|
|
2187
|
+
init_dialog();
|
|
1267
2188
|
}
|
|
1268
|
-
|
|
2189
|
+
});
|
|
2190
|
+
|
|
2191
|
+
// src/lib/track.ts
|
|
2192
|
+
function trackSnippet(blockName, param, events) {
|
|
2193
|
+
const parts = [` // --- ${TRACK_MARKER} ---`];
|
|
2194
|
+
if (events.click) {
|
|
2195
|
+
parts.push(
|
|
2196
|
+
` ${param}.addEventListener('click', (ev) => {`,
|
|
2197
|
+
" const el = ev.target.closest('a, button');",
|
|
2198
|
+
" if (!el) return;",
|
|
2199
|
+
` trackEvent({ event: 'block_click', block: '${blockName}', label: el.dataset.track || el.textContent.trim() });`,
|
|
2200
|
+
" });"
|
|
2201
|
+
);
|
|
2202
|
+
}
|
|
2203
|
+
if (events.submit) {
|
|
2204
|
+
parts.push(
|
|
2205
|
+
` ${param}.addEventListener('submit', () => {`,
|
|
2206
|
+
` trackEvent({ event: 'form_submit', block: '${blockName}' });`,
|
|
2207
|
+
" });"
|
|
2208
|
+
);
|
|
2209
|
+
}
|
|
2210
|
+
return parts.join("\n");
|
|
1269
2211
|
}
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
2212
|
+
function instrumentJs(js, blockName, events) {
|
|
2213
|
+
if (js.includes(TRACK_MARKER)) return { js, changed: false, reason: "already instrumented" };
|
|
2214
|
+
if (!events.click && !events.submit) return { js, changed: false, reason: "no events selected" };
|
|
2215
|
+
const decorate = js.match(
|
|
2216
|
+
/export default (?:async )?function decorate\s*\(\s*([A-Za-z_$][\w$]*)?[^)]*\)\s*\{/
|
|
2217
|
+
);
|
|
2218
|
+
if (!decorate || decorate.index === void 0) {
|
|
2219
|
+
return { js, changed: false, reason: "no `export default function decorate(...)` found" };
|
|
2220
|
+
}
|
|
2221
|
+
const param = decorate[1] || "block";
|
|
2222
|
+
let out = js;
|
|
2223
|
+
if (!out.includes("scripts/analytics.js")) {
|
|
2224
|
+
const imports = [...out.matchAll(/^import .*$/gm)];
|
|
2225
|
+
if (imports.length) {
|
|
2226
|
+
const last = imports[imports.length - 1];
|
|
2227
|
+
const at2 = (last.index ?? 0) + last[0].length;
|
|
2228
|
+
out = `${out.slice(0, at2)}
|
|
2229
|
+
${IMPORT_LINE}${out.slice(at2)}`;
|
|
2230
|
+
} else {
|
|
2231
|
+
out = `${IMPORT_LINE}
|
|
2232
|
+
|
|
2233
|
+
${out}`;
|
|
1277
2234
|
}
|
|
1278
|
-
];
|
|
1279
|
-
if (authoring === "gdrive") {
|
|
1280
|
-
steps.push({
|
|
1281
|
-
title: "Share your content folder",
|
|
1282
|
-
note: "Share the Google Drive folder with the AEM service and set it as the mountpoint."
|
|
1283
|
-
});
|
|
1284
2235
|
}
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
}
|
|
2236
|
+
const d2 = out.match(
|
|
2237
|
+
/export default (?:async )?function decorate\s*\(\s*([A-Za-z_$][\w$]*)?[^)]*\)\s*\{/
|
|
2238
|
+
);
|
|
2239
|
+
if (!d2 || d2.index === void 0)
|
|
2240
|
+
return { js: out, changed: false, reason: "decorate not found" };
|
|
2241
|
+
const at = d2.index + d2[0].length;
|
|
2242
|
+
const snippet = trackSnippet(blockName, param, events);
|
|
2243
|
+
out = `${out.slice(0, at)}
|
|
2244
|
+
${snippet}
|
|
2245
|
+
${out.slice(at)}`;
|
|
2246
|
+
return { js: out, changed: true };
|
|
2247
|
+
}
|
|
2248
|
+
var TRACK_MARKER, IMPORT_LINE;
|
|
2249
|
+
var init_track = __esm({
|
|
2250
|
+
"src/lib/track.ts"() {
|
|
2251
|
+
"use strict";
|
|
2252
|
+
init_esm_shims();
|
|
2253
|
+
TRACK_MARKER = "analytics (eds track)";
|
|
2254
|
+
IMPORT_LINE = "import { trackEvent } from '../../scripts/analytics.js';";
|
|
2255
|
+
}
|
|
2256
|
+
});
|
|
2257
|
+
|
|
2258
|
+
// src/commands/track.ts
|
|
2259
|
+
var track_exports = {};
|
|
2260
|
+
__export(track_exports, {
|
|
2261
|
+
trackBlock: () => trackBlock
|
|
2262
|
+
});
|
|
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";
|
|
2267
|
+
async function trackBlock(blockName, options = {}) {
|
|
2268
|
+
const projectRoot = findProjectRoot();
|
|
2269
|
+
if (!projectRoot) {
|
|
2270
|
+
logger.error("Not inside an EDS project (no EDS project markers found).");
|
|
2271
|
+
process.exitCode = 1;
|
|
2272
|
+
return;
|
|
2273
|
+
}
|
|
2274
|
+
logger.logoOnce(logo("Track block"));
|
|
2275
|
+
const jsPath = path12.join(projectRoot, "blocks", blockName, `${blockName}.js`);
|
|
2276
|
+
if (!existsSync10(jsPath)) {
|
|
2277
|
+
logger.error(`Block "${blockName}" not found (no blocks/${blockName}/${blockName}.js).`);
|
|
2278
|
+
process.exitCode = 1;
|
|
2279
|
+
return;
|
|
1290
2280
|
}
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
});
|
|
2281
|
+
const events = { click: options.click !== false, submit: options.submit !== false };
|
|
2282
|
+
const res = instrumentJs(await readFile7(jsPath, "utf-8"), blockName, events);
|
|
2283
|
+
if (!res.changed) {
|
|
2284
|
+
logger.warn(`Not instrumented: ${res.reason}.`);
|
|
2285
|
+
return;
|
|
1297
2286
|
}
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
2287
|
+
await writeFile7(jsPath, res.js);
|
|
2288
|
+
logger.info(heading("Instrumented"));
|
|
2289
|
+
const picked = [events.click ? "block_click" : null, events.submit ? "form_submit" : null].filter(
|
|
2290
|
+
Boolean
|
|
2291
|
+
);
|
|
2292
|
+
logger.info(accentLine(`blocks/${blockName}/${blockName}.js`, picked.join(" + ")));
|
|
2293
|
+
if (!existsSync10(path12.join(projectRoot, "scripts", "analytics.js"))) {
|
|
2294
|
+
logger.info("");
|
|
2295
|
+
logger.warn("scripts/analytics.js is missing - run `eds integrate gtm` to create it.");
|
|
1304
2296
|
}
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
const s = steps[i];
|
|
1317
|
-
logger.info(` ${brand.accent(String(i + 1))} ${chalk3.bold(s.title)}`);
|
|
1318
|
-
if (s.url) logger.info(` ${chalk3.cyan(s.url)}`);
|
|
1319
|
-
logger.info(` ${chalk3.dim(s.note)}`);
|
|
1320
|
-
await confirm({ message: " Done / skip?", default: true });
|
|
2297
|
+
logger.info("");
|
|
2298
|
+
logger.info(chalk5.dim(" Events push to window.dataLayer via trackEvent - GTM picks them up."));
|
|
2299
|
+
}
|
|
2300
|
+
var init_track2 = __esm({
|
|
2301
|
+
"src/commands/track.ts"() {
|
|
2302
|
+
"use strict";
|
|
2303
|
+
init_esm_shims();
|
|
2304
|
+
init_logger();
|
|
2305
|
+
init_project();
|
|
2306
|
+
init_track();
|
|
2307
|
+
init_ui();
|
|
1321
2308
|
}
|
|
2309
|
+
});
|
|
2310
|
+
|
|
2311
|
+
// src/commands/migrate.ts
|
|
2312
|
+
var migrate_exports = {};
|
|
2313
|
+
__export(migrate_exports, {
|
|
2314
|
+
migrateComponent: () => migrateComponent
|
|
2315
|
+
});
|
|
2316
|
+
import { existsSync as existsSync11 } from "fs";
|
|
2317
|
+
import { mkdir as mkdir5, writeFile as writeFile8 } from "fs/promises";
|
|
2318
|
+
import path13 from "path";
|
|
2319
|
+
function clientlibBlockCss(blockName, css, files) {
|
|
2320
|
+
return `/* Block: ${blockName} */
|
|
2321
|
+
/* Migrated from the component clientlib: ${files.join(", ")}. */
|
|
2322
|
+
/* TODO: review selectors and scope them under .${blockName} (and port any LESS/SCSS). */
|
|
2323
|
+
|
|
2324
|
+
${css.trim()}
|
|
2325
|
+
`;
|
|
1322
2326
|
}
|
|
1323
|
-
|
|
2327
|
+
function previewFields(fields, depth = 0) {
|
|
2328
|
+
const lines = [];
|
|
2329
|
+
for (const f2 of fields) {
|
|
2330
|
+
const indent = " ".repeat(depth);
|
|
2331
|
+
if (f2.component === "tab") {
|
|
2332
|
+
lines.push(`${indent}${brand.blue("\u25B8")} ${f2.label ?? f2.name}`);
|
|
2333
|
+
} else {
|
|
2334
|
+
const opts = f2.options?.length ? ` (${f2.options.length} options)` : "";
|
|
2335
|
+
const multi = f2.multi ? " \xB7multi" : "";
|
|
2336
|
+
const label = f2.label ? `- ${f2.label}` : "";
|
|
2337
|
+
lines.push(`${indent}${brand.accent(f2.component)}${multi} ${f2.name}${opts} ${label}`);
|
|
2338
|
+
if (f2.fields?.length) lines.push(...previewFields(f2.fields, depth + 1));
|
|
2339
|
+
}
|
|
2340
|
+
}
|
|
2341
|
+
return lines;
|
|
2342
|
+
}
|
|
2343
|
+
function countLeaves(fields) {
|
|
2344
|
+
let n2 = 0;
|
|
2345
|
+
for (const f2 of fields) {
|
|
2346
|
+
if (f2.component === "tab") continue;
|
|
2347
|
+
n2 += 1;
|
|
2348
|
+
if (f2.fields) n2 += countLeaves(f2.fields);
|
|
2349
|
+
}
|
|
2350
|
+
return n2;
|
|
2351
|
+
}
|
|
2352
|
+
async function migrateComponent(dirArg, options = {}) {
|
|
1324
2353
|
const projectRoot = findProjectRoot();
|
|
1325
2354
|
if (!projectRoot) {
|
|
1326
|
-
logger.error(
|
|
1327
|
-
"Not inside an EDS project (no EDS project markers found). Clone an EDS boilerplate first, then run this from its root."
|
|
1328
|
-
);
|
|
2355
|
+
logger.error("Not inside an EDS project (no EDS project markers found).");
|
|
1329
2356
|
process.exitCode = 1;
|
|
1330
2357
|
return;
|
|
1331
2358
|
}
|
|
1332
|
-
await logger.logoOnceAnimated("
|
|
1333
|
-
const { input,
|
|
2359
|
+
await logger.logoOnceAnimated("Migrate component");
|
|
2360
|
+
const { input, confirm } = await import("@inquirer/prompts");
|
|
1334
2361
|
const interactive = !options.yes;
|
|
1335
|
-
let
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
}
|
|
1341
|
-
}
|
|
2362
|
+
let componentDir = dirArg;
|
|
2363
|
+
if (!componentDir && interactive) {
|
|
2364
|
+
componentDir = await input({
|
|
2365
|
+
message: "Path to the classic component folder",
|
|
2366
|
+
validate: (v) => existsSync11(v.trim()) || "Folder not found"
|
|
2367
|
+
});
|
|
1342
2368
|
}
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
)
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
validate: (v) => /^https?:\/\//.test(v.trim()) || "Paste the folder URL"
|
|
1370
|
-
})).trim();
|
|
1371
|
-
}
|
|
2369
|
+
if (!componentDir) {
|
|
2370
|
+
logger.error("Provide the path to the classic component folder.");
|
|
2371
|
+
process.exitCode = 1;
|
|
2372
|
+
return;
|
|
2373
|
+
}
|
|
2374
|
+
componentDir = path13.resolve(componentDir.trim());
|
|
2375
|
+
if (!findDialogFile(componentDir)) {
|
|
2376
|
+
logger.error(`No Touch UI dialog (_cq_dialog/.content.xml) found in ${componentDir}.`);
|
|
2377
|
+
process.exitCode = 1;
|
|
2378
|
+
return;
|
|
2379
|
+
}
|
|
2380
|
+
const parsed = await readDialogFields(componentDir);
|
|
2381
|
+
if (!parsed || parsed.fields.length === 0) {
|
|
2382
|
+
logger.warn("Dialog found but no mappable fields were detected.");
|
|
2383
|
+
if (parsed?.unmapped.length)
|
|
2384
|
+
logger.info(`Unmapped resourceTypes: ${parsed.unmapped.join(", ")}`);
|
|
2385
|
+
process.exitCode = 1;
|
|
2386
|
+
return;
|
|
2387
|
+
}
|
|
2388
|
+
const title = await readComponentTitle(componentDir) ?? path13.basename(componentDir);
|
|
2389
|
+
const blockName = options.name && slug3(options.name) || (interactive ? slug3(await input({ message: "Block name", default: slug3(title) })) : slug3(title));
|
|
2390
|
+
logger.info(heading("Mapped fields", `${countLeaves(parsed.fields)} from dialog`));
|
|
2391
|
+
for (const line of previewFields(parsed.fields)) logger.info(` ${line}`);
|
|
2392
|
+
if (parsed.unmapped.length) {
|
|
2393
|
+
logger.info("");
|
|
2394
|
+
logger.warn(`Skipped (unrecognised): ${parsed.unmapped.join(", ")}`);
|
|
1372
2395
|
}
|
|
1373
|
-
const withCi = options.ci ?? (interactive ? await confirm({ message: "4/4 Add GitHub Actions CI (audits gatekeeper)?", default: true }) : true);
|
|
1374
|
-
logger.info(heading("Summary"));
|
|
1375
|
-
const w = columnWidth(["name", "authoring", "mountpoint", "ci"]);
|
|
1376
|
-
logger.info(accentLine("name", name, w));
|
|
1377
|
-
logger.info(accentLine("authoring", AUTHORING_NAMES[authoring], w));
|
|
1378
|
-
if (mountpoint) logger.info(accentLine("mountpoint", mountpoint, w));
|
|
1379
|
-
else if (needsFstab(authoring))
|
|
1380
|
-
logger.info(accentLine("mountpoint", "(skipped - set it in fstab.yaml later)", w));
|
|
1381
|
-
logger.info(accentLine("ci", withCi ? "yes" : "no", w));
|
|
1382
2396
|
logger.info("");
|
|
1383
2397
|
if (interactive) {
|
|
1384
|
-
const go = await confirm({ message:
|
|
2398
|
+
const go = await confirm({ message: `Generate block "${blockName}"?`, default: true });
|
|
1385
2399
|
if (!go) {
|
|
1386
2400
|
logger.info("Cancelled.");
|
|
1387
2401
|
return;
|
|
1388
2402
|
}
|
|
1389
2403
|
}
|
|
1390
|
-
const
|
|
1391
|
-
if (
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
2404
|
+
const blockDir = path13.join(projectRoot, "blocks", blockName);
|
|
2405
|
+
if (existsSync11(blockDir)) {
|
|
2406
|
+
logger.error(
|
|
2407
|
+
`Block "${blockName}" already exists at blocks/${blockName}. Use --name to pick another.`
|
|
2408
|
+
);
|
|
2409
|
+
process.exitCode = 1;
|
|
2410
|
+
return;
|
|
1397
2411
|
}
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
2412
|
+
const clibDir = options.clientlib ? path13.resolve(options.clientlib.trim()) : await findClientlibDir(componentDir);
|
|
2413
|
+
let cssContent = migratedBlockCss(blockName);
|
|
2414
|
+
let cssMessage = "empty - add styles from clientlib / rendered CSS";
|
|
2415
|
+
let preprocessedNote = [];
|
|
2416
|
+
if (clibDir && existsSync11(clibDir)) {
|
|
2417
|
+
const lib = await readClientlibCss(clibDir);
|
|
2418
|
+
if (lib.css.trim()) {
|
|
2419
|
+
cssContent = clientlibBlockCss(blockName, lib.css, lib.files);
|
|
2420
|
+
cssMessage = `from clientlib (${lib.files.length} file(s)) - review & scope`;
|
|
2421
|
+
}
|
|
2422
|
+
preprocessedNote = lib.preprocessed;
|
|
1401
2423
|
}
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
2424
|
+
await mkdir5(blockDir, { recursive: true });
|
|
2425
|
+
await writeFile8(
|
|
2426
|
+
path13.join(blockDir, `${blockName}.js`),
|
|
2427
|
+
migratedBlockJs(blockName, parsed.fields)
|
|
2428
|
+
);
|
|
2429
|
+
await writeFile8(path13.join(blockDir, `${blockName}.css`), cssContent);
|
|
2430
|
+
await writeFile8(
|
|
2431
|
+
path13.join(blockDir, `_${blockName}.json`),
|
|
2432
|
+
migratedModelFile(blockName, title, parsed.fields)
|
|
2433
|
+
);
|
|
2434
|
+
logger.info(heading("Created"));
|
|
2435
|
+
const files = [`${blockName}.js`, `${blockName}.css`, `_${blockName}.json`];
|
|
2436
|
+
const w = columnWidth(files);
|
|
2437
|
+
logger.info(accentLine(`${blockName}.js`, "decorator stub (port HTL logic)", w));
|
|
2438
|
+
logger.info(accentLine(`${blockName}.css`, cssMessage, w));
|
|
2439
|
+
logger.info(accentLine(`_${blockName}.json`, "Universal Editor model", w));
|
|
2440
|
+
if (preprocessedNote.length) {
|
|
2441
|
+
logger.info("");
|
|
2442
|
+
logger.warn(`LESS/SCSS found (needs a build, not copied): ${preprocessedNote.join(", ")}`);
|
|
1407
2443
|
}
|
|
1408
2444
|
logger.info("");
|
|
1409
|
-
if (interactive)
|
|
1410
|
-
|
|
2445
|
+
if (interactive) {
|
|
2446
|
+
const addTracking = await confirm({
|
|
2447
|
+
message: "Instrument this block for analytics (dataLayer)?",
|
|
2448
|
+
default: false
|
|
2449
|
+
});
|
|
2450
|
+
if (addTracking) {
|
|
2451
|
+
const { trackBlock: trackBlock2 } = await Promise.resolve().then(() => (init_track2(), track_exports));
|
|
2452
|
+
await trackBlock2(blockName, {});
|
|
2453
|
+
}
|
|
2454
|
+
}
|
|
2455
|
+
logger.info(
|
|
2456
|
+
box([`"${blockName}" migrated. Next: add CSS, then \`eds block preview ${blockName}\``])
|
|
2457
|
+
);
|
|
1411
2458
|
}
|
|
1412
|
-
var
|
|
1413
|
-
var
|
|
1414
|
-
"src/commands/
|
|
2459
|
+
var slug3;
|
|
2460
|
+
var init_migrate = __esm({
|
|
2461
|
+
"src/commands/migrate.ts"() {
|
|
1415
2462
|
"use strict";
|
|
1416
2463
|
init_esm_shims();
|
|
1417
2464
|
init_logger();
|
|
2465
|
+
init_clientlib();
|
|
2466
|
+
init_component();
|
|
1418
2467
|
init_project();
|
|
1419
|
-
init_ci();
|
|
1420
|
-
init_fstab();
|
|
1421
2468
|
init_ui();
|
|
1422
|
-
|
|
1423
|
-
slug = (s) => s.trim().toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
2469
|
+
slug3 = (s) => s.trim().toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
1424
2470
|
}
|
|
1425
2471
|
});
|
|
1426
2472
|
|
|
1427
|
-
// src/lib/
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
2473
|
+
// src/lib/model/helpers.ts
|
|
2474
|
+
function n(prefix, base) {
|
|
2475
|
+
if (!prefix) return base;
|
|
2476
|
+
return prefix + base.charAt(0).toUpperCase() + base.slice(1);
|
|
2477
|
+
}
|
|
2478
|
+
var f;
|
|
2479
|
+
var init_helpers2 = __esm({
|
|
2480
|
+
"src/lib/model/helpers.ts"() {
|
|
2481
|
+
"use strict";
|
|
2482
|
+
init_esm_shims();
|
|
2483
|
+
f = {
|
|
2484
|
+
text: (name, label) => ({
|
|
2485
|
+
component: "text",
|
|
2486
|
+
valueType: "string",
|
|
2487
|
+
name,
|
|
2488
|
+
label
|
|
2489
|
+
}),
|
|
2490
|
+
textarea: (name, label) => ({
|
|
2491
|
+
component: "textarea",
|
|
2492
|
+
valueType: "string",
|
|
2493
|
+
name,
|
|
2494
|
+
label
|
|
2495
|
+
}),
|
|
2496
|
+
richtext: (name, label) => ({
|
|
2497
|
+
component: "richtext",
|
|
2498
|
+
valueType: "string",
|
|
2499
|
+
name,
|
|
2500
|
+
value: "",
|
|
2501
|
+
label
|
|
2502
|
+
}),
|
|
2503
|
+
number: (name, label) => ({
|
|
2504
|
+
component: "number",
|
|
2505
|
+
valueType: "number",
|
|
2506
|
+
name,
|
|
2507
|
+
label
|
|
2508
|
+
}),
|
|
2509
|
+
boolean: (name, label, value = false) => ({
|
|
2510
|
+
component: "boolean",
|
|
2511
|
+
valueType: "boolean",
|
|
2512
|
+
name,
|
|
2513
|
+
value,
|
|
2514
|
+
label
|
|
2515
|
+
}),
|
|
2516
|
+
dateTime: (name, label) => ({
|
|
2517
|
+
component: "date-time",
|
|
2518
|
+
valueType: "date",
|
|
2519
|
+
name,
|
|
2520
|
+
label
|
|
2521
|
+
}),
|
|
2522
|
+
select: (name, label, options, value) => ({
|
|
2523
|
+
component: "select",
|
|
2524
|
+
valueType: "string",
|
|
2525
|
+
name,
|
|
2526
|
+
label,
|
|
2527
|
+
options,
|
|
2528
|
+
...value !== void 0 ? { value } : {}
|
|
2529
|
+
}),
|
|
2530
|
+
multiselect: (name, label, options) => ({
|
|
2531
|
+
component: "multiselect",
|
|
2532
|
+
valueType: "string",
|
|
2533
|
+
name,
|
|
2534
|
+
label,
|
|
2535
|
+
options
|
|
2536
|
+
}),
|
|
2537
|
+
radioGroup: (name, label, options, value) => ({
|
|
2538
|
+
component: "radio-group",
|
|
2539
|
+
valueType: "string",
|
|
2540
|
+
name,
|
|
2541
|
+
label,
|
|
2542
|
+
options,
|
|
2543
|
+
...value !== void 0 ? { value } : {}
|
|
2544
|
+
}),
|
|
2545
|
+
checkboxGroup: (name, label, options) => ({
|
|
2546
|
+
component: "checkbox-group",
|
|
2547
|
+
valueType: "string[]",
|
|
2548
|
+
name,
|
|
2549
|
+
label,
|
|
2550
|
+
options
|
|
2551
|
+
}),
|
|
2552
|
+
reference: (name, label) => ({
|
|
2553
|
+
component: "reference",
|
|
2554
|
+
valueType: "string",
|
|
2555
|
+
name,
|
|
2556
|
+
label
|
|
2557
|
+
}),
|
|
2558
|
+
aemContent: (name, label) => ({ component: "aem-content", name, label }),
|
|
2559
|
+
aemContentFragment: (name, label) => ({
|
|
2560
|
+
component: "aem-content-fragment",
|
|
2561
|
+
name,
|
|
2562
|
+
label
|
|
2563
|
+
}),
|
|
2564
|
+
aemExperienceFragment: (name, label) => ({
|
|
2565
|
+
component: "aem-experience-fragment",
|
|
2566
|
+
name,
|
|
2567
|
+
label
|
|
2568
|
+
}),
|
|
2569
|
+
aemTag: (name, label) => ({
|
|
2570
|
+
component: "aem-tag",
|
|
2571
|
+
valueType: "string",
|
|
2572
|
+
name,
|
|
2573
|
+
label
|
|
2574
|
+
}),
|
|
2575
|
+
tab: (name, label) => ({ component: "tab", name, label }),
|
|
2576
|
+
container: (name, label, fields, collapsible = true) => ({
|
|
2577
|
+
component: "container",
|
|
2578
|
+
name,
|
|
2579
|
+
label,
|
|
2580
|
+
multi: true,
|
|
2581
|
+
collapsible,
|
|
2582
|
+
fields
|
|
2583
|
+
})
|
|
2584
|
+
};
|
|
1469
2585
|
}
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
preprocessed
|
|
1476
|
-
};
|
|
2586
|
+
});
|
|
2587
|
+
|
|
2588
|
+
// src/lib/model/partials.ts
|
|
2589
|
+
function partialById(id) {
|
|
2590
|
+
return PARTIALS.find((p) => p.id === id);
|
|
1477
2591
|
}
|
|
1478
|
-
var
|
|
1479
|
-
|
|
2592
|
+
var HEADING_LEVELS, LINK_TARGETS, BUTTON_STYLES, SECTION_STYLES, heading2, eyebrow, image, richtext, link, cta, ctaGroup, classes, embed, teaser, card, PARTIALS;
|
|
2593
|
+
var init_partials = __esm({
|
|
2594
|
+
"src/lib/model/partials.ts"() {
|
|
1480
2595
|
"use strict";
|
|
1481
2596
|
init_esm_shims();
|
|
2597
|
+
init_helpers2();
|
|
2598
|
+
HEADING_LEVELS = [
|
|
2599
|
+
{ name: "H1", value: "h1" },
|
|
2600
|
+
{ name: "H2", value: "h2" },
|
|
2601
|
+
{ name: "H3", value: "h3" },
|
|
2602
|
+
{ name: "H4", value: "h4" },
|
|
2603
|
+
{ name: "H5", value: "h5" },
|
|
2604
|
+
{ name: "H6", value: "h6" }
|
|
2605
|
+
];
|
|
2606
|
+
LINK_TARGETS = [
|
|
2607
|
+
{ name: "Same tab", value: "_self" },
|
|
2608
|
+
{ name: "New tab", value: "_blank" }
|
|
2609
|
+
];
|
|
2610
|
+
BUTTON_STYLES = [
|
|
2611
|
+
{ name: "Default", value: "" },
|
|
2612
|
+
{ name: "Primary", value: "primary" },
|
|
2613
|
+
{ name: "Secondary", value: "secondary" }
|
|
2614
|
+
];
|
|
2615
|
+
SECTION_STYLES = [
|
|
2616
|
+
{ name: "Highlight", value: "highlight" },
|
|
2617
|
+
{ name: "Dark", value: "dark" },
|
|
2618
|
+
{ name: "Centered", value: "centered" },
|
|
2619
|
+
{ name: "Full width", value: "full-width" }
|
|
2620
|
+
];
|
|
2621
|
+
heading2 = (p = "") => [
|
|
2622
|
+
f.text(n(p, "title"), "Title"),
|
|
2623
|
+
f.select(n(p, "titleType"), "Heading level", HEADING_LEVELS, "h2")
|
|
2624
|
+
];
|
|
2625
|
+
eyebrow = (p = "") => [f.text(n(p, "eyebrow"), "Eyebrow")];
|
|
2626
|
+
image = (p = "") => [
|
|
2627
|
+
f.reference(n(p, "image"), "Image"),
|
|
2628
|
+
f.text(n(p, "imageAlt"), "Alt text")
|
|
2629
|
+
];
|
|
2630
|
+
richtext = (p = "") => [f.richtext(n(p, "text"), "Text")];
|
|
2631
|
+
link = (p = "") => [
|
|
2632
|
+
f.text(n(p, "linkText"), "Link text"),
|
|
2633
|
+
f.aemContent(n(p, "link"), "Link"),
|
|
2634
|
+
f.select(n(p, "linkTarget"), "Open in", LINK_TARGETS, "_self")
|
|
2635
|
+
];
|
|
2636
|
+
cta = (p = "") => [
|
|
2637
|
+
...link(p),
|
|
2638
|
+
f.select(n(p, "ctaStyle"), "Style", BUTTON_STYLES, "")
|
|
2639
|
+
];
|
|
2640
|
+
ctaGroup = (p = "") => [
|
|
2641
|
+
f.container(n(p, "ctas") || "ctas", "Buttons", cta())
|
|
2642
|
+
];
|
|
2643
|
+
classes = (p = "") => [
|
|
2644
|
+
f.multiselect(n(p, "classes"), "Styles", SECTION_STYLES)
|
|
2645
|
+
];
|
|
2646
|
+
embed = (p = "") => [f.aemContent(n(p, "url"), "URL (video / embed)")];
|
|
2647
|
+
teaser = (p = "") => [
|
|
2648
|
+
...image(p),
|
|
2649
|
+
...eyebrow(p),
|
|
2650
|
+
...heading2(p),
|
|
2651
|
+
...richtext(p),
|
|
2652
|
+
...cta(p)
|
|
2653
|
+
];
|
|
2654
|
+
card = (p = "") => [
|
|
2655
|
+
...image(p),
|
|
2656
|
+
f.text(n(p, "title"), "Title"),
|
|
2657
|
+
f.richtext(n(p, "description"), "Description"),
|
|
2658
|
+
...link(p)
|
|
2659
|
+
];
|
|
2660
|
+
PARTIALS = [
|
|
2661
|
+
{ id: "heading", label: "Heading (title + level)", build: heading2 },
|
|
2662
|
+
{ id: "eyebrow", label: "Eyebrow (kicker line)", build: eyebrow },
|
|
2663
|
+
{ id: "image", label: "Image (reference + alt)", build: image },
|
|
2664
|
+
{ id: "richtext", label: "Rich text body", build: richtext },
|
|
2665
|
+
{ id: "link", label: "Link (text + url + target)", build: link },
|
|
2666
|
+
{ id: "cta", label: "CTA (link + style)", build: cta },
|
|
2667
|
+
{ id: "cta-group", label: "CTA group (repeatable buttons)", build: ctaGroup },
|
|
2668
|
+
{ id: "classes", label: "Section styles (CSS modifiers)", build: classes },
|
|
2669
|
+
{ id: "embed", label: "Embed / video (URL)", build: embed },
|
|
2670
|
+
{ id: "teaser", label: "Teaser (image + eyebrow + title + text + CTA)", build: teaser },
|
|
2671
|
+
{ id: "card", label: "Card (image + title + description + link)", build: card }
|
|
2672
|
+
];
|
|
1482
2673
|
}
|
|
1483
2674
|
});
|
|
1484
2675
|
|
|
1485
|
-
// src/
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
}
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
return typeof v === "string" ? v.replace(/^\{[^}]+\}/, "") : v;
|
|
1500
|
-
}
|
|
1501
|
-
function attr(n, name) {
|
|
1502
|
-
return stripType(attrsOf(n)[`@_${name}`]);
|
|
1503
|
-
}
|
|
1504
|
-
function leafComponent(rt, n) {
|
|
1505
|
-
const multiple = attr(n, "multiple") === "true" || attr(n, "multiple") === "{Boolean}true";
|
|
1506
|
-
if (rt.endsWith("/form/textfield")) return "text";
|
|
1507
|
-
if (rt.endsWith("/form/textarea")) return "textarea";
|
|
1508
|
-
if (rt.includes("/richtext")) return "richtext";
|
|
1509
|
-
if (rt.endsWith("/form/numberfield")) return "number";
|
|
1510
|
-
if (rt.endsWith("/form/checkbox")) return "boolean";
|
|
1511
|
-
if (rt.endsWith("/form/radiogroup")) return "radio-group";
|
|
1512
|
-
if (rt.endsWith("/form/select")) return multiple ? "multiselect" : "select";
|
|
1513
|
-
if (rt.endsWith("/form/datepicker")) return "date-time";
|
|
1514
|
-
if (rt.endsWith("/form/pathfield") || rt.endsWith("/form/pathbrowser")) return "aem-content";
|
|
1515
|
-
if (rt.includes("/fileupload") || rt.endsWith("/form/fileupload")) return "reference";
|
|
1516
|
-
if (rt.includes("tagfield") || rt.includes("tagpicker") || rt.includes("tagbrowser"))
|
|
1517
|
-
return "aem-tag";
|
|
1518
|
-
if (rt.includes("contentfragment")) return "aem-content-fragment";
|
|
1519
|
-
if (rt.includes("experiencefragment")) return "aem-experience-fragment";
|
|
1520
|
-
return null;
|
|
1521
|
-
}
|
|
1522
|
-
function isWrapper(rt) {
|
|
1523
|
-
return rt.includes("/foundation/container") || rt.includes("/foundation/fixedcolumns") || rt.includes("/foundation/well") || rt.includes("/foundation/tabs") || rt.includes("/foundation/form/fieldset") || rt.includes("/foundation/accordion");
|
|
1524
|
-
}
|
|
1525
|
-
function valueTypeFor(c) {
|
|
1526
|
-
switch (c) {
|
|
1527
|
-
case "number":
|
|
1528
|
-
return "number";
|
|
1529
|
-
case "boolean":
|
|
1530
|
-
return "boolean";
|
|
1531
|
-
case "date-time":
|
|
1532
|
-
return "date";
|
|
1533
|
-
case "checkbox-group":
|
|
1534
|
-
return "string[]";
|
|
1535
|
-
case "aem-content":
|
|
1536
|
-
case "aem-content-fragment":
|
|
1537
|
-
case "aem-experience-fragment":
|
|
1538
|
-
return void 0;
|
|
1539
|
-
default:
|
|
1540
|
-
return "string";
|
|
2676
|
+
// src/commands/model.ts
|
|
2677
|
+
var model_exports = {};
|
|
2678
|
+
__export(model_exports, {
|
|
2679
|
+
modelAdd: () => modelAdd
|
|
2680
|
+
});
|
|
2681
|
+
import { existsSync as existsSync12 } from "fs";
|
|
2682
|
+
import { readFile as readFile8, writeFile as writeFile9 } from "fs/promises";
|
|
2683
|
+
import path14 from "path";
|
|
2684
|
+
async function modelAdd(blockArg, partialArgs, options = {}) {
|
|
2685
|
+
const projectRoot = findProjectRoot();
|
|
2686
|
+
if (!projectRoot) {
|
|
2687
|
+
logger.error("Not inside an EDS project (no EDS project markers found).");
|
|
2688
|
+
process.exitCode = 1;
|
|
2689
|
+
return;
|
|
1541
2690
|
}
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
const
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
function walk(nodes, out, unmapped, counter) {
|
|
1557
|
-
for (const node of nodes) {
|
|
1558
|
-
const rt = attr(node, "sling:resourceType") ?? "";
|
|
1559
|
-
if (rt.endsWith("/foundation/tabs")) {
|
|
1560
|
-
const items = childrenOf(node).find((c) => tagOf(c) === "items");
|
|
1561
|
-
for (const tab of items ? childrenOf(items) : []) {
|
|
1562
|
-
const label = attr(tab, "jcr:title");
|
|
1563
|
-
out.push({ component: "tab", name: cleanName(void 0, tagOf(tab)), label });
|
|
1564
|
-
walk(childrenOf(tab), out, unmapped, counter);
|
|
1565
|
-
}
|
|
1566
|
-
continue;
|
|
1567
|
-
}
|
|
1568
|
-
if (rt.endsWith("/form/multifield")) {
|
|
1569
|
-
const sub = [];
|
|
1570
|
-
walk(childrenOf(node), sub, unmapped, counter);
|
|
1571
|
-
const name = cleanName(attr(node, "name"), `items${counter.i++}`);
|
|
1572
|
-
out.push({
|
|
1573
|
-
component: "container",
|
|
1574
|
-
name,
|
|
1575
|
-
label: attr(node, "fieldLabel") ?? "Items",
|
|
1576
|
-
multi: true,
|
|
1577
|
-
collapsible: true,
|
|
1578
|
-
fields: sub
|
|
1579
|
-
});
|
|
1580
|
-
continue;
|
|
2691
|
+
logger.logoOnce(logo("Model - add partials"));
|
|
2692
|
+
const { select, checkbox } = await import("@inquirer/prompts");
|
|
2693
|
+
const interactive = !options.yes;
|
|
2694
|
+
let blockName = blockArg;
|
|
2695
|
+
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`))
|
|
2700
|
+
).map((e) => e.name) : [];
|
|
2701
|
+
if (dirs.length === 0) {
|
|
2702
|
+
logger.error("No block with a _<name>.json model found. Create one with `eds block create`.");
|
|
2703
|
+
process.exitCode = 1;
|
|
2704
|
+
return;
|
|
1581
2705
|
}
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
2706
|
+
blockName = await select({
|
|
2707
|
+
message: "Which block's model?",
|
|
2708
|
+
choices: dirs.map((d) => ({ name: d, value: d }))
|
|
2709
|
+
});
|
|
2710
|
+
}
|
|
2711
|
+
if (!blockName) {
|
|
2712
|
+
logger.error("Provide a block name.");
|
|
2713
|
+
process.exitCode = 1;
|
|
2714
|
+
return;
|
|
2715
|
+
}
|
|
2716
|
+
const modelFile = path14.join(projectRoot, "blocks", blockName, `_${blockName}.json`);
|
|
2717
|
+
if (!existsSync12(modelFile)) {
|
|
2718
|
+
logger.error(`No model at blocks/${blockName}/_${blockName}.json. Create the block first.`);
|
|
2719
|
+
process.exitCode = 1;
|
|
2720
|
+
return;
|
|
2721
|
+
}
|
|
2722
|
+
let ids = partialArgs;
|
|
2723
|
+
if (ids.length === 0 && interactive) {
|
|
2724
|
+
ids = await checkbox({
|
|
2725
|
+
message: "Which partials to add?",
|
|
2726
|
+
choices: PARTIALS.map((p) => ({ name: p.label, value: p.id }))
|
|
2727
|
+
});
|
|
2728
|
+
}
|
|
2729
|
+
const partials = ids.map((id) => partialById(id.trim())).filter((p) => !!p);
|
|
2730
|
+
const unknown = ids.filter((id) => !partialById(id.trim()));
|
|
2731
|
+
if (unknown.length) {
|
|
2732
|
+
logger.warn(
|
|
2733
|
+
`Unknown partial(s): ${unknown.join(", ")}. Available: ${PARTIALS.map((p) => p.id).join(", ")}`
|
|
2734
|
+
);
|
|
2735
|
+
}
|
|
2736
|
+
if (partials.length === 0) {
|
|
2737
|
+
logger.info("Nothing to add.");
|
|
2738
|
+
return;
|
|
2739
|
+
}
|
|
2740
|
+
let model2;
|
|
2741
|
+
try {
|
|
2742
|
+
model2 = JSON.parse(await readFile8(modelFile, "utf-8"));
|
|
2743
|
+
} catch {
|
|
2744
|
+
logger.error(`Could not parse ${modelFile}.`);
|
|
2745
|
+
process.exitCode = 1;
|
|
2746
|
+
return;
|
|
2747
|
+
}
|
|
2748
|
+
const models = Array.isArray(model2.models) ? model2.models : [];
|
|
2749
|
+
const target = models.find((m) => m.id === blockName) ?? models[0];
|
|
2750
|
+
if (!target) {
|
|
2751
|
+
logger.error("Model has no `models[]` entry to add fields to.");
|
|
2752
|
+
process.exitCode = 1;
|
|
2753
|
+
return;
|
|
2754
|
+
}
|
|
2755
|
+
target.fields = Array.isArray(target.fields) ? target.fields : [];
|
|
2756
|
+
const existing = new Set(target.fields.map((x) => x.name));
|
|
2757
|
+
const added = [];
|
|
2758
|
+
const skipped = [];
|
|
2759
|
+
for (const p of partials) {
|
|
2760
|
+
for (const field of p.build()) {
|
|
2761
|
+
if (existing.has(field.name)) {
|
|
2762
|
+
skipped.push(field.name);
|
|
2763
|
+
continue;
|
|
1593
2764
|
}
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
continue;
|
|
2765
|
+
existing.add(field.name);
|
|
2766
|
+
target.fields.push(field);
|
|
2767
|
+
added.push(field.name);
|
|
1598
2768
|
}
|
|
1599
|
-
if (rt && attr(node, "name") && !isWrapper(rt)) unmapped.add(rt);
|
|
1600
|
-
walk(childrenOf(node), out, unmapped, counter);
|
|
1601
2769
|
}
|
|
2770
|
+
await writeFile9(modelFile, `${JSON.stringify(model2, null, 2)}
|
|
2771
|
+
`);
|
|
2772
|
+
logger.info(heading("Model updated", `blocks/${blockName}/_${blockName}.json`));
|
|
2773
|
+
logger.info(accentLine("partials", partials.map((p) => p.id).join(", ")));
|
|
2774
|
+
logger.info(accentLine("fields added", added.length ? added.join(", ") : "(none)"));
|
|
2775
|
+
if (skipped.length) logger.info(statusLine("warn", "skipped (exists)", skipped.join(", ")));
|
|
2776
|
+
logger.info("");
|
|
2777
|
+
logger.info(box([`${added.length} field(s) added to "${blockName}"`]));
|
|
1602
2778
|
}
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
ignoreAttributes: false,
|
|
1606
|
-
attributeNamePrefix: "@_",
|
|
1607
|
-
preserveOrder: true,
|
|
1608
|
-
removeNSPrefix: false,
|
|
1609
|
-
trimValues: true
|
|
1610
|
-
});
|
|
1611
|
-
const tree = parser.parse(xml);
|
|
1612
|
-
const root = tree.find((n) => tagOf(n) === "jcr:root");
|
|
1613
|
-
const fields = [];
|
|
1614
|
-
const unmapped = /* @__PURE__ */ new Set();
|
|
1615
|
-
if (root) walk(childrenOf(root), fields, unmapped, { i: 1 });
|
|
1616
|
-
return { fields, unmapped: [...unmapped] };
|
|
1617
|
-
}
|
|
1618
|
-
var init_dialog = __esm({
|
|
1619
|
-
"src/lib/migrate/dialog.ts"() {
|
|
2779
|
+
var init_model = __esm({
|
|
2780
|
+
"src/commands/model.ts"() {
|
|
1620
2781
|
"use strict";
|
|
1621
2782
|
init_esm_shims();
|
|
2783
|
+
init_logger();
|
|
2784
|
+
init_partials();
|
|
2785
|
+
init_project();
|
|
2786
|
+
init_ui();
|
|
1622
2787
|
}
|
|
1623
2788
|
});
|
|
1624
2789
|
|
|
1625
|
-
// src/lib/
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
const
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
const
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
const
|
|
1643
|
-
const
|
|
1644
|
-
const
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
return void 0;
|
|
2790
|
+
// src/lib/schema/jsonld.ts
|
|
2791
|
+
function schemaTypeById(id) {
|
|
2792
|
+
return SCHEMA_TYPES.find((t) => t.id.toLowerCase() === id.toLowerCase());
|
|
2793
|
+
}
|
|
2794
|
+
function injectJsonLd(js, type) {
|
|
2795
|
+
if (js.includes(SCHEMA_MARKER)) return { js, changed: false, reason: "already has JSON-LD" };
|
|
2796
|
+
const decorate = js.match(
|
|
2797
|
+
/export default (?:async )?function decorate\s*\(\s*([A-Za-z_$][\w$]*)?[^)]*\)\s*\{/
|
|
2798
|
+
);
|
|
2799
|
+
if (!decorate || decorate.index === void 0) {
|
|
2800
|
+
return { js, changed: false, reason: "no `export default function decorate(...)` found" };
|
|
2801
|
+
}
|
|
2802
|
+
const param = decorate[1] || "block";
|
|
2803
|
+
const at = decorate.index + decorate[0].length;
|
|
2804
|
+
const snippet = `
|
|
2805
|
+
// --- ${SCHEMA_MARKER}: ${type.id}) ---
|
|
2806
|
+
{
|
|
2807
|
+
const root = ${param};
|
|
2808
|
+
const ld = ${type.object};
|
|
2809
|
+
const ldScript = document.createElement('script');
|
|
2810
|
+
ldScript.type = 'application/ld+json';
|
|
2811
|
+
ldScript.textContent = JSON.stringify(ld);
|
|
2812
|
+
document.head.append(ldScript);
|
|
1649
2813
|
}
|
|
1650
|
-
}
|
|
1651
|
-
async function readDialogFields(componentDir) {
|
|
1652
|
-
const dialog = findDialogFile(componentDir);
|
|
1653
|
-
if (!dialog) return null;
|
|
1654
|
-
return parseDialog(await readFile4(dialog, "utf-8"));
|
|
1655
|
-
}
|
|
1656
|
-
function migratedModelFile(blockName, title, fields) {
|
|
1657
|
-
const model = {
|
|
1658
|
-
definitions: [
|
|
1659
|
-
{
|
|
1660
|
-
title,
|
|
1661
|
-
id: blockName,
|
|
1662
|
-
plugins: {
|
|
1663
|
-
xwalk: {
|
|
1664
|
-
page: {
|
|
1665
|
-
resourceType: "core/franklin/components/block/v1/block",
|
|
1666
|
-
template: { name: title, model: blockName }
|
|
1667
|
-
}
|
|
1668
|
-
}
|
|
1669
|
-
}
|
|
1670
|
-
}
|
|
1671
|
-
],
|
|
1672
|
-
models: [{ id: blockName, fields }],
|
|
1673
|
-
filters: []
|
|
1674
|
-
};
|
|
1675
|
-
return `${JSON.stringify(model, null, 2)}
|
|
1676
|
-
`;
|
|
1677
|
-
}
|
|
1678
|
-
function migratedBlockJs(blockName, fields) {
|
|
1679
|
-
const hasContainer = fields.some((f) => f.component === "container" && f.multi);
|
|
1680
|
-
const body = hasContainer ? ` // This block was migrated from a classic AEM component with a multifield.
|
|
1681
|
-
// Each child row is one repeated item - enhance them here.
|
|
1682
|
-
[...block.children].forEach((row) => {
|
|
1683
|
-
row.classList.add('${blockName}-item');
|
|
1684
|
-
});` : ` // Migrated from a classic AEM component. The authored fields arrive as
|
|
1685
|
-
// rows; read and enhance them here (port the old HTL logic as needed).`;
|
|
1686
|
-
return `// Block: ${blockName}
|
|
1687
|
-
// Migrated from a classic AEM component by \`eds migrate component\`.
|
|
1688
|
-
// TODO: port the component's HTL/JS behaviour into this decorator.
|
|
1689
|
-
export default function decorate(block) {
|
|
1690
|
-
block.classList.add('${blockName}');
|
|
1691
|
-
${body}
|
|
1692
|
-
}
|
|
1693
|
-
`;
|
|
1694
|
-
}
|
|
1695
|
-
function migratedBlockCss(blockName) {
|
|
1696
|
-
return `/* Block: ${blockName} */
|
|
1697
|
-
/* Migrated from a classic AEM component. Bring the component's styles here */
|
|
1698
|
-
/* (from its clientlib or the rendered page) and keep them scoped to the block. */
|
|
1699
|
-
.${blockName} {
|
|
1700
|
-
}
|
|
1701
2814
|
`;
|
|
2815
|
+
return { js: `${js.slice(0, at)}${snippet}${js.slice(at)}`, changed: true };
|
|
1702
2816
|
}
|
|
1703
|
-
var
|
|
1704
|
-
|
|
2817
|
+
var SCHEMA_MARKER, SCHEMA_TYPES;
|
|
2818
|
+
var init_jsonld = __esm({
|
|
2819
|
+
"src/lib/schema/jsonld.ts"() {
|
|
1705
2820
|
"use strict";
|
|
1706
2821
|
init_esm_shims();
|
|
1707
|
-
|
|
2822
|
+
SCHEMA_MARKER = "json-ld (eds schema";
|
|
2823
|
+
SCHEMA_TYPES = [
|
|
2824
|
+
{
|
|
2825
|
+
id: "Article",
|
|
2826
|
+
label: "Article / BlogPosting",
|
|
2827
|
+
object: `{
|
|
2828
|
+
'@context': 'https://schema.org',
|
|
2829
|
+
'@type': 'Article',
|
|
2830
|
+
headline: root.querySelector('h1, h2, h3')?.textContent?.trim() || document.title,
|
|
2831
|
+
image: [...root.querySelectorAll('img')].map((img) => img.src),
|
|
2832
|
+
description: root.querySelector('p')?.textContent?.trim(),
|
|
2833
|
+
url: window.location.href,
|
|
2834
|
+
// TODO: author, datePublished, dateModified
|
|
2835
|
+
}`
|
|
2836
|
+
},
|
|
2837
|
+
{
|
|
2838
|
+
id: "FAQPage",
|
|
2839
|
+
label: "FAQ page",
|
|
2840
|
+
object: `{
|
|
2841
|
+
'@context': 'https://schema.org',
|
|
2842
|
+
'@type': 'FAQPage',
|
|
2843
|
+
mainEntity: [...root.children].map((row) => {
|
|
2844
|
+
const [q, a] = row.children;
|
|
2845
|
+
return {
|
|
2846
|
+
'@type': 'Question',
|
|
2847
|
+
name: q?.textContent?.trim(),
|
|
2848
|
+
acceptedAnswer: { '@type': 'Answer', text: a?.innerHTML?.trim() },
|
|
2849
|
+
};
|
|
2850
|
+
}),
|
|
2851
|
+
}`
|
|
2852
|
+
},
|
|
2853
|
+
{
|
|
2854
|
+
id: "BreadcrumbList",
|
|
2855
|
+
label: "Breadcrumb list",
|
|
2856
|
+
object: `{
|
|
2857
|
+
'@context': 'https://schema.org',
|
|
2858
|
+
'@type': 'BreadcrumbList',
|
|
2859
|
+
itemListElement: [...root.querySelectorAll('a')].map((a, i) => ({
|
|
2860
|
+
'@type': 'ListItem',
|
|
2861
|
+
position: i + 1,
|
|
2862
|
+
name: a.textContent?.trim(),
|
|
2863
|
+
item: a.href,
|
|
2864
|
+
})),
|
|
2865
|
+
}`
|
|
2866
|
+
},
|
|
2867
|
+
{
|
|
2868
|
+
id: "Product",
|
|
2869
|
+
label: "Product",
|
|
2870
|
+
object: `{
|
|
2871
|
+
'@context': 'https://schema.org',
|
|
2872
|
+
'@type': 'Product',
|
|
2873
|
+
name: root.querySelector('h1, h2, h3')?.textContent?.trim(),
|
|
2874
|
+
image: [...root.querySelectorAll('img')].map((img) => img.src),
|
|
2875
|
+
description: root.querySelector('p')?.textContent?.trim(),
|
|
2876
|
+
// TODO: brand, sku, offers { '@type': 'Offer', price, priceCurrency, availability }
|
|
2877
|
+
}`
|
|
2878
|
+
},
|
|
2879
|
+
{
|
|
2880
|
+
id: "Organization",
|
|
2881
|
+
label: "Organization",
|
|
2882
|
+
object: `{
|
|
2883
|
+
'@context': 'https://schema.org',
|
|
2884
|
+
'@type': 'Organization',
|
|
2885
|
+
name: document.title,
|
|
2886
|
+
url: window.location.origin,
|
|
2887
|
+
logo: root.querySelector('img')?.src,
|
|
2888
|
+
// TODO: sameAs: [social profile URLs], contactPoint
|
|
2889
|
+
}`
|
|
2890
|
+
},
|
|
2891
|
+
{
|
|
2892
|
+
id: "LocalBusiness",
|
|
2893
|
+
label: "Local business",
|
|
2894
|
+
object: `{
|
|
2895
|
+
'@context': 'https://schema.org',
|
|
2896
|
+
'@type': 'LocalBusiness',
|
|
2897
|
+
name: root.querySelector('h1, h2, h3')?.textContent?.trim() || document.title,
|
|
2898
|
+
image: root.querySelector('img')?.src,
|
|
2899
|
+
url: window.location.origin,
|
|
2900
|
+
// TODO: address (PostalAddress), telephone, openingHours, geo
|
|
2901
|
+
}`
|
|
2902
|
+
},
|
|
2903
|
+
{
|
|
2904
|
+
id: "Event",
|
|
2905
|
+
label: "Event",
|
|
2906
|
+
object: `{
|
|
2907
|
+
'@context': 'https://schema.org',
|
|
2908
|
+
'@type': 'Event',
|
|
2909
|
+
name: root.querySelector('h1, h2, h3')?.textContent?.trim(),
|
|
2910
|
+
image: [...root.querySelectorAll('img')].map((img) => img.src),
|
|
2911
|
+
url: window.location.href,
|
|
2912
|
+
// TODO: startDate, endDate, location (Place), offers
|
|
2913
|
+
}`
|
|
2914
|
+
},
|
|
2915
|
+
{
|
|
2916
|
+
id: "VideoObject",
|
|
2917
|
+
label: "Video",
|
|
2918
|
+
object: `{
|
|
2919
|
+
'@context': 'https://schema.org',
|
|
2920
|
+
'@type': 'VideoObject',
|
|
2921
|
+
name: root.querySelector('h1, h2, h3')?.textContent?.trim() || document.title,
|
|
2922
|
+
thumbnailUrl: root.querySelector('img')?.src,
|
|
2923
|
+
contentUrl: root.querySelector('a[href], video source[src]')?.href,
|
|
2924
|
+
// TODO: description, uploadDate, duration (ISO 8601)
|
|
2925
|
+
}`
|
|
2926
|
+
},
|
|
2927
|
+
{
|
|
2928
|
+
id: "Recipe",
|
|
2929
|
+
label: "Recipe",
|
|
2930
|
+
object: `{
|
|
2931
|
+
'@context': 'https://schema.org',
|
|
2932
|
+
'@type': 'Recipe',
|
|
2933
|
+
name: root.querySelector('h1, h2, h3')?.textContent?.trim(),
|
|
2934
|
+
image: [...root.querySelectorAll('img')].map((img) => img.src),
|
|
2935
|
+
recipeIngredient: [...root.querySelectorAll('li')].map((li) => li.textContent?.trim()),
|
|
2936
|
+
// TODO: recipeInstructions, prepTime, cookTime, nutrition
|
|
2937
|
+
}`
|
|
2938
|
+
},
|
|
2939
|
+
{
|
|
2940
|
+
id: "HowTo",
|
|
2941
|
+
label: "How-to",
|
|
2942
|
+
object: `{
|
|
2943
|
+
'@context': 'https://schema.org',
|
|
2944
|
+
'@type': 'HowTo',
|
|
2945
|
+
name: root.querySelector('h1, h2, h3')?.textContent?.trim(),
|
|
2946
|
+
step: [...root.querySelectorAll('li')].map((li, i) => ({
|
|
2947
|
+
'@type': 'HowToStep',
|
|
2948
|
+
position: i + 1,
|
|
2949
|
+
text: li.textContent?.trim(),
|
|
2950
|
+
})),
|
|
2951
|
+
}`
|
|
2952
|
+
}
|
|
2953
|
+
];
|
|
1708
2954
|
}
|
|
1709
2955
|
});
|
|
1710
2956
|
|
|
1711
|
-
// src/commands/
|
|
1712
|
-
var
|
|
1713
|
-
__export(
|
|
1714
|
-
|
|
2957
|
+
// src/commands/schema.ts
|
|
2958
|
+
var schema_exports = {};
|
|
2959
|
+
__export(schema_exports, {
|
|
2960
|
+
schemaBlock: () => schemaBlock
|
|
1715
2961
|
});
|
|
1716
|
-
import { existsSync as
|
|
1717
|
-
import {
|
|
1718
|
-
import
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
/* Migrated from the component clientlib: ${files.join(", ")}. */
|
|
1722
|
-
/* TODO: review selectors and scope them under .${blockName} (and port any LESS/SCSS). */
|
|
1723
|
-
|
|
1724
|
-
${css.trim()}
|
|
1725
|
-
`;
|
|
1726
|
-
}
|
|
1727
|
-
function previewFields(fields, depth = 0) {
|
|
1728
|
-
const lines = [];
|
|
1729
|
-
for (const f of fields) {
|
|
1730
|
-
const indent = " ".repeat(depth);
|
|
1731
|
-
if (f.component === "tab") {
|
|
1732
|
-
lines.push(`${indent}${brand.blue("\u25B8")} ${f.label ?? f.name}`);
|
|
1733
|
-
} else {
|
|
1734
|
-
const opts = f.options?.length ? ` (${f.options.length} options)` : "";
|
|
1735
|
-
const multi = f.multi ? " \xB7multi" : "";
|
|
1736
|
-
const label = f.label ? `- ${f.label}` : "";
|
|
1737
|
-
lines.push(`${indent}${brand.accent(f.component)}${multi} ${f.name}${opts} ${label}`);
|
|
1738
|
-
if (f.fields?.length) lines.push(...previewFields(f.fields, depth + 1));
|
|
1739
|
-
}
|
|
1740
|
-
}
|
|
1741
|
-
return lines;
|
|
1742
|
-
}
|
|
1743
|
-
function countLeaves(fields) {
|
|
1744
|
-
let n = 0;
|
|
1745
|
-
for (const f of fields) {
|
|
1746
|
-
if (f.component === "tab") continue;
|
|
1747
|
-
n += 1;
|
|
1748
|
-
if (f.fields) n += countLeaves(f.fields);
|
|
1749
|
-
}
|
|
1750
|
-
return n;
|
|
1751
|
-
}
|
|
1752
|
-
async function migrateComponent(dirArg, options = {}) {
|
|
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";
|
|
2966
|
+
async function schemaBlock(blockName, options = {}) {
|
|
1753
2967
|
const projectRoot = findProjectRoot();
|
|
1754
2968
|
if (!projectRoot) {
|
|
1755
2969
|
logger.error("Not inside an EDS project (no EDS project markers found).");
|
|
1756
2970
|
process.exitCode = 1;
|
|
1757
2971
|
return;
|
|
1758
2972
|
}
|
|
1759
|
-
|
|
1760
|
-
const
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
if (!componentDir && interactive) {
|
|
1764
|
-
componentDir = await input({
|
|
1765
|
-
message: "Path to the classic component folder",
|
|
1766
|
-
validate: (v) => existsSync7(v.trim()) || "Folder not found"
|
|
1767
|
-
});
|
|
1768
|
-
}
|
|
1769
|
-
if (!componentDir) {
|
|
1770
|
-
logger.error("Provide the path to the classic component folder.");
|
|
1771
|
-
process.exitCode = 1;
|
|
1772
|
-
return;
|
|
1773
|
-
}
|
|
1774
|
-
componentDir = path9.resolve(componentDir.trim());
|
|
1775
|
-
if (!findDialogFile(componentDir)) {
|
|
1776
|
-
logger.error(`No Touch UI dialog (_cq_dialog/.content.xml) found in ${componentDir}.`);
|
|
1777
|
-
process.exitCode = 1;
|
|
1778
|
-
return;
|
|
1779
|
-
}
|
|
1780
|
-
const parsed = await readDialogFields(componentDir);
|
|
1781
|
-
if (!parsed || parsed.fields.length === 0) {
|
|
1782
|
-
logger.warn("Dialog found but no mappable fields were detected.");
|
|
1783
|
-
if (parsed?.unmapped.length)
|
|
1784
|
-
logger.info(`Unmapped resourceTypes: ${parsed.unmapped.join(", ")}`);
|
|
2973
|
+
logger.logoOnce(logo("Schema - JSON-LD"));
|
|
2974
|
+
const jsPath = path15.join(projectRoot, "blocks", blockName, `${blockName}.js`);
|
|
2975
|
+
if (!existsSync13(jsPath)) {
|
|
2976
|
+
logger.error(`Block "${blockName}" not found (no blocks/${blockName}/${blockName}.js).`);
|
|
1785
2977
|
process.exitCode = 1;
|
|
1786
2978
|
return;
|
|
1787
2979
|
}
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
}
|
|
1796
|
-
logger.info("");
|
|
1797
|
-
if (interactive) {
|
|
1798
|
-
const go = await confirm({ message: `Generate block "${blockName}"?`, default: true });
|
|
1799
|
-
if (!go) {
|
|
1800
|
-
logger.info("Cancelled.");
|
|
1801
|
-
return;
|
|
1802
|
-
}
|
|
2980
|
+
let typeId = options.type;
|
|
2981
|
+
if (!typeId && !options.yes) {
|
|
2982
|
+
const { select } = await import("@inquirer/prompts");
|
|
2983
|
+
typeId = await select({
|
|
2984
|
+
message: "Which schema.org type?",
|
|
2985
|
+
choices: SCHEMA_TYPES.map((t) => ({ name: `${t.id} - ${t.label}`, value: t.id }))
|
|
2986
|
+
});
|
|
1803
2987
|
}
|
|
1804
|
-
const
|
|
1805
|
-
if (
|
|
2988
|
+
const type = typeId ? schemaTypeById(typeId) : void 0;
|
|
2989
|
+
if (!type) {
|
|
1806
2990
|
logger.error(
|
|
1807
|
-
`
|
|
2991
|
+
`Unknown schema type "${typeId}". Options: ${SCHEMA_TYPES.map((t) => t.id).join(", ")}`
|
|
1808
2992
|
);
|
|
1809
2993
|
process.exitCode = 1;
|
|
1810
2994
|
return;
|
|
1811
2995
|
}
|
|
1812
|
-
const
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
if (clibDir && existsSync7(clibDir)) {
|
|
1817
|
-
const lib = await readClientlibCss(clibDir);
|
|
1818
|
-
if (lib.css.trim()) {
|
|
1819
|
-
cssContent = clientlibBlockCss(blockName, lib.css, lib.files);
|
|
1820
|
-
cssMessage = `from clientlib (${lib.files.length} file(s)) - review & scope`;
|
|
1821
|
-
}
|
|
1822
|
-
preprocessedNote = lib.preprocessed;
|
|
1823
|
-
}
|
|
1824
|
-
await mkdir3(blockDir, { recursive: true });
|
|
1825
|
-
await writeFile5(
|
|
1826
|
-
path9.join(blockDir, `${blockName}.js`),
|
|
1827
|
-
migratedBlockJs(blockName, parsed.fields)
|
|
1828
|
-
);
|
|
1829
|
-
await writeFile5(path9.join(blockDir, `${blockName}.css`), cssContent);
|
|
1830
|
-
await writeFile5(
|
|
1831
|
-
path9.join(blockDir, `_${blockName}.json`),
|
|
1832
|
-
migratedModelFile(blockName, title, parsed.fields)
|
|
1833
|
-
);
|
|
1834
|
-
logger.info(heading("Created"));
|
|
1835
|
-
const files = [`${blockName}.js`, `${blockName}.css`, `_${blockName}.json`];
|
|
1836
|
-
const w = columnWidth(files);
|
|
1837
|
-
logger.info(accentLine(`${blockName}.js`, "decorator stub (port HTL logic)", w));
|
|
1838
|
-
logger.info(accentLine(`${blockName}.css`, cssMessage, w));
|
|
1839
|
-
logger.info(accentLine(`_${blockName}.json`, "Universal Editor model", w));
|
|
1840
|
-
if (preprocessedNote.length) {
|
|
1841
|
-
logger.info("");
|
|
1842
|
-
logger.warn(`LESS/SCSS found (needs a build, not copied): ${preprocessedNote.join(", ")}`);
|
|
2996
|
+
const res = injectJsonLd(await readFile9(jsPath, "utf-8"), type);
|
|
2997
|
+
if (!res.changed) {
|
|
2998
|
+
logger.warn(`Not added: ${res.reason}.`);
|
|
2999
|
+
return;
|
|
1843
3000
|
}
|
|
3001
|
+
await writeFile10(jsPath, res.js);
|
|
3002
|
+
logger.info(heading("JSON-LD added"));
|
|
3003
|
+
logger.info(accentLine(`blocks/${blockName}/${blockName}.js`, `${type.id} (${type.label})`));
|
|
1844
3004
|
logger.info("");
|
|
1845
3005
|
logger.info(
|
|
1846
|
-
|
|
3006
|
+
chalk6.dim(
|
|
3007
|
+
" Review the generated object - some fields are heuristic or TODO. Validate at search.google.com/test/rich-results."
|
|
3008
|
+
)
|
|
1847
3009
|
);
|
|
1848
3010
|
}
|
|
1849
|
-
var
|
|
1850
|
-
|
|
1851
|
-
"src/commands/migrate.ts"() {
|
|
3011
|
+
var init_schema = __esm({
|
|
3012
|
+
"src/commands/schema.ts"() {
|
|
1852
3013
|
"use strict";
|
|
1853
3014
|
init_esm_shims();
|
|
1854
3015
|
init_logger();
|
|
1855
|
-
init_clientlib();
|
|
1856
|
-
init_component();
|
|
1857
3016
|
init_project();
|
|
3017
|
+
init_jsonld();
|
|
1858
3018
|
init_ui();
|
|
1859
|
-
slug2 = (s) => s.trim().toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
1860
3019
|
}
|
|
1861
3020
|
});
|
|
1862
3021
|
|
|
@@ -1866,16 +3025,16 @@ __export(create_exports, {
|
|
|
1866
3025
|
createBlock: () => createBlock,
|
|
1867
3026
|
ueModelFor: () => ueModelFor
|
|
1868
3027
|
});
|
|
1869
|
-
import { existsSync as
|
|
1870
|
-
import { mkdir as
|
|
1871
|
-
import
|
|
1872
|
-
import
|
|
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";
|
|
1873
3032
|
function titleCase(name) {
|
|
1874
3033
|
return name.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
1875
3034
|
}
|
|
1876
3035
|
function ueModelFor(name) {
|
|
1877
3036
|
const title = titleCase(name);
|
|
1878
|
-
const
|
|
3037
|
+
const model2 = {
|
|
1879
3038
|
definitions: [
|
|
1880
3039
|
{
|
|
1881
3040
|
title,
|
|
@@ -1928,7 +3087,7 @@ function ueModelFor(name) {
|
|
|
1928
3087
|
],
|
|
1929
3088
|
filters: []
|
|
1930
3089
|
};
|
|
1931
|
-
return `${JSON.stringify(
|
|
3090
|
+
return `${JSON.stringify(model2, null, 2)}
|
|
1932
3091
|
`;
|
|
1933
3092
|
}
|
|
1934
3093
|
async function createBlock(name, options = {}) {
|
|
@@ -1947,13 +3106,13 @@ async function createBlock(name, options = {}) {
|
|
|
1947
3106
|
process.exitCode = 1;
|
|
1948
3107
|
return;
|
|
1949
3108
|
}
|
|
1950
|
-
const blocksDir =
|
|
1951
|
-
if (
|
|
3109
|
+
const blocksDir = path16.join(projectRoot, "blocks", name);
|
|
3110
|
+
if (existsSync14(blocksDir)) {
|
|
1952
3111
|
logger.error(`Block "${name}" already exists at ${blocksDir}`);
|
|
1953
3112
|
process.exitCode = 1;
|
|
1954
3113
|
return;
|
|
1955
3114
|
}
|
|
1956
|
-
await
|
|
3115
|
+
await mkdir6(blocksDir, { recursive: true });
|
|
1957
3116
|
const jsContent = `export default function decorate(block) {
|
|
1958
3117
|
// TODO: implement ${name} block
|
|
1959
3118
|
}
|
|
@@ -1962,14 +3121,14 @@ async function createBlock(name, options = {}) {
|
|
|
1962
3121
|
/* TODO: style ${name} block */
|
|
1963
3122
|
}
|
|
1964
3123
|
`;
|
|
1965
|
-
await
|
|
1966
|
-
await
|
|
3124
|
+
await writeFile11(path16.join(blocksDir, `${name}.js`), jsContent);
|
|
3125
|
+
await writeFile11(path16.join(blocksDir, `${name}.css`), cssContent);
|
|
1967
3126
|
const withUe = options.ueModel !== false;
|
|
1968
3127
|
if (withUe) {
|
|
1969
|
-
await
|
|
3128
|
+
await writeFile11(path16.join(blocksDir, `_${name}.json`), ueModelFor(name));
|
|
1970
3129
|
}
|
|
1971
3130
|
logger.success(
|
|
1972
|
-
`Block "${name}" created at ${
|
|
3131
|
+
`Block "${name}" created at ${chalk7.cyan(path16.relative(projectRoot, blocksDir))}${withUe ? chalk7.dim(" (+ Universal Editor model)") : ""}`
|
|
1973
3132
|
);
|
|
1974
3133
|
}
|
|
1975
3134
|
var init_create = __esm({
|
|
@@ -1983,10 +3142,10 @@ var init_create = __esm({
|
|
|
1983
3142
|
});
|
|
1984
3143
|
|
|
1985
3144
|
// src/lib/agents/detect.ts
|
|
1986
|
-
import { existsSync as
|
|
1987
|
-
import { readFile as
|
|
3145
|
+
import { existsSync as existsSync15 } from "fs";
|
|
3146
|
+
import { readFile as readFile10 } from "fs/promises";
|
|
1988
3147
|
import { homedir } from "os";
|
|
1989
|
-
import
|
|
3148
|
+
import path17 from "path";
|
|
1990
3149
|
async function whichBinary(name) {
|
|
1991
3150
|
const { execaCommand } = await import("execa");
|
|
1992
3151
|
try {
|
|
@@ -2014,8 +3173,8 @@ function findFigmaServerKey(node) {
|
|
|
2014
3173
|
async function findFigmaMcpServer(configPaths) {
|
|
2015
3174
|
for (const configPath of configPaths) {
|
|
2016
3175
|
try {
|
|
2017
|
-
if (!
|
|
2018
|
-
const content = await
|
|
3176
|
+
if (!existsSync15(configPath)) continue;
|
|
3177
|
+
const content = await readFile10(configPath, "utf-8");
|
|
2019
3178
|
try {
|
|
2020
3179
|
const key = findFigmaServerKey(JSON.parse(content));
|
|
2021
3180
|
if (key) return key;
|
|
@@ -2037,7 +3196,7 @@ async function detectAgent(preferred) {
|
|
|
2037
3196
|
return {
|
|
2038
3197
|
type: def.type,
|
|
2039
3198
|
path: binPath,
|
|
2040
|
-
configDir:
|
|
3199
|
+
configDir: existsSync15(def.configDir) ? def.configDir : void 0,
|
|
2041
3200
|
hasFigmaMcp: !!figmaMcpServerName,
|
|
2042
3201
|
figmaMcpServerName
|
|
2043
3202
|
};
|
|
@@ -2052,7 +3211,7 @@ async function detectAgent(preferred) {
|
|
|
2052
3211
|
return {
|
|
2053
3212
|
type: def.type,
|
|
2054
3213
|
path: binPath,
|
|
2055
|
-
configDir:
|
|
3214
|
+
configDir: existsSync15(def.configDir) ? def.configDir : void 0,
|
|
2056
3215
|
hasFigmaMcp: !!figmaMcpServerName,
|
|
2057
3216
|
figmaMcpServerName
|
|
2058
3217
|
};
|
|
@@ -2069,7 +3228,7 @@ async function detectAllAgents() {
|
|
|
2069
3228
|
agents.push({
|
|
2070
3229
|
type: def.type,
|
|
2071
3230
|
path: binPath,
|
|
2072
|
-
configDir:
|
|
3231
|
+
configDir: existsSync15(def.configDir) ? def.configDir : void 0,
|
|
2073
3232
|
hasFigmaMcp: !!figmaMcpServerName,
|
|
2074
3233
|
figmaMcpServerName
|
|
2075
3234
|
});
|
|
@@ -2087,22 +3246,22 @@ var init_detect = __esm({
|
|
|
2087
3246
|
{
|
|
2088
3247
|
type: "claude",
|
|
2089
3248
|
binary: "claude",
|
|
2090
|
-
configDir:
|
|
3249
|
+
configDir: path17.join(homedir(), ".claude"),
|
|
2091
3250
|
mcpConfigPaths: [
|
|
2092
|
-
|
|
2093
|
-
|
|
3251
|
+
path17.join(homedir(), ".claude", "plugins.json"),
|
|
3252
|
+
path17.join(homedir(), ".claude.json")
|
|
2094
3253
|
]
|
|
2095
3254
|
},
|
|
2096
3255
|
{
|
|
2097
3256
|
type: "cursor",
|
|
2098
3257
|
binary: "cursor",
|
|
2099
|
-
configDir:
|
|
2100
|
-
mcpConfigPaths: [
|
|
3258
|
+
configDir: path17.join(homedir(), ".cursor"),
|
|
3259
|
+
mcpConfigPaths: [path17.join(homedir(), ".cursor", "mcp.json")]
|
|
2101
3260
|
},
|
|
2102
3261
|
{
|
|
2103
3262
|
type: "codex",
|
|
2104
3263
|
binary: "codex",
|
|
2105
|
-
configDir:
|
|
3264
|
+
configDir: path17.join(homedir(), ".codex"),
|
|
2106
3265
|
mcpConfigPaths: []
|
|
2107
3266
|
}
|
|
2108
3267
|
];
|
|
@@ -2110,27 +3269,27 @@ var init_detect = __esm({
|
|
|
2110
3269
|
});
|
|
2111
3270
|
|
|
2112
3271
|
// src/lib/agents/invoke.ts
|
|
2113
|
-
import { mkdir as
|
|
3272
|
+
import { mkdir as mkdir7, readFile as readFile11, writeFile as writeFile12 } from "fs/promises";
|
|
2114
3273
|
import { tmpdir } from "os";
|
|
2115
|
-
import
|
|
3274
|
+
import path18 from "path";
|
|
2116
3275
|
import readline from "readline";
|
|
2117
|
-
import
|
|
3276
|
+
import chalk8 from "chalk";
|
|
2118
3277
|
async function savePromptToFile(prompt, projectRoot) {
|
|
2119
|
-
const base = projectRoot ?
|
|
2120
|
-
const dir =
|
|
2121
|
-
await
|
|
3278
|
+
const base = projectRoot ? path18.join(projectRoot, "tmp") : tmpdir();
|
|
3279
|
+
const dir = path18.join(base, "eds-cli");
|
|
3280
|
+
await mkdir7(dir, { recursive: true });
|
|
2122
3281
|
const filename = `from-design-${Date.now()}.md`;
|
|
2123
|
-
const filePath =
|
|
2124
|
-
await
|
|
3282
|
+
const filePath = path18.join(dir, filename);
|
|
3283
|
+
await writeFile12(filePath, prompt, "utf-8");
|
|
2125
3284
|
return filePath;
|
|
2126
3285
|
}
|
|
2127
3286
|
async function invokeAgent(agent, promptFile, cwd) {
|
|
2128
3287
|
const { execa } = await import("execa");
|
|
2129
|
-
const prompt = await
|
|
3288
|
+
const prompt = await readFile11(promptFile, "utf-8");
|
|
2130
3289
|
try {
|
|
2131
3290
|
switch (agent.type) {
|
|
2132
3291
|
case "claude": {
|
|
2133
|
-
logger.info(`Running Claude Code ${
|
|
3292
|
+
logger.info(`Running Claude Code ${chalk8.dim("- live log:")}`);
|
|
2134
3293
|
const figmaServers = [
|
|
2135
3294
|
...agent.figmaMcpServerName ? [agent.figmaMcpServerName] : [],
|
|
2136
3295
|
"figma-sse",
|
|
@@ -2193,10 +3352,10 @@ async function renderClaudeStream(sub, cwd) {
|
|
|
2193
3352
|
const ora4 = (await import("ora")).default;
|
|
2194
3353
|
const start = Date.now();
|
|
2195
3354
|
const elapsed = () => `${Math.round((Date.now() - start) / 1e3)}s`;
|
|
2196
|
-
const spinner = ora4({ text: `thinking ${
|
|
3355
|
+
const spinner = ora4({ text: `thinking ${chalk8.dim(elapsed())}`, color: "yellow" }).start();
|
|
2197
3356
|
let activity = "thinking";
|
|
2198
3357
|
const timer = setInterval(() => {
|
|
2199
|
-
spinner.text = `${activity} ${
|
|
3358
|
+
spinner.text = `${activity} ${chalk8.dim(elapsed())}`;
|
|
2200
3359
|
}, 300);
|
|
2201
3360
|
const pending = /* @__PURE__ */ new Map();
|
|
2202
3361
|
const emit = (line) => {
|
|
@@ -2222,7 +3381,7 @@ async function renderClaudeStream(sub, cwd) {
|
|
|
2222
3381
|
for (const block2 of content) {
|
|
2223
3382
|
if (block2.type === "text" && "text" in block2) {
|
|
2224
3383
|
const first = block2.text.trim().split("\n")[0].slice(0, 100);
|
|
2225
|
-
if (first) emit(
|
|
3384
|
+
if (first) emit(chalk8.dim(first));
|
|
2226
3385
|
} else if (block2.type === "tool_use" && "name" in block2) {
|
|
2227
3386
|
const id = "id" in block2 && block2.id ? block2.id : `${Math.random()}`;
|
|
2228
3387
|
pending.set(id, {
|
|
@@ -2238,8 +3397,8 @@ async function renderClaudeStream(sub, cwd) {
|
|
|
2238
3397
|
const p = pending.get(block2.tool_use_id);
|
|
2239
3398
|
if (p) {
|
|
2240
3399
|
const dur = `${((Date.now() - p.start) / 1e3).toFixed(1)}s`;
|
|
2241
|
-
const failed = "is_error" in block2 && block2.is_error ? ` ${
|
|
2242
|
-
emit(`${p.label} ${
|
|
3400
|
+
const failed = "is_error" in block2 && block2.is_error ? ` ${chalk8.red("failed")}` : "";
|
|
3401
|
+
emit(`${p.label} ${chalk8.dim(dur)}${failed}`);
|
|
2243
3402
|
pending.delete(block2.tool_use_id);
|
|
2244
3403
|
}
|
|
2245
3404
|
}
|
|
@@ -2251,9 +3410,9 @@ async function renderClaudeStream(sub, cwd) {
|
|
|
2251
3410
|
}
|
|
2252
3411
|
}
|
|
2253
3412
|
}
|
|
2254
|
-
for (const p of pending.values()) emit(`${p.label} ${
|
|
3413
|
+
for (const p of pending.values()) emit(`${p.label} ${chalk8.dim("\u2026")}`);
|
|
2255
3414
|
clearInterval(timer);
|
|
2256
|
-
if (result) spinner.succeed(`${brand.accent("agent finished")} ${
|
|
3415
|
+
if (result) spinner.succeed(`${brand.accent("agent finished")} ${chalk8.dim(result)}`);
|
|
2257
3416
|
else spinner.stop();
|
|
2258
3417
|
} catch (err) {
|
|
2259
3418
|
clearInterval(timer);
|
|
@@ -2266,22 +3425,22 @@ function toolLabel(name, input, cwd) {
|
|
|
2266
3425
|
const rel = (key) => {
|
|
2267
3426
|
const p = input?.[key];
|
|
2268
3427
|
if (typeof p !== "string") return "";
|
|
2269
|
-
return p.startsWith(cwd) ?
|
|
3428
|
+
return p.startsWith(cwd) ? path18.relative(cwd, p) : path18.basename(p);
|
|
2270
3429
|
};
|
|
2271
3430
|
if (name.includes("figma")) {
|
|
2272
3431
|
const short = name.split("__").pop() ?? name;
|
|
2273
|
-
return `${icon.figma} ${
|
|
3432
|
+
return `${icon.figma} ${chalk8.bold(short)} ${chalk8.dim("Figma MCP")}`;
|
|
2274
3433
|
}
|
|
2275
3434
|
switch (name) {
|
|
2276
3435
|
case "Write":
|
|
2277
3436
|
case "Edit":
|
|
2278
3437
|
case "NotebookEdit":
|
|
2279
|
-
return `${brand.accent("\u270E")} ${
|
|
3438
|
+
return `${brand.accent("\u270E")} ${chalk8.cyan(rel("file_path"))}`;
|
|
2280
3439
|
case "Read":
|
|
2281
|
-
return
|
|
3440
|
+
return chalk8.dim(`\xB7 read ${rel("file_path")}`);
|
|
2282
3441
|
case "Bash": {
|
|
2283
3442
|
const cmd = typeof input?.command === "string" ? input.command.split("\n")[0].slice(0, 70) : "";
|
|
2284
|
-
return
|
|
3443
|
+
return chalk8.dim(`$ ${cmd}`);
|
|
2285
3444
|
}
|
|
2286
3445
|
default:
|
|
2287
3446
|
return `${brand.blue("\u2192")} ${name}`;
|
|
@@ -2297,13 +3456,13 @@ var init_invoke = __esm({
|
|
|
2297
3456
|
});
|
|
2298
3457
|
|
|
2299
3458
|
// src/lib/config.ts
|
|
2300
|
-
import { existsSync as
|
|
2301
|
-
import { readFile as
|
|
3459
|
+
import { existsSync as existsSync16 } from "fs";
|
|
3460
|
+
import { readFile as readFile12 } from "fs/promises";
|
|
2302
3461
|
import { homedir as homedir2 } from "os";
|
|
2303
|
-
import
|
|
3462
|
+
import path19 from "path";
|
|
2304
3463
|
async function loadJsonFile(filePath) {
|
|
2305
3464
|
try {
|
|
2306
|
-
const content = await
|
|
3465
|
+
const content = await readFile12(filePath, "utf-8");
|
|
2307
3466
|
return JSON.parse(content);
|
|
2308
3467
|
} catch {
|
|
2309
3468
|
return void 0;
|
|
@@ -2311,7 +3470,7 @@ async function loadJsonFile(filePath) {
|
|
|
2311
3470
|
}
|
|
2312
3471
|
async function loadConfig(projectRoot) {
|
|
2313
3472
|
const globalRaw = await loadJsonFile(GLOBAL_CONFIG_PATH);
|
|
2314
|
-
const projectRaw = projectRoot ? await loadJsonFile(
|
|
3473
|
+
const projectRaw = projectRoot ? await loadJsonFile(path19.join(projectRoot, PROJECT_CONFIG_NAME)) : void 0;
|
|
2315
3474
|
const merged = {
|
|
2316
3475
|
...typeof globalRaw === "object" && globalRaw !== null ? globalRaw : {},
|
|
2317
3476
|
...typeof projectRaw === "object" && projectRaw !== null ? projectRaw : {}
|
|
@@ -2330,7 +3489,7 @@ function globalConfigPath() {
|
|
|
2330
3489
|
return GLOBAL_CONFIG_PATH;
|
|
2331
3490
|
}
|
|
2332
3491
|
function globalConfigExists() {
|
|
2333
|
-
return
|
|
3492
|
+
return existsSync16(GLOBAL_CONFIG_PATH);
|
|
2334
3493
|
}
|
|
2335
3494
|
var GLOBAL_CONFIG_DIR, GLOBAL_CONFIG_PATH, PROJECT_CONFIG_NAME;
|
|
2336
3495
|
var init_config = __esm({
|
|
@@ -2339,8 +3498,8 @@ var init_config = __esm({
|
|
|
2339
3498
|
init_esm_shims();
|
|
2340
3499
|
init_logger();
|
|
2341
3500
|
init_schemas();
|
|
2342
|
-
GLOBAL_CONFIG_DIR =
|
|
2343
|
-
GLOBAL_CONFIG_PATH =
|
|
3501
|
+
GLOBAL_CONFIG_DIR = path19.join(homedir2(), ".eds");
|
|
3502
|
+
GLOBAL_CONFIG_PATH = path19.join(GLOBAL_CONFIG_DIR, "config.json");
|
|
2344
3503
|
PROJECT_CONFIG_NAME = ".edsrc.json";
|
|
2345
3504
|
}
|
|
2346
3505
|
});
|
|
@@ -2390,9 +3549,9 @@ var init_node_id = __esm({
|
|
|
2390
3549
|
});
|
|
2391
3550
|
|
|
2392
3551
|
// src/lib/figma/prompt-builder.ts
|
|
2393
|
-
import { existsSync as
|
|
2394
|
-
import { readFile as
|
|
2395
|
-
import
|
|
3552
|
+
import { existsSync as existsSync17 } from "fs";
|
|
3553
|
+
import { readFile as readFile13 } from "fs/promises";
|
|
3554
|
+
import path20 from "path";
|
|
2396
3555
|
function contentSourceGuidance(source, cfHint, blockName) {
|
|
2397
3556
|
if (!source || source === "document" || source === "ue") return "";
|
|
2398
3557
|
const q = cfHint ? ` (model / persisted query: \`${cfHint}\`)` : "";
|
|
@@ -2521,6 +3680,9 @@ Rules:
|
|
|
2521
3680
|
- **Self-check before finishing:** every field \`name\` in the model must be read by \`decorate()\` (or applied via \`classes\`). List each field and where the JS/CSS consumes it. Unconsumed fields = broken authoring.
|
|
2522
3681
|
|
|
2523
3682
|
If the project uses a **single aggregated model file** (\`component-definition.json\` + \`component-models.json\` + \`component-filters.json\`) instead of per-block \`_${ctx.blockName}.json\`, detect that convention and add the block's definition/model/filter to those files instead (and register the block in the \`section\` filter). Prefer the per-block \`_${ctx.blockName}.json\` when neither exists. Full field reference and a repeatable-cards round-trip example: \`docs/universal-editor-fields.md\`.
|
|
3683
|
+
|
|
3684
|
+
**Reusable field groups (partials):** when the design shows one of these common shapes, reuse the standard group instead of inventing fields \u2014 same groups \`eds model add\` uses, so the model stays consistent across the project:
|
|
3685
|
+
${PARTIALS.map((p) => `- \`${p.id}\` \u2014 ${p.label}`).join("\n")}
|
|
2524
3686
|
` : "";
|
|
2525
3687
|
return `# Pixel-Perfect EDS Block from ${providerName}
|
|
2526
3688
|
|
|
@@ -2604,10 +3766,10 @@ ${tokensHint}
|
|
|
2604
3766
|
`;
|
|
2605
3767
|
}
|
|
2606
3768
|
async function getTokensHint(projectRoot) {
|
|
2607
|
-
const stylesPath =
|
|
2608
|
-
if (!
|
|
3769
|
+
const stylesPath = path20.join(projectRoot, "styles", "styles.css");
|
|
3770
|
+
if (!existsSync17(stylesPath)) return "";
|
|
2609
3771
|
try {
|
|
2610
|
-
const css = await
|
|
3772
|
+
const css = await readFile13(stylesPath, "utf-8");
|
|
2611
3773
|
const rootMatch = css.match(/:root\s*\{([^}]+)\}/);
|
|
2612
3774
|
if (!rootMatch) return "";
|
|
2613
3775
|
const props = rootMatch[1].split("\n").filter((line) => line.trim().startsWith("--")).map((line) => line.trim()).join("\n ");
|
|
@@ -2632,12 +3794,14 @@ var init_prompt_builder = __esm({
|
|
|
2632
3794
|
"src/lib/figma/prompt-builder.ts"() {
|
|
2633
3795
|
"use strict";
|
|
2634
3796
|
init_esm_shims();
|
|
3797
|
+
init_partials();
|
|
2635
3798
|
PROMPT_VERSION = "0.5.0";
|
|
2636
3799
|
PROVIDER_NAMES = {
|
|
2637
3800
|
figma: "Figma",
|
|
2638
3801
|
stitch: "Google Stitch",
|
|
2639
3802
|
canva: "Canva",
|
|
2640
|
-
sketch: "Sketch"
|
|
3803
|
+
sketch: "Sketch",
|
|
3804
|
+
framer: "Framer"
|
|
2641
3805
|
};
|
|
2642
3806
|
}
|
|
2643
3807
|
});
|
|
@@ -2747,11 +3911,11 @@ var init_harness = __esm({
|
|
|
2747
3911
|
});
|
|
2748
3912
|
|
|
2749
3913
|
// src/lib/preview/server.ts
|
|
2750
|
-
import { createReadStream, existsSync as
|
|
3914
|
+
import { createReadStream, existsSync as existsSync18, statSync } from "fs";
|
|
2751
3915
|
import http from "http";
|
|
2752
|
-
import
|
|
3916
|
+
import path21 from "path";
|
|
2753
3917
|
function startServer(root, routes, port, host = "127.0.0.1") {
|
|
2754
|
-
const resolvedRoot =
|
|
3918
|
+
const resolvedRoot = path21.resolve(root);
|
|
2755
3919
|
const clients = /* @__PURE__ */ new Set();
|
|
2756
3920
|
const server = http.createServer((req, res) => {
|
|
2757
3921
|
const urlPath = decodeURIComponent((req.url ?? "/").split("?")[0]);
|
|
@@ -2777,19 +3941,19 @@ function startServer(root, routes, port, host = "127.0.0.1") {
|
|
|
2777
3941
|
res.end(route.body);
|
|
2778
3942
|
return;
|
|
2779
3943
|
}
|
|
2780
|
-
const filePath =
|
|
3944
|
+
const filePath = path21.join(resolvedRoot, urlPath);
|
|
2781
3945
|
if (!filePath.startsWith(resolvedRoot)) {
|
|
2782
3946
|
res.writeHead(403);
|
|
2783
3947
|
res.end("Forbidden");
|
|
2784
3948
|
return;
|
|
2785
3949
|
}
|
|
2786
|
-
if (!
|
|
3950
|
+
if (!existsSync18(filePath) || !statSync(filePath).isFile()) {
|
|
2787
3951
|
res.writeHead(404, { "content-type": "text/plain" });
|
|
2788
3952
|
res.end(`Not found: ${urlPath}`);
|
|
2789
3953
|
return;
|
|
2790
3954
|
}
|
|
2791
3955
|
res.writeHead(200, {
|
|
2792
|
-
"content-type": MIME[
|
|
3956
|
+
"content-type": MIME[path21.extname(filePath).toLowerCase()] ?? "application/octet-stream"
|
|
2793
3957
|
});
|
|
2794
3958
|
createReadStream(filePath).pipe(res);
|
|
2795
3959
|
});
|
|
@@ -2853,10 +4017,10 @@ var preview_exports = {};
|
|
|
2853
4017
|
__export(preview_exports, {
|
|
2854
4018
|
previewBlock: () => previewBlock
|
|
2855
4019
|
});
|
|
2856
|
-
import { existsSync as
|
|
2857
|
-
import { readFile as
|
|
2858
|
-
import
|
|
2859
|
-
import
|
|
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";
|
|
2860
4024
|
function labelFor(width) {
|
|
2861
4025
|
if (width <= 480) return `${width} mobile`;
|
|
2862
4026
|
if (width <= 1024) return `${width} tablet`;
|
|
@@ -2908,30 +4072,30 @@ async function previewBlock(name, options = {}) {
|
|
|
2908
4072
|
process.exitCode = 1;
|
|
2909
4073
|
return;
|
|
2910
4074
|
}
|
|
2911
|
-
const blockDir =
|
|
2912
|
-
if (!
|
|
4075
|
+
const blockDir = path22.join(projectRoot, "blocks", name);
|
|
4076
|
+
if (!existsSync19(blockDir)) {
|
|
2913
4077
|
logger.error(`Block "${name}" not found at blocks/${name}/.`);
|
|
2914
4078
|
process.exitCode = 1;
|
|
2915
4079
|
return;
|
|
2916
4080
|
}
|
|
2917
|
-
if (!
|
|
4081
|
+
if (!existsSync19(path22.join(blockDir, `${name}.css`)) && !existsSync19(path22.join(blockDir, `${name}.js`))) {
|
|
2918
4082
|
logger.error(`Block "${name}" has no ${name}.css / ${name}.js to preview.`);
|
|
2919
4083
|
process.exitCode = 1;
|
|
2920
4084
|
return;
|
|
2921
4085
|
}
|
|
2922
4086
|
let meta = null;
|
|
2923
|
-
const metaPath =
|
|
2924
|
-
if (
|
|
4087
|
+
const metaPath = path22.join(blockDir, ".eds-meta.json");
|
|
4088
|
+
if (existsSync19(metaPath)) {
|
|
2925
4089
|
try {
|
|
2926
|
-
meta = JSON.parse(await
|
|
4090
|
+
meta = JSON.parse(await readFile14(metaPath, "utf-8"));
|
|
2927
4091
|
} catch {
|
|
2928
4092
|
}
|
|
2929
4093
|
}
|
|
2930
|
-
const samplePath =
|
|
4094
|
+
const samplePath = path22.join(blockDir, `_${name}.preview.html`);
|
|
2931
4095
|
let sampleContent;
|
|
2932
4096
|
let placeholder = false;
|
|
2933
|
-
if (
|
|
2934
|
-
sampleContent = await
|
|
4097
|
+
if (existsSync19(samplePath)) {
|
|
4098
|
+
sampleContent = await readFile14(samplePath, "utf-8");
|
|
2935
4099
|
} else {
|
|
2936
4100
|
sampleContent = defaultSample(name);
|
|
2937
4101
|
placeholder = true;
|
|
@@ -2981,9 +4145,9 @@ async function previewBlock(name, options = {}) {
|
|
|
2981
4145
|
reloadTimer = setTimeout(() => server.reload(), 150);
|
|
2982
4146
|
});
|
|
2983
4147
|
logger.info("");
|
|
2984
|
-
logger.info(` ${
|
|
4148
|
+
logger.info(` ${chalk9.bold("\u279C")} ${chalk9.cyan(server.url)}`);
|
|
2985
4149
|
logger.info(statusLine("pass", "live reload", "edits to the block refresh the browser"));
|
|
2986
|
-
logger.info(
|
|
4150
|
+
logger.info(chalk9.dim(" Press Ctrl+C to stop.\n"));
|
|
2987
4151
|
if (options.open !== false) await openInBrowser(server.url);
|
|
2988
4152
|
const stop = async () => {
|
|
2989
4153
|
watcher.close();
|
|
@@ -3012,10 +4176,10 @@ var from_design_exports = {};
|
|
|
3012
4176
|
__export(from_design_exports, {
|
|
3013
4177
|
blockFromDesign: () => blockFromDesign
|
|
3014
4178
|
});
|
|
3015
|
-
import { existsSync as
|
|
3016
|
-
import { readFile as
|
|
3017
|
-
import
|
|
3018
|
-
import
|
|
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";
|
|
3019
4183
|
import ora from "ora";
|
|
3020
4184
|
async function previewRunning(port = 8777) {
|
|
3021
4185
|
try {
|
|
@@ -3038,7 +4202,8 @@ async function runWizard(figmaUrl) {
|
|
|
3038
4202
|
{ name: "Figma", value: "figma" },
|
|
3039
4203
|
{ name: "Google Stitch", value: "stitch" },
|
|
3040
4204
|
{ name: "Canva", value: "canva" },
|
|
3041
|
-
{ name: "Sketch", value: "sketch" }
|
|
4205
|
+
{ name: "Sketch", value: "sketch" },
|
|
4206
|
+
{ name: "Framer", value: "framer" }
|
|
3042
4207
|
],
|
|
3043
4208
|
default: "figma"
|
|
3044
4209
|
});
|
|
@@ -3164,7 +4329,7 @@ async function blockFromDesign(figmaUrlArg, optionsArg) {
|
|
|
3164
4329
|
try {
|
|
3165
4330
|
figma2 = parseFigmaUrl(designUrl);
|
|
3166
4331
|
spinner.succeed(
|
|
3167
|
-
`Parsed Figma URL ${
|
|
4332
|
+
`Parsed Figma URL ${chalk10.dim(figma2.nodeId ? `node ${figma2.nodeId}` : `file ${figma2.fileKey}`)}`
|
|
3168
4333
|
);
|
|
3169
4334
|
} catch (err) {
|
|
3170
4335
|
spinner.fail(err.message);
|
|
@@ -3172,7 +4337,7 @@ async function blockFromDesign(figmaUrlArg, optionsArg) {
|
|
|
3172
4337
|
return;
|
|
3173
4338
|
}
|
|
3174
4339
|
} else {
|
|
3175
|
-
logger.info(` ${icon.pass} ${providerName} design ${
|
|
4340
|
+
logger.info(` ${icon.pass} ${providerName} design ${chalk10.dim(designRef)}`);
|
|
3176
4341
|
}
|
|
3177
4342
|
const blockName = options.name || (figma2 ? inferBlockName(figma2.fileName) : "");
|
|
3178
4343
|
if (!kebabCaseRegex.test(blockName)) {
|
|
@@ -3195,20 +4360,20 @@ async function blockFromDesign(figmaUrlArg, optionsArg) {
|
|
|
3195
4360
|
screenshot: options.screenshot !== false
|
|
3196
4361
|
});
|
|
3197
4362
|
const promptFile = await savePromptToFile(prompt, projectRoot);
|
|
3198
|
-
const hasTokens =
|
|
4363
|
+
const hasTokens = existsSync20(path23.join(projectRoot, "styles", "styles.css"));
|
|
3199
4364
|
promptSpinner.succeed(
|
|
3200
|
-
hasTokens ? `Loaded design tokens ${
|
|
4365
|
+
hasTokens ? `Loaded design tokens ${chalk10.dim("styles/styles.css")}` : `Built prompt ${chalk10.dim("no project tokens found")}`
|
|
3201
4366
|
);
|
|
3202
|
-
logger.info(
|
|
4367
|
+
logger.info(chalk10.dim(` prompt \u2192 ${promptFile}`));
|
|
3203
4368
|
if (options.dryRun) {
|
|
3204
4369
|
logger.info("");
|
|
3205
|
-
logger.info(
|
|
3206
|
-
logger.info(`Prompt file: ${
|
|
3207
|
-
logger.info(`Block name: ${
|
|
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)}`);
|
|
3208
4373
|
logger.info(`Prompt version: ${PROMPT_VERSION}`);
|
|
3209
4374
|
logger.info("");
|
|
3210
4375
|
logger.info("Preview:");
|
|
3211
|
-
logger.info(
|
|
4376
|
+
logger.info(chalk10.dim("\u2500".repeat(60)));
|
|
3212
4377
|
console.log(prompt);
|
|
3213
4378
|
return;
|
|
3214
4379
|
}
|
|
@@ -3216,22 +4381,22 @@ async function blockFromDesign(figmaUrlArg, optionsArg) {
|
|
|
3216
4381
|
const preferredAgent = options.agent || config.agent;
|
|
3217
4382
|
if (options.agent === "none") {
|
|
3218
4383
|
logger.info("Agent skipped (--agent none). Use the prompt file manually:");
|
|
3219
|
-
logger.info(` ${
|
|
4384
|
+
logger.info(` ${chalk10.cyan(promptFile)}`);
|
|
3220
4385
|
return;
|
|
3221
4386
|
}
|
|
3222
4387
|
const agentSpinner = ora("Detecting AI agent...").start();
|
|
3223
4388
|
const agent = await detectAgent(preferredAgent);
|
|
3224
4389
|
if (!agent) {
|
|
3225
4390
|
agentSpinner.fail("No AI agent found (claude, cursor, or codex).");
|
|
3226
|
-
logger.info(`Install Claude Code: ${
|
|
3227
|
-
logger.info(`Or use the prompt file manually: ${
|
|
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)}`);
|
|
3228
4393
|
process.exitCode = 1;
|
|
3229
4394
|
return;
|
|
3230
4395
|
}
|
|
3231
|
-
agentSpinner.succeed(`Agent ready ${
|
|
4396
|
+
agentSpinner.succeed(`Agent ready ${chalk10.cyan(agent.type)} ${chalk10.dim(agent.path)}`);
|
|
3232
4397
|
if (provider === "figma" && !agent.hasFigmaMcp) {
|
|
3233
4398
|
logger.warn(`Figma MCP may not be configured for ${agent.type}.`);
|
|
3234
|
-
logger.info(`Run ${
|
|
4399
|
+
logger.info(`Run ${chalk10.cyan("eds figma setup")} to configure it.`);
|
|
3235
4400
|
}
|
|
3236
4401
|
if (!options.yes) {
|
|
3237
4402
|
const { confirm } = await import("@inquirer/prompts");
|
|
@@ -3240,24 +4405,24 @@ async function blockFromDesign(figmaUrlArg, optionsArg) {
|
|
|
3240
4405
|
default: true
|
|
3241
4406
|
});
|
|
3242
4407
|
if (!proceed) {
|
|
3243
|
-
logger.info(`Prompt saved to: ${
|
|
4408
|
+
logger.info(`Prompt saved to: ${chalk10.cyan(promptFile)}`);
|
|
3244
4409
|
return;
|
|
3245
4410
|
}
|
|
3246
4411
|
}
|
|
3247
4412
|
logger.info(
|
|
3248
|
-
` ${
|
|
4413
|
+
` ${chalk10.dim("\u2192")} Handing off to ${chalk10.cyan(agent.type)} ${chalk10.dim(`via ${providerName} MCP`)}`
|
|
3249
4414
|
);
|
|
3250
4415
|
if (provider === "figma") {
|
|
3251
|
-
logger.info(
|
|
4416
|
+
logger.info(chalk10.dim(" get_design_context \xB7 get_variable_defs \xB7 get_screenshot"));
|
|
3252
4417
|
}
|
|
3253
4418
|
const success = await invokeAgent(agent, promptFile, projectRoot);
|
|
3254
4419
|
if (!success) {
|
|
3255
4420
|
process.exitCode = 1;
|
|
3256
4421
|
return;
|
|
3257
4422
|
}
|
|
3258
|
-
const blockDir =
|
|
4423
|
+
const blockDir = path23.join(projectRoot, "blocks", blockName);
|
|
3259
4424
|
const ok = await verifyGeneratedBlock(blockDir, blockName, options.ueModel !== false);
|
|
3260
|
-
if (
|
|
4425
|
+
if (existsSync20(blockDir)) {
|
|
3261
4426
|
const meta = {
|
|
3262
4427
|
provider,
|
|
3263
4428
|
designRef,
|
|
@@ -3267,7 +4432,7 @@ async function blockFromDesign(figmaUrlArg, optionsArg) {
|
|
|
3267
4432
|
promptVersion: PROMPT_VERSION,
|
|
3268
4433
|
agentUsed: agent.type
|
|
3269
4434
|
};
|
|
3270
|
-
await
|
|
4435
|
+
await writeFile13(path23.join(blockDir, ".eds-meta.json"), `${JSON.stringify(meta, null, 2)}
|
|
3271
4436
|
`);
|
|
3272
4437
|
}
|
|
3273
4438
|
if (!ok) {
|
|
@@ -3278,10 +4443,21 @@ async function blockFromDesign(figmaUrlArg, optionsArg) {
|
|
|
3278
4443
|
return;
|
|
3279
4444
|
}
|
|
3280
4445
|
logger.success(`Block "${blockName}" generation complete.`);
|
|
4446
|
+
if (!options.yes) {
|
|
4447
|
+
const { confirm } = await import("@inquirer/prompts");
|
|
4448
|
+
const addTracking = await confirm({
|
|
4449
|
+
message: "Instrument this block for analytics (dataLayer)?",
|
|
4450
|
+
default: false
|
|
4451
|
+
});
|
|
4452
|
+
if (addTracking) {
|
|
4453
|
+
const { trackBlock: trackBlock2 } = await Promise.resolve().then(() => (init_track2(), track_exports));
|
|
4454
|
+
await trackBlock2(blockName, {});
|
|
4455
|
+
}
|
|
4456
|
+
}
|
|
3281
4457
|
if (options.serve !== false) {
|
|
3282
4458
|
if (await previewRunning()) {
|
|
3283
4459
|
logger.info(
|
|
3284
|
-
|
|
4460
|
+
chalk10.dim(
|
|
3285
4461
|
" A preview server is already running at http://127.0.0.1:8777 \u2014 your change will refresh there."
|
|
3286
4462
|
)
|
|
3287
4463
|
);
|
|
@@ -3291,15 +4467,15 @@ async function blockFromDesign(figmaUrlArg, optionsArg) {
|
|
|
3291
4467
|
await previewBlock2(blockName, {});
|
|
3292
4468
|
}
|
|
3293
4469
|
} else {
|
|
3294
|
-
logger.info(` ${
|
|
4470
|
+
logger.info(` ${chalk10.dim("\u2192")} Next: ${chalk10.cyan(`eds block preview ${blockName}`)}`);
|
|
3295
4471
|
}
|
|
3296
4472
|
}
|
|
3297
4473
|
async function verifyGeneratedBlock(blockDir, blockName, withUeModel) {
|
|
3298
4474
|
const checks = [];
|
|
3299
|
-
const jsPath =
|
|
3300
|
-
const cssPath =
|
|
3301
|
-
const hasJs =
|
|
3302
|
-
const hasCss =
|
|
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);
|
|
3303
4479
|
checks.push({
|
|
3304
4480
|
status: hasJs ? "pass" : "fail",
|
|
3305
4481
|
name: `${blockName}.js`,
|
|
@@ -3311,7 +4487,7 @@ async function verifyGeneratedBlock(blockDir, blockName, withUeModel) {
|
|
|
3311
4487
|
message: hasCss ? "created" : "missing \u2014 agent did not write the CSS file"
|
|
3312
4488
|
});
|
|
3313
4489
|
if (hasJs) {
|
|
3314
|
-
const js = await
|
|
4490
|
+
const js = await readFile15(jsPath, "utf-8");
|
|
3315
4491
|
checks.push({
|
|
3316
4492
|
status: /export\s+default/.test(js) ? "pass" : "fail",
|
|
3317
4493
|
name: "decorate export",
|
|
@@ -3326,7 +4502,7 @@ async function verifyGeneratedBlock(blockDir, blockName, withUeModel) {
|
|
|
3326
4502
|
}
|
|
3327
4503
|
}
|
|
3328
4504
|
if (hasCss) {
|
|
3329
|
-
const css = await
|
|
4505
|
+
const css = await readFile15(cssPath, "utf-8");
|
|
3330
4506
|
checks.push({
|
|
3331
4507
|
status: css.includes(`.${blockName}`) ? "pass" : "warn",
|
|
3332
4508
|
name: "scoped CSS",
|
|
@@ -3334,12 +4510,12 @@ async function verifyGeneratedBlock(blockDir, blockName, withUeModel) {
|
|
|
3334
4510
|
});
|
|
3335
4511
|
}
|
|
3336
4512
|
if (withUeModel) {
|
|
3337
|
-
const hasPerBlock =
|
|
3338
|
-
const modelsFile =
|
|
4513
|
+
const hasPerBlock = existsSync20(path23.join(blockDir, `_${blockName}.json`));
|
|
4514
|
+
const modelsFile = path23.join(blockDir, "..", "..", "component-models.json");
|
|
3339
4515
|
let hasAggregated = false;
|
|
3340
|
-
if (!hasPerBlock &&
|
|
4516
|
+
if (!hasPerBlock && existsSync20(modelsFile)) {
|
|
3341
4517
|
try {
|
|
3342
|
-
hasAggregated = (await
|
|
4518
|
+
hasAggregated = (await readFile15(modelsFile, "utf-8")).includes(`"${blockName}"`);
|
|
3343
4519
|
} catch {
|
|
3344
4520
|
}
|
|
3345
4521
|
}
|
|
@@ -3351,8 +4527,8 @@ async function verifyGeneratedBlock(blockDir, blockName, withUeModel) {
|
|
|
3351
4527
|
});
|
|
3352
4528
|
if (hasPerBlock && hasJs) {
|
|
3353
4529
|
try {
|
|
3354
|
-
const modelRaw = await
|
|
3355
|
-
const js = await
|
|
4530
|
+
const modelRaw = await readFile15(path23.join(blockDir, `_${blockName}.json`), "utf-8");
|
|
4531
|
+
const js = await readFile15(jsPath, "utf-8");
|
|
3356
4532
|
const isRepeatable = /"resourceType"\s*:\s*"[^"]*\/item"/.test(modelRaw) || /"filter"\s*:/.test(modelRaw) || /"multi"\s*:\s*true/.test(modelRaw);
|
|
3357
4533
|
const iterates = /\.children/.test(js) || /\bfor\s*\(/.test(js) || /\.forEach\s*\(/.test(js);
|
|
3358
4534
|
if (isRepeatable) {
|
|
@@ -3395,10 +4571,10 @@ var list_exports = {};
|
|
|
3395
4571
|
__export(list_exports, {
|
|
3396
4572
|
listBlocks: () => listBlocks
|
|
3397
4573
|
});
|
|
3398
|
-
import { existsSync as
|
|
3399
|
-
import { readFile as
|
|
3400
|
-
import
|
|
3401
|
-
import
|
|
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";
|
|
3402
4578
|
async function listBlocks(options = {}) {
|
|
3403
4579
|
const projectRoot = findProjectRoot();
|
|
3404
4580
|
if (!projectRoot) {
|
|
@@ -3406,27 +4582,27 @@ async function listBlocks(options = {}) {
|
|
|
3406
4582
|
process.exitCode = 1;
|
|
3407
4583
|
return;
|
|
3408
4584
|
}
|
|
3409
|
-
const blocksDir =
|
|
3410
|
-
if (!
|
|
4585
|
+
const blocksDir = path24.join(projectRoot, "blocks");
|
|
4586
|
+
if (!existsSync21(blocksDir)) {
|
|
3411
4587
|
logger.info("No blocks/ directory found.");
|
|
3412
4588
|
return;
|
|
3413
4589
|
}
|
|
3414
|
-
const entries = await
|
|
4590
|
+
const entries = await readdir3(blocksDir, { withFileTypes: true });
|
|
3415
4591
|
const blocks = [];
|
|
3416
4592
|
for (const entry of entries) {
|
|
3417
4593
|
if (!entry.isDirectory()) continue;
|
|
3418
4594
|
const name = entry.name;
|
|
3419
|
-
const blockDir =
|
|
4595
|
+
const blockDir = path24.join(blocksDir, name);
|
|
3420
4596
|
const info = {
|
|
3421
4597
|
name,
|
|
3422
|
-
hasJs:
|
|
3423
|
-
hasCss:
|
|
3424
|
-
hasUeModel:
|
|
4598
|
+
hasJs: existsSync21(path24.join(blockDir, `${name}.js`)),
|
|
4599
|
+
hasCss: existsSync21(path24.join(blockDir, `${name}.css`)),
|
|
4600
|
+
hasUeModel: existsSync21(path24.join(blockDir, `_${name}.json`))
|
|
3425
4601
|
};
|
|
3426
|
-
const metaFile =
|
|
3427
|
-
if (
|
|
4602
|
+
const metaFile = path24.join(blockDir, ".eds-meta.json");
|
|
4603
|
+
if (existsSync21(metaFile)) {
|
|
3428
4604
|
try {
|
|
3429
|
-
const meta = JSON.parse(await
|
|
4605
|
+
const meta = JSON.parse(await readFile16(metaFile, "utf-8"));
|
|
3430
4606
|
info.figmaSource = {
|
|
3431
4607
|
fileKey: meta.figmaFileKey,
|
|
3432
4608
|
nodeId: meta.figmaNodeId,
|
|
@@ -3449,17 +4625,17 @@ async function listBlocks(options = {}) {
|
|
|
3449
4625
|
const nameWidth = columnWidth(blocks.map((b) => b.name));
|
|
3450
4626
|
for (const b of blocks) {
|
|
3451
4627
|
const badges = [
|
|
3452
|
-
b.hasJs ? brand.ok("js") :
|
|
3453
|
-
b.hasCss ? brand.ok("css") :
|
|
4628
|
+
b.hasJs ? brand.ok("js") : chalk11.dim.strikethrough("js"),
|
|
4629
|
+
b.hasCss ? brand.ok("css") : chalk11.dim.strikethrough("css")
|
|
3454
4630
|
];
|
|
3455
4631
|
if (b.hasUeModel) badges.push(brand.blue("ue-model"));
|
|
3456
4632
|
if (b.figmaSource) badges.push(`${icon.figma} ${brand.dotPurple("figma")}`);
|
|
3457
|
-
const name = padEndVisible(
|
|
3458
|
-
logger.info(` ${name} ${
|
|
4633
|
+
const name = padEndVisible(chalk11.bold(b.name), nameWidth);
|
|
4634
|
+
logger.info(` ${name} ${chalk11.dim("[")}${badges.join(" ")}${chalk11.dim("]")}`);
|
|
3459
4635
|
if (b.figmaSource) {
|
|
3460
4636
|
const synced = new Date(b.figmaSource.lastSyncedAt).toLocaleDateString();
|
|
3461
4637
|
logger.info(
|
|
3462
|
-
` ${" ".repeat(nameWidth)} ${icon.arrow} ${
|
|
4638
|
+
` ${" ".repeat(nameWidth)} ${icon.arrow} ${chalk11.dim(
|
|
3463
4639
|
`figma:${b.figmaSource.fileKey} \xB7 synced ${synced}`
|
|
3464
4640
|
)}`
|
|
3465
4641
|
);
|
|
@@ -3499,22 +4675,85 @@ var init_integrations = __esm({
|
|
|
3499
4675
|
id: "gtm",
|
|
3500
4676
|
name: "Google Tag Manager",
|
|
3501
4677
|
fields: [{ name: "id", message: "GTM container ID", placeholder: "GTM-XXXXXXX" }],
|
|
3502
|
-
|
|
3503
|
-
(
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
4678
|
+
// delayed.js imports the loader + page-meta, pushes page metadata, then GTM.
|
|
4679
|
+
code: () => `// Google Tag Manager - loaded from ./analytics.js (delayed phase, after LCP).
|
|
4680
|
+
import { loadGTM } from './analytics.js';
|
|
4681
|
+
import pageMetaPush from './page-meta.js';
|
|
4682
|
+
|
|
4683
|
+
const pageMetaReady = pageMetaPush();
|
|
4684
|
+
pageMetaReady.then(loadGTM);`,
|
|
4685
|
+
module: (v) => [
|
|
4686
|
+
{
|
|
4687
|
+
file: "scripts/analytics.js",
|
|
4688
|
+
content: `// scripts/analytics.js - Google Tag Manager (generated by \`eds integrate gtm\`).
|
|
4689
|
+
// The container ID comes from the \`gtm-id\` metadata, falling back to the one
|
|
4690
|
+
// configured here. Import { trackEvent } from './analytics.js' to push events.
|
|
4691
|
+
|
|
4692
|
+
const FALLBACK_ID = ${JSON.stringify(v.id || "")};
|
|
4693
|
+
|
|
4694
|
+
function getMetadata(name) {
|
|
4695
|
+
const meta = document.querySelector(\`meta[name="\${name}"]\`);
|
|
4696
|
+
return meta ? meta.content : '';
|
|
4697
|
+
}
|
|
4698
|
+
|
|
4699
|
+
function isDev() {
|
|
4700
|
+
const h = window.location.hostname;
|
|
4701
|
+
return h === 'localhost' || h === '127.0.0.1' || h.endsWith('.aem.page') || h.endsWith('.hlx.page');
|
|
4702
|
+
}
|
|
4703
|
+
|
|
4704
|
+
// dataLayer must exist before anything pushes to it (trackEvent may run before loadGTM).
|
|
4705
|
+
window.dataLayer = window.dataLayer || [];
|
|
4706
|
+
|
|
4707
|
+
export function trackEvent(event) {
|
|
4708
|
+
if (isDev()) {
|
|
4709
|
+
// eslint-disable-next-line no-console
|
|
4710
|
+
console.log('[gtm]', JSON.stringify(event, null, 2));
|
|
4711
|
+
}
|
|
4712
|
+
window.dataLayer.push(event);
|
|
4713
|
+
}
|
|
4714
|
+
|
|
4715
|
+
let loaded = false;
|
|
4716
|
+
|
|
4717
|
+
export function loadGTM() {
|
|
4718
|
+
if (loaded) return;
|
|
4719
|
+
const raw = getMetadata('gtm-id') || FALLBACK_ID;
|
|
4720
|
+
if (!raw) return;
|
|
4721
|
+
loaded = true;
|
|
4722
|
+
|
|
4723
|
+
const id = raw.startsWith('GTM-') ? raw : \`GTM-\${raw}\`;
|
|
4724
|
+
window.dataLayer.push({ 'gtm.start': Date.now(), event: 'gtm.js' });
|
|
4725
|
+
|
|
4726
|
+
const script = document.createElement('script');
|
|
4727
|
+
script.async = true;
|
|
4728
|
+
script.src = \`https://www.googletagmanager.com/gtm.js?id=\${id}\`;
|
|
4729
|
+
document.head.append(script);
|
|
4730
|
+
}
|
|
4731
|
+
`
|
|
4732
|
+
},
|
|
4733
|
+
{
|
|
4734
|
+
file: "scripts/page-meta.js",
|
|
4735
|
+
content: `// scripts/page-meta.js - pushes page-level metadata to the dataLayer before
|
|
4736
|
+
// GTM initialises. Extend \`pageMeta\` with the fields your container expects.
|
|
4737
|
+
import { trackEvent } from './analytics.js';
|
|
4738
|
+
|
|
4739
|
+
export default async function pageMetaPush() {
|
|
4740
|
+
const pageMeta = {
|
|
4741
|
+
event: 'page_meta',
|
|
4742
|
+
};
|
|
4743
|
+
|
|
4744
|
+
trackEvent(pageMeta);
|
|
4745
|
+
|
|
4746
|
+
return pageMeta;
|
|
4747
|
+
}
|
|
4748
|
+
`
|
|
4749
|
+
}
|
|
4750
|
+
]
|
|
3512
4751
|
},
|
|
3513
4752
|
{
|
|
3514
4753
|
id: "ga4",
|
|
3515
4754
|
name: "Google Analytics 4",
|
|
3516
4755
|
fields: [{ name: "id", message: "Measurement ID", placeholder: "G-XXXXXXXXXX" }],
|
|
3517
|
-
code: (v) => `// Google Analytics 4 (${v.id})
|
|
4756
|
+
code: (v) => `// Google Analytics 4 (${v.id}) - delayed phase.
|
|
3518
4757
|
const gaScript = document.createElement('script');
|
|
3519
4758
|
gaScript.async = true;
|
|
3520
4759
|
gaScript.src = 'https://www.googletagmanager.com/gtag/js?id=${v.id}';
|
|
@@ -3531,7 +4770,7 @@ gtag('config', '${v.id}');`
|
|
|
3531
4770
|
{ name: "label", message: "Button label", placeholder: "Chat with us" },
|
|
3532
4771
|
{ name: "src", message: "Chat widget script URL" }
|
|
3533
4772
|
],
|
|
3534
|
-
code: (v) => `// Chat widget
|
|
4773
|
+
code: (v) => `// Chat widget - facade pattern: load the heavy script only on first interaction.
|
|
3535
4774
|
const chatBtn = document.createElement('button');
|
|
3536
4775
|
chatBtn.type = 'button';
|
|
3537
4776
|
chatBtn.className = 'chat-facade';
|
|
@@ -3549,7 +4788,7 @@ document.body.appendChild(chatBtn);`
|
|
|
3549
4788
|
id: "cookiebot",
|
|
3550
4789
|
name: "Cookie consent (Cookiebot)",
|
|
3551
4790
|
fields: [{ name: "id", message: "Cookiebot domain group ID (CBID)" }],
|
|
3552
|
-
code: (v) => `// Cookiebot consent
|
|
4791
|
+
code: (v) => `// Cookiebot consent - load early in delayed; configure GTM consent mode to respect it.
|
|
3553
4792
|
const cb = document.createElement('script');
|
|
3554
4793
|
cb.id = 'Cookiebot';
|
|
3555
4794
|
cb.src = 'https://consent.cookiebot.com/uc.js';
|
|
@@ -3561,7 +4800,7 @@ document.head.appendChild(cb);`
|
|
|
3561
4800
|
id: "custom",
|
|
3562
4801
|
name: "Custom third-party script",
|
|
3563
4802
|
fields: [{ name: "url", message: "Script URL" }],
|
|
3564
|
-
code: (v) => `// Custom third-party script
|
|
4803
|
+
code: (v) => `// Custom third-party script - delayed phase.
|
|
3565
4804
|
const s = document.createElement('script');
|
|
3566
4805
|
s.async = true;
|
|
3567
4806
|
s.src = ${JSON.stringify(v.url || "")};
|
|
@@ -3576,10 +4815,10 @@ var integrate_exports = {};
|
|
|
3576
4815
|
__export(integrate_exports, {
|
|
3577
4816
|
integrate: () => integrate
|
|
3578
4817
|
});
|
|
3579
|
-
import { existsSync as
|
|
3580
|
-
import { mkdir as
|
|
3581
|
-
import
|
|
3582
|
-
import
|
|
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";
|
|
3583
4822
|
async function integrate(type) {
|
|
3584
4823
|
const projectRoot = findProjectRoot();
|
|
3585
4824
|
if (!projectRoot) {
|
|
@@ -3587,7 +4826,7 @@ async function integrate(type) {
|
|
|
3587
4826
|
process.exitCode = 1;
|
|
3588
4827
|
return;
|
|
3589
4828
|
}
|
|
3590
|
-
logger.logoOnce(logo("Integrate
|
|
4829
|
+
logger.logoOnce(logo("Integrate - third-party service"));
|
|
3591
4830
|
const { select, input } = await import("@inquirer/prompts");
|
|
3592
4831
|
const chosenId = type || await select({
|
|
3593
4832
|
message: "Which service do you want to add?",
|
|
@@ -3602,30 +4841,43 @@ async function integrate(type) {
|
|
|
3602
4841
|
return;
|
|
3603
4842
|
}
|
|
3604
4843
|
const values = {};
|
|
3605
|
-
for (const
|
|
3606
|
-
const answer = await input({ message: ` ${
|
|
3607
|
-
values[
|
|
3608
|
-
}
|
|
3609
|
-
const
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
4844
|
+
for (const f2 of integration.fields) {
|
|
4845
|
+
const answer = await input({ message: ` ${f2.message}`, default: f2.placeholder });
|
|
4846
|
+
values[f2.name] = answer.trim();
|
|
4847
|
+
}
|
|
4848
|
+
const modulePaths = [];
|
|
4849
|
+
if (integration.module) {
|
|
4850
|
+
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);
|
|
4854
|
+
modulePaths.push(mod.file);
|
|
4855
|
+
}
|
|
4856
|
+
}
|
|
4857
|
+
const block2 = integration.module ? `
|
|
4858
|
+
// ${integration.name} (eds integrate)
|
|
4859
|
+
${integration.code(values)}
|
|
4860
|
+
` : 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*$/, "")}
|
|
3614
4865
|
${block2}`);
|
|
3615
4866
|
logger.info(heading("Added"));
|
|
3616
|
-
logger.info(accentLine(integration.name,
|
|
4867
|
+
for (const p of modulePaths) logger.info(accentLine(integration.name, `wrote ${p}`));
|
|
4868
|
+
logger.info(accentLine(integration.name, "wired into scripts/delayed.js"));
|
|
3617
4869
|
logger.info("");
|
|
3618
4870
|
logger.info(
|
|
3619
|
-
|
|
4871
|
+
chalk12.dim(" Loads after LCP (delayed phase) - zero impact on your Core Web Vitals.")
|
|
3620
4872
|
);
|
|
3621
4873
|
if (integration.id === "gtm" || integration.id === "ga4") {
|
|
3622
4874
|
logger.info(
|
|
3623
|
-
|
|
4875
|
+
chalk12.dim(" Tip: gate analytics behind user consent (add the Cookiebot integration).")
|
|
3624
4876
|
);
|
|
3625
4877
|
}
|
|
3626
4878
|
if (integration.id === "chat") {
|
|
3627
4879
|
logger.info(
|
|
3628
|
-
|
|
4880
|
+
chalk12.dim(" The widget loads only when the visitor clicks the button (facade pattern).")
|
|
3629
4881
|
);
|
|
3630
4882
|
}
|
|
3631
4883
|
logger.info("");
|
|
@@ -3640,7 +4892,7 @@ var init_integrate = __esm({
|
|
|
3640
4892
|
init_logger();
|
|
3641
4893
|
init_project();
|
|
3642
4894
|
init_ui();
|
|
3643
|
-
DELAYED_HEADER = `// delayed.js
|
|
4895
|
+
DELAYED_HEADER = `// delayed.js - third-party / non-critical scripts.
|
|
3644
4896
|
// Loaded in the EDS delayed phase (after LCP), so nothing here blocks the page.
|
|
3645
4897
|
`;
|
|
3646
4898
|
}
|
|
@@ -3651,9 +4903,9 @@ var setup_exports = {};
|
|
|
3651
4903
|
__export(setup_exports, {
|
|
3652
4904
|
figmaSetup: () => figmaSetup
|
|
3653
4905
|
});
|
|
3654
|
-
import { existsSync as
|
|
3655
|
-
import { mkdir as
|
|
3656
|
-
import
|
|
4906
|
+
import { existsSync as existsSync23 } from "fs";
|
|
4907
|
+
import { mkdir as mkdir9, writeFile as writeFile15 } from "fs/promises";
|
|
4908
|
+
import chalk13 from "chalk";
|
|
3657
4909
|
async function figmaSetup() {
|
|
3658
4910
|
const { select, confirm } = await import("@inquirer/prompts");
|
|
3659
4911
|
logger.logoOnce(logo());
|
|
@@ -3663,15 +4915,15 @@ async function figmaSetup() {
|
|
|
3663
4915
|
let selectedAgent;
|
|
3664
4916
|
if (agents.length === 0) {
|
|
3665
4917
|
logger.warn("No AI agents found (claude, cursor, codex).");
|
|
3666
|
-
logger.info(`Install Claude Code: ${
|
|
4918
|
+
logger.info(`Install Claude Code: ${chalk13.cyan("npm install -g @anthropic-ai/claude-code")}`);
|
|
3667
4919
|
selectedAgent = "none";
|
|
3668
4920
|
} else {
|
|
3669
|
-
logger.success(`Found: ${agents.map((a) =>
|
|
4921
|
+
logger.success(`Found: ${agents.map((a) => chalk13.cyan(a.type)).join(", ")}`);
|
|
3670
4922
|
selectedAgent = await select({
|
|
3671
4923
|
message: "Which agent do you want to use with eds-cli?",
|
|
3672
4924
|
choices: [
|
|
3673
4925
|
...agents.map((a) => ({
|
|
3674
|
-
name: `${a.type}${a.hasFigmaMcp ?
|
|
4926
|
+
name: `${a.type}${a.hasFigmaMcp ? chalk13.green(" (Figma MCP detected)") : ""}`,
|
|
3675
4927
|
value: a.type
|
|
3676
4928
|
})),
|
|
3677
4929
|
{ name: "None (manual mode)", value: "none" }
|
|
@@ -3687,17 +4939,17 @@ async function figmaSetup() {
|
|
|
3687
4939
|
switch (selectedAgent) {
|
|
3688
4940
|
case "claude":
|
|
3689
4941
|
logger.info(
|
|
3690
|
-
` ${
|
|
4942
|
+
` ${chalk13.cyan("claude mcp add --transport sse figma-sse https://mcp.figma.com/sse")}`
|
|
3691
4943
|
);
|
|
3692
4944
|
logger.info(" or for Figma Desktop app:");
|
|
3693
4945
|
logger.info(
|
|
3694
|
-
` ${
|
|
4946
|
+
` ${chalk13.cyan("claude mcp add --transport http figma-desktop http://127.0.0.1:3845/mcp")}`
|
|
3695
4947
|
);
|
|
3696
4948
|
break;
|
|
3697
4949
|
case "cursor":
|
|
3698
|
-
logger.info(` Add to ${
|
|
4950
|
+
logger.info(` Add to ${chalk13.cyan("~/.cursor/mcp.json")}:`);
|
|
3699
4951
|
logger.info(
|
|
3700
|
-
|
|
4952
|
+
chalk13.dim(
|
|
3701
4953
|
JSON.stringify(
|
|
3702
4954
|
{
|
|
3703
4955
|
mcpServers: {
|
|
@@ -3722,7 +4974,7 @@ async function figmaSetup() {
|
|
|
3722
4974
|
}
|
|
3723
4975
|
}
|
|
3724
4976
|
logger.info(
|
|
3725
|
-
|
|
4977
|
+
chalk13.dim(
|
|
3726
4978
|
"\nNote: Figma MCP requires a Figma seat (Dev or Full on paid plans for desktop server)."
|
|
3727
4979
|
)
|
|
3728
4980
|
);
|
|
@@ -3732,22 +4984,22 @@ async function figmaSetup() {
|
|
|
3732
4984
|
});
|
|
3733
4985
|
if (shouldSave) {
|
|
3734
4986
|
const configDir = globalConfigDir();
|
|
3735
|
-
if (!
|
|
3736
|
-
await
|
|
4987
|
+
if (!existsSync23(configDir)) {
|
|
4988
|
+
await mkdir9(configDir, { recursive: true });
|
|
3737
4989
|
}
|
|
3738
4990
|
const config = {};
|
|
3739
4991
|
if (selectedAgent !== "none") {
|
|
3740
4992
|
config.agent = selectedAgent;
|
|
3741
4993
|
}
|
|
3742
|
-
await
|
|
4994
|
+
await writeFile15(globalConfigPath(), `${JSON.stringify(config, null, 2)}
|
|
3743
4995
|
`, {
|
|
3744
4996
|
mode: 384
|
|
3745
4997
|
});
|
|
3746
|
-
logger.success(`Config saved to ${
|
|
4998
|
+
logger.success(`Config saved to ${chalk13.cyan(globalConfigPath())}`);
|
|
3747
4999
|
}
|
|
3748
|
-
logger.info(
|
|
5000
|
+
logger.info(chalk13.bold("\nSetup complete!"));
|
|
3749
5001
|
if (selectedAgent !== "none") {
|
|
3750
|
-
logger.info(`Try: ${
|
|
5002
|
+
logger.info(`Try: ${chalk13.cyan('eds block from-design "<figma-url>" --dry-run')}`);
|
|
3751
5003
|
}
|
|
3752
5004
|
}
|
|
3753
5005
|
var init_setup = __esm({
|
|
@@ -3766,10 +5018,10 @@ var doctor_exports = {};
|
|
|
3766
5018
|
__export(doctor_exports, {
|
|
3767
5019
|
doctor: () => doctor
|
|
3768
5020
|
});
|
|
3769
|
-
import { existsSync as
|
|
3770
|
-
import { readFile as
|
|
3771
|
-
import
|
|
3772
|
-
import { parse as
|
|
5021
|
+
import { existsSync as existsSync24 } from "fs";
|
|
5022
|
+
import { readFile as readFile18, readdir as readdir4 } from "fs/promises";
|
|
5023
|
+
import path26 from "path";
|
|
5024
|
+
import { parse as parseYaml2 } from "yaml";
|
|
3773
5025
|
async function doctor(options = {}) {
|
|
3774
5026
|
const projectRoot = findProjectRoot();
|
|
3775
5027
|
if (!projectRoot) {
|
|
@@ -3790,8 +5042,8 @@ async function doctor(options = {}) {
|
|
|
3790
5042
|
});
|
|
3791
5043
|
if (project.hasFstab) {
|
|
3792
5044
|
try {
|
|
3793
|
-
const content = await
|
|
3794
|
-
|
|
5045
|
+
const content = await readFile18(path26.join(projectRoot, "fstab.yaml"), "utf-8");
|
|
5046
|
+
parseYaml2(content);
|
|
3795
5047
|
results.push({ name: "fstab.yaml valid YAML", status: "pass", message: "Valid" });
|
|
3796
5048
|
} catch {
|
|
3797
5049
|
results.push({
|
|
@@ -3812,28 +5064,28 @@ async function doctor(options = {}) {
|
|
|
3812
5064
|
message: project.hasBlocks ? "Found" : "Missing - no blocks yet"
|
|
3813
5065
|
});
|
|
3814
5066
|
if (project.hasBlocks) {
|
|
3815
|
-
const blocksDir =
|
|
3816
|
-
const entries = await
|
|
5067
|
+
const blocksDir = path26.join(projectRoot, "blocks");
|
|
5068
|
+
const entries = await readdir4(blocksDir, { withFileTypes: true });
|
|
3817
5069
|
let aggregatedModels = "";
|
|
3818
|
-
const aggregatedPath =
|
|
3819
|
-
if (
|
|
5070
|
+
const aggregatedPath = path26.join(projectRoot, "component-models.json");
|
|
5071
|
+
if (existsSync24(aggregatedPath)) {
|
|
3820
5072
|
try {
|
|
3821
|
-
aggregatedModels = await
|
|
5073
|
+
aggregatedModels = await readFile18(aggregatedPath, "utf-8");
|
|
3822
5074
|
} catch {
|
|
3823
5075
|
}
|
|
3824
5076
|
}
|
|
3825
5077
|
for (const entry of entries) {
|
|
3826
5078
|
if (!entry.isDirectory()) continue;
|
|
3827
5079
|
const blockName = entry.name;
|
|
3828
|
-
const jsFile =
|
|
3829
|
-
const hasJs =
|
|
5080
|
+
const jsFile = path26.join(blocksDir, blockName, `${blockName}.js`);
|
|
5081
|
+
const hasJs = existsSync24(jsFile);
|
|
3830
5082
|
results.push({
|
|
3831
5083
|
name: `blocks/${blockName} has JS`,
|
|
3832
5084
|
status: hasJs ? "pass" : "warn",
|
|
3833
5085
|
message: hasJs ? "Found" : `Missing ${blockName}.js`
|
|
3834
5086
|
});
|
|
3835
5087
|
if (hasJs) {
|
|
3836
|
-
const jsContent = await
|
|
5088
|
+
const jsContent = await readFile18(jsFile, "utf-8");
|
|
3837
5089
|
if (jsContent.includes("console.log")) {
|
|
3838
5090
|
results.push({
|
|
3839
5091
|
name: `blocks/${blockName} no console.log`,
|
|
@@ -3842,17 +5094,17 @@ async function doctor(options = {}) {
|
|
|
3842
5094
|
});
|
|
3843
5095
|
}
|
|
3844
5096
|
}
|
|
3845
|
-
const hasPerBlockModel =
|
|
5097
|
+
const hasPerBlockModel = existsSync24(path26.join(blocksDir, blockName, `_${blockName}.json`));
|
|
3846
5098
|
const hasAggregatedModel = aggregatedModels.includes(`"${blockName}"`);
|
|
3847
5099
|
results.push({
|
|
3848
5100
|
name: `blocks/${blockName} UE model`,
|
|
3849
5101
|
status: hasPerBlockModel || hasAggregatedModel ? "pass" : "warn",
|
|
3850
5102
|
message: hasPerBlockModel || hasAggregatedModel ? hasPerBlockModel ? `_${blockName}.json` : "in component-models.json" : "no Universal Editor model - authors can't edit this block in UE"
|
|
3851
5103
|
});
|
|
3852
|
-
const metaFile =
|
|
3853
|
-
if (
|
|
5104
|
+
const metaFile = path26.join(blocksDir, blockName, ".eds-meta.json");
|
|
5105
|
+
if (existsSync24(metaFile)) {
|
|
3854
5106
|
try {
|
|
3855
|
-
const meta = JSON.parse(await
|
|
5107
|
+
const meta = JSON.parse(await readFile18(metaFile, "utf-8"));
|
|
3856
5108
|
const syncDate = new Date(meta.lastSyncedAt);
|
|
3857
5109
|
const daysSinceSync = Math.floor(
|
|
3858
5110
|
(Date.now() - syncDate.getTime()) / (1e3 * 60 * 60 * 24)
|
|
@@ -3872,11 +5124,11 @@ async function doctor(options = {}) {
|
|
|
3872
5124
|
}
|
|
3873
5125
|
}
|
|
3874
5126
|
}
|
|
3875
|
-
const queryFile =
|
|
3876
|
-
if (
|
|
5127
|
+
const queryFile = path26.join(projectRoot, "helix-query.yaml");
|
|
5128
|
+
if (existsSync24(queryFile)) {
|
|
3877
5129
|
try {
|
|
3878
|
-
const content = await
|
|
3879
|
-
|
|
5130
|
+
const content = await readFile18(queryFile, "utf-8");
|
|
5131
|
+
parseYaml2(content);
|
|
3880
5132
|
results.push({ name: "helix-query.yaml valid", status: "pass", message: "Valid YAML" });
|
|
3881
5133
|
} catch {
|
|
3882
5134
|
results.push({
|
|
@@ -3932,10 +5184,10 @@ var audit_loading_exports = {};
|
|
|
3932
5184
|
__export(audit_loading_exports, {
|
|
3933
5185
|
auditLoading: () => auditLoading
|
|
3934
5186
|
});
|
|
3935
|
-
import { existsSync as
|
|
3936
|
-
import { readFile as
|
|
3937
|
-
import
|
|
3938
|
-
import
|
|
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";
|
|
3939
5191
|
async function auditLoading(options = {}) {
|
|
3940
5192
|
const projectRoot = findProjectRoot();
|
|
3941
5193
|
if (!projectRoot) {
|
|
@@ -3968,8 +5220,8 @@ async function auditLoading(options = {}) {
|
|
|
3968
5220
|
printResults(results);
|
|
3969
5221
|
}
|
|
3970
5222
|
async function checkHeadHtml(root, results) {
|
|
3971
|
-
const filePath =
|
|
3972
|
-
if (!
|
|
5223
|
+
const filePath = path27.join(root, "head.html");
|
|
5224
|
+
if (!existsSync25(filePath)) {
|
|
3973
5225
|
results.push({
|
|
3974
5226
|
rule: "head.html exists",
|
|
3975
5227
|
status: "warn",
|
|
@@ -3977,7 +5229,7 @@ async function checkHeadHtml(root, results) {
|
|
|
3977
5229
|
});
|
|
3978
5230
|
return;
|
|
3979
5231
|
}
|
|
3980
|
-
const content = await
|
|
5232
|
+
const content = await readFile19(filePath, "utf-8");
|
|
3981
5233
|
const lines = content.split("\n");
|
|
3982
5234
|
for (let i = 0; i < lines.length; i++) {
|
|
3983
5235
|
const line = lines[i];
|
|
@@ -4049,8 +5301,8 @@ async function checkHeadHtml(root, results) {
|
|
|
4049
5301
|
}
|
|
4050
5302
|
}
|
|
4051
5303
|
async function checkScriptsJs(root, results) {
|
|
4052
|
-
const filePath =
|
|
4053
|
-
if (!
|
|
5304
|
+
const filePath = path27.join(root, "scripts", "scripts.js");
|
|
5305
|
+
if (!existsSync25(filePath)) {
|
|
4054
5306
|
results.push({
|
|
4055
5307
|
rule: "scripts/scripts.js exists",
|
|
4056
5308
|
status: "warn",
|
|
@@ -4058,7 +5310,7 @@ async function checkScriptsJs(root, results) {
|
|
|
4058
5310
|
});
|
|
4059
5311
|
return;
|
|
4060
5312
|
}
|
|
4061
|
-
const content = await
|
|
5313
|
+
const content = await readFile19(filePath, "utf-8");
|
|
4062
5314
|
const lines = content.split("\n");
|
|
4063
5315
|
const hasLoadEager = /function\s+loadEager|loadEager\s*=/i.test(content);
|
|
4064
5316
|
const hasLoadLazy = /function\s+loadLazy|loadLazy\s*=/i.test(content);
|
|
@@ -4136,8 +5388,8 @@ async function checkScriptsJs(root, results) {
|
|
|
4136
5388
|
}
|
|
4137
5389
|
}
|
|
4138
5390
|
async function checkStylesCss(root, results) {
|
|
4139
|
-
const filePath =
|
|
4140
|
-
if (!
|
|
5391
|
+
const filePath = path27.join(root, "styles", "styles.css");
|
|
5392
|
+
if (!existsSync25(filePath)) return;
|
|
4141
5393
|
const stats = await stat3(filePath);
|
|
4142
5394
|
const sizeKb = stats.size / 1024;
|
|
4143
5395
|
results.push({
|
|
@@ -4146,7 +5398,7 @@ async function checkStylesCss(root, results) {
|
|
|
4146
5398
|
message: `${sizeKb.toFixed(1)} KB${sizeKb > 40 ? " \u2014 large CSS competes with 100KB LCP budget" : ""}`,
|
|
4147
5399
|
file: "styles/styles.css"
|
|
4148
5400
|
});
|
|
4149
|
-
const content = await
|
|
5401
|
+
const content = await readFile19(filePath, "utf-8");
|
|
4150
5402
|
if (/@import\s/.test(content)) {
|
|
4151
5403
|
results.push({
|
|
4152
5404
|
rule: "No @import in styles.css",
|
|
@@ -4157,8 +5409,8 @@ async function checkStylesCss(root, results) {
|
|
|
4157
5409
|
}
|
|
4158
5410
|
}
|
|
4159
5411
|
async function checkDelayedJs(root, results) {
|
|
4160
|
-
const filePath =
|
|
4161
|
-
if (!
|
|
5412
|
+
const filePath = path27.join(root, "scripts", "delayed.js");
|
|
5413
|
+
if (!existsSync25(filePath)) {
|
|
4162
5414
|
results.push({
|
|
4163
5415
|
rule: "scripts/delayed.js exists",
|
|
4164
5416
|
status: "info",
|
|
@@ -4166,7 +5418,7 @@ async function checkDelayedJs(root, results) {
|
|
|
4166
5418
|
});
|
|
4167
5419
|
return;
|
|
4168
5420
|
}
|
|
4169
|
-
const content = await
|
|
5421
|
+
const content = await readFile19(filePath, "utf-8");
|
|
4170
5422
|
const stripped = content.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "").trim();
|
|
4171
5423
|
if (!stripped) {
|
|
4172
5424
|
results.push({
|
|
@@ -4178,15 +5430,15 @@ async function checkDelayedJs(root, results) {
|
|
|
4178
5430
|
}
|
|
4179
5431
|
}
|
|
4180
5432
|
async function checkBlocks(root, results) {
|
|
4181
|
-
const blocksDir =
|
|
4182
|
-
if (!
|
|
4183
|
-
const entries = await
|
|
5433
|
+
const blocksDir = path27.join(root, "blocks");
|
|
5434
|
+
if (!existsSync25(blocksDir)) return;
|
|
5435
|
+
const entries = await readdir5(blocksDir, { withFileTypes: true });
|
|
4184
5436
|
for (const entry of entries) {
|
|
4185
5437
|
if (!entry.isDirectory()) continue;
|
|
4186
5438
|
const blockName = entry.name;
|
|
4187
|
-
const jsFile =
|
|
4188
|
-
if (!
|
|
4189
|
-
const content = await
|
|
5439
|
+
const jsFile = path27.join(blocksDir, blockName, `${blockName}.js`);
|
|
5440
|
+
if (!existsSync25(jsFile)) continue;
|
|
5441
|
+
const content = await readFile19(jsFile, "utf-8");
|
|
4190
5442
|
const lines = content.split("\n");
|
|
4191
5443
|
const heavyLibs = [
|
|
4192
5444
|
"jquery",
|
|
@@ -4236,9 +5488,9 @@ async function checkBlocks(root, results) {
|
|
|
4236
5488
|
for (const entry of entries) {
|
|
4237
5489
|
if (!entry.isDirectory()) continue;
|
|
4238
5490
|
const blockName = entry.name;
|
|
4239
|
-
const cssFile =
|
|
4240
|
-
if (!
|
|
4241
|
-
const content = await
|
|
5491
|
+
const cssFile = path27.join(blocksDir, blockName, `${blockName}.css`);
|
|
5492
|
+
if (!existsSync25(cssFile)) continue;
|
|
5493
|
+
const content = await readFile19(cssFile, "utf-8");
|
|
4242
5494
|
const selectorPattern = /^([a-z][a-z0-9-]*)\s*\{/gm;
|
|
4243
5495
|
for (const selectorMatch of content.matchAll(selectorPattern)) {
|
|
4244
5496
|
const selector = selectorMatch[1];
|
|
@@ -4254,8 +5506,8 @@ async function checkBlocks(root, results) {
|
|
|
4254
5506
|
}
|
|
4255
5507
|
}
|
|
4256
5508
|
async function checkAemJs(root, results) {
|
|
4257
|
-
const filePath =
|
|
4258
|
-
if (!
|
|
5509
|
+
const filePath = path27.join(root, "scripts", "aem.js");
|
|
5510
|
+
if (!existsSync25(filePath)) {
|
|
4259
5511
|
results.push({
|
|
4260
5512
|
rule: "scripts/aem.js exists",
|
|
4261
5513
|
status: "warn",
|
|
@@ -4293,12 +5545,12 @@ function printResults(results) {
|
|
|
4293
5545
|
grouped.set(key, bucket);
|
|
4294
5546
|
}
|
|
4295
5547
|
for (const [file, fileResults] of grouped) {
|
|
4296
|
-
logger.info(` ${icon.bullet} ${
|
|
5548
|
+
logger.info(` ${icon.bullet} ${chalk14.underline(file)}`);
|
|
4297
5549
|
for (const r of fileResults) {
|
|
4298
|
-
const loc = r.line ?
|
|
5550
|
+
const loc = r.line ? chalk14.dim(`:${r.line}`) : "";
|
|
4299
5551
|
logger.info(` ${statusIcon(r.status)} ${r.rule}${loc}`);
|
|
4300
5552
|
if (r.status !== "pass") {
|
|
4301
|
-
logger.info(
|
|
5553
|
+
logger.info(chalk14.dim(` ${r.message}`));
|
|
4302
5554
|
}
|
|
4303
5555
|
}
|
|
4304
5556
|
logger.info("");
|
|
@@ -4327,14 +5579,14 @@ var audit_security_exports = {};
|
|
|
4327
5579
|
__export(audit_security_exports, {
|
|
4328
5580
|
auditSecurity: () => auditSecurity
|
|
4329
5581
|
});
|
|
4330
|
-
import { existsSync as
|
|
4331
|
-
import { readFile as
|
|
4332
|
-
import
|
|
4333
|
-
import
|
|
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";
|
|
4334
5586
|
async function* walk2(dir) {
|
|
4335
|
-
const entries = await
|
|
5587
|
+
const entries = await readdir6(dir, { withFileTypes: true });
|
|
4336
5588
|
for (const entry of entries) {
|
|
4337
|
-
const full =
|
|
5589
|
+
const full = path28.join(dir, entry.name);
|
|
4338
5590
|
if (entry.isDirectory()) {
|
|
4339
5591
|
if (IGNORE_DIRS.has(entry.name)) continue;
|
|
4340
5592
|
yield* walk2(full);
|
|
@@ -4368,11 +5620,11 @@ function scanContent(content, relFile, ext) {
|
|
|
4368
5620
|
return findings;
|
|
4369
5621
|
}
|
|
4370
5622
|
async function auditDependencies(root) {
|
|
4371
|
-
if (!
|
|
5623
|
+
if (!existsSync26(path28.join(root, "package.json"))) {
|
|
4372
5624
|
return { status: "skipped", note: "no package.json" };
|
|
4373
5625
|
}
|
|
4374
|
-
const hasLock =
|
|
4375
|
-
if (!hasLock && !
|
|
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"))) {
|
|
4376
5628
|
return { status: "skipped", note: "no package-lock.json / node_modules \u2014 run npm install" };
|
|
4377
5629
|
}
|
|
4378
5630
|
try {
|
|
@@ -4404,15 +5656,15 @@ async function auditSecurity(options = {}) {
|
|
|
4404
5656
|
}
|
|
4405
5657
|
const findings = [];
|
|
4406
5658
|
for await (const file of walk2(projectRoot)) {
|
|
4407
|
-
const ext =
|
|
5659
|
+
const ext = path28.extname(file).slice(1).toLowerCase();
|
|
4408
5660
|
if (!SCAN_EXT.has(ext)) continue;
|
|
4409
|
-
const rel =
|
|
5661
|
+
const rel = path28.relative(projectRoot, file);
|
|
4410
5662
|
if (rel.endsWith("aem.js") || rel.endsWith("lib-franklin.js")) continue;
|
|
4411
|
-
const content = await
|
|
5663
|
+
const content = await readFile20(file, "utf-8");
|
|
4412
5664
|
findings.push(...scanContent(content, rel, ext));
|
|
4413
5665
|
}
|
|
4414
5666
|
const deps = await auditDependencies(projectRoot);
|
|
4415
|
-
const bySeverity = (s) => findings.filter((
|
|
5667
|
+
const bySeverity = (s) => findings.filter((f2) => f2.severity === s);
|
|
4416
5668
|
const codeCounts = {
|
|
4417
5669
|
critical: bySeverity("critical").length,
|
|
4418
5670
|
high: bySeverity("high").length,
|
|
@@ -4454,11 +5706,11 @@ async function auditSecurity(options = {}) {
|
|
|
4454
5706
|
const sorted = [...findings].sort(
|
|
4455
5707
|
(a, b) => SEVERITY_ORDER.indexOf(a.severity) - SEVERITY_ORDER.indexOf(b.severity)
|
|
4456
5708
|
);
|
|
4457
|
-
for (const
|
|
4458
|
-
const meta = SEVERITY_META[
|
|
4459
|
-
const loc =
|
|
4460
|
-
logger.info(` ${statusIcon(meta.status)} ${
|
|
4461
|
-
logger.info(
|
|
5709
|
+
for (const f2 of sorted) {
|
|
5710
|
+
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}`));
|
|
4462
5714
|
}
|
|
4463
5715
|
}
|
|
4464
5716
|
const summaryLine = summary({
|
|
@@ -4665,7 +5917,7 @@ __export(preview_exports2, {
|
|
|
4665
5917
|
preview: () => preview,
|
|
4666
5918
|
resolveAdminConfig: () => resolveAdminConfig
|
|
4667
5919
|
});
|
|
4668
|
-
import
|
|
5920
|
+
import chalk16 from "chalk";
|
|
4669
5921
|
import ora2 from "ora";
|
|
4670
5922
|
async function preview(paths, options) {
|
|
4671
5923
|
const projectRoot = findProjectRoot();
|
|
@@ -4678,7 +5930,7 @@ async function preview(paths, options) {
|
|
|
4678
5930
|
let hasErrors = false;
|
|
4679
5931
|
for (const r of results) {
|
|
4680
5932
|
if (r.ok) {
|
|
4681
|
-
logger.success(`${r.path} \u2192 ${
|
|
5933
|
+
logger.success(`${r.path} \u2192 ${chalk16.cyan(r.previewUrl || "preview updated")}`);
|
|
4682
5934
|
} else {
|
|
4683
5935
|
logger.error(`${r.path} \u2014 ${r.message}`);
|
|
4684
5936
|
hasErrors = true;
|
|
@@ -4692,7 +5944,7 @@ function resolveAdminConfig(config, options) {
|
|
|
4692
5944
|
const ref = options.ref || config.admin?.ref || "main";
|
|
4693
5945
|
if (!org || !site) {
|
|
4694
5946
|
logger.error("Missing --org and --site. Set them via flags or in .edsrc.json:");
|
|
4695
|
-
logger.info(
|
|
5947
|
+
logger.info(chalk16.dim(' { "admin": { "org": "my-org", "site": "my-site" } }'));
|
|
4696
5948
|
process.exitCode = 1;
|
|
4697
5949
|
return void 0;
|
|
4698
5950
|
}
|
|
@@ -4714,7 +5966,7 @@ var publish_exports = {};
|
|
|
4714
5966
|
__export(publish_exports, {
|
|
4715
5967
|
publish: () => publish
|
|
4716
5968
|
});
|
|
4717
|
-
import
|
|
5969
|
+
import chalk17 from "chalk";
|
|
4718
5970
|
import ora3 from "ora";
|
|
4719
5971
|
async function publish(paths, options) {
|
|
4720
5972
|
const projectRoot = findProjectRoot();
|
|
@@ -4727,7 +5979,7 @@ async function publish(paths, options) {
|
|
|
4727
5979
|
let hasErrors = false;
|
|
4728
5980
|
for (const r of results) {
|
|
4729
5981
|
if (r.ok) {
|
|
4730
|
-
logger.success(`${r.path} \u2192 ${
|
|
5982
|
+
logger.success(`${r.path} \u2192 ${chalk17.cyan(r.liveUrl || "published")}`);
|
|
4731
5983
|
} else {
|
|
4732
5984
|
logger.error(`${r.path} \u2014 ${r.message}`);
|
|
4733
5985
|
hasErrors = true;
|
|
@@ -4826,13 +6078,13 @@ var menu_exports = {};
|
|
|
4826
6078
|
__export(menu_exports, {
|
|
4827
6079
|
mainMenu: () => mainMenu
|
|
4828
6080
|
});
|
|
4829
|
-
import { existsSync as
|
|
4830
|
-
import { readdir as
|
|
4831
|
-
import
|
|
6081
|
+
import { existsSync as existsSync27 } from "fs";
|
|
6082
|
+
import { readdir as readdir7 } from "fs/promises";
|
|
6083
|
+
import path29 from "path";
|
|
4832
6084
|
async function listBlockNames(projectRoot) {
|
|
4833
|
-
const dir =
|
|
4834
|
-
if (!
|
|
4835
|
-
const entries = await
|
|
6085
|
+
const dir = path29.join(projectRoot, "blocks");
|
|
6086
|
+
if (!existsSync27(dir)) return [];
|
|
6087
|
+
const entries = await readdir7(dir, { withFileTypes: true });
|
|
4836
6088
|
return entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
4837
6089
|
}
|
|
4838
6090
|
async function mainMenu() {
|
|
@@ -4842,11 +6094,18 @@ async function mainMenu() {
|
|
|
4842
6094
|
message: "Choose an action",
|
|
4843
6095
|
choices: [
|
|
4844
6096
|
{ name: "Set up this project (authoring model, CI, naming)", value: "init" },
|
|
4845
|
-
{ name: "Generate a
|
|
6097
|
+
{ name: "Generate a page template (initial content)", value: "template" },
|
|
6098
|
+
{
|
|
6099
|
+
name: "Generate a block from a design (Figma/Stitch/Canva/Sketch/Framer)",
|
|
6100
|
+
value: "from-design"
|
|
6101
|
+
},
|
|
4846
6102
|
{ name: "Migrate a classic AEM component to EDS", value: "migrate" },
|
|
4847
6103
|
{ name: "Scaffold Universal Editor components", value: "scaffold" },
|
|
4848
6104
|
{ name: "Create an empty block", value: "create" },
|
|
4849
6105
|
{ name: "Add a third-party integration (GTM, chat, consent\u2026)", value: "integrate" },
|
|
6106
|
+
{ name: "Add fields to a block model (partials)", value: "model" },
|
|
6107
|
+
{ name: "Instrument a block for analytics (dataLayer)", value: "track" },
|
|
6108
|
+
{ name: "Add JSON-LD structured data to a block", value: "schema" },
|
|
4850
6109
|
{ name: "Preview a block in the browser", value: "preview-block" },
|
|
4851
6110
|
{ name: "Preview / publish pages (Admin API)", value: "admin" },
|
|
4852
6111
|
{ name: "Audit the project", value: "audit" },
|
|
@@ -4859,6 +6118,15 @@ async function mainMenu() {
|
|
|
4859
6118
|
await initProject2({});
|
|
4860
6119
|
return;
|
|
4861
6120
|
}
|
|
6121
|
+
case "template": {
|
|
6122
|
+
const name = await input({
|
|
6123
|
+
message: "Template name",
|
|
6124
|
+
validate: (v) => !!v.trim() || "Required"
|
|
6125
|
+
});
|
|
6126
|
+
const { templateNew: templateNew2 } = await Promise.resolve().then(() => (init_template(), template_exports));
|
|
6127
|
+
await templateNew2(name.trim(), {});
|
|
6128
|
+
return;
|
|
6129
|
+
}
|
|
4862
6130
|
case "from-design": {
|
|
4863
6131
|
const { blockFromDesign: blockFromDesign2 } = await Promise.resolve().then(() => (init_from_design(), from_design_exports));
|
|
4864
6132
|
await blockFromDesign2(void 0, {});
|
|
@@ -4907,6 +6175,49 @@ async function mainMenu() {
|
|
|
4907
6175
|
await previewBlock2(name, {});
|
|
4908
6176
|
return;
|
|
4909
6177
|
}
|
|
6178
|
+
case "model": {
|
|
6179
|
+
const { modelAdd: modelAdd2 } = await Promise.resolve().then(() => (init_model(), model_exports));
|
|
6180
|
+
await modelAdd2(void 0, [], {});
|
|
6181
|
+
return;
|
|
6182
|
+
}
|
|
6183
|
+
case "track": {
|
|
6184
|
+
const projectRoot = findProjectRoot();
|
|
6185
|
+
if (!projectRoot) {
|
|
6186
|
+
logger.error("Not inside an EDS project (no EDS project markers found).");
|
|
6187
|
+
return;
|
|
6188
|
+
}
|
|
6189
|
+
const blocks = await listBlockNames(projectRoot);
|
|
6190
|
+
if (blocks.length === 0) {
|
|
6191
|
+
logger.warn("No blocks found in blocks/.");
|
|
6192
|
+
return;
|
|
6193
|
+
}
|
|
6194
|
+
const name = await select({
|
|
6195
|
+
message: "Which block to instrument?",
|
|
6196
|
+
choices: blocks.map((b) => ({ name: b, value: b }))
|
|
6197
|
+
});
|
|
6198
|
+
const { trackBlock: trackBlock2 } = await Promise.resolve().then(() => (init_track2(), track_exports));
|
|
6199
|
+
await trackBlock2(name, {});
|
|
6200
|
+
return;
|
|
6201
|
+
}
|
|
6202
|
+
case "schema": {
|
|
6203
|
+
const projectRoot = findProjectRoot();
|
|
6204
|
+
if (!projectRoot) {
|
|
6205
|
+
logger.error("Not inside an EDS project (no EDS project markers found).");
|
|
6206
|
+
return;
|
|
6207
|
+
}
|
|
6208
|
+
const blocks = await listBlockNames(projectRoot);
|
|
6209
|
+
if (blocks.length === 0) {
|
|
6210
|
+
logger.warn("No blocks found in blocks/.");
|
|
6211
|
+
return;
|
|
6212
|
+
}
|
|
6213
|
+
const name = await select({
|
|
6214
|
+
message: "Which block?",
|
|
6215
|
+
choices: blocks.map((b) => ({ name: b, value: b }))
|
|
6216
|
+
});
|
|
6217
|
+
const { schemaBlock: schemaBlock2 } = await Promise.resolve().then(() => (init_schema(), schema_exports));
|
|
6218
|
+
await schemaBlock2(name, {});
|
|
6219
|
+
return;
|
|
6220
|
+
}
|
|
4910
6221
|
case "admin": {
|
|
4911
6222
|
const { adminWizard: adminWizard2 } = await Promise.resolve().then(() => (init_admin(), admin_exports));
|
|
4912
6223
|
await adminWizard2();
|
|
@@ -4952,13 +6263,13 @@ var init_menu = __esm({
|
|
|
4952
6263
|
|
|
4953
6264
|
// src/bin/eds.ts
|
|
4954
6265
|
init_esm_shims();
|
|
4955
|
-
import
|
|
6266
|
+
import chalk18 from "chalk";
|
|
4956
6267
|
import { Command } from "commander";
|
|
4957
6268
|
|
|
4958
6269
|
// package.json
|
|
4959
6270
|
var package_default = {
|
|
4960
6271
|
name: "aem-eds-kit",
|
|
4961
|
-
version: "0.4.
|
|
6272
|
+
version: "0.4.16",
|
|
4962
6273
|
description: "CLI for AEM Edge Delivery Services with Figma MCP integration",
|
|
4963
6274
|
type: "module",
|
|
4964
6275
|
engines: {
|
|
@@ -5034,9 +6345,18 @@ program.name("eds").description("CLI for AEM Edge Delivery Services with Figma i
|
|
|
5034
6345
|
const opts = thisCommand.opts();
|
|
5035
6346
|
if (opts.verbose) logger.setLevel("debug");
|
|
5036
6347
|
if (opts.quiet) logger.setQuiet(true);
|
|
5037
|
-
if (opts.color === false)
|
|
6348
|
+
if (opts.color === false) chalk18.level = 0;
|
|
5038
6349
|
});
|
|
5039
6350
|
program.addHelpText("beforeAll", () => logo());
|
|
6351
|
+
var template = program.command("template").description("Generate page templates / initial content");
|
|
6352
|
+
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
|
+
"--blocks <list>",
|
|
6354
|
+
"Comma-separated block ids to seed",
|
|
6355
|
+
(v) => v.split(",").map((b) => b.trim())
|
|
6356
|
+
).option("--area <area>", "Site area / template label (stored in metadata)").option("--authoring <model>", "Force authoring model: da | gdrive | sharepoint | ue").option("--push", "Push the page to DA (da.live)").option("--org <org>", "DA org (else from .edsrc.json)").option("--site <site>", "DA site (else from .edsrc.json)").option("--yes", "Skip prompts").action(async (name, options) => {
|
|
6357
|
+
const { templateNew: templateNew2 } = await Promise.resolve().then(() => (init_template(), template_exports));
|
|
6358
|
+
await templateNew2(name, options);
|
|
6359
|
+
});
|
|
5040
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) => {
|
|
5041
6361
|
const { initProject: initProject2 } = await Promise.resolve().then(() => (init_init(), init_exports));
|
|
5042
6362
|
await initProject2(options);
|
|
@@ -5046,6 +6366,21 @@ migrate.command("component [dir]").description("Migrate a classic AEM component'
|
|
|
5046
6366
|
const { migrateComponent: migrateComponent2 } = await Promise.resolve().then(() => (init_migrate(), migrate_exports));
|
|
5047
6367
|
await migrateComponent2(dir, options);
|
|
5048
6368
|
});
|
|
6369
|
+
var model = program.command("model").description("Compose Universal Editor models");
|
|
6370
|
+
model.command("add <block> [partials...]").description("Add reusable field groups (partials) to a block's UE model").option("--yes", "Skip prompts").action(async (blockName, partials, options) => {
|
|
6371
|
+
const { modelAdd: modelAdd2 } = await Promise.resolve().then(() => (init_model(), model_exports));
|
|
6372
|
+
await modelAdd2(blockName, partials, options);
|
|
6373
|
+
});
|
|
6374
|
+
var schema = program.command("schema").description("Structured data (JSON-LD) for blocks");
|
|
6375
|
+
schema.command("block <name>").description("Inject a JSON-LD (schema.org) builder into a block's decorate()").option("--type <type>", "schema.org type (Article, FAQPage, Product, ...)").option("--yes", "Skip prompts").action(async (name, options) => {
|
|
6376
|
+
const { schemaBlock: schemaBlock2 } = await Promise.resolve().then(() => (init_schema(), schema_exports));
|
|
6377
|
+
await schemaBlock2(name, options);
|
|
6378
|
+
});
|
|
6379
|
+
var track = program.command("track").description("Instrument blocks for analytics");
|
|
6380
|
+
track.command("block <name>").description("Add dataLayer tracking (click + form submit) to a block's decorate()").option("--no-click", "Skip click tracking").option("--no-submit", "Skip form-submit tracking").action(async (name, options) => {
|
|
6381
|
+
const { trackBlock: trackBlock2 } = await Promise.resolve().then(() => (init_track2(), track_exports));
|
|
6382
|
+
await trackBlock2(name, options);
|
|
6383
|
+
});
|
|
5049
6384
|
var block = program.command("block").description("Manage EDS blocks");
|
|
5050
6385
|
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) => {
|
|
5051
6386
|
const { createBlock: createBlock2 } = await Promise.resolve().then(() => (init_create(), create_exports));
|
|
@@ -5081,6 +6416,10 @@ scaffold.command("ci").description("Scaffold a GitHub Actions workflow (doctor +
|
|
|
5081
6416
|
const { scaffoldCi: scaffoldCi2 } = await Promise.resolve().then(() => (init_scaffold(), scaffold_exports));
|
|
5082
6417
|
await scaffoldCi2();
|
|
5083
6418
|
});
|
|
6419
|
+
scaffold.command("helpers [names...]").description("Scaffold scripts/utils.js with chosen EDS helpers (interactive picker)").action(async (names) => {
|
|
6420
|
+
const { scaffoldHelpers: scaffoldHelpers2 } = await Promise.resolve().then(() => (init_scaffold(), scaffold_exports));
|
|
6421
|
+
await scaffoldHelpers2(names);
|
|
6422
|
+
});
|
|
5084
6423
|
program.command("integrate [type]").description("Add a third-party service (GTM, GA4, chat, consent) to scripts/delayed.js").action(async (type) => {
|
|
5085
6424
|
const { integrate: integrate2 } = await Promise.resolve().then(() => (init_integrate(), integrate_exports));
|
|
5086
6425
|
await integrate2(type);
|