aem-eds-kit 0.4.12 → 0.4.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +108 -2
- package/README.md +132 -0
- package/dist/bin/eds.js +2828 -784
- 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({
|
|
@@ -273,6 +273,178 @@ ${logoArt()}
|
|
|
273
273
|
}
|
|
274
274
|
});
|
|
275
275
|
|
|
276
|
+
// src/lib/sandbox/api.ts
|
|
277
|
+
function headers(token) {
|
|
278
|
+
return {
|
|
279
|
+
Authorization: `Bearer ${token}`,
|
|
280
|
+
Accept: "application/vnd.github+json",
|
|
281
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
282
|
+
"User-Agent": "aem-eds-kit",
|
|
283
|
+
"Content-Type": "application/json"
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
function generateEndpoint(template2) {
|
|
287
|
+
return `${API}/repos/${template2}/generate`;
|
|
288
|
+
}
|
|
289
|
+
async function getUser(token) {
|
|
290
|
+
const res = await fetch(`${API}/user`, { headers: headers(token) });
|
|
291
|
+
if (!res.ok) throw new Error(`GitHub auth failed: ${res.status}`);
|
|
292
|
+
return (await res.json()).login;
|
|
293
|
+
}
|
|
294
|
+
async function repoExists(token, owner, repo) {
|
|
295
|
+
const res = await fetch(`${API}/repos/${owner}/${repo}`, { headers: headers(token) });
|
|
296
|
+
if (res.status === 404) return false;
|
|
297
|
+
if (res.ok) return true;
|
|
298
|
+
throw new Error(`existence check failed: ${res.status} ${await res.text()}`);
|
|
299
|
+
}
|
|
300
|
+
async function createFromTemplate(token, template2, opts) {
|
|
301
|
+
const res = await fetch(generateEndpoint(template2), {
|
|
302
|
+
method: "POST",
|
|
303
|
+
headers: headers(token),
|
|
304
|
+
body: JSON.stringify({
|
|
305
|
+
owner: opts.owner,
|
|
306
|
+
name: opts.name,
|
|
307
|
+
private: !!opts.private,
|
|
308
|
+
include_all_branches: false
|
|
309
|
+
})
|
|
310
|
+
});
|
|
311
|
+
if (!res.ok) throw new Error(`create failed: ${res.status} ${await res.text()}`);
|
|
312
|
+
const r = await res.json();
|
|
313
|
+
return { fullName: r.full_name, htmlUrl: r.html_url, cloneUrl: r.clone_url };
|
|
314
|
+
}
|
|
315
|
+
async function addTopics(token, owner, repo, topics) {
|
|
316
|
+
await fetch(`${API}/repos/${owner}/${repo}/topics`, {
|
|
317
|
+
method: "PUT",
|
|
318
|
+
headers: headers(token),
|
|
319
|
+
body: JSON.stringify({ names: topics })
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
async function deleteRepo(token, owner, repo) {
|
|
323
|
+
const res = await fetch(`${API}/repos/${owner}/${repo}`, {
|
|
324
|
+
method: "DELETE",
|
|
325
|
+
headers: headers(token)
|
|
326
|
+
});
|
|
327
|
+
if (!res.ok && res.status !== 204) {
|
|
328
|
+
throw new Error(`delete failed: ${res.status} ${await res.text()}`);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
async function searchByTopic(token, login, topic) {
|
|
332
|
+
const q = encodeURIComponent(`user:${login} topic:${topic} fork:true`);
|
|
333
|
+
const res = await fetch(`${API}/search/repositories?q=${q}&per_page=100`, {
|
|
334
|
+
headers: headers(token)
|
|
335
|
+
});
|
|
336
|
+
if (!res.ok) throw new Error(`search failed: ${res.status}`);
|
|
337
|
+
const data = await res.json();
|
|
338
|
+
return data.items.map((i) => i.full_name);
|
|
339
|
+
}
|
|
340
|
+
var API;
|
|
341
|
+
var init_api = __esm({
|
|
342
|
+
"src/lib/sandbox/api.ts"() {
|
|
343
|
+
"use strict";
|
|
344
|
+
init_esm_shims();
|
|
345
|
+
API = "https://api.github.com";
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
// src/lib/sandbox/auth.ts
|
|
350
|
+
import { existsSync } from "fs";
|
|
351
|
+
import { mkdir, readFile, rm, writeFile } from "fs/promises";
|
|
352
|
+
import os from "os";
|
|
353
|
+
import path2 from "path";
|
|
354
|
+
async function getToken() {
|
|
355
|
+
const env = process.env.GITHUB_TOKEN || process.env.GH_TOKEN;
|
|
356
|
+
if (env) return env;
|
|
357
|
+
if (existsSync(TOKEN_FILE)) {
|
|
358
|
+
try {
|
|
359
|
+
return JSON.parse(await readFile(TOKEN_FILE, "utf-8")).token ?? null;
|
|
360
|
+
} catch {
|
|
361
|
+
return null;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
return null;
|
|
365
|
+
}
|
|
366
|
+
async function saveToken(token) {
|
|
367
|
+
await mkdir(CONFIG_DIR, { recursive: true, mode: 448 });
|
|
368
|
+
await writeFile(TOKEN_FILE, `${JSON.stringify({ token }, null, 2)}
|
|
369
|
+
`, { mode: 384 });
|
|
370
|
+
}
|
|
371
|
+
async function clearToken() {
|
|
372
|
+
if (existsSync(TOKEN_FILE)) await rm(TOKEN_FILE);
|
|
373
|
+
}
|
|
374
|
+
async function startDeviceFlow(scopes) {
|
|
375
|
+
const res = await fetch("https://github.com/login/device/code", {
|
|
376
|
+
method: "POST",
|
|
377
|
+
headers: { Accept: "application/json", "Content-Type": "application/json" },
|
|
378
|
+
body: JSON.stringify({ client_id: CLIENT_ID, scope: scopes.join(" ") })
|
|
379
|
+
});
|
|
380
|
+
if (!res.ok) throw new Error(`device code request failed: ${res.status}`);
|
|
381
|
+
return await res.json();
|
|
382
|
+
}
|
|
383
|
+
async function pollDeviceToken(deviceCode, interval, expiresIn) {
|
|
384
|
+
let wait = Math.max(interval, 1);
|
|
385
|
+
const deadline = Date.now() + expiresIn * 1e3;
|
|
386
|
+
while (Date.now() < deadline) {
|
|
387
|
+
await new Promise((r) => setTimeout(r, wait * 1e3));
|
|
388
|
+
const res = await fetch("https://github.com/login/oauth/access_token", {
|
|
389
|
+
method: "POST",
|
|
390
|
+
headers: { Accept: "application/json", "Content-Type": "application/json" },
|
|
391
|
+
body: JSON.stringify({
|
|
392
|
+
client_id: CLIENT_ID,
|
|
393
|
+
device_code: deviceCode,
|
|
394
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code"
|
|
395
|
+
})
|
|
396
|
+
});
|
|
397
|
+
const data = await res.json();
|
|
398
|
+
if (data.access_token) return data.access_token;
|
|
399
|
+
if (data.error === "slow_down") wait += 5;
|
|
400
|
+
else if (data.error !== "authorization_pending") {
|
|
401
|
+
throw new Error(data.error ?? "device flow failed");
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
throw new Error("device flow timed out");
|
|
405
|
+
}
|
|
406
|
+
var CONFIG_DIR, TOKEN_FILE, CLIENT_ID;
|
|
407
|
+
var init_auth = __esm({
|
|
408
|
+
"src/lib/sandbox/auth.ts"() {
|
|
409
|
+
"use strict";
|
|
410
|
+
init_esm_shims();
|
|
411
|
+
CONFIG_DIR = path2.join(os.homedir(), ".eds");
|
|
412
|
+
TOKEN_FILE = path2.join(CONFIG_DIR, "github.json");
|
|
413
|
+
CLIENT_ID = process.env.EDS_GITHUB_CLIENT_ID ?? "";
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
// src/lib/sandbox/github.ts
|
|
418
|
+
function previewUrl(owner, repo, ref = "main") {
|
|
419
|
+
return `https://${ref}--${repo}--${owner}.aem.page/`;
|
|
420
|
+
}
|
|
421
|
+
function codeSyncInstallUrl() {
|
|
422
|
+
return "https://github.com/apps/aem-code-sync/installations/new";
|
|
423
|
+
}
|
|
424
|
+
function createArgs(repoRef, opts) {
|
|
425
|
+
return [
|
|
426
|
+
"repo",
|
|
427
|
+
"create",
|
|
428
|
+
repoRef,
|
|
429
|
+
"--template",
|
|
430
|
+
opts.template,
|
|
431
|
+
opts.private ? "--private" : "--public",
|
|
432
|
+
...opts.clone ? ["--clone"] : []
|
|
433
|
+
];
|
|
434
|
+
}
|
|
435
|
+
var SANDBOX_TOPIC, BOILERPLATES;
|
|
436
|
+
var init_github = __esm({
|
|
437
|
+
"src/lib/sandbox/github.ts"() {
|
|
438
|
+
"use strict";
|
|
439
|
+
init_esm_shims();
|
|
440
|
+
SANDBOX_TOPIC = "eds-sandbox";
|
|
441
|
+
BOILERPLATES = {
|
|
442
|
+
document: "adobe/aem-boilerplate",
|
|
443
|
+
ue: "adobe/aem-boilerplate-xwalk"
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
|
|
276
448
|
// src/lib/schemas.ts
|
|
277
449
|
import { z } from "zod";
|
|
278
450
|
var figmaUrlSchema, edsConfigSchema, edsProjectSchema, edsMetaSchema, kebabCaseRegex;
|
|
@@ -315,26 +487,26 @@ var init_schemas = __esm({
|
|
|
315
487
|
});
|
|
316
488
|
|
|
317
489
|
// src/lib/project.ts
|
|
318
|
-
import { existsSync } from "fs";
|
|
319
|
-
import
|
|
490
|
+
import { existsSync as existsSync2 } from "fs";
|
|
491
|
+
import path3 from "path";
|
|
320
492
|
function findProjectRoot(startDir = process.cwd()) {
|
|
321
|
-
let dir =
|
|
322
|
-
const { root } =
|
|
493
|
+
let dir = path3.resolve(startDir);
|
|
494
|
+
const { root } = path3.parse(dir);
|
|
323
495
|
while (dir !== root) {
|
|
324
|
-
if (ROOT_MARKERS.some((m) =>
|
|
496
|
+
if (ROOT_MARKERS.some((m) => existsSync2(path3.join(dir, m)))) {
|
|
325
497
|
return dir;
|
|
326
498
|
}
|
|
327
|
-
dir =
|
|
499
|
+
dir = path3.dirname(dir);
|
|
328
500
|
}
|
|
329
|
-
if (ROOT_MARKERS.some((m) =>
|
|
501
|
+
if (ROOT_MARKERS.some((m) => existsSync2(path3.join(root, m)))) return root;
|
|
330
502
|
return void 0;
|
|
331
503
|
}
|
|
332
504
|
function detectProject(rootDir) {
|
|
333
505
|
return edsProjectSchema.parse({
|
|
334
506
|
root: rootDir,
|
|
335
|
-
hasFstab:
|
|
336
|
-
hasBlocks:
|
|
337
|
-
hasHeadHtml:
|
|
507
|
+
hasFstab: existsSync2(path3.join(rootDir, "fstab.yaml")),
|
|
508
|
+
hasBlocks: existsSync2(path3.join(rootDir, "blocks")),
|
|
509
|
+
hasHeadHtml: existsSync2(path3.join(rootDir, "head.html"))
|
|
338
510
|
});
|
|
339
511
|
}
|
|
340
512
|
var ROOT_MARKERS;
|
|
@@ -356,14 +528,14 @@ var init_project = __esm({
|
|
|
356
528
|
});
|
|
357
529
|
|
|
358
530
|
// src/lib/scaffold/ci.ts
|
|
359
|
-
import { existsSync as
|
|
360
|
-
import { mkdir, writeFile } from "fs/promises";
|
|
361
|
-
import
|
|
531
|
+
import { existsSync as existsSync3 } from "fs";
|
|
532
|
+
import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
|
|
533
|
+
import path4 from "path";
|
|
362
534
|
async function writeCiWorkflow(projectRoot) {
|
|
363
|
-
const file =
|
|
364
|
-
if (
|
|
365
|
-
await
|
|
366
|
-
await
|
|
535
|
+
const file = path4.join(projectRoot, ".github", "workflows", "eds.yml");
|
|
536
|
+
if (existsSync3(file)) return { path: CI_WORKFLOW_PATH, created: false };
|
|
537
|
+
await mkdir2(path4.dirname(file), { recursive: true });
|
|
538
|
+
await writeFile2(file, CI_WORKFLOW);
|
|
367
539
|
return { path: CI_WORKFLOW_PATH, created: true };
|
|
368
540
|
}
|
|
369
541
|
var CI_WORKFLOW_PATH, CI_WORKFLOW;
|
|
@@ -419,9 +591,46 @@ jobs:
|
|
|
419
591
|
}
|
|
420
592
|
});
|
|
421
593
|
|
|
594
|
+
// src/lib/scaffold/env.ts
|
|
595
|
+
import { existsSync as existsSync4 } from "fs";
|
|
596
|
+
import { readFile as readFile2, writeFile as writeFile3 } from "fs/promises";
|
|
597
|
+
import path5 from "path";
|
|
598
|
+
function buildEnv(opts = {}) {
|
|
599
|
+
const open = opts.open ?? "/";
|
|
600
|
+
const port = opts.port ?? 3007;
|
|
601
|
+
const pagesUrl = opts.pagesUrl ?? "";
|
|
602
|
+
return `AEM_OPEN=${open}
|
|
603
|
+
AEM_PORT=${port}
|
|
604
|
+
AEM_PAGES_URL=${pagesUrl}
|
|
605
|
+
`;
|
|
606
|
+
}
|
|
607
|
+
async function ensureEnvIgnored(projectRoot) {
|
|
608
|
+
const file = path5.join(projectRoot, ".gitignore");
|
|
609
|
+
let current = "";
|
|
610
|
+
if (existsSync4(file)) current = await readFile2(file, "utf-8");
|
|
611
|
+
if (current.split(/\r?\n/).some((l) => l.trim() === ".env")) return false;
|
|
612
|
+
const prefix = current && !current.endsWith("\n") ? "\n" : "";
|
|
613
|
+
await writeFile3(file, `${current}${prefix}.env
|
|
614
|
+
`);
|
|
615
|
+
return true;
|
|
616
|
+
}
|
|
617
|
+
async function writeEnv(projectRoot, opts = {}) {
|
|
618
|
+
const file = path5.join(projectRoot, ".env");
|
|
619
|
+
const created = !existsSync4(file);
|
|
620
|
+
if (created) await writeFile3(file, buildEnv(opts));
|
|
621
|
+
const gitignoreUpdated = await ensureEnvIgnored(projectRoot);
|
|
622
|
+
return { path: ".env", created, gitignoreUpdated };
|
|
623
|
+
}
|
|
624
|
+
var init_env = __esm({
|
|
625
|
+
"src/lib/scaffold/env.ts"() {
|
|
626
|
+
"use strict";
|
|
627
|
+
init_esm_shims();
|
|
628
|
+
}
|
|
629
|
+
});
|
|
630
|
+
|
|
422
631
|
// src/lib/scaffold/fstab.ts
|
|
423
|
-
import { writeFile as
|
|
424
|
-
import
|
|
632
|
+
import { writeFile as writeFile4 } from "fs/promises";
|
|
633
|
+
import path6 from "path";
|
|
425
634
|
function needsFstab(a) {
|
|
426
635
|
return a !== "ue";
|
|
427
636
|
}
|
|
@@ -434,7 +643,7 @@ function fstabYaml(mountpoint) {
|
|
|
434
643
|
`;
|
|
435
644
|
}
|
|
436
645
|
async function writeFstab(projectRoot, mountpoint) {
|
|
437
|
-
await
|
|
646
|
+
await writeFile4(path6.join(projectRoot, "fstab.yaml"), fstabYaml(mountpoint));
|
|
438
647
|
return "fstab.yaml";
|
|
439
648
|
}
|
|
440
649
|
function pathsJson(site) {
|
|
@@ -449,7 +658,7 @@ function pathsJson(site) {
|
|
|
449
658
|
`;
|
|
450
659
|
}
|
|
451
660
|
async function writePaths(projectRoot, site) {
|
|
452
|
-
await
|
|
661
|
+
await writeFile4(path6.join(projectRoot, "paths.json"), pathsJson(site));
|
|
453
662
|
return "paths.json";
|
|
454
663
|
}
|
|
455
664
|
var AUTHORING_NAMES;
|
|
@@ -466,6 +675,58 @@ var init_fstab = __esm({
|
|
|
466
675
|
}
|
|
467
676
|
});
|
|
468
677
|
|
|
678
|
+
// src/lib/scaffold/xwalk.ts
|
|
679
|
+
import { existsSync as existsSync5 } from "fs";
|
|
680
|
+
import { writeFile as writeFile5 } from "fs/promises";
|
|
681
|
+
import path7 from "path";
|
|
682
|
+
function buildXwalk() {
|
|
683
|
+
return `${JSON.stringify(XWALK_CONFIG, null, 2)}
|
|
684
|
+
`;
|
|
685
|
+
}
|
|
686
|
+
async function writeXwalk(projectRoot) {
|
|
687
|
+
const file = path7.join(projectRoot, "xwalk.json");
|
|
688
|
+
if (existsSync5(file)) return { path: "xwalk.json", created: false };
|
|
689
|
+
await writeFile5(file, buildXwalk());
|
|
690
|
+
return { path: "xwalk.json", created: true };
|
|
691
|
+
}
|
|
692
|
+
var XWALK_CONFIG;
|
|
693
|
+
var init_xwalk = __esm({
|
|
694
|
+
"src/lib/scaffold/xwalk.ts"() {
|
|
695
|
+
"use strict";
|
|
696
|
+
init_esm_shims();
|
|
697
|
+
XWALK_CONFIG = {
|
|
698
|
+
public: {
|
|
699
|
+
xwalk: {
|
|
700
|
+
"multi-field": {
|
|
701
|
+
enabled: true
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
// src/lib/ue/link.ts
|
|
710
|
+
function parseRemote(url) {
|
|
711
|
+
const m = url.trim().match(/github\.com[:/]([^/]+)\/(.+?)(?:\.git)?\/?$/i);
|
|
712
|
+
return m ? { owner: m[1], repo: m[2] } : null;
|
|
713
|
+
}
|
|
714
|
+
function previewHost(owner, repo, ref = "main") {
|
|
715
|
+
return `${ref}--${repo}--${owner}.aem.page`;
|
|
716
|
+
}
|
|
717
|
+
function editorLink({ host, path: path36 = "/", org }) {
|
|
718
|
+
const cleanHost = host.replace(/^https?:\/\//, "").replace(/\/+$/, "");
|
|
719
|
+
const p = path36.startsWith("/") ? path36 : `/${path36}`;
|
|
720
|
+
const orgSeg = org ? `@${org}/` : "";
|
|
721
|
+
return `https://experience.adobe.com/#/${orgSeg}aem/editor/canvas/${cleanHost}${p}`;
|
|
722
|
+
}
|
|
723
|
+
var init_link = __esm({
|
|
724
|
+
"src/lib/ue/link.ts"() {
|
|
725
|
+
"use strict";
|
|
726
|
+
init_esm_shims();
|
|
727
|
+
}
|
|
728
|
+
});
|
|
729
|
+
|
|
469
730
|
// src/lib/scaffold/blocks.ts
|
|
470
731
|
function json(value) {
|
|
471
732
|
return `${JSON.stringify(value, null, 2)}
|
|
@@ -479,14 +740,14 @@ var init_blocks = __esm({
|
|
|
479
740
|
"src/lib/scaffold/blocks.ts"() {
|
|
480
741
|
"use strict";
|
|
481
742
|
init_esm_shims();
|
|
482
|
-
blockDef = (title, id,
|
|
743
|
+
blockDef = (title, id, model2) => ({
|
|
483
744
|
title,
|
|
484
745
|
id,
|
|
485
746
|
plugins: {
|
|
486
747
|
xwalk: {
|
|
487
748
|
page: {
|
|
488
749
|
resourceType: "core/franklin/components/block/v1/block",
|
|
489
|
-
template: { name: title, model }
|
|
750
|
+
template: { name: title, model: model2 }
|
|
490
751
|
}
|
|
491
752
|
}
|
|
492
753
|
}
|
|
@@ -503,14 +764,14 @@ var init_blocks = __esm({
|
|
|
503
764
|
}
|
|
504
765
|
}
|
|
505
766
|
});
|
|
506
|
-
itemDef = (title, id,
|
|
767
|
+
itemDef = (title, id, model2) => ({
|
|
507
768
|
title,
|
|
508
769
|
id,
|
|
509
770
|
plugins: {
|
|
510
771
|
xwalk: {
|
|
511
772
|
page: {
|
|
512
773
|
resourceType: "core/franklin/components/block/v1/block/item",
|
|
513
|
-
template: { name: title, model }
|
|
774
|
+
template: { name: title, model: model2 }
|
|
514
775
|
}
|
|
515
776
|
}
|
|
516
777
|
}
|
|
@@ -787,6 +1048,204 @@ export default function decorate(block) {
|
|
|
787
1048
|
}
|
|
788
1049
|
});
|
|
789
1050
|
|
|
1051
|
+
// src/lib/scaffold/helpers.ts
|
|
1052
|
+
import { existsSync as existsSync6 } from "fs";
|
|
1053
|
+
import { mkdir as mkdir3, writeFile as writeFile6 } from "fs/promises";
|
|
1054
|
+
import path8 from "path";
|
|
1055
|
+
function helperById(id) {
|
|
1056
|
+
return HELPERS.find((h) => h.id === id);
|
|
1057
|
+
}
|
|
1058
|
+
function resolveHelpers(ids) {
|
|
1059
|
+
const want = /* @__PURE__ */ new Set();
|
|
1060
|
+
const add = (id) => {
|
|
1061
|
+
if (want.has(id)) return;
|
|
1062
|
+
const h = helperById(id);
|
|
1063
|
+
if (!h) return;
|
|
1064
|
+
for (const dep of h.deps ?? []) add(dep);
|
|
1065
|
+
want.add(id);
|
|
1066
|
+
};
|
|
1067
|
+
for (const id of ids) add(id);
|
|
1068
|
+
return HELPERS.filter((h) => want.has(h.id)).map((h) => h.id);
|
|
1069
|
+
}
|
|
1070
|
+
function buildHelpers(ids) {
|
|
1071
|
+
const resolved = resolveHelpers(ids);
|
|
1072
|
+
const body = resolved.map((id) => helperById(id)).filter((h) => !!h).map((h) => h.code).join("\n\n");
|
|
1073
|
+
return `// scripts/utils.js - common EDS helpers (generated by \`eds scaffold helpers\`).
|
|
1074
|
+
|
|
1075
|
+
${body}
|
|
1076
|
+
`;
|
|
1077
|
+
}
|
|
1078
|
+
async function writeHelpers(projectRoot, ids = ALL_HELPER_IDS) {
|
|
1079
|
+
const resolved = resolveHelpers(ids);
|
|
1080
|
+
const file = path8.join(projectRoot, "scripts", "utils.js");
|
|
1081
|
+
if (existsSync6(file)) return { path: HELPERS_PATH, created: false, helpers: resolved };
|
|
1082
|
+
await mkdir3(path8.dirname(file), { recursive: true });
|
|
1083
|
+
await writeFile6(file, buildHelpers(ids));
|
|
1084
|
+
return { path: HELPERS_PATH, created: true, helpers: resolved };
|
|
1085
|
+
}
|
|
1086
|
+
var HELPERS_PATH, HELPERS, ALL_HELPER_IDS;
|
|
1087
|
+
var init_helpers = __esm({
|
|
1088
|
+
"src/lib/scaffold/helpers.ts"() {
|
|
1089
|
+
"use strict";
|
|
1090
|
+
init_esm_shims();
|
|
1091
|
+
HELPERS_PATH = "scripts/utils.js";
|
|
1092
|
+
HELPERS = [
|
|
1093
|
+
{
|
|
1094
|
+
id: "getMetadata",
|
|
1095
|
+
label: "getMetadata (read a page meta value)",
|
|
1096
|
+
code: `/**
|
|
1097
|
+
* Reads a page metadata value by name (meta[name] or meta[property]).
|
|
1098
|
+
* @param {string} name The metadata name.
|
|
1099
|
+
* @returns {string} The value, or an empty string.
|
|
1100
|
+
*/
|
|
1101
|
+
export function getMetadata(name) {
|
|
1102
|
+
const meta =
|
|
1103
|
+
document.querySelector(\`meta[name="\${name}"]\`) ||
|
|
1104
|
+
document.querySelector(\`meta[property="\${name}"]\`);
|
|
1105
|
+
return meta ? meta.content : '';
|
|
1106
|
+
}`
|
|
1107
|
+
},
|
|
1108
|
+
{
|
|
1109
|
+
id: "toCamelCase",
|
|
1110
|
+
label: "toCamelCase (dash/underscore/space -> camelCase)",
|
|
1111
|
+
code: `/**
|
|
1112
|
+
* Converts a string to camelCase, handling dashes, underscores and spaces.
|
|
1113
|
+
* @param {string} value The string to convert.
|
|
1114
|
+
* @returns {string} The camelCased string.
|
|
1115
|
+
*/
|
|
1116
|
+
export function toCamelCase(value) {
|
|
1117
|
+
return String(value || '')
|
|
1118
|
+
.trim()
|
|
1119
|
+
.toLowerCase()
|
|
1120
|
+
.replace(/[-_\\s]+(.)/g, (_, char) => char.toUpperCase());
|
|
1121
|
+
}`
|
|
1122
|
+
},
|
|
1123
|
+
{
|
|
1124
|
+
id: "getLanguageRootPath",
|
|
1125
|
+
label: "getLanguageRootPath (e.g. /en, /en-us)",
|
|
1126
|
+
code: `/**
|
|
1127
|
+
* Returns the language root of a path, e.g. '/en' or '/en-us' ('' if none).
|
|
1128
|
+
* @param {string} path The page path.
|
|
1129
|
+
* @returns {string} The language root prefix.
|
|
1130
|
+
*/
|
|
1131
|
+
export function getLanguageRootPath(path) {
|
|
1132
|
+
const match = String(path || '').match(/^\\/[a-z]{2}(?:-[a-z]{2})?(?=\\/|$)/i);
|
|
1133
|
+
return match ? match[0] : '';
|
|
1134
|
+
}`
|
|
1135
|
+
},
|
|
1136
|
+
{
|
|
1137
|
+
id: "getPagePath",
|
|
1138
|
+
label: "getPagePath (path without content root)",
|
|
1139
|
+
deps: ["getLanguageRootPath"],
|
|
1140
|
+
code: `// Content root stripped from page paths. Set this if your site is mounted under
|
|
1141
|
+
// a sub-path (e.g. '/content/my-site'); leave empty for a root-mounted site.
|
|
1142
|
+
const ROOT_PATH = '';
|
|
1143
|
+
|
|
1144
|
+
/**
|
|
1145
|
+
* Returns the page path without the content root; \`clean\` drops the language root.
|
|
1146
|
+
* @param {boolean} [clean=false] Also strip the language root.
|
|
1147
|
+
* @returns {string} The page path or 'NA' for the language root.
|
|
1148
|
+
*/
|
|
1149
|
+
export function getPagePath({ clean = false } = {}) {
|
|
1150
|
+
const path = window.location.pathname.replace(ROOT_PATH, '').replace(/\\.html$/i, '');
|
|
1151
|
+
|
|
1152
|
+
if (clean) {
|
|
1153
|
+
const langRoot = getLanguageRootPath(path);
|
|
1154
|
+
const cleaned = path.startsWith(langRoot) ? path.slice(langRoot.length) : path;
|
|
1155
|
+
return cleaned || 'NA';
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
return path || 'NA';
|
|
1159
|
+
}`
|
|
1160
|
+
},
|
|
1161
|
+
{
|
|
1162
|
+
id: "getSiteArea",
|
|
1163
|
+
label: "getSiteArea (blog / products / home)",
|
|
1164
|
+
deps: ["getPagePath"],
|
|
1165
|
+
code: `/**
|
|
1166
|
+
* Returns the top-level area of the site from the current path - e.g. 'blog' or
|
|
1167
|
+
* 'products', and 'home' at the site root. Uses the language-cleaned path, so you
|
|
1168
|
+
* can tell whether you're on the blog or somewhere else.
|
|
1169
|
+
* @returns {string} The site area.
|
|
1170
|
+
*/
|
|
1171
|
+
export function getSiteArea() {
|
|
1172
|
+
const path = getPagePath({ clean: true });
|
|
1173
|
+
if (!path || path === 'NA') return 'home';
|
|
1174
|
+
return path.split('/').filter(Boolean)[0] || 'home';
|
|
1175
|
+
}`
|
|
1176
|
+
},
|
|
1177
|
+
{
|
|
1178
|
+
id: "getEnvironment",
|
|
1179
|
+
label: "getEnvironment (dev / stage / prod)",
|
|
1180
|
+
code: `/**
|
|
1181
|
+
* Detects the current environment from the hostname.
|
|
1182
|
+
* @returns {'NA'|'dev'|'stage'|'prod'} The environment name.
|
|
1183
|
+
*/
|
|
1184
|
+
export function getEnvironment() {
|
|
1185
|
+
const { hostname } = window.location;
|
|
1186
|
+
if (hostname.includes('author')) return 'NA';
|
|
1187
|
+
if (hostname === 'localhost') return 'dev';
|
|
1188
|
+
const repo = hostname.match(/^[^.]+--([^.]+)--[^.]+\\.aem\\.(?:page|live)$/)?.[1] || '';
|
|
1189
|
+
if (repo.includes('dev')) return 'dev';
|
|
1190
|
+
if (repo.includes('stage')) return 'stage';
|
|
1191
|
+
return 'prod';
|
|
1192
|
+
}`
|
|
1193
|
+
},
|
|
1194
|
+
{
|
|
1195
|
+
id: "getContentTopic",
|
|
1196
|
+
label: "getContentTopic (cq-tags content-topic values)",
|
|
1197
|
+
deps: ["getMetadata"],
|
|
1198
|
+
code: `/**
|
|
1199
|
+
* Reads all content-topic values from the page's cq-tags metadata.
|
|
1200
|
+
* @returns {string[]} The topics (part after \`content-topic/\`), empty if none.
|
|
1201
|
+
*/
|
|
1202
|
+
export function getContentTopic() {
|
|
1203
|
+
return getMetadata('cq-tags')
|
|
1204
|
+
.split(',')
|
|
1205
|
+
.map((t) => t.trim())
|
|
1206
|
+
.filter((t) => t.includes('content-topic/'))
|
|
1207
|
+
.map((t) => t.split('content-topic/')[1]);
|
|
1208
|
+
}`
|
|
1209
|
+
},
|
|
1210
|
+
{
|
|
1211
|
+
id: "isUEEdit",
|
|
1212
|
+
label: "isUEEdit (Universal Editor edit mode)",
|
|
1213
|
+
code: `/**
|
|
1214
|
+
* Checks whether the current page is opened in the Universal Editor Edit mode.
|
|
1215
|
+
* @returns {boolean} True in Universal Editor Edit mode.
|
|
1216
|
+
*/
|
|
1217
|
+
export function isUEEdit() {
|
|
1218
|
+
return document.documentElement.classList.contains('adobe-ue-edit');
|
|
1219
|
+
}`
|
|
1220
|
+
},
|
|
1221
|
+
{
|
|
1222
|
+
id: "isUEPreview",
|
|
1223
|
+
label: "isUEPreview (Universal Editor preview mode)",
|
|
1224
|
+
code: `/**
|
|
1225
|
+
* Checks whether the current page is opened in the Universal Editor Preview mode.
|
|
1226
|
+
* @returns {boolean} True in Universal Editor Preview mode.
|
|
1227
|
+
*/
|
|
1228
|
+
export function isUEPreview() {
|
|
1229
|
+
return document.documentElement.classList.contains('adobe-ue-preview');
|
|
1230
|
+
}`
|
|
1231
|
+
},
|
|
1232
|
+
{
|
|
1233
|
+
id: "isUE",
|
|
1234
|
+
label: "isUE (Universal Editor, any mode)",
|
|
1235
|
+
deps: ["isUEEdit", "isUEPreview"],
|
|
1236
|
+
code: `/**
|
|
1237
|
+
* Checks whether the current page is opened in the Universal Editor (any mode).
|
|
1238
|
+
* @returns {boolean} True in the Universal Editor.
|
|
1239
|
+
*/
|
|
1240
|
+
export function isUE() {
|
|
1241
|
+
return isUEEdit() || isUEPreview();
|
|
1242
|
+
}`
|
|
1243
|
+
}
|
|
1244
|
+
];
|
|
1245
|
+
ALL_HELPER_IDS = HELPERS.map((h) => h.id);
|
|
1246
|
+
}
|
|
1247
|
+
});
|
|
1248
|
+
|
|
790
1249
|
// src/lib/scaffold/ue.ts
|
|
791
1250
|
function fieldReferenceFields() {
|
|
792
1251
|
return [
|
|
@@ -872,7 +1331,7 @@ function fieldReferenceFields() {
|
|
|
872
1331
|
];
|
|
873
1332
|
}
|
|
874
1333
|
function fieldReferenceModelFile() {
|
|
875
|
-
const
|
|
1334
|
+
const model2 = {
|
|
876
1335
|
definitions: [
|
|
877
1336
|
{
|
|
878
1337
|
title: "Field Reference",
|
|
@@ -890,7 +1349,7 @@ function fieldReferenceModelFile() {
|
|
|
890
1349
|
models: [{ id: "field-reference", fields: fieldReferenceFields() }],
|
|
891
1350
|
filters: []
|
|
892
1351
|
};
|
|
893
|
-
return `${JSON.stringify(
|
|
1352
|
+
return `${JSON.stringify(model2, null, 2)}
|
|
894
1353
|
`;
|
|
895
1354
|
}
|
|
896
1355
|
function mergeById(existing, additions) {
|
|
@@ -913,7 +1372,7 @@ function mergeDefinitions(existing, defaultContent, blockDefs) {
|
|
|
913
1372
|
}
|
|
914
1373
|
function mergeSectionFilter(existing, ids) {
|
|
915
1374
|
const filters = existing ? [...existing] : [];
|
|
916
|
-
let section = filters.find((
|
|
1375
|
+
let section = filters.find((f2) => f2.id === "section");
|
|
917
1376
|
if (!section) {
|
|
918
1377
|
section = { id: "section", components: ["text", "image", "button", "title"] };
|
|
919
1378
|
filters.push(section);
|
|
@@ -1049,22 +1508,25 @@ var scaffold_exports = {};
|
|
|
1049
1508
|
__export(scaffold_exports, {
|
|
1050
1509
|
scaffoldBlocks: () => scaffoldBlocks,
|
|
1051
1510
|
scaffoldCi: () => scaffoldCi,
|
|
1511
|
+
scaffoldEnv: () => scaffoldEnv,
|
|
1512
|
+
scaffoldHelpers: () => scaffoldHelpers,
|
|
1052
1513
|
scaffoldInteractive: () => scaffoldInteractive,
|
|
1053
|
-
scaffoldUe: () => scaffoldUe
|
|
1514
|
+
scaffoldUe: () => scaffoldUe,
|
|
1515
|
+
scaffoldXwalk: () => scaffoldXwalk
|
|
1054
1516
|
});
|
|
1055
|
-
import { existsSync as
|
|
1056
|
-
import { mkdir as
|
|
1057
|
-
import
|
|
1517
|
+
import { existsSync as existsSync7 } from "fs";
|
|
1518
|
+
import { mkdir as mkdir4, readFile as readFile3, writeFile as writeFile7 } from "fs/promises";
|
|
1519
|
+
import path9 from "path";
|
|
1058
1520
|
async function readJson(file) {
|
|
1059
|
-
if (!
|
|
1521
|
+
if (!existsSync7(file)) return null;
|
|
1060
1522
|
try {
|
|
1061
|
-
return JSON.parse(await
|
|
1523
|
+
return JSON.parse(await readFile3(file, "utf-8"));
|
|
1062
1524
|
} catch {
|
|
1063
1525
|
return null;
|
|
1064
1526
|
}
|
|
1065
1527
|
}
|
|
1066
1528
|
async function writeJson(file, data) {
|
|
1067
|
-
await
|
|
1529
|
+
await writeFile7(file, `${JSON.stringify(data, null, 2)}
|
|
1068
1530
|
`);
|
|
1069
1531
|
}
|
|
1070
1532
|
async function scaffoldUe() {
|
|
@@ -1076,15 +1538,15 @@ async function scaffoldUe() {
|
|
|
1076
1538
|
}
|
|
1077
1539
|
logger.logoOnce(logo("Scaffold - Universal Editor"));
|
|
1078
1540
|
const created = [];
|
|
1079
|
-
const blockDir =
|
|
1080
|
-
await
|
|
1081
|
-
await
|
|
1082
|
-
await
|
|
1083
|
-
await
|
|
1541
|
+
const blockDir = path9.join(projectRoot, "blocks", "field-reference");
|
|
1542
|
+
await mkdir4(blockDir, { recursive: true });
|
|
1543
|
+
await writeFile7(path9.join(blockDir, "field-reference.js"), FIELD_REFERENCE_JS);
|
|
1544
|
+
await writeFile7(path9.join(blockDir, "field-reference.css"), FIELD_REFERENCE_CSS);
|
|
1545
|
+
await writeFile7(path9.join(blockDir, "_field-reference.json"), fieldReferenceModelFile());
|
|
1084
1546
|
created.push("blocks/field-reference/ (all 17 fields + multifield)");
|
|
1085
|
-
const defFile =
|
|
1086
|
-
const modelsFile =
|
|
1087
|
-
const filtersFile =
|
|
1547
|
+
const defFile = path9.join(projectRoot, "component-definition.json");
|
|
1548
|
+
const modelsFile = path9.join(projectRoot, "component-models.json");
|
|
1549
|
+
const filtersFile = path9.join(projectRoot, "component-filters.json");
|
|
1088
1550
|
const blockDefs = [
|
|
1089
1551
|
{
|
|
1090
1552
|
title: "Field Reference",
|
|
@@ -1134,8 +1596,8 @@ async function scaffoldBlocks(names) {
|
|
|
1134
1596
|
logger.logoOnce(logo("Scaffold \u2014 standard blocks"));
|
|
1135
1597
|
let selected;
|
|
1136
1598
|
if (names?.length) {
|
|
1137
|
-
selected = names.map((
|
|
1138
|
-
const unknown = names.filter((
|
|
1599
|
+
selected = names.map((n2) => blockById(n2.trim())).filter((b) => !!b);
|
|
1600
|
+
const unknown = names.filter((n2) => !blockById(n2.trim()));
|
|
1139
1601
|
if (unknown.length) {
|
|
1140
1602
|
logger.warn(
|
|
1141
1603
|
`Unknown block(s): ${unknown.join(", ")}. Available: ${STANDARD_BLOCKS.map((b) => b.id).join(", ")}`
|
|
@@ -1147,18 +1609,18 @@ async function scaffoldBlocks(names) {
|
|
|
1147
1609
|
const createdIds = [];
|
|
1148
1610
|
const skipped = [];
|
|
1149
1611
|
for (const b of selected) {
|
|
1150
|
-
const dir =
|
|
1151
|
-
if (
|
|
1612
|
+
const dir = path9.join(projectRoot, "blocks", b.id);
|
|
1613
|
+
if (existsSync7(dir)) {
|
|
1152
1614
|
skipped.push(b.id);
|
|
1153
1615
|
continue;
|
|
1154
1616
|
}
|
|
1155
|
-
await
|
|
1156
|
-
await
|
|
1157
|
-
await
|
|
1158
|
-
await
|
|
1617
|
+
await mkdir4(dir, { recursive: true });
|
|
1618
|
+
await writeFile7(path9.join(dir, `${b.id}.js`), b.js);
|
|
1619
|
+
await writeFile7(path9.join(dir, `${b.id}.css`), b.css);
|
|
1620
|
+
await writeFile7(path9.join(dir, `_${b.id}.json`), b.model);
|
|
1159
1621
|
createdIds.push(b.id);
|
|
1160
1622
|
}
|
|
1161
|
-
const filtersFile =
|
|
1623
|
+
const filtersFile = path9.join(projectRoot, "component-filters.json");
|
|
1162
1624
|
const filters = mergeSectionFilter(
|
|
1163
1625
|
await readJson(filtersFile),
|
|
1164
1626
|
selected.map((b) => b.id)
|
|
@@ -1193,6 +1655,96 @@ async function scaffoldCi() {
|
|
|
1193
1655
|
])
|
|
1194
1656
|
);
|
|
1195
1657
|
}
|
|
1658
|
+
async function scaffoldHelpers(ids) {
|
|
1659
|
+
const projectRoot = findProjectRoot();
|
|
1660
|
+
if (!projectRoot) {
|
|
1661
|
+
logger.error("Not inside an EDS project (no EDS project markers found).");
|
|
1662
|
+
process.exitCode = 1;
|
|
1663
|
+
return;
|
|
1664
|
+
}
|
|
1665
|
+
logger.logoOnce(logo("Scaffold - helpers"));
|
|
1666
|
+
let selected = ids?.length ? ids : void 0;
|
|
1667
|
+
if (!selected) {
|
|
1668
|
+
if (process.stdout.isTTY) {
|
|
1669
|
+
const { checkbox } = await import("@inquirer/prompts");
|
|
1670
|
+
selected = await checkbox({
|
|
1671
|
+
message: "Which helpers?",
|
|
1672
|
+
choices: HELPERS.map((h) => ({ name: h.label, value: h.id, checked: true }))
|
|
1673
|
+
});
|
|
1674
|
+
} else {
|
|
1675
|
+
selected = ALL_HELPER_IDS;
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
if (!selected.length) {
|
|
1679
|
+
logger.info("Nothing selected.");
|
|
1680
|
+
return;
|
|
1681
|
+
}
|
|
1682
|
+
const res = await writeHelpers(projectRoot, selected);
|
|
1683
|
+
logger.info(heading("Helpers"));
|
|
1684
|
+
if (res.created) {
|
|
1685
|
+
logger.info(accentLine(res.path, res.helpers.join(", ")));
|
|
1686
|
+
} else {
|
|
1687
|
+
logger.info(statusLine("warn", res.path, "already exists - skipped"));
|
|
1688
|
+
}
|
|
1689
|
+
logger.info("");
|
|
1690
|
+
logger.info(
|
|
1691
|
+
box([
|
|
1692
|
+
res.created ? `${res.helpers.length} helper(s) -> scripts/utils.js` : "Helpers left untouched"
|
|
1693
|
+
])
|
|
1694
|
+
);
|
|
1695
|
+
}
|
|
1696
|
+
async function scaffoldEnv(options = {}) {
|
|
1697
|
+
const projectRoot = findProjectRoot();
|
|
1698
|
+
if (!projectRoot) {
|
|
1699
|
+
logger.error("Not inside an EDS project (no EDS project markers found).");
|
|
1700
|
+
process.exitCode = 1;
|
|
1701
|
+
return;
|
|
1702
|
+
}
|
|
1703
|
+
logger.logoOnce(logo("Scaffold - .env"));
|
|
1704
|
+
let pagesUrl = options.pagesUrl;
|
|
1705
|
+
if (pagesUrl === void 0) {
|
|
1706
|
+
try {
|
|
1707
|
+
const { execa } = await import("execa");
|
|
1708
|
+
const res2 = await execa("git", ["remote", "get-url", "origin"], { cwd: projectRoot });
|
|
1709
|
+
const parsed = parseRemote(res2.stdout.trim());
|
|
1710
|
+
pagesUrl = parsed ? `https://${previewHost(parsed.owner, parsed.repo)}` : "";
|
|
1711
|
+
} catch {
|
|
1712
|
+
pagesUrl = "";
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
let open = options.open ?? "/";
|
|
1716
|
+
let port = options.port ?? "3007";
|
|
1717
|
+
if (!options.yes && process.stdout.isTTY) {
|
|
1718
|
+
const { input } = await import("@inquirer/prompts");
|
|
1719
|
+
open = await input({ message: "AEM_OPEN (path opened on start)", default: open });
|
|
1720
|
+
port = await input({ message: "AEM_PORT", default: String(port) });
|
|
1721
|
+
pagesUrl = await input({ message: "AEM_PAGES_URL (content origin)", default: pagesUrl });
|
|
1722
|
+
}
|
|
1723
|
+
const res = await writeEnv(projectRoot, { open, port, pagesUrl });
|
|
1724
|
+
logger.info(heading(".env"));
|
|
1725
|
+
if (res.created) logger.info(accentLine(res.path, `AEM_OPEN=${open} AEM_PORT=${port}`));
|
|
1726
|
+
else logger.info(statusLine("warn", res.path, "already exists - left untouched"));
|
|
1727
|
+
if (res.gitignoreUpdated) logger.info(accentLine(".gitignore", "added .env"));
|
|
1728
|
+
logger.info("");
|
|
1729
|
+
logger.info(box([res.created ? ".env ready for `aem up`" : ".env left untouched"]));
|
|
1730
|
+
}
|
|
1731
|
+
async function scaffoldXwalk() {
|
|
1732
|
+
const projectRoot = findProjectRoot();
|
|
1733
|
+
if (!projectRoot) {
|
|
1734
|
+
logger.error("Not inside an EDS project (no EDS project markers found).");
|
|
1735
|
+
process.exitCode = 1;
|
|
1736
|
+
return;
|
|
1737
|
+
}
|
|
1738
|
+
logger.logoOnce(logo("Scaffold - xwalk.json"));
|
|
1739
|
+
const res = await writeXwalk(projectRoot);
|
|
1740
|
+
logger.info(heading("xwalk.json"));
|
|
1741
|
+
if (res.created) logger.info(accentLine(res.path, "multi-field enabled"));
|
|
1742
|
+
else logger.info(statusLine("warn", res.path, "already exists - left untouched"));
|
|
1743
|
+
logger.info("");
|
|
1744
|
+
logger.info(
|
|
1745
|
+
box([res.created ? "xwalk.json ready (UE multi-field)" : "xwalk.json left untouched"])
|
|
1746
|
+
);
|
|
1747
|
+
}
|
|
1196
1748
|
async function scaffoldInteractive() {
|
|
1197
1749
|
const { select, checkbox } = await import("@inquirer/prompts");
|
|
1198
1750
|
logger.logoOnce(logo("Scaffold"));
|
|
@@ -1202,9 +1754,14 @@ async function scaffoldInteractive() {
|
|
|
1202
1754
|
{ name: "Universal Editor config + field-reference (all 17 field types)", value: "ue" },
|
|
1203
1755
|
{ name: "Standard blocks (hero, cards, columns, accordion, embed)", value: "blocks" },
|
|
1204
1756
|
{ name: "GitHub Actions CI (doctor + audits + lint/build)", value: "ci" },
|
|
1757
|
+
{ name: "Helper functions (scripts/utils.js)", value: "helpers" },
|
|
1758
|
+
{ name: "Local-dev .env (aem up: port / open / pages URL)", value: "env" },
|
|
1759
|
+
{ name: "xwalk.json (enable Universal Editor multi-field)", value: "xwalk" },
|
|
1205
1760
|
{ name: "UE config + standard blocks", value: "both" }
|
|
1206
1761
|
]
|
|
1207
1762
|
});
|
|
1763
|
+
if (what === "env") return scaffoldEnv();
|
|
1764
|
+
if (what === "xwalk") return scaffoldXwalk();
|
|
1208
1765
|
if (what === "ue" || what === "both") await scaffoldUe();
|
|
1209
1766
|
if (what === "blocks" || what === "both") {
|
|
1210
1767
|
const picked = await checkbox({
|
|
@@ -1214,6 +1771,7 @@ async function scaffoldInteractive() {
|
|
|
1214
1771
|
await scaffoldBlocks(picked);
|
|
1215
1772
|
}
|
|
1216
1773
|
if (what === "ci") await scaffoldCi();
|
|
1774
|
+
if (what === "helpers") await scaffoldHelpers();
|
|
1217
1775
|
}
|
|
1218
1776
|
var init_scaffold = __esm({
|
|
1219
1777
|
"src/commands/scaffold.ts"() {
|
|
@@ -1223,7 +1781,11 @@ var init_scaffold = __esm({
|
|
|
1223
1781
|
init_project();
|
|
1224
1782
|
init_blocks();
|
|
1225
1783
|
init_ci();
|
|
1784
|
+
init_env();
|
|
1785
|
+
init_helpers();
|
|
1226
1786
|
init_ue();
|
|
1787
|
+
init_xwalk();
|
|
1788
|
+
init_link();
|
|
1227
1789
|
init_ui();
|
|
1228
1790
|
}
|
|
1229
1791
|
});
|
|
@@ -1233,17 +1795,27 @@ var init_exports = {};
|
|
|
1233
1795
|
__export(init_exports, {
|
|
1234
1796
|
initProject: () => initProject
|
|
1235
1797
|
});
|
|
1236
|
-
import { existsSync as
|
|
1237
|
-
import { readFile as
|
|
1238
|
-
import
|
|
1798
|
+
import { existsSync as existsSync8 } from "fs";
|
|
1799
|
+
import { readFile as readFile4, writeFile as writeFile8 } from "fs/promises";
|
|
1800
|
+
import path10 from "path";
|
|
1239
1801
|
import chalk3 from "chalk";
|
|
1802
|
+
async function derivePagesUrl(root) {
|
|
1803
|
+
try {
|
|
1804
|
+
const { execa } = await import("execa");
|
|
1805
|
+
const res = await execa("git", ["remote", "get-url", "origin"], { cwd: root });
|
|
1806
|
+
const parsed = parseRemote(res.stdout.trim());
|
|
1807
|
+
return parsed ? `https://${previewHost(parsed.owner, parsed.repo)}` : "";
|
|
1808
|
+
} catch {
|
|
1809
|
+
return "";
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1240
1812
|
async function setPackageName(root, name) {
|
|
1241
|
-
const file =
|
|
1242
|
-
if (!
|
|
1813
|
+
const file = path10.join(root, "package.json");
|
|
1814
|
+
if (!existsSync8(file)) return false;
|
|
1243
1815
|
try {
|
|
1244
|
-
const pkg = JSON.parse(await
|
|
1816
|
+
const pkg = JSON.parse(await readFile4(file, "utf-8"));
|
|
1245
1817
|
pkg.name = name;
|
|
1246
|
-
await
|
|
1818
|
+
await writeFile8(file, `${JSON.stringify(pkg, null, 2)}
|
|
1247
1819
|
`);
|
|
1248
1820
|
return true;
|
|
1249
1821
|
} catch {
|
|
@@ -1253,13 +1825,13 @@ async function setPackageName(root, name) {
|
|
|
1253
1825
|
async function replacePlaceholders(root, name) {
|
|
1254
1826
|
const touched = [];
|
|
1255
1827
|
for (const rel of ["README.md", "index.html", "head.html"]) {
|
|
1256
|
-
const file =
|
|
1257
|
-
if (!
|
|
1828
|
+
const file = path10.join(root, rel);
|
|
1829
|
+
if (!existsSync8(file)) continue;
|
|
1258
1830
|
try {
|
|
1259
|
-
const before = await
|
|
1831
|
+
const before = await readFile4(file, "utf-8");
|
|
1260
1832
|
const after = before.replace(/aem-boilerplate/gi, name);
|
|
1261
1833
|
if (after !== before) {
|
|
1262
|
-
await
|
|
1834
|
+
await writeFile8(file, after);
|
|
1263
1835
|
touched.push(rel);
|
|
1264
1836
|
}
|
|
1265
1837
|
} catch {
|
|
@@ -1332,11 +1904,11 @@ async function initProject(options = {}) {
|
|
|
1332
1904
|
await logger.logoOnceAnimated("Project setup");
|
|
1333
1905
|
const { input, select, confirm } = await import("@inquirer/prompts");
|
|
1334
1906
|
const interactive = !options.yes;
|
|
1335
|
-
let defaultName =
|
|
1336
|
-
const pkgFile =
|
|
1337
|
-
if (
|
|
1907
|
+
let defaultName = path10.basename(projectRoot);
|
|
1908
|
+
const pkgFile = path10.join(projectRoot, "package.json");
|
|
1909
|
+
if (existsSync8(pkgFile)) {
|
|
1338
1910
|
try {
|
|
1339
|
-
defaultName = JSON.parse(await
|
|
1911
|
+
defaultName = JSON.parse(await readFile4(pkgFile, "utf-8")).name || defaultName;
|
|
1340
1912
|
} catch {
|
|
1341
1913
|
}
|
|
1342
1914
|
}
|
|
@@ -1399,11 +1971,19 @@ async function initProject(options = {}) {
|
|
|
1399
1971
|
const ci = await writeCiWorkflow(projectRoot);
|
|
1400
1972
|
done.push(ci.created ? ci.path : `${ci.path} (exists, skipped)`);
|
|
1401
1973
|
}
|
|
1974
|
+
if (options.env !== false) {
|
|
1975
|
+
const res = await writeEnv(projectRoot, { pagesUrl: await derivePagesUrl(projectRoot) });
|
|
1976
|
+
done.push(res.created ? ".env (aem up config)" : ".env (exists, skipped)");
|
|
1977
|
+
}
|
|
1978
|
+
if (options.xwalk !== false && authoring === "ue") {
|
|
1979
|
+
const res = await writeXwalk(projectRoot);
|
|
1980
|
+
done.push(res.created ? `${res.path} (UE multi-field)` : `${res.path} (exists, skipped)`);
|
|
1981
|
+
}
|
|
1402
1982
|
logger.info(heading("Configured"));
|
|
1403
1983
|
const dw = columnWidth(done.map((d) => d.split(" ")[0]));
|
|
1404
1984
|
for (const d of done) {
|
|
1405
|
-
const [
|
|
1406
|
-
logger.info(accentLine(
|
|
1985
|
+
const [n2, ...rest] = d.split(" ");
|
|
1986
|
+
logger.info(accentLine(n2, rest.join(" ").replace(/^\((.*)\)$/, "$1"), dw));
|
|
1407
1987
|
}
|
|
1408
1988
|
logger.info("");
|
|
1409
1989
|
if (interactive) await externalSteps(authoring);
|
|
@@ -1417,321 +1997,1505 @@ var init_init = __esm({
|
|
|
1417
1997
|
init_logger();
|
|
1418
1998
|
init_project();
|
|
1419
1999
|
init_ci();
|
|
2000
|
+
init_env();
|
|
1420
2001
|
init_fstab();
|
|
2002
|
+
init_xwalk();
|
|
2003
|
+
init_link();
|
|
1421
2004
|
init_ui();
|
|
1422
2005
|
init_scaffold();
|
|
1423
2006
|
slug = (s) => s.trim().toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
1424
2007
|
}
|
|
1425
2008
|
});
|
|
1426
2009
|
|
|
1427
|
-
// src/
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
};
|
|
1440
|
-
|
|
1441
|
-
return
|
|
2010
|
+
// src/commands/sandbox.ts
|
|
2011
|
+
var sandbox_exports = {};
|
|
2012
|
+
__export(sandbox_exports, {
|
|
2013
|
+
sandboxList: () => sandboxList,
|
|
2014
|
+
sandboxLogin: () => sandboxLogin,
|
|
2015
|
+
sandboxLogout: () => sandboxLogout,
|
|
2016
|
+
sandboxNew: () => sandboxNew,
|
|
2017
|
+
sandboxRm: () => sandboxRm
|
|
2018
|
+
});
|
|
2019
|
+
import path11 from "path";
|
|
2020
|
+
import chalk4 from "chalk";
|
|
2021
|
+
async function run(cmd, args, opts = {}) {
|
|
2022
|
+
const { execa } = await import("execa");
|
|
2023
|
+
const res = await execa(cmd, args, opts);
|
|
2024
|
+
return (res.stdout ?? "").toString().trim();
|
|
1442
2025
|
}
|
|
1443
|
-
async function
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
return hasCss ? componentDir : null;
|
|
2026
|
+
async function openUrl(url) {
|
|
2027
|
+
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
2028
|
+
try {
|
|
2029
|
+
await run(cmd, [url], { stdio: "ignore" });
|
|
2030
|
+
} catch {
|
|
2031
|
+
}
|
|
1450
2032
|
}
|
|
1451
|
-
async function
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
const lines = (await readFile3(cssTxt, "utf-8")).split("\n").map((l) => l.trim());
|
|
1458
|
-
let base = clientlibDir;
|
|
1459
|
-
const listed = [];
|
|
1460
|
-
for (const l of lines) {
|
|
1461
|
-
if (!l) continue;
|
|
1462
|
-
const baseMatch = l.match(/^#base\s*=\s*(.+)$/);
|
|
1463
|
-
if (baseMatch) base = path7.join(clientlibDir, baseMatch[1].trim());
|
|
1464
|
-
else if (!l.startsWith("#")) listed.push(l);
|
|
1465
|
-
}
|
|
1466
|
-
cssFiles = listed.map((rel) => path7.join(base, rel)).filter((f) => existsSync5(f) && f.endsWith(".css"));
|
|
1467
|
-
} else {
|
|
1468
|
-
cssFiles = all.filter((f) => f.endsWith(".css"));
|
|
2033
|
+
async function ghLogin() {
|
|
2034
|
+
try {
|
|
2035
|
+
await run("gh", ["auth", "status"]);
|
|
2036
|
+
return await run("gh", ["api", "user", "-q", ".login"]);
|
|
2037
|
+
} catch {
|
|
2038
|
+
return null;
|
|
1469
2039
|
}
|
|
1470
|
-
const parts = [];
|
|
1471
|
-
for (const f of cssFiles) parts.push(await readFile3(f, "utf-8"));
|
|
1472
|
-
return {
|
|
1473
|
-
css: parts.join("\n\n"),
|
|
1474
|
-
files: cssFiles.map((f) => path7.relative(clientlibDir, f)),
|
|
1475
|
-
preprocessed
|
|
1476
|
-
};
|
|
1477
2040
|
}
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
2041
|
+
async function resolveAuth() {
|
|
2042
|
+
const token = await getToken();
|
|
2043
|
+
if (token) {
|
|
2044
|
+
try {
|
|
2045
|
+
return { mode: "api", token, login: await getUser(token) };
|
|
2046
|
+
} catch {
|
|
2047
|
+
}
|
|
1482
2048
|
}
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
import { XMLParser } from "fast-xml-parser";
|
|
1487
|
-
function tagOf(n) {
|
|
1488
|
-
return Object.keys(n).find((k) => k !== ":@") ?? "";
|
|
2049
|
+
const login = await ghLogin();
|
|
2050
|
+
if (login) return { mode: "gh", login };
|
|
2051
|
+
return null;
|
|
1489
2052
|
}
|
|
1490
|
-
function
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
2053
|
+
async function exists(auth, owner, name) {
|
|
2054
|
+
if (auth.mode === "api") return repoExists(auth.token, owner, name);
|
|
2055
|
+
try {
|
|
2056
|
+
await run("gh", ["repo", "view", `${owner}/${name}`, "--json", "name"]);
|
|
2057
|
+
return true;
|
|
2058
|
+
} catch {
|
|
2059
|
+
return false;
|
|
2060
|
+
}
|
|
1494
2061
|
}
|
|
1495
|
-
function
|
|
1496
|
-
|
|
2062
|
+
function noAuth() {
|
|
2063
|
+
logger.error("Not authenticated with GitHub.");
|
|
2064
|
+
logger.info(" Run `eds sandbox login` to authorize GitHub, or install + `gh auth login`.");
|
|
2065
|
+
process.exitCode = 1;
|
|
1497
2066
|
}
|
|
1498
|
-
function
|
|
1499
|
-
|
|
2067
|
+
async function sandboxLogin() {
|
|
2068
|
+
await logger.logoOnceAnimated("Sandbox - GitHub login");
|
|
2069
|
+
const { input } = await import("@inquirer/prompts");
|
|
2070
|
+
if (CLIENT_ID) {
|
|
2071
|
+
const code = await startDeviceFlow(["repo", "delete_repo"]);
|
|
2072
|
+
logger.info(heading("Authorize GitHub"));
|
|
2073
|
+
logger.info(accentLine("code", code.user_code));
|
|
2074
|
+
logger.info(accentLine("open", code.verification_uri));
|
|
2075
|
+
logger.info("");
|
|
2076
|
+
await openUrl(code.verification_uri);
|
|
2077
|
+
logger.info(chalk4.dim(" Waiting for you to authorize in the browser..."));
|
|
2078
|
+
try {
|
|
2079
|
+
const token2 = await pollDeviceToken(code.device_code, code.interval, code.expires_in);
|
|
2080
|
+
await saveToken(token2);
|
|
2081
|
+
logger.success(`Authorized as ${await getUser(token2)}.`);
|
|
2082
|
+
} catch (err) {
|
|
2083
|
+
logger.error(`Device flow failed: ${err.message}`);
|
|
2084
|
+
process.exitCode = 1;
|
|
2085
|
+
}
|
|
2086
|
+
return;
|
|
2087
|
+
}
|
|
2088
|
+
logger.info(heading("Authorize GitHub (least-privilege token)"));
|
|
2089
|
+
logger.info(" Create a fine-grained token limited to your sandboxes only:");
|
|
2090
|
+
logger.info(` ${chalk4.cyan("https://github.com/settings/personal-access-tokens/new")}`);
|
|
2091
|
+
logger.info(
|
|
2092
|
+
chalk4.dim(" - Resource owner: a dedicated sandbox org (recommended) or your account")
|
|
2093
|
+
);
|
|
2094
|
+
logger.info(
|
|
2095
|
+
chalk4.dim(" - Repository access: All repositories in that org (to create new sandboxes),")
|
|
2096
|
+
);
|
|
2097
|
+
logger.info(chalk4.dim(" or Only select repositories to manage existing ones"));
|
|
2098
|
+
logger.info(
|
|
2099
|
+
chalk4.dim(" - Permissions: Administration = Read and write, Contents = Read and write,")
|
|
2100
|
+
);
|
|
2101
|
+
logger.info(chalk4.dim(" Metadata = Read. Set a short expiry."));
|
|
2102
|
+
logger.info(
|
|
2103
|
+
chalk4.dim(
|
|
2104
|
+
" Tip: `eds sandbox new --org <sandbox-org>` so nothing in your personal account is touched."
|
|
2105
|
+
)
|
|
2106
|
+
);
|
|
2107
|
+
logger.info("");
|
|
2108
|
+
if (await (await import("@inquirer/prompts")).confirm({
|
|
2109
|
+
message: "Open the token page?",
|
|
2110
|
+
default: true
|
|
2111
|
+
})) {
|
|
2112
|
+
await openUrl("https://github.com/settings/personal-access-tokens/new");
|
|
2113
|
+
}
|
|
2114
|
+
const token = (await input({ message: "Paste the token", validate: (v) => !!v.trim() || "Required" })).trim();
|
|
2115
|
+
try {
|
|
2116
|
+
const login = await getUser(token);
|
|
2117
|
+
await saveToken(token);
|
|
2118
|
+
logger.success(`Authorized as ${login}.`);
|
|
2119
|
+
} catch {
|
|
2120
|
+
logger.error("That token didn't work (check the scopes).");
|
|
2121
|
+
process.exitCode = 1;
|
|
2122
|
+
}
|
|
1500
2123
|
}
|
|
1501
|
-
function
|
|
1502
|
-
|
|
2124
|
+
async function sandboxLogout() {
|
|
2125
|
+
await clearToken();
|
|
2126
|
+
logger.success("Signed out (stored GitHub token removed).");
|
|
1503
2127
|
}
|
|
1504
|
-
function
|
|
1505
|
-
const
|
|
1506
|
-
if (
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
if (
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
2128
|
+
async function sandboxNew(name, options = {}) {
|
|
2129
|
+
const auth = await resolveAuth();
|
|
2130
|
+
if (!auth) return noAuth();
|
|
2131
|
+
await logger.logoOnceAnimated("Sandbox");
|
|
2132
|
+
const interactive = !options.yes;
|
|
2133
|
+
const { select, confirm } = await import("@inquirer/prompts");
|
|
2134
|
+
let template2 = options.boilerplate;
|
|
2135
|
+
if (!template2) {
|
|
2136
|
+
if (options.ue) template2 = BOILERPLATES.ue;
|
|
2137
|
+
else if (interactive) {
|
|
2138
|
+
template2 = await select({
|
|
2139
|
+
message: "Which boilerplate?",
|
|
2140
|
+
choices: [
|
|
2141
|
+
{ name: "Document Authoring (adobe/aem-boilerplate)", value: BOILERPLATES.document },
|
|
2142
|
+
{
|
|
2143
|
+
name: "Universal Editor / crosswalk (adobe/aem-boilerplate-xwalk)",
|
|
2144
|
+
value: BOILERPLATES.ue
|
|
2145
|
+
}
|
|
2146
|
+
]
|
|
2147
|
+
});
|
|
2148
|
+
} else template2 = BOILERPLATES.document;
|
|
2149
|
+
}
|
|
2150
|
+
let priv = options.private ?? false;
|
|
2151
|
+
if (options.private === void 0 && interactive) {
|
|
2152
|
+
priv = await select({
|
|
2153
|
+
message: "Visibility?",
|
|
2154
|
+
choices: [
|
|
2155
|
+
{ name: "Public", value: false },
|
|
2156
|
+
{ name: "Private", value: true }
|
|
2157
|
+
],
|
|
2158
|
+
default: false
|
|
2159
|
+
});
|
|
2160
|
+
}
|
|
2161
|
+
const owner = options.org ?? auth.login;
|
|
2162
|
+
if (await exists(auth, owner, name)) {
|
|
2163
|
+
logger.error(`A repository ${owner}/${name} already exists - refusing to touch it.`);
|
|
2164
|
+
logger.info(
|
|
2165
|
+
chalk4.dim(" Pick another name, or remove it first (e.g. `eds sandbox rm` for a sandbox).")
|
|
2166
|
+
);
|
|
2167
|
+
process.exitCode = 1;
|
|
2168
|
+
return;
|
|
2169
|
+
}
|
|
2170
|
+
logger.info(heading("Creating sandbox", `${owner}/${name} \xB7 ${template2}`));
|
|
2171
|
+
try {
|
|
2172
|
+
if (auth.mode === "api") {
|
|
2173
|
+
const repo = await createFromTemplate(auth.token, template2, { owner, name, private: priv });
|
|
2174
|
+
await addTopics(auth.token, owner, name, [SANDBOX_TOPIC]);
|
|
2175
|
+
logger.info(chalk4.dim(" Cloning..."));
|
|
2176
|
+
await run("git", ["clone", repo.cloneUrl, name], { stdio: "inherit" });
|
|
2177
|
+
} else {
|
|
2178
|
+
const repoRef = options.org ? `${options.org}/${name}` : name;
|
|
2179
|
+
await run("gh", createArgs(repoRef, { template: template2, private: priv, clone: true }), {
|
|
2180
|
+
stdio: "inherit"
|
|
2181
|
+
});
|
|
2182
|
+
try {
|
|
2183
|
+
await run("gh", ["repo", "edit", `${owner}/${name}`, "--add-topic", SANDBOX_TOPIC]);
|
|
2184
|
+
} catch {
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2187
|
+
} catch (err) {
|
|
2188
|
+
logger.error(`Create failed: ${err.message}`);
|
|
2189
|
+
process.exitCode = 1;
|
|
2190
|
+
return;
|
|
2191
|
+
}
|
|
2192
|
+
logger.info("");
|
|
2193
|
+
logger.info(heading("Sandbox ready"));
|
|
2194
|
+
logger.info(accentLine("repo", `https://github.com/${owner}/${name}`));
|
|
2195
|
+
logger.info(accentLine("preview", previewUrl(owner, name)));
|
|
2196
|
+
logger.info("");
|
|
2197
|
+
if (interactive) {
|
|
2198
|
+
if (await confirm({ message: "Open the AEM Code Sync install page now?", default: true })) {
|
|
2199
|
+
await openUrl(codeSyncInstallUrl());
|
|
2200
|
+
}
|
|
2201
|
+
if (await confirm({ message: `Configure authoring now (eds init in ./${name})?`, default: true })) {
|
|
2202
|
+
const prevCwd = process.cwd();
|
|
2203
|
+
try {
|
|
2204
|
+
process.chdir(path11.join(prevCwd, name));
|
|
2205
|
+
const { initProject: initProject2 } = await Promise.resolve().then(() => (init_init(), init_exports));
|
|
2206
|
+
await initProject2({});
|
|
2207
|
+
} finally {
|
|
2208
|
+
process.chdir(prevCwd);
|
|
2209
|
+
}
|
|
2210
|
+
}
|
|
2211
|
+
} else {
|
|
2212
|
+
logger.info(chalk4.dim(` Install AEM Code Sync: ${codeSyncInstallUrl()}`));
|
|
2213
|
+
logger.info(chalk4.dim(` Configure authoring: cd ${name} && eds init`));
|
|
2214
|
+
}
|
|
2215
|
+
logger.info("");
|
|
2216
|
+
logger.info(box([`Sandbox "${name}" ready - tear down with \`eds sandbox rm ${name}\``]));
|
|
1521
2217
|
}
|
|
1522
|
-
function
|
|
1523
|
-
|
|
2218
|
+
async function sandboxList() {
|
|
2219
|
+
const auth = await resolveAuth();
|
|
2220
|
+
if (!auth) return noAuth();
|
|
2221
|
+
let names = [];
|
|
2222
|
+
try {
|
|
2223
|
+
if (auth.mode === "api") {
|
|
2224
|
+
names = await searchByTopic(auth.token, auth.login, SANDBOX_TOPIC);
|
|
2225
|
+
} else {
|
|
2226
|
+
const out = await run("gh", [
|
|
2227
|
+
"repo",
|
|
2228
|
+
"list",
|
|
2229
|
+
"--topic",
|
|
2230
|
+
SANDBOX_TOPIC,
|
|
2231
|
+
"--limit",
|
|
2232
|
+
"100",
|
|
2233
|
+
"--json",
|
|
2234
|
+
"nameWithOwner",
|
|
2235
|
+
"-q",
|
|
2236
|
+
".[].nameWithOwner"
|
|
2237
|
+
]);
|
|
2238
|
+
names = out ? out.split("\n").filter(Boolean) : [];
|
|
2239
|
+
}
|
|
2240
|
+
} catch (err) {
|
|
2241
|
+
logger.error(`List failed: ${err.message}`);
|
|
2242
|
+
process.exitCode = 1;
|
|
2243
|
+
return;
|
|
2244
|
+
}
|
|
2245
|
+
logger.info(heading("Sandboxes", `${names.length} tagged ${SANDBOX_TOPIC}`));
|
|
2246
|
+
if (names.length === 0) {
|
|
2247
|
+
logger.info(chalk4.dim(" None yet. Create one with `eds sandbox new <name>`."));
|
|
2248
|
+
return;
|
|
2249
|
+
}
|
|
2250
|
+
const w = columnWidth(names);
|
|
2251
|
+
for (const full of names) {
|
|
2252
|
+
const [o, n2] = full.split("/");
|
|
2253
|
+
logger.info(accentLine(full, previewUrl(o, n2), w));
|
|
2254
|
+
}
|
|
1524
2255
|
}
|
|
1525
|
-
function
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
2256
|
+
async function sandboxRm(name, options = {}) {
|
|
2257
|
+
const auth = await resolveAuth();
|
|
2258
|
+
if (!auth) return noAuth();
|
|
2259
|
+
const owner = options.org ?? auth.login;
|
|
2260
|
+
const repoRef = `${owner}/${name}`;
|
|
2261
|
+
if (!options.yes) {
|
|
2262
|
+
const { confirm } = await import("@inquirer/prompts");
|
|
2263
|
+
const go = await confirm({
|
|
2264
|
+
message: `Permanently delete ${repoRef}? This cannot be undone.`,
|
|
2265
|
+
default: false
|
|
2266
|
+
});
|
|
2267
|
+
if (!go) {
|
|
2268
|
+
logger.info("Cancelled.");
|
|
2269
|
+
return;
|
|
2270
|
+
}
|
|
2271
|
+
}
|
|
2272
|
+
try {
|
|
2273
|
+
if (auth.mode === "api") await deleteRepo(auth.token, owner, name);
|
|
2274
|
+
else await run("gh", ["repo", "delete", repoRef, "--yes"]);
|
|
2275
|
+
} catch (err) {
|
|
2276
|
+
logger.error(`Delete failed: ${err.message}`);
|
|
2277
|
+
logger.info(
|
|
2278
|
+
chalk4.dim(
|
|
2279
|
+
" Deleting needs the delete_repo scope (re-run `eds sandbox login`, or `gh auth refresh -s delete_repo`)."
|
|
2280
|
+
)
|
|
2281
|
+
);
|
|
2282
|
+
process.exitCode = 1;
|
|
2283
|
+
return;
|
|
2284
|
+
}
|
|
2285
|
+
logger.info(heading("Deleted"));
|
|
2286
|
+
logger.info(accentLine(repoRef, "removed"));
|
|
2287
|
+
}
|
|
2288
|
+
var init_sandbox = __esm({
|
|
2289
|
+
"src/commands/sandbox.ts"() {
|
|
2290
|
+
"use strict";
|
|
2291
|
+
init_esm_shims();
|
|
2292
|
+
init_logger();
|
|
2293
|
+
init_api();
|
|
2294
|
+
init_auth();
|
|
2295
|
+
init_github();
|
|
2296
|
+
init_ui();
|
|
2297
|
+
}
|
|
2298
|
+
});
|
|
2299
|
+
|
|
2300
|
+
// src/lib/template/authoring.ts
|
|
2301
|
+
import { existsSync as existsSync9 } from "fs";
|
|
2302
|
+
import { readFile as readFile5 } from "fs/promises";
|
|
2303
|
+
import path12 from "path";
|
|
2304
|
+
import { parse as parseYaml } from "yaml";
|
|
2305
|
+
async function detectAuthoring(projectRoot) {
|
|
2306
|
+
const fstab = path12.join(projectRoot, "fstab.yaml");
|
|
2307
|
+
if (existsSync9(fstab)) {
|
|
2308
|
+
try {
|
|
2309
|
+
const parsed = parseYaml(await readFile5(fstab, "utf-8")) ?? {};
|
|
2310
|
+
const mp = parsed.mountpoints?.["/"];
|
|
2311
|
+
const url = typeof mp === "string" ? mp : mp?.url ?? "";
|
|
2312
|
+
const da = url.match(/content\.da\.live\/([^/]+)\/([^/]+)/);
|
|
2313
|
+
if (da) return { model: "da", org: da[1], site: da[2], mountpoint: url };
|
|
2314
|
+
if (/drive\.google\.com/.test(url)) return { model: "gdrive", mountpoint: url };
|
|
2315
|
+
if (/sharepoint\.com|-my\.sharepoint/.test(url))
|
|
2316
|
+
return { model: "sharepoint", mountpoint: url };
|
|
2317
|
+
return { model: "document", mountpoint: url };
|
|
2318
|
+
} catch {
|
|
2319
|
+
}
|
|
2320
|
+
}
|
|
2321
|
+
return { model: "ue" };
|
|
2322
|
+
}
|
|
2323
|
+
var init_authoring = __esm({
|
|
2324
|
+
"src/lib/template/authoring.ts"() {
|
|
2325
|
+
"use strict";
|
|
2326
|
+
init_esm_shims();
|
|
2327
|
+
}
|
|
2328
|
+
});
|
|
2329
|
+
|
|
2330
|
+
// src/lib/template/da.ts
|
|
2331
|
+
function daSourceUrl(org, site, path36) {
|
|
2332
|
+
const clean = path36.replace(/^\/+/, "").replace(/\.html$/i, "");
|
|
2333
|
+
return `https://admin.da.live/source/${org}/${site}/${clean}.html`;
|
|
2334
|
+
}
|
|
2335
|
+
function daEditUrl(org, site, path36) {
|
|
2336
|
+
const clean = path36.replace(/^\/+/, "").replace(/\.html$/i, "");
|
|
2337
|
+
return `https://da.live/edit#/${org}/${site}/${clean}`;
|
|
2338
|
+
}
|
|
2339
|
+
async function pushToDa(org, site, path36, html, token) {
|
|
2340
|
+
const url = daSourceUrl(org, site, path36);
|
|
2341
|
+
const form = new FormData();
|
|
2342
|
+
form.append("data", new Blob([html], { type: "text/html" }), "content.html");
|
|
2343
|
+
const headers2 = {};
|
|
2344
|
+
if (token) headers2.Authorization = `Bearer ${token}`;
|
|
2345
|
+
const res = await fetch(url, { method: "POST", body: form, headers: headers2 });
|
|
2346
|
+
if (!res.ok) {
|
|
2347
|
+
throw new Error(`DA push failed: ${res.status} ${res.statusText}`);
|
|
2348
|
+
}
|
|
2349
|
+
return url;
|
|
2350
|
+
}
|
|
2351
|
+
var init_da = __esm({
|
|
2352
|
+
"src/lib/template/da.ts"() {
|
|
2353
|
+
"use strict";
|
|
2354
|
+
init_esm_shims();
|
|
2355
|
+
}
|
|
2356
|
+
});
|
|
2357
|
+
|
|
2358
|
+
// src/lib/template/page.ts
|
|
2359
|
+
function blockDiv(name) {
|
|
2360
|
+
return ` <div class="${name}">
|
|
2361
|
+
<div>
|
|
2362
|
+
<div>${name} content</div>
|
|
2363
|
+
</div>
|
|
2364
|
+
</div>`;
|
|
2365
|
+
}
|
|
2366
|
+
function metadataBlock(opts) {
|
|
2367
|
+
const rows = [
|
|
2368
|
+
` <div><div>Title</div><div>${opts.title}</div></div>`,
|
|
2369
|
+
` <div><div>Description</div><div>${opts.description ?? ""}</div></div>`
|
|
2370
|
+
];
|
|
2371
|
+
if (opts.area) rows.push(` <div><div>Template</div><div>${opts.area}</div></div>`);
|
|
2372
|
+
return ` <div class="metadata">
|
|
2373
|
+
${rows.join("\n")}
|
|
2374
|
+
</div>`;
|
|
2375
|
+
}
|
|
2376
|
+
function buildPageHtml(opts) {
|
|
2377
|
+
const blocks = opts.blocks ?? [];
|
|
2378
|
+
const intro = ` <div>
|
|
2379
|
+
<h1>${opts.title}</h1>
|
|
2380
|
+
<p>${opts.description ?? "Intro copy goes here."}</p>
|
|
2381
|
+
</div>`;
|
|
2382
|
+
const blockSections = blocks.map((b) => ` <div>
|
|
2383
|
+
${blockDiv(b)}
|
|
2384
|
+
</div>`);
|
|
2385
|
+
const metaSection = ` <div>
|
|
2386
|
+
${metadataBlock(opts)}
|
|
2387
|
+
</div>`;
|
|
2388
|
+
return `<body>
|
|
2389
|
+
<header></header>
|
|
2390
|
+
<main>
|
|
2391
|
+
${[intro, ...blockSections, metaSection].join("\n")}
|
|
2392
|
+
</main>
|
|
2393
|
+
<footer></footer>
|
|
2394
|
+
</body>
|
|
2395
|
+
`;
|
|
2396
|
+
}
|
|
2397
|
+
var init_page = __esm({
|
|
2398
|
+
"src/lib/template/page.ts"() {
|
|
2399
|
+
"use strict";
|
|
2400
|
+
init_esm_shims();
|
|
2401
|
+
}
|
|
2402
|
+
});
|
|
2403
|
+
|
|
2404
|
+
// src/lib/template/ue.ts
|
|
2405
|
+
import { existsSync as existsSync10 } from "fs";
|
|
2406
|
+
import { readFile as readFile6 } from "fs/promises";
|
|
2407
|
+
import path13 from "path";
|
|
2408
|
+
function fieldDefault(field) {
|
|
2409
|
+
if (field.value !== void 0) return field.value;
|
|
2410
|
+
switch (field.valueType) {
|
|
1529
2411
|
case "boolean":
|
|
1530
|
-
return
|
|
1531
|
-
case "
|
|
1532
|
-
return
|
|
1533
|
-
case "
|
|
1534
|
-
return
|
|
1535
|
-
case "aem-content":
|
|
1536
|
-
case "aem-content-fragment":
|
|
1537
|
-
case "aem-experience-fragment":
|
|
1538
|
-
return void 0;
|
|
2412
|
+
return false;
|
|
2413
|
+
case "number":
|
|
2414
|
+
return 0;
|
|
2415
|
+
case "string[]":
|
|
2416
|
+
return [];
|
|
1539
2417
|
default:
|
|
1540
|
-
return "
|
|
2418
|
+
return "";
|
|
1541
2419
|
}
|
|
1542
2420
|
}
|
|
1543
|
-
function
|
|
1544
|
-
const
|
|
1545
|
-
|
|
2421
|
+
async function blockDefaults(projectRoot, block2) {
|
|
2422
|
+
const file = path13.join(projectRoot, "blocks", block2, `_${block2}.json`);
|
|
2423
|
+
if (!existsSync10(file)) return {};
|
|
2424
|
+
try {
|
|
2425
|
+
const parsed = JSON.parse(await readFile6(file, "utf-8"));
|
|
2426
|
+
const models = Array.isArray(parsed.models) ? parsed.models : [];
|
|
2427
|
+
const model2 = models.find((m) => m.id === block2) ?? models[0];
|
|
2428
|
+
const fields = model2?.fields ?? [];
|
|
2429
|
+
const out = {};
|
|
2430
|
+
for (const f2 of fields) {
|
|
2431
|
+
if (f2.component === "tab") continue;
|
|
2432
|
+
out[f2.name] = f2.component === "container" ? [] : fieldDefault(f2);
|
|
2433
|
+
}
|
|
2434
|
+
return out;
|
|
2435
|
+
} catch {
|
|
2436
|
+
return {};
|
|
2437
|
+
}
|
|
1546
2438
|
}
|
|
1547
|
-
function
|
|
1548
|
-
const
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
2439
|
+
async function buildUeTemplate(projectRoot, opts) {
|
|
2440
|
+
const components = [];
|
|
2441
|
+
for (const b of opts.blocks) {
|
|
2442
|
+
components.push({ name: b, model: b, fields: await blockDefaults(projectRoot, b) });
|
|
2443
|
+
}
|
|
2444
|
+
const template2 = { title: opts.title, template: opts.name, components };
|
|
2445
|
+
return `${JSON.stringify(template2, null, 2)}
|
|
2446
|
+
`;
|
|
1555
2447
|
}
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
2448
|
+
var init_ue2 = __esm({
|
|
2449
|
+
"src/lib/template/ue.ts"() {
|
|
2450
|
+
"use strict";
|
|
2451
|
+
init_esm_shims();
|
|
2452
|
+
}
|
|
2453
|
+
});
|
|
2454
|
+
|
|
2455
|
+
// src/commands/template.ts
|
|
2456
|
+
var template_exports = {};
|
|
2457
|
+
__export(template_exports, {
|
|
2458
|
+
templateNew: () => templateNew
|
|
2459
|
+
});
|
|
2460
|
+
import { existsSync as existsSync11 } from "fs";
|
|
2461
|
+
import { mkdir as mkdir5, readFile as readFile7, readdir, writeFile as writeFile9 } from "fs/promises";
|
|
2462
|
+
import path14 from "path";
|
|
2463
|
+
import chalk5 from "chalk";
|
|
2464
|
+
async function projectBlocks(projectRoot) {
|
|
2465
|
+
const dir = path14.join(projectRoot, "blocks");
|
|
2466
|
+
if (!existsSync11(dir)) return [];
|
|
2467
|
+
return (await readdir(dir, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
2468
|
+
}
|
|
2469
|
+
async function readEdsrcAdmin(projectRoot) {
|
|
2470
|
+
const rc = path14.join(projectRoot, ".edsrc.json");
|
|
2471
|
+
if (!existsSync11(rc)) return {};
|
|
2472
|
+
try {
|
|
2473
|
+
return JSON.parse(await readFile7(rc, "utf-8")).admin ?? {};
|
|
2474
|
+
} catch {
|
|
2475
|
+
return {};
|
|
2476
|
+
}
|
|
2477
|
+
}
|
|
2478
|
+
async function registerBlocks(projectRoot, blocks) {
|
|
2479
|
+
if (!blocks.length) return false;
|
|
2480
|
+
const file = path14.join(projectRoot, "component-filters.json");
|
|
2481
|
+
let existing = null;
|
|
2482
|
+
if (existsSync11(file)) {
|
|
2483
|
+
try {
|
|
2484
|
+
existing = JSON.parse(await readFile7(file, "utf-8"));
|
|
2485
|
+
} catch {
|
|
2486
|
+
existing = null;
|
|
1567
2487
|
}
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
2488
|
+
}
|
|
2489
|
+
const filters = mergeSectionFilter(existing, blocks);
|
|
2490
|
+
await writeFile9(file, `${JSON.stringify(filters, null, 2)}
|
|
2491
|
+
`);
|
|
2492
|
+
return true;
|
|
2493
|
+
}
|
|
2494
|
+
async function templateNew(nameArg, options = {}) {
|
|
2495
|
+
const projectRoot = findProjectRoot();
|
|
2496
|
+
if (!projectRoot) {
|
|
2497
|
+
logger.error("Not inside an EDS project (no EDS project markers found).");
|
|
2498
|
+
process.exitCode = 1;
|
|
2499
|
+
return;
|
|
2500
|
+
}
|
|
2501
|
+
await logger.logoOnceAnimated("Page template");
|
|
2502
|
+
const { input, select, confirm } = await import("@inquirer/prompts");
|
|
2503
|
+
const interactive = !options.yes;
|
|
2504
|
+
const detected = await detectAuthoring(projectRoot);
|
|
2505
|
+
const model2 = options.authoring ?? detected.model;
|
|
2506
|
+
const name = slug2(nameArg);
|
|
2507
|
+
const title = options.title ?? (interactive ? await input({ message: "Page title", default: nameArg }) : nameArg);
|
|
2508
|
+
const description = options.description ?? (interactive ? await input({ message: "Description (optional)" }) : "");
|
|
2509
|
+
const pagePath = options.path ?? `/templates/${name}`;
|
|
2510
|
+
let blocks = options.blocks;
|
|
2511
|
+
if (!blocks && interactive) {
|
|
2512
|
+
const available = await projectBlocks(projectRoot);
|
|
2513
|
+
const pool = available.length ? available : STANDARD_BLOCKS.map((b) => b.id);
|
|
2514
|
+
const ordered = [];
|
|
2515
|
+
let adding = true;
|
|
2516
|
+
while (adding) {
|
|
2517
|
+
const pick = await select({
|
|
2518
|
+
message: `Add a block in order (${ordered.length} so far)`,
|
|
2519
|
+
choices: [
|
|
2520
|
+
...pool.map((b) => ({ name: b, value: b })),
|
|
2521
|
+
{ name: ordered.length ? "- done -" : "- done (empty page) -", value: "__done" }
|
|
2522
|
+
]
|
|
1579
2523
|
});
|
|
1580
|
-
|
|
2524
|
+
if (pick === "__done") adding = false;
|
|
2525
|
+
else ordered.push(pick);
|
|
1581
2526
|
}
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
2527
|
+
blocks = ordered;
|
|
2528
|
+
}
|
|
2529
|
+
const seeded = blocks ?? [];
|
|
2530
|
+
const html = buildPageHtml({
|
|
2531
|
+
title,
|
|
2532
|
+
description: description || void 0,
|
|
2533
|
+
blocks: seeded,
|
|
2534
|
+
area: options.area
|
|
2535
|
+
});
|
|
2536
|
+
const localRel = path14.join("templates", `${name}.html`);
|
|
2537
|
+
await mkdir5(path14.join(projectRoot, "templates"), { recursive: true });
|
|
2538
|
+
await writeFile9(path14.join(projectRoot, localRel), html);
|
|
2539
|
+
logger.info(heading("Page template", `${pagePath} \xB7 ${model2}`));
|
|
2540
|
+
logger.info(accentLine(localRel, `${seeded.length} block(s) + metadata`));
|
|
2541
|
+
if (model2 === "da") {
|
|
2542
|
+
const admin = await readEdsrcAdmin(projectRoot);
|
|
2543
|
+
const org = options.org ?? admin.org ?? detected.org;
|
|
2544
|
+
const site = options.site ?? admin.site ?? detected.site;
|
|
2545
|
+
const wantPush = options.push ?? (interactive ? await confirm({ message: "Push to DA (da.live)?", default: true }) : true);
|
|
2546
|
+
if (wantPush) {
|
|
2547
|
+
if (!org || !site) {
|
|
2548
|
+
logger.warn("No DA org/site. Pass --org/--site or set admin in .edsrc.json.");
|
|
2549
|
+
} else {
|
|
2550
|
+
try {
|
|
2551
|
+
await pushToDa(org, site, pagePath, html, process.env.DA_TOKEN);
|
|
2552
|
+
logger.info(accentLine("pushed to DA", daEditUrl(org, site, pagePath)));
|
|
2553
|
+
} catch (err) {
|
|
2554
|
+
logger.error(`DA push failed: ${err.message}`);
|
|
2555
|
+
logger.info(chalk5.dim(" Open projects allow anonymous writes; others need DA_TOKEN."));
|
|
2556
|
+
}
|
|
1593
2557
|
}
|
|
1594
|
-
const def = attr(node, "value") ?? (comp === "boolean" ? attr(node, "checked") : void 0);
|
|
1595
|
-
if (def !== void 0 && def !== "") field.value = comp === "boolean" ? def === "true" : def;
|
|
1596
|
-
out.push(field);
|
|
1597
|
-
continue;
|
|
1598
2558
|
}
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
2559
|
+
} else if (model2 === "gdrive" || model2 === "sharepoint") {
|
|
2560
|
+
const where = model2 === "gdrive" ? "a Google Doc" : "a Word doc";
|
|
2561
|
+
logger.info("");
|
|
2562
|
+
logger.info(
|
|
2563
|
+
chalk5.dim(` Document model: import ${localRel} into ${where} in your mounted folder,`)
|
|
2564
|
+
);
|
|
2565
|
+
logger.info(
|
|
2566
|
+
chalk5.dim(" then preview/publish. (Share the folder + install aem-code-sync first.)")
|
|
2567
|
+
);
|
|
2568
|
+
} else if (model2 === "ue") {
|
|
2569
|
+
const jsonRel = path14.join("templates", `${name}.json`);
|
|
2570
|
+
await writeFile9(
|
|
2571
|
+
path14.join(projectRoot, jsonRel),
|
|
2572
|
+
await buildUeTemplate(projectRoot, { title, name, blocks: seeded })
|
|
2573
|
+
);
|
|
2574
|
+
logger.info(
|
|
2575
|
+
accentLine(jsonRel, `initial content (${seeded.length} component(s), in order)`)
|
|
2576
|
+
);
|
|
2577
|
+
const registered = await registerBlocks(projectRoot, seeded);
|
|
2578
|
+
if (registered) {
|
|
2579
|
+
logger.info(
|
|
2580
|
+
accentLine("component-filters.json", "seeded blocks allowed in Universal Editor")
|
|
2581
|
+
);
|
|
2582
|
+
}
|
|
2583
|
+
logger.info("");
|
|
2584
|
+
logger.info(
|
|
2585
|
+
chalk5.dim(" Universal Editor: use the template JSON as the page's initial content, then")
|
|
2586
|
+
);
|
|
2587
|
+
logger.info(chalk5.dim(" author it in the Universal Editor (wire the repo to AEM)."));
|
|
2588
|
+
}
|
|
2589
|
+
logger.info("");
|
|
2590
|
+
logger.info(box([`"${name}" template (${model2}) - preview with \`eds preview ${pagePath}\``]));
|
|
2591
|
+
}
|
|
2592
|
+
var slug2;
|
|
2593
|
+
var init_template = __esm({
|
|
2594
|
+
"src/commands/template.ts"() {
|
|
2595
|
+
"use strict";
|
|
2596
|
+
init_esm_shims();
|
|
2597
|
+
init_logger();
|
|
2598
|
+
init_project();
|
|
2599
|
+
init_blocks();
|
|
2600
|
+
init_ue();
|
|
2601
|
+
init_authoring();
|
|
2602
|
+
init_da();
|
|
2603
|
+
init_page();
|
|
2604
|
+
init_ue2();
|
|
2605
|
+
init_ui();
|
|
2606
|
+
slug2 = (s) => s.trim().toLowerCase().replace(/[^a-z0-9-/]+/g, "-").replace(/^-+|-+$/g, "");
|
|
2607
|
+
}
|
|
2608
|
+
});
|
|
2609
|
+
|
|
2610
|
+
// src/lib/migrate/clientlib.ts
|
|
2611
|
+
import { existsSync as existsSync12 } from "fs";
|
|
2612
|
+
import { readFile as readFile8, readdir as readdir2 } from "fs/promises";
|
|
2613
|
+
import path15 from "path";
|
|
2614
|
+
async function listFiles(dir) {
|
|
2615
|
+
const out = [];
|
|
2616
|
+
const walk3 = async (d) => {
|
|
2617
|
+
for (const e of await readdir2(d, { withFileTypes: true })) {
|
|
2618
|
+
const p = path15.join(d, e.name);
|
|
2619
|
+
if (e.isDirectory()) await walk3(p);
|
|
2620
|
+
else out.push(p);
|
|
2621
|
+
}
|
|
2622
|
+
};
|
|
2623
|
+
if (existsSync12(dir)) await walk3(dir);
|
|
2624
|
+
return out;
|
|
2625
|
+
}
|
|
2626
|
+
async function findClientlibDir(componentDir) {
|
|
2627
|
+
if (!existsSync12(componentDir)) return null;
|
|
2628
|
+
const entries = await readdir2(componentDir, { withFileTypes: true });
|
|
2629
|
+
const match = entries.find((e) => e.isDirectory() && /clientlib/i.test(e.name));
|
|
2630
|
+
if (match) return path15.join(componentDir, match.name);
|
|
2631
|
+
const hasCss = entries.some((e) => e.isFile() && e.name.endsWith(".css"));
|
|
2632
|
+
return hasCss ? componentDir : null;
|
|
2633
|
+
}
|
|
2634
|
+
async function readClientlibCss(clientlibDir) {
|
|
2635
|
+
const all = await listFiles(clientlibDir);
|
|
2636
|
+
const preprocessed = all.filter((f2) => /\.(less|scss|sass)$/i.test(f2)).map((f2) => path15.relative(clientlibDir, f2));
|
|
2637
|
+
let cssFiles;
|
|
2638
|
+
const cssTxt = path15.join(clientlibDir, "css.txt");
|
|
2639
|
+
if (existsSync12(cssTxt)) {
|
|
2640
|
+
const lines = (await readFile8(cssTxt, "utf-8")).split("\n").map((l) => l.trim());
|
|
2641
|
+
let base = clientlibDir;
|
|
2642
|
+
const listed = [];
|
|
2643
|
+
for (const l of lines) {
|
|
2644
|
+
if (!l) continue;
|
|
2645
|
+
const baseMatch = l.match(/^#base\s*=\s*(.+)$/);
|
|
2646
|
+
if (baseMatch) base = path15.join(clientlibDir, baseMatch[1].trim());
|
|
2647
|
+
else if (!l.startsWith("#")) listed.push(l);
|
|
2648
|
+
}
|
|
2649
|
+
cssFiles = listed.map((rel) => path15.join(base, rel)).filter((f2) => existsSync12(f2) && f2.endsWith(".css"));
|
|
2650
|
+
} else {
|
|
2651
|
+
cssFiles = all.filter((f2) => f2.endsWith(".css"));
|
|
2652
|
+
}
|
|
2653
|
+
const parts = [];
|
|
2654
|
+
for (const f2 of cssFiles) parts.push(await readFile8(f2, "utf-8"));
|
|
2655
|
+
return {
|
|
2656
|
+
css: parts.join("\n\n"),
|
|
2657
|
+
files: cssFiles.map((f2) => path15.relative(clientlibDir, f2)),
|
|
2658
|
+
preprocessed
|
|
2659
|
+
};
|
|
2660
|
+
}
|
|
2661
|
+
var init_clientlib = __esm({
|
|
2662
|
+
"src/lib/migrate/clientlib.ts"() {
|
|
2663
|
+
"use strict";
|
|
2664
|
+
init_esm_shims();
|
|
2665
|
+
}
|
|
2666
|
+
});
|
|
2667
|
+
|
|
2668
|
+
// src/lib/migrate/dialog.ts
|
|
2669
|
+
import { XMLParser } from "fast-xml-parser";
|
|
2670
|
+
function tagOf(n2) {
|
|
2671
|
+
return Object.keys(n2).find((k) => k !== ":@") ?? "";
|
|
2672
|
+
}
|
|
2673
|
+
function childrenOf(n2) {
|
|
2674
|
+
const t = tagOf(n2);
|
|
2675
|
+
const v = n2[t];
|
|
2676
|
+
return Array.isArray(v) ? v : [];
|
|
2677
|
+
}
|
|
2678
|
+
function attrsOf(n2) {
|
|
2679
|
+
return n2[":@"] ?? {};
|
|
2680
|
+
}
|
|
2681
|
+
function stripType(v) {
|
|
2682
|
+
return typeof v === "string" ? v.replace(/^\{[^}]+\}/, "") : v;
|
|
2683
|
+
}
|
|
2684
|
+
function attr(n2, name) {
|
|
2685
|
+
return stripType(attrsOf(n2)[`@_${name}`]);
|
|
2686
|
+
}
|
|
2687
|
+
function leafComponent(rt, n2) {
|
|
2688
|
+
const multiple = attr(n2, "multiple") === "true" || attr(n2, "multiple") === "{Boolean}true";
|
|
2689
|
+
if (rt.endsWith("/form/textfield")) return "text";
|
|
2690
|
+
if (rt.endsWith("/form/textarea")) return "textarea";
|
|
2691
|
+
if (rt.includes("/richtext")) return "richtext";
|
|
2692
|
+
if (rt.endsWith("/form/numberfield")) return "number";
|
|
2693
|
+
if (rt.endsWith("/form/checkbox")) return "boolean";
|
|
2694
|
+
if (rt.endsWith("/form/radiogroup")) return "radio-group";
|
|
2695
|
+
if (rt.endsWith("/form/select")) return multiple ? "multiselect" : "select";
|
|
2696
|
+
if (rt.endsWith("/form/datepicker")) return "date-time";
|
|
2697
|
+
if (rt.endsWith("/form/pathfield") || rt.endsWith("/form/pathbrowser")) return "aem-content";
|
|
2698
|
+
if (rt.includes("/fileupload") || rt.endsWith("/form/fileupload")) return "reference";
|
|
2699
|
+
if (rt.includes("tagfield") || rt.includes("tagpicker") || rt.includes("tagbrowser"))
|
|
2700
|
+
return "aem-tag";
|
|
2701
|
+
if (rt.includes("contentfragment")) return "aem-content-fragment";
|
|
2702
|
+
if (rt.includes("experiencefragment")) return "aem-experience-fragment";
|
|
2703
|
+
return null;
|
|
2704
|
+
}
|
|
2705
|
+
function isWrapper(rt) {
|
|
2706
|
+
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");
|
|
2707
|
+
}
|
|
2708
|
+
function valueTypeFor(c) {
|
|
2709
|
+
switch (c) {
|
|
2710
|
+
case "number":
|
|
2711
|
+
return "number";
|
|
2712
|
+
case "boolean":
|
|
2713
|
+
return "boolean";
|
|
2714
|
+
case "date-time":
|
|
2715
|
+
return "date";
|
|
2716
|
+
case "checkbox-group":
|
|
2717
|
+
return "string[]";
|
|
2718
|
+
case "aem-content":
|
|
2719
|
+
case "aem-content-fragment":
|
|
2720
|
+
case "aem-experience-fragment":
|
|
2721
|
+
return void 0;
|
|
2722
|
+
default:
|
|
2723
|
+
return "string";
|
|
2724
|
+
}
|
|
2725
|
+
}
|
|
2726
|
+
function cleanName(raw, fallback) {
|
|
2727
|
+
const n2 = (raw ?? "").replace(/^\.\//, "").trim();
|
|
2728
|
+
return n2 || fallback;
|
|
2729
|
+
}
|
|
2730
|
+
function readOptions(fieldNode) {
|
|
2731
|
+
const items = childrenOf(fieldNode).find((c) => tagOf(c) === "items");
|
|
2732
|
+
if (!items) return [];
|
|
2733
|
+
return childrenOf(items).map((opt) => {
|
|
2734
|
+
const value = attr(opt, "value") ?? "";
|
|
2735
|
+
const name = attr(opt, "text") ?? attr(opt, "jcr:title") ?? value;
|
|
2736
|
+
return { name, value };
|
|
2737
|
+
});
|
|
2738
|
+
}
|
|
2739
|
+
function walk(nodes, out, unmapped, counter) {
|
|
2740
|
+
for (const node of nodes) {
|
|
2741
|
+
const rt = attr(node, "sling:resourceType") ?? "";
|
|
2742
|
+
if (rt.endsWith("/foundation/tabs")) {
|
|
2743
|
+
const items = childrenOf(node).find((c) => tagOf(c) === "items");
|
|
2744
|
+
for (const tab of items ? childrenOf(items) : []) {
|
|
2745
|
+
const label = attr(tab, "jcr:title");
|
|
2746
|
+
out.push({ component: "tab", name: cleanName(void 0, tagOf(tab)), label });
|
|
2747
|
+
walk(childrenOf(tab), out, unmapped, counter);
|
|
2748
|
+
}
|
|
2749
|
+
continue;
|
|
2750
|
+
}
|
|
2751
|
+
if (rt.endsWith("/form/multifield")) {
|
|
2752
|
+
const sub = [];
|
|
2753
|
+
walk(childrenOf(node), sub, unmapped, counter);
|
|
2754
|
+
const name = cleanName(attr(node, "name"), `items${counter.i++}`);
|
|
2755
|
+
out.push({
|
|
2756
|
+
component: "container",
|
|
2757
|
+
name,
|
|
2758
|
+
label: attr(node, "fieldLabel") ?? "Items",
|
|
2759
|
+
multi: true,
|
|
2760
|
+
collapsible: true,
|
|
2761
|
+
fields: sub
|
|
2762
|
+
});
|
|
2763
|
+
continue;
|
|
2764
|
+
}
|
|
2765
|
+
const comp = rt ? leafComponent(rt, node) : null;
|
|
2766
|
+
if (comp) {
|
|
2767
|
+
const name = cleanName(attr(node, "name"), `field${counter.i++}`);
|
|
2768
|
+
const field = { component: comp, name };
|
|
2769
|
+
const vt = valueTypeFor(comp);
|
|
2770
|
+
if (vt) field.valueType = vt;
|
|
2771
|
+
const label = attr(node, "fieldLabel") ?? attr(node, "text");
|
|
2772
|
+
if (label) field.label = label;
|
|
2773
|
+
if (comp === "select" || comp === "radio-group" || comp === "multiselect") {
|
|
2774
|
+
const options = readOptions(node);
|
|
2775
|
+
if (options.length) field.options = options;
|
|
2776
|
+
}
|
|
2777
|
+
const def = attr(node, "value") ?? (comp === "boolean" ? attr(node, "checked") : void 0);
|
|
2778
|
+
if (def !== void 0 && def !== "") field.value = comp === "boolean" ? def === "true" : def;
|
|
2779
|
+
out.push(field);
|
|
2780
|
+
continue;
|
|
2781
|
+
}
|
|
2782
|
+
if (rt && attr(node, "name") && !isWrapper(rt)) unmapped.add(rt);
|
|
2783
|
+
walk(childrenOf(node), out, unmapped, counter);
|
|
2784
|
+
}
|
|
2785
|
+
}
|
|
2786
|
+
function parseDialog(xml) {
|
|
2787
|
+
const parser = new XMLParser({
|
|
2788
|
+
ignoreAttributes: false,
|
|
2789
|
+
attributeNamePrefix: "@_",
|
|
2790
|
+
preserveOrder: true,
|
|
1608
2791
|
removeNSPrefix: false,
|
|
1609
2792
|
trimValues: true
|
|
1610
2793
|
});
|
|
1611
2794
|
const tree = parser.parse(xml);
|
|
1612
|
-
const root = tree.find((
|
|
2795
|
+
const root = tree.find((n2) => tagOf(n2) === "jcr:root");
|
|
1613
2796
|
const fields = [];
|
|
1614
2797
|
const unmapped = /* @__PURE__ */ new Set();
|
|
1615
2798
|
if (root) walk(childrenOf(root), fields, unmapped, { i: 1 });
|
|
1616
2799
|
return { fields, unmapped: [...unmapped] };
|
|
1617
2800
|
}
|
|
1618
|
-
var init_dialog = __esm({
|
|
1619
|
-
"src/lib/migrate/dialog.ts"() {
|
|
2801
|
+
var init_dialog = __esm({
|
|
2802
|
+
"src/lib/migrate/dialog.ts"() {
|
|
2803
|
+
"use strict";
|
|
2804
|
+
init_esm_shims();
|
|
2805
|
+
}
|
|
2806
|
+
});
|
|
2807
|
+
|
|
2808
|
+
// src/lib/migrate/component.ts
|
|
2809
|
+
import { existsSync as existsSync13 } from "fs";
|
|
2810
|
+
import { readFile as readFile9 } from "fs/promises";
|
|
2811
|
+
import path16 from "path";
|
|
2812
|
+
import { XMLParser as XMLParser2 } from "fast-xml-parser";
|
|
2813
|
+
function findDialogFile(componentDir) {
|
|
2814
|
+
const candidates = [
|
|
2815
|
+
path16.join(componentDir, "_cq_dialog", ".content.xml"),
|
|
2816
|
+
path16.join(componentDir, "_cq_dialog.xml"),
|
|
2817
|
+
path16.join(componentDir, "cq:dialog", ".content.xml")
|
|
2818
|
+
];
|
|
2819
|
+
return candidates.find((c) => existsSync13(c)) ?? null;
|
|
2820
|
+
}
|
|
2821
|
+
async function readComponentTitle(componentDir) {
|
|
2822
|
+
const file = path16.join(componentDir, ".content.xml");
|
|
2823
|
+
if (!existsSync13(file)) return void 0;
|
|
2824
|
+
try {
|
|
2825
|
+
const parser = new XMLParser2({ ignoreAttributes: false, attributeNamePrefix: "@_" });
|
|
2826
|
+
const parsed = parser.parse(await readFile9(file, "utf-8"));
|
|
2827
|
+
const root = parsed["jcr:root"] ?? {};
|
|
2828
|
+
const title = root["@_jcr:title"];
|
|
2829
|
+
return typeof title === "string" ? title.replace(/^\{[^}]+\}/, "") : void 0;
|
|
2830
|
+
} catch {
|
|
2831
|
+
return void 0;
|
|
2832
|
+
}
|
|
2833
|
+
}
|
|
2834
|
+
async function readDialogFields(componentDir) {
|
|
2835
|
+
const dialog = findDialogFile(componentDir);
|
|
2836
|
+
if (!dialog) return null;
|
|
2837
|
+
return parseDialog(await readFile9(dialog, "utf-8"));
|
|
2838
|
+
}
|
|
2839
|
+
function migratedModelFile(blockName, title, fields) {
|
|
2840
|
+
const model2 = {
|
|
2841
|
+
definitions: [
|
|
2842
|
+
{
|
|
2843
|
+
title,
|
|
2844
|
+
id: blockName,
|
|
2845
|
+
plugins: {
|
|
2846
|
+
xwalk: {
|
|
2847
|
+
page: {
|
|
2848
|
+
resourceType: "core/franklin/components/block/v1/block",
|
|
2849
|
+
template: { name: title, model: blockName }
|
|
2850
|
+
}
|
|
2851
|
+
}
|
|
2852
|
+
}
|
|
2853
|
+
}
|
|
2854
|
+
],
|
|
2855
|
+
models: [{ id: blockName, fields }],
|
|
2856
|
+
filters: []
|
|
2857
|
+
};
|
|
2858
|
+
return `${JSON.stringify(model2, null, 2)}
|
|
2859
|
+
`;
|
|
2860
|
+
}
|
|
2861
|
+
function migratedBlockJs(blockName, fields) {
|
|
2862
|
+
const hasContainer = fields.some((f2) => f2.component === "container" && f2.multi);
|
|
2863
|
+
const body = hasContainer ? ` // This block was migrated from a classic AEM component with a multifield.
|
|
2864
|
+
// Each child row is one repeated item - enhance them here.
|
|
2865
|
+
[...block.children].forEach((row) => {
|
|
2866
|
+
row.classList.add('${blockName}-item');
|
|
2867
|
+
});` : ` // Migrated from a classic AEM component. The authored fields arrive as
|
|
2868
|
+
// rows; read and enhance them here (port the old HTL logic as needed).`;
|
|
2869
|
+
return `// Block: ${blockName}
|
|
2870
|
+
// Migrated from a classic AEM component by \`eds migrate component\`.
|
|
2871
|
+
// TODO: port the component's HTL/JS behaviour into this decorator.
|
|
2872
|
+
export default function decorate(block) {
|
|
2873
|
+
block.classList.add('${blockName}');
|
|
2874
|
+
${body}
|
|
2875
|
+
}
|
|
2876
|
+
`;
|
|
2877
|
+
}
|
|
2878
|
+
function migratedBlockCss(blockName) {
|
|
2879
|
+
return `/* Block: ${blockName} */
|
|
2880
|
+
/* Migrated from a classic AEM component. Bring the component's styles here */
|
|
2881
|
+
/* (from its clientlib or the rendered page) and keep them scoped to the block. */
|
|
2882
|
+
.${blockName} {
|
|
2883
|
+
}
|
|
2884
|
+
`;
|
|
2885
|
+
}
|
|
2886
|
+
var init_component = __esm({
|
|
2887
|
+
"src/lib/migrate/component.ts"() {
|
|
2888
|
+
"use strict";
|
|
2889
|
+
init_esm_shims();
|
|
2890
|
+
init_dialog();
|
|
2891
|
+
}
|
|
2892
|
+
});
|
|
2893
|
+
|
|
2894
|
+
// src/lib/track.ts
|
|
2895
|
+
function trackSnippet(blockName, param, events) {
|
|
2896
|
+
const parts = [` // --- ${TRACK_MARKER} ---`];
|
|
2897
|
+
if (events.click) {
|
|
2898
|
+
parts.push(
|
|
2899
|
+
` ${param}.addEventListener('click', (ev) => {`,
|
|
2900
|
+
" const el = ev.target.closest('a, button');",
|
|
2901
|
+
" if (!el) return;",
|
|
2902
|
+
` trackEvent({ event: 'block_click', block: '${blockName}', label: el.dataset.track || el.textContent.trim() });`,
|
|
2903
|
+
" });"
|
|
2904
|
+
);
|
|
2905
|
+
}
|
|
2906
|
+
if (events.submit) {
|
|
2907
|
+
parts.push(
|
|
2908
|
+
` ${param}.addEventListener('submit', () => {`,
|
|
2909
|
+
` trackEvent({ event: 'form_submit', block: '${blockName}' });`,
|
|
2910
|
+
" });"
|
|
2911
|
+
);
|
|
2912
|
+
}
|
|
2913
|
+
return parts.join("\n");
|
|
2914
|
+
}
|
|
2915
|
+
function instrumentJs(js, blockName, events) {
|
|
2916
|
+
if (js.includes(TRACK_MARKER)) return { js, changed: false, reason: "already instrumented" };
|
|
2917
|
+
if (!events.click && !events.submit) return { js, changed: false, reason: "no events selected" };
|
|
2918
|
+
const decorate = js.match(
|
|
2919
|
+
/export default (?:async )?function decorate\s*\(\s*([A-Za-z_$][\w$]*)?[^)]*\)\s*\{/
|
|
2920
|
+
);
|
|
2921
|
+
if (!decorate || decorate.index === void 0) {
|
|
2922
|
+
return { js, changed: false, reason: "no `export default function decorate(...)` found" };
|
|
2923
|
+
}
|
|
2924
|
+
const param = decorate[1] || "block";
|
|
2925
|
+
let out = js;
|
|
2926
|
+
if (!out.includes("scripts/analytics.js")) {
|
|
2927
|
+
const imports = [...out.matchAll(/^import .*$/gm)];
|
|
2928
|
+
if (imports.length) {
|
|
2929
|
+
const last = imports[imports.length - 1];
|
|
2930
|
+
const at2 = (last.index ?? 0) + last[0].length;
|
|
2931
|
+
out = `${out.slice(0, at2)}
|
|
2932
|
+
${IMPORT_LINE}${out.slice(at2)}`;
|
|
2933
|
+
} else {
|
|
2934
|
+
out = `${IMPORT_LINE}
|
|
2935
|
+
|
|
2936
|
+
${out}`;
|
|
2937
|
+
}
|
|
2938
|
+
}
|
|
2939
|
+
const d2 = out.match(
|
|
2940
|
+
/export default (?:async )?function decorate\s*\(\s*([A-Za-z_$][\w$]*)?[^)]*\)\s*\{/
|
|
2941
|
+
);
|
|
2942
|
+
if (!d2 || d2.index === void 0)
|
|
2943
|
+
return { js: out, changed: false, reason: "decorate not found" };
|
|
2944
|
+
const at = d2.index + d2[0].length;
|
|
2945
|
+
const snippet = trackSnippet(blockName, param, events);
|
|
2946
|
+
out = `${out.slice(0, at)}
|
|
2947
|
+
${snippet}
|
|
2948
|
+
${out.slice(at)}`;
|
|
2949
|
+
return { js: out, changed: true };
|
|
2950
|
+
}
|
|
2951
|
+
var TRACK_MARKER, IMPORT_LINE;
|
|
2952
|
+
var init_track = __esm({
|
|
2953
|
+
"src/lib/track.ts"() {
|
|
2954
|
+
"use strict";
|
|
2955
|
+
init_esm_shims();
|
|
2956
|
+
TRACK_MARKER = "analytics (eds track)";
|
|
2957
|
+
IMPORT_LINE = "import { trackEvent } from '../../scripts/analytics.js';";
|
|
2958
|
+
}
|
|
2959
|
+
});
|
|
2960
|
+
|
|
2961
|
+
// src/commands/track.ts
|
|
2962
|
+
var track_exports = {};
|
|
2963
|
+
__export(track_exports, {
|
|
2964
|
+
trackBlock: () => trackBlock
|
|
2965
|
+
});
|
|
2966
|
+
import { existsSync as existsSync14 } from "fs";
|
|
2967
|
+
import { readFile as readFile10, writeFile as writeFile10 } from "fs/promises";
|
|
2968
|
+
import path17 from "path";
|
|
2969
|
+
import chalk6 from "chalk";
|
|
2970
|
+
async function trackBlock(blockName, options = {}) {
|
|
2971
|
+
const projectRoot = findProjectRoot();
|
|
2972
|
+
if (!projectRoot) {
|
|
2973
|
+
logger.error("Not inside an EDS project (no EDS project markers found).");
|
|
2974
|
+
process.exitCode = 1;
|
|
2975
|
+
return;
|
|
2976
|
+
}
|
|
2977
|
+
logger.logoOnce(logo("Track block"));
|
|
2978
|
+
const jsPath = path17.join(projectRoot, "blocks", blockName, `${blockName}.js`);
|
|
2979
|
+
if (!existsSync14(jsPath)) {
|
|
2980
|
+
logger.error(`Block "${blockName}" not found (no blocks/${blockName}/${blockName}.js).`);
|
|
2981
|
+
process.exitCode = 1;
|
|
2982
|
+
return;
|
|
2983
|
+
}
|
|
2984
|
+
const events = { click: options.click !== false, submit: options.submit !== false };
|
|
2985
|
+
const res = instrumentJs(await readFile10(jsPath, "utf-8"), blockName, events);
|
|
2986
|
+
if (!res.changed) {
|
|
2987
|
+
logger.warn(`Not instrumented: ${res.reason}.`);
|
|
2988
|
+
return;
|
|
2989
|
+
}
|
|
2990
|
+
await writeFile10(jsPath, res.js);
|
|
2991
|
+
logger.info(heading("Instrumented"));
|
|
2992
|
+
const picked = [events.click ? "block_click" : null, events.submit ? "form_submit" : null].filter(
|
|
2993
|
+
Boolean
|
|
2994
|
+
);
|
|
2995
|
+
logger.info(accentLine(`blocks/${blockName}/${blockName}.js`, picked.join(" + ")));
|
|
2996
|
+
if (!existsSync14(path17.join(projectRoot, "scripts", "analytics.js"))) {
|
|
2997
|
+
logger.info("");
|
|
2998
|
+
logger.warn("scripts/analytics.js is missing - run `eds integrate gtm` to create it.");
|
|
2999
|
+
}
|
|
3000
|
+
logger.info("");
|
|
3001
|
+
logger.info(chalk6.dim(" Events push to window.dataLayer via trackEvent - GTM picks them up."));
|
|
3002
|
+
}
|
|
3003
|
+
var init_track2 = __esm({
|
|
3004
|
+
"src/commands/track.ts"() {
|
|
3005
|
+
"use strict";
|
|
3006
|
+
init_esm_shims();
|
|
3007
|
+
init_logger();
|
|
3008
|
+
init_project();
|
|
3009
|
+
init_track();
|
|
3010
|
+
init_ui();
|
|
3011
|
+
}
|
|
3012
|
+
});
|
|
3013
|
+
|
|
3014
|
+
// src/commands/migrate.ts
|
|
3015
|
+
var migrate_exports = {};
|
|
3016
|
+
__export(migrate_exports, {
|
|
3017
|
+
migrateComponent: () => migrateComponent
|
|
3018
|
+
});
|
|
3019
|
+
import { existsSync as existsSync15 } from "fs";
|
|
3020
|
+
import { mkdir as mkdir6, writeFile as writeFile11 } from "fs/promises";
|
|
3021
|
+
import path18 from "path";
|
|
3022
|
+
function clientlibBlockCss(blockName, css, files) {
|
|
3023
|
+
return `/* Block: ${blockName} */
|
|
3024
|
+
/* Migrated from the component clientlib: ${files.join(", ")}. */
|
|
3025
|
+
/* TODO: review selectors and scope them under .${blockName} (and port any LESS/SCSS). */
|
|
3026
|
+
|
|
3027
|
+
${css.trim()}
|
|
3028
|
+
`;
|
|
3029
|
+
}
|
|
3030
|
+
function previewFields(fields, depth = 0) {
|
|
3031
|
+
const lines = [];
|
|
3032
|
+
for (const f2 of fields) {
|
|
3033
|
+
const indent = " ".repeat(depth);
|
|
3034
|
+
if (f2.component === "tab") {
|
|
3035
|
+
lines.push(`${indent}${brand.blue("\u25B8")} ${f2.label ?? f2.name}`);
|
|
3036
|
+
} else {
|
|
3037
|
+
const opts = f2.options?.length ? ` (${f2.options.length} options)` : "";
|
|
3038
|
+
const multi = f2.multi ? " \xB7multi" : "";
|
|
3039
|
+
const label = f2.label ? `- ${f2.label}` : "";
|
|
3040
|
+
lines.push(`${indent}${brand.accent(f2.component)}${multi} ${f2.name}${opts} ${label}`);
|
|
3041
|
+
if (f2.fields?.length) lines.push(...previewFields(f2.fields, depth + 1));
|
|
3042
|
+
}
|
|
3043
|
+
}
|
|
3044
|
+
return lines;
|
|
3045
|
+
}
|
|
3046
|
+
function countLeaves(fields) {
|
|
3047
|
+
let n2 = 0;
|
|
3048
|
+
for (const f2 of fields) {
|
|
3049
|
+
if (f2.component === "tab") continue;
|
|
3050
|
+
n2 += 1;
|
|
3051
|
+
if (f2.fields) n2 += countLeaves(f2.fields);
|
|
3052
|
+
}
|
|
3053
|
+
return n2;
|
|
3054
|
+
}
|
|
3055
|
+
async function migrateComponent(dirArg, options = {}) {
|
|
3056
|
+
const projectRoot = findProjectRoot();
|
|
3057
|
+
if (!projectRoot) {
|
|
3058
|
+
logger.error("Not inside an EDS project (no EDS project markers found).");
|
|
3059
|
+
process.exitCode = 1;
|
|
3060
|
+
return;
|
|
3061
|
+
}
|
|
3062
|
+
await logger.logoOnceAnimated("Migrate component");
|
|
3063
|
+
const { input, confirm } = await import("@inquirer/prompts");
|
|
3064
|
+
const interactive = !options.yes;
|
|
3065
|
+
let componentDir = dirArg;
|
|
3066
|
+
if (!componentDir && interactive) {
|
|
3067
|
+
componentDir = await input({
|
|
3068
|
+
message: "Path to the classic component folder",
|
|
3069
|
+
validate: (v) => existsSync15(v.trim()) || "Folder not found"
|
|
3070
|
+
});
|
|
3071
|
+
}
|
|
3072
|
+
if (!componentDir) {
|
|
3073
|
+
logger.error("Provide the path to the classic component folder.");
|
|
3074
|
+
process.exitCode = 1;
|
|
3075
|
+
return;
|
|
3076
|
+
}
|
|
3077
|
+
componentDir = path18.resolve(componentDir.trim());
|
|
3078
|
+
if (!findDialogFile(componentDir)) {
|
|
3079
|
+
logger.error(`No Touch UI dialog (_cq_dialog/.content.xml) found in ${componentDir}.`);
|
|
3080
|
+
process.exitCode = 1;
|
|
3081
|
+
return;
|
|
3082
|
+
}
|
|
3083
|
+
const parsed = await readDialogFields(componentDir);
|
|
3084
|
+
if (!parsed || parsed.fields.length === 0) {
|
|
3085
|
+
logger.warn("Dialog found but no mappable fields were detected.");
|
|
3086
|
+
if (parsed?.unmapped.length)
|
|
3087
|
+
logger.info(`Unmapped resourceTypes: ${parsed.unmapped.join(", ")}`);
|
|
3088
|
+
process.exitCode = 1;
|
|
3089
|
+
return;
|
|
3090
|
+
}
|
|
3091
|
+
const title = await readComponentTitle(componentDir) ?? path18.basename(componentDir);
|
|
3092
|
+
const blockName = options.name && slug3(options.name) || (interactive ? slug3(await input({ message: "Block name", default: slug3(title) })) : slug3(title));
|
|
3093
|
+
logger.info(heading("Mapped fields", `${countLeaves(parsed.fields)} from dialog`));
|
|
3094
|
+
for (const line of previewFields(parsed.fields)) logger.info(` ${line}`);
|
|
3095
|
+
if (parsed.unmapped.length) {
|
|
3096
|
+
logger.info("");
|
|
3097
|
+
logger.warn(`Skipped (unrecognised): ${parsed.unmapped.join(", ")}`);
|
|
3098
|
+
}
|
|
3099
|
+
logger.info("");
|
|
3100
|
+
if (interactive) {
|
|
3101
|
+
const go = await confirm({ message: `Generate block "${blockName}"?`, default: true });
|
|
3102
|
+
if (!go) {
|
|
3103
|
+
logger.info("Cancelled.");
|
|
3104
|
+
return;
|
|
3105
|
+
}
|
|
3106
|
+
}
|
|
3107
|
+
const blockDir = path18.join(projectRoot, "blocks", blockName);
|
|
3108
|
+
if (existsSync15(blockDir)) {
|
|
3109
|
+
logger.error(
|
|
3110
|
+
`Block "${blockName}" already exists at blocks/${blockName}. Use --name to pick another.`
|
|
3111
|
+
);
|
|
3112
|
+
process.exitCode = 1;
|
|
3113
|
+
return;
|
|
3114
|
+
}
|
|
3115
|
+
const clibDir = options.clientlib ? path18.resolve(options.clientlib.trim()) : await findClientlibDir(componentDir);
|
|
3116
|
+
let cssContent = migratedBlockCss(blockName);
|
|
3117
|
+
let cssMessage = "empty - add styles from clientlib / rendered CSS";
|
|
3118
|
+
let preprocessedNote = [];
|
|
3119
|
+
if (clibDir && existsSync15(clibDir)) {
|
|
3120
|
+
const lib = await readClientlibCss(clibDir);
|
|
3121
|
+
if (lib.css.trim()) {
|
|
3122
|
+
cssContent = clientlibBlockCss(blockName, lib.css, lib.files);
|
|
3123
|
+
cssMessage = `from clientlib (${lib.files.length} file(s)) - review & scope`;
|
|
3124
|
+
}
|
|
3125
|
+
preprocessedNote = lib.preprocessed;
|
|
3126
|
+
}
|
|
3127
|
+
await mkdir6(blockDir, { recursive: true });
|
|
3128
|
+
await writeFile11(
|
|
3129
|
+
path18.join(blockDir, `${blockName}.js`),
|
|
3130
|
+
migratedBlockJs(blockName, parsed.fields)
|
|
3131
|
+
);
|
|
3132
|
+
await writeFile11(path18.join(blockDir, `${blockName}.css`), cssContent);
|
|
3133
|
+
await writeFile11(
|
|
3134
|
+
path18.join(blockDir, `_${blockName}.json`),
|
|
3135
|
+
migratedModelFile(blockName, title, parsed.fields)
|
|
3136
|
+
);
|
|
3137
|
+
logger.info(heading("Created"));
|
|
3138
|
+
const files = [`${blockName}.js`, `${blockName}.css`, `_${blockName}.json`];
|
|
3139
|
+
const w = columnWidth(files);
|
|
3140
|
+
logger.info(accentLine(`${blockName}.js`, "decorator stub (port HTL logic)", w));
|
|
3141
|
+
logger.info(accentLine(`${blockName}.css`, cssMessage, w));
|
|
3142
|
+
logger.info(accentLine(`_${blockName}.json`, "Universal Editor model", w));
|
|
3143
|
+
if (preprocessedNote.length) {
|
|
3144
|
+
logger.info("");
|
|
3145
|
+
logger.warn(`LESS/SCSS found (needs a build, not copied): ${preprocessedNote.join(", ")}`);
|
|
3146
|
+
}
|
|
3147
|
+
logger.info("");
|
|
3148
|
+
if (interactive) {
|
|
3149
|
+
const addTracking = await confirm({
|
|
3150
|
+
message: "Instrument this block for analytics (dataLayer)?",
|
|
3151
|
+
default: false
|
|
3152
|
+
});
|
|
3153
|
+
if (addTracking) {
|
|
3154
|
+
const { trackBlock: trackBlock2 } = await Promise.resolve().then(() => (init_track2(), track_exports));
|
|
3155
|
+
await trackBlock2(blockName, {});
|
|
3156
|
+
}
|
|
3157
|
+
}
|
|
3158
|
+
logger.info(
|
|
3159
|
+
box([`"${blockName}" migrated. Next: add CSS, then \`eds block preview ${blockName}\``])
|
|
3160
|
+
);
|
|
3161
|
+
}
|
|
3162
|
+
var slug3;
|
|
3163
|
+
var init_migrate = __esm({
|
|
3164
|
+
"src/commands/migrate.ts"() {
|
|
3165
|
+
"use strict";
|
|
3166
|
+
init_esm_shims();
|
|
3167
|
+
init_logger();
|
|
3168
|
+
init_clientlib();
|
|
3169
|
+
init_component();
|
|
3170
|
+
init_project();
|
|
3171
|
+
init_ui();
|
|
3172
|
+
slug3 = (s) => s.trim().toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
3173
|
+
}
|
|
3174
|
+
});
|
|
3175
|
+
|
|
3176
|
+
// src/lib/model/helpers.ts
|
|
3177
|
+
function n(prefix, base) {
|
|
3178
|
+
if (!prefix) return base;
|
|
3179
|
+
return prefix + base.charAt(0).toUpperCase() + base.slice(1);
|
|
3180
|
+
}
|
|
3181
|
+
var f;
|
|
3182
|
+
var init_helpers2 = __esm({
|
|
3183
|
+
"src/lib/model/helpers.ts"() {
|
|
3184
|
+
"use strict";
|
|
3185
|
+
init_esm_shims();
|
|
3186
|
+
f = {
|
|
3187
|
+
text: (name, label) => ({
|
|
3188
|
+
component: "text",
|
|
3189
|
+
valueType: "string",
|
|
3190
|
+
name,
|
|
3191
|
+
label
|
|
3192
|
+
}),
|
|
3193
|
+
textarea: (name, label) => ({
|
|
3194
|
+
component: "textarea",
|
|
3195
|
+
valueType: "string",
|
|
3196
|
+
name,
|
|
3197
|
+
label
|
|
3198
|
+
}),
|
|
3199
|
+
richtext: (name, label) => ({
|
|
3200
|
+
component: "richtext",
|
|
3201
|
+
valueType: "string",
|
|
3202
|
+
name,
|
|
3203
|
+
value: "",
|
|
3204
|
+
label
|
|
3205
|
+
}),
|
|
3206
|
+
number: (name, label) => ({
|
|
3207
|
+
component: "number",
|
|
3208
|
+
valueType: "number",
|
|
3209
|
+
name,
|
|
3210
|
+
label
|
|
3211
|
+
}),
|
|
3212
|
+
boolean: (name, label, value = false) => ({
|
|
3213
|
+
component: "boolean",
|
|
3214
|
+
valueType: "boolean",
|
|
3215
|
+
name,
|
|
3216
|
+
value,
|
|
3217
|
+
label
|
|
3218
|
+
}),
|
|
3219
|
+
dateTime: (name, label) => ({
|
|
3220
|
+
component: "date-time",
|
|
3221
|
+
valueType: "date",
|
|
3222
|
+
name,
|
|
3223
|
+
label
|
|
3224
|
+
}),
|
|
3225
|
+
select: (name, label, options, value) => ({
|
|
3226
|
+
component: "select",
|
|
3227
|
+
valueType: "string",
|
|
3228
|
+
name,
|
|
3229
|
+
label,
|
|
3230
|
+
options,
|
|
3231
|
+
...value !== void 0 ? { value } : {}
|
|
3232
|
+
}),
|
|
3233
|
+
multiselect: (name, label, options) => ({
|
|
3234
|
+
component: "multiselect",
|
|
3235
|
+
valueType: "string",
|
|
3236
|
+
name,
|
|
3237
|
+
label,
|
|
3238
|
+
options
|
|
3239
|
+
}),
|
|
3240
|
+
radioGroup: (name, label, options, value) => ({
|
|
3241
|
+
component: "radio-group",
|
|
3242
|
+
valueType: "string",
|
|
3243
|
+
name,
|
|
3244
|
+
label,
|
|
3245
|
+
options,
|
|
3246
|
+
...value !== void 0 ? { value } : {}
|
|
3247
|
+
}),
|
|
3248
|
+
checkboxGroup: (name, label, options) => ({
|
|
3249
|
+
component: "checkbox-group",
|
|
3250
|
+
valueType: "string[]",
|
|
3251
|
+
name,
|
|
3252
|
+
label,
|
|
3253
|
+
options
|
|
3254
|
+
}),
|
|
3255
|
+
reference: (name, label) => ({
|
|
3256
|
+
component: "reference",
|
|
3257
|
+
valueType: "string",
|
|
3258
|
+
name,
|
|
3259
|
+
label
|
|
3260
|
+
}),
|
|
3261
|
+
aemContent: (name, label) => ({ component: "aem-content", name, label }),
|
|
3262
|
+
aemContentFragment: (name, label) => ({
|
|
3263
|
+
component: "aem-content-fragment",
|
|
3264
|
+
name,
|
|
3265
|
+
label
|
|
3266
|
+
}),
|
|
3267
|
+
aemExperienceFragment: (name, label) => ({
|
|
3268
|
+
component: "aem-experience-fragment",
|
|
3269
|
+
name,
|
|
3270
|
+
label
|
|
3271
|
+
}),
|
|
3272
|
+
aemTag: (name, label) => ({
|
|
3273
|
+
component: "aem-tag",
|
|
3274
|
+
valueType: "string",
|
|
3275
|
+
name,
|
|
3276
|
+
label
|
|
3277
|
+
}),
|
|
3278
|
+
tab: (name, label) => ({ component: "tab", name, label }),
|
|
3279
|
+
container: (name, label, fields, collapsible = true) => ({
|
|
3280
|
+
component: "container",
|
|
3281
|
+
name,
|
|
3282
|
+
label,
|
|
3283
|
+
multi: true,
|
|
3284
|
+
collapsible,
|
|
3285
|
+
fields
|
|
3286
|
+
})
|
|
3287
|
+
};
|
|
3288
|
+
}
|
|
3289
|
+
});
|
|
3290
|
+
|
|
3291
|
+
// src/lib/model/partials.ts
|
|
3292
|
+
function partialById(id) {
|
|
3293
|
+
return PARTIALS.find((p) => p.id === id);
|
|
3294
|
+
}
|
|
3295
|
+
var HEADING_LEVELS, LINK_TARGETS, BUTTON_STYLES, SECTION_STYLES, heading2, eyebrow, image, richtext, link, cta, ctaGroup, classes, embed, teaser, card, PARTIALS;
|
|
3296
|
+
var init_partials = __esm({
|
|
3297
|
+
"src/lib/model/partials.ts"() {
|
|
1620
3298
|
"use strict";
|
|
1621
3299
|
init_esm_shims();
|
|
3300
|
+
init_helpers2();
|
|
3301
|
+
HEADING_LEVELS = [
|
|
3302
|
+
{ name: "H1", value: "h1" },
|
|
3303
|
+
{ name: "H2", value: "h2" },
|
|
3304
|
+
{ name: "H3", value: "h3" },
|
|
3305
|
+
{ name: "H4", value: "h4" },
|
|
3306
|
+
{ name: "H5", value: "h5" },
|
|
3307
|
+
{ name: "H6", value: "h6" }
|
|
3308
|
+
];
|
|
3309
|
+
LINK_TARGETS = [
|
|
3310
|
+
{ name: "Same tab", value: "_self" },
|
|
3311
|
+
{ name: "New tab", value: "_blank" }
|
|
3312
|
+
];
|
|
3313
|
+
BUTTON_STYLES = [
|
|
3314
|
+
{ name: "Default", value: "" },
|
|
3315
|
+
{ name: "Primary", value: "primary" },
|
|
3316
|
+
{ name: "Secondary", value: "secondary" }
|
|
3317
|
+
];
|
|
3318
|
+
SECTION_STYLES = [
|
|
3319
|
+
{ name: "Highlight", value: "highlight" },
|
|
3320
|
+
{ name: "Dark", value: "dark" },
|
|
3321
|
+
{ name: "Centered", value: "centered" },
|
|
3322
|
+
{ name: "Full width", value: "full-width" }
|
|
3323
|
+
];
|
|
3324
|
+
heading2 = (p = "") => [
|
|
3325
|
+
f.text(n(p, "title"), "Title"),
|
|
3326
|
+
f.select(n(p, "titleType"), "Heading level", HEADING_LEVELS, "h2")
|
|
3327
|
+
];
|
|
3328
|
+
eyebrow = (p = "") => [f.text(n(p, "eyebrow"), "Eyebrow")];
|
|
3329
|
+
image = (p = "") => [
|
|
3330
|
+
f.reference(n(p, "image"), "Image"),
|
|
3331
|
+
f.text(n(p, "imageAlt"), "Alt text")
|
|
3332
|
+
];
|
|
3333
|
+
richtext = (p = "") => [f.richtext(n(p, "text"), "Text")];
|
|
3334
|
+
link = (p = "") => [
|
|
3335
|
+
f.text(n(p, "linkText"), "Link text"),
|
|
3336
|
+
f.aemContent(n(p, "link"), "Link"),
|
|
3337
|
+
f.select(n(p, "linkTarget"), "Open in", LINK_TARGETS, "_self")
|
|
3338
|
+
];
|
|
3339
|
+
cta = (p = "") => [
|
|
3340
|
+
...link(p),
|
|
3341
|
+
f.select(n(p, "ctaStyle"), "Style", BUTTON_STYLES, "")
|
|
3342
|
+
];
|
|
3343
|
+
ctaGroup = (p = "") => [
|
|
3344
|
+
f.container(n(p, "ctas") || "ctas", "Buttons", cta())
|
|
3345
|
+
];
|
|
3346
|
+
classes = (p = "") => [
|
|
3347
|
+
f.multiselect(n(p, "classes"), "Styles", SECTION_STYLES)
|
|
3348
|
+
];
|
|
3349
|
+
embed = (p = "") => [f.aemContent(n(p, "url"), "URL (video / embed)")];
|
|
3350
|
+
teaser = (p = "") => [
|
|
3351
|
+
...image(p),
|
|
3352
|
+
...eyebrow(p),
|
|
3353
|
+
...heading2(p),
|
|
3354
|
+
...richtext(p),
|
|
3355
|
+
...cta(p)
|
|
3356
|
+
];
|
|
3357
|
+
card = (p = "") => [
|
|
3358
|
+
...image(p),
|
|
3359
|
+
f.text(n(p, "title"), "Title"),
|
|
3360
|
+
f.richtext(n(p, "description"), "Description"),
|
|
3361
|
+
...link(p)
|
|
3362
|
+
];
|
|
3363
|
+
PARTIALS = [
|
|
3364
|
+
{ id: "heading", label: "Heading (title + level)", build: heading2 },
|
|
3365
|
+
{ id: "eyebrow", label: "Eyebrow (kicker line)", build: eyebrow },
|
|
3366
|
+
{ id: "image", label: "Image (reference + alt)", build: image },
|
|
3367
|
+
{ id: "richtext", label: "Rich text body", build: richtext },
|
|
3368
|
+
{ id: "link", label: "Link (text + url + target)", build: link },
|
|
3369
|
+
{ id: "cta", label: "CTA (link + style)", build: cta },
|
|
3370
|
+
{ id: "cta-group", label: "CTA group (repeatable buttons)", build: ctaGroup },
|
|
3371
|
+
{ id: "classes", label: "Section styles (CSS modifiers)", build: classes },
|
|
3372
|
+
{ id: "embed", label: "Embed / video (URL)", build: embed },
|
|
3373
|
+
{ id: "teaser", label: "Teaser (image + eyebrow + title + text + CTA)", build: teaser },
|
|
3374
|
+
{ id: "card", label: "Card (image + title + description + link)", build: card }
|
|
3375
|
+
];
|
|
1622
3376
|
}
|
|
1623
3377
|
});
|
|
1624
3378
|
|
|
1625
|
-
// src/
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
3379
|
+
// src/commands/model.ts
|
|
3380
|
+
var model_exports = {};
|
|
3381
|
+
__export(model_exports, {
|
|
3382
|
+
modelAdd: () => modelAdd
|
|
3383
|
+
});
|
|
3384
|
+
import { existsSync as existsSync16 } from "fs";
|
|
3385
|
+
import { readFile as readFile11, writeFile as writeFile12 } from "fs/promises";
|
|
3386
|
+
import path19 from "path";
|
|
3387
|
+
async function modelAdd(blockArg, partialArgs, options = {}) {
|
|
3388
|
+
const projectRoot = findProjectRoot();
|
|
3389
|
+
if (!projectRoot) {
|
|
3390
|
+
logger.error("Not inside an EDS project (no EDS project markers found).");
|
|
3391
|
+
process.exitCode = 1;
|
|
3392
|
+
return;
|
|
3393
|
+
}
|
|
3394
|
+
logger.logoOnce(logo("Model - add partials"));
|
|
3395
|
+
const { select, checkbox } = await import("@inquirer/prompts");
|
|
3396
|
+
const interactive = !options.yes;
|
|
3397
|
+
let blockName = blockArg;
|
|
3398
|
+
if (!blockName && interactive) {
|
|
3399
|
+
const { readdir: readdir9 } = await import("fs/promises");
|
|
3400
|
+
const blocksDir = path19.join(projectRoot, "blocks");
|
|
3401
|
+
const dirs = existsSync16(blocksDir) ? (await readdir9(blocksDir, { withFileTypes: true })).filter(
|
|
3402
|
+
(e) => e.isDirectory() && existsSync16(path19.join(blocksDir, e.name, `_${e.name}.json`))
|
|
3403
|
+
).map((e) => e.name) : [];
|
|
3404
|
+
if (dirs.length === 0) {
|
|
3405
|
+
logger.error("No block with a _<name>.json model found. Create one with `eds block create`.");
|
|
3406
|
+
process.exitCode = 1;
|
|
3407
|
+
return;
|
|
3408
|
+
}
|
|
3409
|
+
blockName = await select({
|
|
3410
|
+
message: "Which block's model?",
|
|
3411
|
+
choices: dirs.map((d) => ({ name: d, value: d }))
|
|
3412
|
+
});
|
|
3413
|
+
}
|
|
3414
|
+
if (!blockName) {
|
|
3415
|
+
logger.error("Provide a block name.");
|
|
3416
|
+
process.exitCode = 1;
|
|
3417
|
+
return;
|
|
3418
|
+
}
|
|
3419
|
+
const modelFile = path19.join(projectRoot, "blocks", blockName, `_${blockName}.json`);
|
|
3420
|
+
if (!existsSync16(modelFile)) {
|
|
3421
|
+
logger.error(`No model at blocks/${blockName}/_${blockName}.json. Create the block first.`);
|
|
3422
|
+
process.exitCode = 1;
|
|
3423
|
+
return;
|
|
3424
|
+
}
|
|
3425
|
+
let ids = partialArgs;
|
|
3426
|
+
if (ids.length === 0 && interactive) {
|
|
3427
|
+
ids = await checkbox({
|
|
3428
|
+
message: "Which partials to add?",
|
|
3429
|
+
choices: PARTIALS.map((p) => ({ name: p.label, value: p.id }))
|
|
3430
|
+
});
|
|
3431
|
+
}
|
|
3432
|
+
const partials = ids.map((id) => partialById(id.trim())).filter((p) => !!p);
|
|
3433
|
+
const unknown = ids.filter((id) => !partialById(id.trim()));
|
|
3434
|
+
if (unknown.length) {
|
|
3435
|
+
logger.warn(
|
|
3436
|
+
`Unknown partial(s): ${unknown.join(", ")}. Available: ${PARTIALS.map((p) => p.id).join(", ")}`
|
|
3437
|
+
);
|
|
3438
|
+
}
|
|
3439
|
+
if (partials.length === 0) {
|
|
3440
|
+
logger.info("Nothing to add.");
|
|
3441
|
+
return;
|
|
3442
|
+
}
|
|
3443
|
+
let model2;
|
|
1641
3444
|
try {
|
|
1642
|
-
|
|
1643
|
-
const parsed = parser.parse(await readFile4(file, "utf-8"));
|
|
1644
|
-
const root = parsed["jcr:root"] ?? {};
|
|
1645
|
-
const title = root["@_jcr:title"];
|
|
1646
|
-
return typeof title === "string" ? title.replace(/^\{[^}]+\}/, "") : void 0;
|
|
3445
|
+
model2 = JSON.parse(await readFile11(modelFile, "utf-8"));
|
|
1647
3446
|
} catch {
|
|
1648
|
-
|
|
3447
|
+
logger.error(`Could not parse ${modelFile}.`);
|
|
3448
|
+
process.exitCode = 1;
|
|
3449
|
+
return;
|
|
1649
3450
|
}
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
template: { name: title, model: blockName }
|
|
1667
|
-
}
|
|
1668
|
-
}
|
|
1669
|
-
}
|
|
3451
|
+
const models = Array.isArray(model2.models) ? model2.models : [];
|
|
3452
|
+
const target = models.find((m) => m.id === blockName) ?? models[0];
|
|
3453
|
+
if (!target) {
|
|
3454
|
+
logger.error("Model has no `models[]` entry to add fields to.");
|
|
3455
|
+
process.exitCode = 1;
|
|
3456
|
+
return;
|
|
3457
|
+
}
|
|
3458
|
+
target.fields = Array.isArray(target.fields) ? target.fields : [];
|
|
3459
|
+
const existing = new Set(target.fields.map((x) => x.name));
|
|
3460
|
+
const added = [];
|
|
3461
|
+
const skipped = [];
|
|
3462
|
+
for (const p of partials) {
|
|
3463
|
+
for (const field of p.build()) {
|
|
3464
|
+
if (existing.has(field.name)) {
|
|
3465
|
+
skipped.push(field.name);
|
|
3466
|
+
continue;
|
|
1670
3467
|
}
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
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
|
-
`;
|
|
3468
|
+
existing.add(field.name);
|
|
3469
|
+
target.fields.push(field);
|
|
3470
|
+
added.push(field.name);
|
|
3471
|
+
}
|
|
3472
|
+
}
|
|
3473
|
+
await writeFile12(modelFile, `${JSON.stringify(model2, null, 2)}
|
|
3474
|
+
`);
|
|
3475
|
+
logger.info(heading("Model updated", `blocks/${blockName}/_${blockName}.json`));
|
|
3476
|
+
logger.info(accentLine("partials", partials.map((p) => p.id).join(", ")));
|
|
3477
|
+
logger.info(accentLine("fields added", added.length ? added.join(", ") : "(none)"));
|
|
3478
|
+
if (skipped.length) logger.info(statusLine("warn", "skipped (exists)", skipped.join(", ")));
|
|
3479
|
+
logger.info("");
|
|
3480
|
+
logger.info(box([`${added.length} field(s) added to "${blockName}"`]));
|
|
1702
3481
|
}
|
|
1703
|
-
var
|
|
1704
|
-
"src/
|
|
3482
|
+
var init_model = __esm({
|
|
3483
|
+
"src/commands/model.ts"() {
|
|
1705
3484
|
"use strict";
|
|
1706
3485
|
init_esm_shims();
|
|
1707
|
-
|
|
3486
|
+
init_logger();
|
|
3487
|
+
init_partials();
|
|
3488
|
+
init_project();
|
|
3489
|
+
init_ui();
|
|
1708
3490
|
}
|
|
1709
3491
|
});
|
|
1710
3492
|
|
|
1711
|
-
// src/lib/
|
|
1712
|
-
function
|
|
1713
|
-
|
|
1714
|
-
if (events.click) {
|
|
1715
|
-
parts.push(
|
|
1716
|
-
` ${param}.addEventListener('click', (ev) => {`,
|
|
1717
|
-
" const el = ev.target.closest('a, button');",
|
|
1718
|
-
" if (!el) return;",
|
|
1719
|
-
` trackEvent({ event: 'block_click', block: '${blockName}', label: el.dataset.track || el.textContent.trim() });`,
|
|
1720
|
-
" });"
|
|
1721
|
-
);
|
|
1722
|
-
}
|
|
1723
|
-
if (events.submit) {
|
|
1724
|
-
parts.push(
|
|
1725
|
-
` ${param}.addEventListener('submit', () => {`,
|
|
1726
|
-
` trackEvent({ event: 'form_submit', block: '${blockName}' });`,
|
|
1727
|
-
" });"
|
|
1728
|
-
);
|
|
1729
|
-
}
|
|
1730
|
-
return parts.join("\n");
|
|
3493
|
+
// src/lib/schema/jsonld.ts
|
|
3494
|
+
function schemaTypeById(id) {
|
|
3495
|
+
return SCHEMA_TYPES.find((t) => t.id.toLowerCase() === id.toLowerCase());
|
|
1731
3496
|
}
|
|
1732
|
-
function
|
|
1733
|
-
if (js.includes(
|
|
1734
|
-
if (!events.click && !events.submit) return { js, changed: false, reason: "no events selected" };
|
|
3497
|
+
function injectJsonLd(js, type) {
|
|
3498
|
+
if (js.includes(SCHEMA_MARKER)) return { js, changed: false, reason: "already has JSON-LD" };
|
|
1735
3499
|
const decorate = js.match(
|
|
1736
3500
|
/export default (?:async )?function decorate\s*\(\s*([A-Za-z_$][\w$]*)?[^)]*\)\s*\{/
|
|
1737
3501
|
);
|
|
@@ -1739,254 +3503,404 @@ function instrumentJs(js, blockName, events) {
|
|
|
1739
3503
|
return { js, changed: false, reason: "no `export default function decorate(...)` found" };
|
|
1740
3504
|
}
|
|
1741
3505
|
const param = decorate[1] || "block";
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
${out}`;
|
|
1754
|
-
}
|
|
3506
|
+
const at = decorate.index + decorate[0].length;
|
|
3507
|
+
const snippet = `
|
|
3508
|
+
// --- ${SCHEMA_MARKER}: ${type.id}) ---
|
|
3509
|
+
{
|
|
3510
|
+
const root = ${param};
|
|
3511
|
+
const ld = ${type.object};
|
|
3512
|
+
const ldScript = document.createElement('script');
|
|
3513
|
+
ldScript.type = 'application/ld+json';
|
|
3514
|
+
ldScript.textContent = JSON.stringify(ld);
|
|
3515
|
+
document.head.append(ldScript);
|
|
1755
3516
|
}
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
);
|
|
1759
|
-
if (!d2 || d2.index === void 0)
|
|
1760
|
-
return { js: out, changed: false, reason: "decorate not found" };
|
|
1761
|
-
const at = d2.index + d2[0].length;
|
|
1762
|
-
const snippet = trackSnippet(blockName, param, events);
|
|
1763
|
-
out = `${out.slice(0, at)}
|
|
1764
|
-
${snippet}
|
|
1765
|
-
${out.slice(at)}`;
|
|
1766
|
-
return { js: out, changed: true };
|
|
3517
|
+
`;
|
|
3518
|
+
return { js: `${js.slice(0, at)}${snippet}${js.slice(at)}`, changed: true };
|
|
1767
3519
|
}
|
|
1768
|
-
var
|
|
1769
|
-
var
|
|
1770
|
-
"src/lib/
|
|
3520
|
+
var SCHEMA_MARKER, SCHEMA_TYPES;
|
|
3521
|
+
var init_jsonld = __esm({
|
|
3522
|
+
"src/lib/schema/jsonld.ts"() {
|
|
1771
3523
|
"use strict";
|
|
1772
3524
|
init_esm_shims();
|
|
1773
|
-
|
|
1774
|
-
|
|
3525
|
+
SCHEMA_MARKER = "json-ld (eds schema";
|
|
3526
|
+
SCHEMA_TYPES = [
|
|
3527
|
+
{
|
|
3528
|
+
id: "Article",
|
|
3529
|
+
label: "Article / BlogPosting",
|
|
3530
|
+
object: `{
|
|
3531
|
+
'@context': 'https://schema.org',
|
|
3532
|
+
'@type': 'Article',
|
|
3533
|
+
headline: root.querySelector('h1, h2, h3')?.textContent?.trim() || document.title,
|
|
3534
|
+
image: [...root.querySelectorAll('img')].map((img) => img.src),
|
|
3535
|
+
description: root.querySelector('p')?.textContent?.trim(),
|
|
3536
|
+
url: window.location.href,
|
|
3537
|
+
// TODO: author, datePublished, dateModified
|
|
3538
|
+
}`
|
|
3539
|
+
},
|
|
3540
|
+
{
|
|
3541
|
+
id: "FAQPage",
|
|
3542
|
+
label: "FAQ page",
|
|
3543
|
+
object: `{
|
|
3544
|
+
'@context': 'https://schema.org',
|
|
3545
|
+
'@type': 'FAQPage',
|
|
3546
|
+
mainEntity: [...root.children].map((row) => {
|
|
3547
|
+
const [q, a] = row.children;
|
|
3548
|
+
return {
|
|
3549
|
+
'@type': 'Question',
|
|
3550
|
+
name: q?.textContent?.trim(),
|
|
3551
|
+
acceptedAnswer: { '@type': 'Answer', text: a?.innerHTML?.trim() },
|
|
3552
|
+
};
|
|
3553
|
+
}),
|
|
3554
|
+
}`
|
|
3555
|
+
},
|
|
3556
|
+
{
|
|
3557
|
+
id: "BreadcrumbList",
|
|
3558
|
+
label: "Breadcrumb list",
|
|
3559
|
+
object: `{
|
|
3560
|
+
'@context': 'https://schema.org',
|
|
3561
|
+
'@type': 'BreadcrumbList',
|
|
3562
|
+
itemListElement: [...root.querySelectorAll('a')].map((a, i) => ({
|
|
3563
|
+
'@type': 'ListItem',
|
|
3564
|
+
position: i + 1,
|
|
3565
|
+
name: a.textContent?.trim(),
|
|
3566
|
+
item: a.href,
|
|
3567
|
+
})),
|
|
3568
|
+
}`
|
|
3569
|
+
},
|
|
3570
|
+
{
|
|
3571
|
+
id: "Product",
|
|
3572
|
+
label: "Product",
|
|
3573
|
+
object: `{
|
|
3574
|
+
'@context': 'https://schema.org',
|
|
3575
|
+
'@type': 'Product',
|
|
3576
|
+
name: root.querySelector('h1, h2, h3')?.textContent?.trim(),
|
|
3577
|
+
image: [...root.querySelectorAll('img')].map((img) => img.src),
|
|
3578
|
+
description: root.querySelector('p')?.textContent?.trim(),
|
|
3579
|
+
// TODO: brand, sku, offers { '@type': 'Offer', price, priceCurrency, availability }
|
|
3580
|
+
}`
|
|
3581
|
+
},
|
|
3582
|
+
{
|
|
3583
|
+
id: "Organization",
|
|
3584
|
+
label: "Organization",
|
|
3585
|
+
object: `{
|
|
3586
|
+
'@context': 'https://schema.org',
|
|
3587
|
+
'@type': 'Organization',
|
|
3588
|
+
name: document.title,
|
|
3589
|
+
url: window.location.origin,
|
|
3590
|
+
logo: root.querySelector('img')?.src,
|
|
3591
|
+
// TODO: sameAs: [social profile URLs], contactPoint
|
|
3592
|
+
}`
|
|
3593
|
+
},
|
|
3594
|
+
{
|
|
3595
|
+
id: "LocalBusiness",
|
|
3596
|
+
label: "Local business",
|
|
3597
|
+
object: `{
|
|
3598
|
+
'@context': 'https://schema.org',
|
|
3599
|
+
'@type': 'LocalBusiness',
|
|
3600
|
+
name: root.querySelector('h1, h2, h3')?.textContent?.trim() || document.title,
|
|
3601
|
+
image: root.querySelector('img')?.src,
|
|
3602
|
+
url: window.location.origin,
|
|
3603
|
+
// TODO: address (PostalAddress), telephone, openingHours, geo
|
|
3604
|
+
}`
|
|
3605
|
+
},
|
|
3606
|
+
{
|
|
3607
|
+
id: "Event",
|
|
3608
|
+
label: "Event",
|
|
3609
|
+
object: `{
|
|
3610
|
+
'@context': 'https://schema.org',
|
|
3611
|
+
'@type': 'Event',
|
|
3612
|
+
name: root.querySelector('h1, h2, h3')?.textContent?.trim(),
|
|
3613
|
+
image: [...root.querySelectorAll('img')].map((img) => img.src),
|
|
3614
|
+
url: window.location.href,
|
|
3615
|
+
// TODO: startDate, endDate, location (Place), offers
|
|
3616
|
+
}`
|
|
3617
|
+
},
|
|
3618
|
+
{
|
|
3619
|
+
id: "VideoObject",
|
|
3620
|
+
label: "Video",
|
|
3621
|
+
object: `{
|
|
3622
|
+
'@context': 'https://schema.org',
|
|
3623
|
+
'@type': 'VideoObject',
|
|
3624
|
+
name: root.querySelector('h1, h2, h3')?.textContent?.trim() || document.title,
|
|
3625
|
+
thumbnailUrl: root.querySelector('img')?.src,
|
|
3626
|
+
contentUrl: root.querySelector('a[href], video source[src]')?.href,
|
|
3627
|
+
// TODO: description, uploadDate, duration (ISO 8601)
|
|
3628
|
+
}`
|
|
3629
|
+
},
|
|
3630
|
+
{
|
|
3631
|
+
id: "Recipe",
|
|
3632
|
+
label: "Recipe",
|
|
3633
|
+
object: `{
|
|
3634
|
+
'@context': 'https://schema.org',
|
|
3635
|
+
'@type': 'Recipe',
|
|
3636
|
+
name: root.querySelector('h1, h2, h3')?.textContent?.trim(),
|
|
3637
|
+
image: [...root.querySelectorAll('img')].map((img) => img.src),
|
|
3638
|
+
recipeIngredient: [...root.querySelectorAll('li')].map((li) => li.textContent?.trim()),
|
|
3639
|
+
// TODO: recipeInstructions, prepTime, cookTime, nutrition
|
|
3640
|
+
}`
|
|
3641
|
+
},
|
|
3642
|
+
{
|
|
3643
|
+
id: "HowTo",
|
|
3644
|
+
label: "How-to",
|
|
3645
|
+
object: `{
|
|
3646
|
+
'@context': 'https://schema.org',
|
|
3647
|
+
'@type': 'HowTo',
|
|
3648
|
+
name: root.querySelector('h1, h2, h3')?.textContent?.trim(),
|
|
3649
|
+
step: [...root.querySelectorAll('li')].map((li, i) => ({
|
|
3650
|
+
'@type': 'HowToStep',
|
|
3651
|
+
position: i + 1,
|
|
3652
|
+
text: li.textContent?.trim(),
|
|
3653
|
+
})),
|
|
3654
|
+
}`
|
|
3655
|
+
}
|
|
3656
|
+
];
|
|
1775
3657
|
}
|
|
1776
3658
|
});
|
|
1777
3659
|
|
|
1778
|
-
// src/commands/
|
|
1779
|
-
var
|
|
1780
|
-
__export(
|
|
1781
|
-
|
|
3660
|
+
// src/commands/schema.ts
|
|
3661
|
+
var schema_exports = {};
|
|
3662
|
+
__export(schema_exports, {
|
|
3663
|
+
schemaBlock: () => schemaBlock
|
|
1782
3664
|
});
|
|
1783
|
-
import { existsSync as
|
|
1784
|
-
import { readFile as
|
|
1785
|
-
import
|
|
1786
|
-
import
|
|
1787
|
-
async function
|
|
3665
|
+
import { existsSync as existsSync17 } from "fs";
|
|
3666
|
+
import { readFile as readFile12, writeFile as writeFile13 } from "fs/promises";
|
|
3667
|
+
import path20 from "path";
|
|
3668
|
+
import chalk7 from "chalk";
|
|
3669
|
+
async function schemaBlock(blockName, options = {}) {
|
|
1788
3670
|
const projectRoot = findProjectRoot();
|
|
1789
3671
|
if (!projectRoot) {
|
|
1790
3672
|
logger.error("Not inside an EDS project (no EDS project markers found).");
|
|
1791
3673
|
process.exitCode = 1;
|
|
1792
3674
|
return;
|
|
1793
3675
|
}
|
|
1794
|
-
logger.logoOnce(logo("
|
|
1795
|
-
const jsPath =
|
|
1796
|
-
if (!
|
|
3676
|
+
logger.logoOnce(logo("Schema - JSON-LD"));
|
|
3677
|
+
const jsPath = path20.join(projectRoot, "blocks", blockName, `${blockName}.js`);
|
|
3678
|
+
if (!existsSync17(jsPath)) {
|
|
1797
3679
|
logger.error(`Block "${blockName}" not found (no blocks/${blockName}/${blockName}.js).`);
|
|
1798
3680
|
process.exitCode = 1;
|
|
1799
3681
|
return;
|
|
1800
3682
|
}
|
|
1801
|
-
|
|
1802
|
-
|
|
3683
|
+
let typeId = options.type;
|
|
3684
|
+
if (!typeId && !options.yes) {
|
|
3685
|
+
const { select } = await import("@inquirer/prompts");
|
|
3686
|
+
typeId = await select({
|
|
3687
|
+
message: "Which schema.org type?",
|
|
3688
|
+
choices: SCHEMA_TYPES.map((t) => ({ name: `${t.id} - ${t.label}`, value: t.id }))
|
|
3689
|
+
});
|
|
3690
|
+
}
|
|
3691
|
+
const type = typeId ? schemaTypeById(typeId) : void 0;
|
|
3692
|
+
if (!type) {
|
|
3693
|
+
logger.error(
|
|
3694
|
+
`Unknown schema type "${typeId}". Options: ${SCHEMA_TYPES.map((t) => t.id).join(", ")}`
|
|
3695
|
+
);
|
|
3696
|
+
process.exitCode = 1;
|
|
3697
|
+
return;
|
|
3698
|
+
}
|
|
3699
|
+
const res = injectJsonLd(await readFile12(jsPath, "utf-8"), type);
|
|
1803
3700
|
if (!res.changed) {
|
|
1804
|
-
logger.warn(`Not
|
|
3701
|
+
logger.warn(`Not added: ${res.reason}.`);
|
|
1805
3702
|
return;
|
|
1806
3703
|
}
|
|
1807
|
-
await
|
|
1808
|
-
logger.info(heading("
|
|
1809
|
-
|
|
1810
|
-
|
|
3704
|
+
await writeFile13(jsPath, res.js);
|
|
3705
|
+
logger.info(heading("JSON-LD added"));
|
|
3706
|
+
logger.info(accentLine(`blocks/${blockName}/${blockName}.js`, `${type.id} (${type.label})`));
|
|
3707
|
+
logger.info("");
|
|
3708
|
+
logger.info(
|
|
3709
|
+
chalk7.dim(
|
|
3710
|
+
" Review the generated object - some fields are heuristic or TODO. Validate at search.google.com/test/rich-results."
|
|
3711
|
+
)
|
|
1811
3712
|
);
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
3713
|
+
}
|
|
3714
|
+
var init_schema = __esm({
|
|
3715
|
+
"src/commands/schema.ts"() {
|
|
3716
|
+
"use strict";
|
|
3717
|
+
init_esm_shims();
|
|
3718
|
+
init_logger();
|
|
3719
|
+
init_project();
|
|
3720
|
+
init_jsonld();
|
|
3721
|
+
init_ui();
|
|
1816
3722
|
}
|
|
1817
|
-
|
|
1818
|
-
|
|
3723
|
+
});
|
|
3724
|
+
|
|
3725
|
+
// src/lib/ue/check.ts
|
|
3726
|
+
function metaContent(html, name) {
|
|
3727
|
+
for (const tag of html.match(/<meta\b[^>]*>/gi) ?? []) {
|
|
3728
|
+
if (tag.match(/\bname=["']([^"']+)["']/i)?.[1] === name) {
|
|
3729
|
+
return tag.match(/\bcontent=["']([^"']*)["']/i)?.[1] ?? "";
|
|
3730
|
+
}
|
|
3731
|
+
}
|
|
3732
|
+
return null;
|
|
3733
|
+
}
|
|
3734
|
+
function hasAemConnection(html) {
|
|
3735
|
+
return metaContent(html, AEM_CONNECTION_META) !== null;
|
|
1819
3736
|
}
|
|
1820
|
-
|
|
1821
|
-
|
|
3737
|
+
function modelIds(componentModels) {
|
|
3738
|
+
if (!Array.isArray(componentModels)) return [];
|
|
3739
|
+
return componentModels.map((m) => m && typeof m === "object" ? m.id : void 0).filter((id) => typeof id === "string");
|
|
3740
|
+
}
|
|
3741
|
+
function assessReadiness(input) {
|
|
3742
|
+
const isUeProject = !!input.configPresent["component-definition.json"] && !!input.configPresent["component-models.json"];
|
|
3743
|
+
const ids = new Set(input.modelIds);
|
|
3744
|
+
const blocks = input.blocks.map((b) => {
|
|
3745
|
+
const editable = b.hasModelFile || ids.has(b.name);
|
|
3746
|
+
return {
|
|
3747
|
+
name: b.name,
|
|
3748
|
+
editable,
|
|
3749
|
+
reason: editable ? void 0 : "no UE model (_<name>.json or component-models entry)"
|
|
3750
|
+
};
|
|
3751
|
+
});
|
|
3752
|
+
return { isUeProject, blocks };
|
|
3753
|
+
}
|
|
3754
|
+
var UE_CONFIG_FILES, AEM_CONNECTION_META;
|
|
3755
|
+
var init_check = __esm({
|
|
3756
|
+
"src/lib/ue/check.ts"() {
|
|
1822
3757
|
"use strict";
|
|
1823
3758
|
init_esm_shims();
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
3759
|
+
UE_CONFIG_FILES = [
|
|
3760
|
+
"component-definition.json",
|
|
3761
|
+
"component-models.json",
|
|
3762
|
+
"component-filters.json"
|
|
3763
|
+
];
|
|
3764
|
+
AEM_CONNECTION_META = "urn:adobe:aue:system:aemconnection";
|
|
1828
3765
|
}
|
|
1829
3766
|
});
|
|
1830
3767
|
|
|
1831
|
-
// src/commands/
|
|
1832
|
-
var
|
|
1833
|
-
__export(
|
|
1834
|
-
|
|
3768
|
+
// src/commands/ue.ts
|
|
3769
|
+
var ue_exports = {};
|
|
3770
|
+
__export(ue_exports, {
|
|
3771
|
+
ueCheck: () => ueCheck,
|
|
3772
|
+
ueOpen: () => ueOpen
|
|
1835
3773
|
});
|
|
1836
|
-
import { existsSync as
|
|
1837
|
-
import {
|
|
1838
|
-
import
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
3774
|
+
import { existsSync as existsSync18 } from "fs";
|
|
3775
|
+
import { readFile as readFile13, readdir as readdir3 } from "fs/promises";
|
|
3776
|
+
import path21 from "path";
|
|
3777
|
+
import chalk8 from "chalk";
|
|
3778
|
+
async function readJson2(file) {
|
|
3779
|
+
if (!existsSync18(file)) return null;
|
|
3780
|
+
try {
|
|
3781
|
+
return JSON.parse(await readFile13(file, "utf-8"));
|
|
3782
|
+
} catch {
|
|
3783
|
+
return null;
|
|
3784
|
+
}
|
|
1846
3785
|
}
|
|
1847
|
-
function
|
|
1848
|
-
const
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
}
|
|
3786
|
+
async function scanBlocks(root) {
|
|
3787
|
+
const blocksDir = path21.join(root, "blocks");
|
|
3788
|
+
if (!existsSync18(blocksDir)) return [];
|
|
3789
|
+
const entries = await readdir3(blocksDir, { withFileTypes: true });
|
|
3790
|
+
return entries.filter((e) => e.isDirectory()).map((e) => ({
|
|
3791
|
+
name: e.name,
|
|
3792
|
+
hasModelFile: existsSync18(path21.join(blocksDir, e.name, `_${e.name}.json`))
|
|
3793
|
+
}));
|
|
3794
|
+
}
|
|
3795
|
+
async function openUrl2(url) {
|
|
3796
|
+
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
3797
|
+
try {
|
|
3798
|
+
const { execa } = await import("execa");
|
|
3799
|
+
await execa(cmd, [url], { stdio: "ignore" });
|
|
3800
|
+
} catch {
|
|
1860
3801
|
}
|
|
1861
|
-
return lines;
|
|
1862
3802
|
}
|
|
1863
|
-
function
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
3803
|
+
async function gitRemote(root) {
|
|
3804
|
+
try {
|
|
3805
|
+
const { execa } = await import("execa");
|
|
3806
|
+
const res = await execa("git", ["remote", "get-url", "origin"], { cwd: root });
|
|
3807
|
+
return res.stdout.trim() || null;
|
|
3808
|
+
} catch {
|
|
3809
|
+
return null;
|
|
1869
3810
|
}
|
|
1870
|
-
return n;
|
|
1871
3811
|
}
|
|
1872
|
-
async function
|
|
1873
|
-
const
|
|
1874
|
-
if (!
|
|
3812
|
+
async function ueCheck() {
|
|
3813
|
+
const root = findProjectRoot();
|
|
3814
|
+
if (!root) {
|
|
1875
3815
|
logger.error("Not inside an EDS project (no EDS project markers found).");
|
|
1876
3816
|
process.exitCode = 1;
|
|
1877
3817
|
return;
|
|
1878
3818
|
}
|
|
1879
|
-
|
|
1880
|
-
const
|
|
1881
|
-
const
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
}
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
componentDir = path10.resolve(componentDir.trim());
|
|
1895
|
-
if (!findDialogFile(componentDir)) {
|
|
1896
|
-
logger.error(`No Touch UI dialog (_cq_dialog/.content.xml) found in ${componentDir}.`);
|
|
1897
|
-
process.exitCode = 1;
|
|
1898
|
-
return;
|
|
1899
|
-
}
|
|
1900
|
-
const parsed = await readDialogFields(componentDir);
|
|
1901
|
-
if (!parsed || parsed.fields.length === 0) {
|
|
1902
|
-
logger.warn("Dialog found but no mappable fields were detected.");
|
|
1903
|
-
if (parsed?.unmapped.length)
|
|
1904
|
-
logger.info(`Unmapped resourceTypes: ${parsed.unmapped.join(", ")}`);
|
|
1905
|
-
process.exitCode = 1;
|
|
1906
|
-
return;
|
|
3819
|
+
logger.logoOnce(logo("Universal Editor - readiness"));
|
|
3820
|
+
const configPresent = {};
|
|
3821
|
+
for (const f2 of UE_CONFIG_FILES) configPresent[f2] = existsSync18(path21.join(root, f2));
|
|
3822
|
+
const componentModels = await readJson2(path21.join(root, "component-models.json"));
|
|
3823
|
+
const blocks = await scanBlocks(root);
|
|
3824
|
+
const { isUeProject, blocks: blockReadiness } = assessReadiness({
|
|
3825
|
+
configPresent,
|
|
3826
|
+
blocks,
|
|
3827
|
+
modelIds: modelIds(componentModels)
|
|
3828
|
+
});
|
|
3829
|
+
logger.info(heading("Universal Editor config"));
|
|
3830
|
+
const names = [...UE_CONFIG_FILES];
|
|
3831
|
+
const w = columnWidth(names);
|
|
3832
|
+
for (const f2 of names) {
|
|
3833
|
+
logger.info(statusLine(configPresent[f2] ? "pass" : "fail", f2, void 0, w));
|
|
1907
3834
|
}
|
|
1908
|
-
|
|
1909
|
-
const blockName = options.name && slug2(options.name) || (interactive ? slug2(await input({ message: "Block name", default: slug2(title) })) : slug2(title));
|
|
1910
|
-
logger.info(heading("Mapped fields", `${countLeaves(parsed.fields)} from dialog`));
|
|
1911
|
-
for (const line of previewFields(parsed.fields)) logger.info(` ${line}`);
|
|
1912
|
-
if (parsed.unmapped.length) {
|
|
3835
|
+
if (!isUeProject) {
|
|
1913
3836
|
logger.info("");
|
|
1914
|
-
logger.warn(
|
|
1915
|
-
|
|
1916
|
-
logger.info("");
|
|
1917
|
-
if (interactive) {
|
|
1918
|
-
const go = await confirm({ message: `Generate block "${blockName}"?`, default: true });
|
|
1919
|
-
if (!go) {
|
|
1920
|
-
logger.info("Cancelled.");
|
|
1921
|
-
return;
|
|
1922
|
-
}
|
|
1923
|
-
}
|
|
1924
|
-
const blockDir = path10.join(projectRoot, "blocks", blockName);
|
|
1925
|
-
if (existsSync8(blockDir)) {
|
|
1926
|
-
logger.error(
|
|
1927
|
-
`Block "${blockName}" already exists at blocks/${blockName}. Use --name to pick another.`
|
|
3837
|
+
logger.warn(
|
|
3838
|
+
"This doesn't look like a Universal Editor (crosswalk) project - run `eds scaffold ue` first."
|
|
1928
3839
|
);
|
|
1929
3840
|
process.exitCode = 1;
|
|
1930
3841
|
return;
|
|
1931
3842
|
}
|
|
1932
|
-
const
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
preprocessedNote = lib.preprocessed;
|
|
3843
|
+
const headFile = path21.join(root, "head.html");
|
|
3844
|
+
if (existsSync18(headFile)) {
|
|
3845
|
+
const connected = hasAemConnection(await readFile13(headFile, "utf-8"));
|
|
3846
|
+
logger.info(
|
|
3847
|
+
statusLine(
|
|
3848
|
+
connected ? "pass" : "info",
|
|
3849
|
+
"head.html",
|
|
3850
|
+
connected ? "aemconnection present" : "no aemconnection (fine for cloud author)"
|
|
3851
|
+
)
|
|
3852
|
+
);
|
|
1943
3853
|
}
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
migratedModelFile(blockName, title, parsed.fields)
|
|
1953
|
-
);
|
|
1954
|
-
logger.info(heading("Created"));
|
|
1955
|
-
const files = [`${blockName}.js`, `${blockName}.css`, `_${blockName}.json`];
|
|
1956
|
-
const w = columnWidth(files);
|
|
1957
|
-
logger.info(accentLine(`${blockName}.js`, "decorator stub (port HTL logic)", w));
|
|
1958
|
-
logger.info(accentLine(`${blockName}.css`, cssMessage, w));
|
|
1959
|
-
logger.info(accentLine(`_${blockName}.json`, "Universal Editor model", w));
|
|
1960
|
-
if (preprocessedNote.length) {
|
|
1961
|
-
logger.info("");
|
|
1962
|
-
logger.warn(`LESS/SCSS found (needs a build, not copied): ${preprocessedNote.join(", ")}`);
|
|
3854
|
+
logger.info(heading("Blocks", `${blockReadiness.length} found`));
|
|
3855
|
+
if (blockReadiness.length === 0) {
|
|
3856
|
+
logger.info(chalk8.dim(" No blocks yet."));
|
|
3857
|
+
} else {
|
|
3858
|
+
const bw = columnWidth(blockReadiness.map((b) => b.name));
|
|
3859
|
+
for (const b of blockReadiness) {
|
|
3860
|
+
logger.info(statusLine(b.editable ? "pass" : "warn", b.name, b.reason, bw));
|
|
3861
|
+
}
|
|
1963
3862
|
}
|
|
3863
|
+
const editable = blockReadiness.filter((b) => b.editable).length;
|
|
3864
|
+
const notEditable = blockReadiness.length - editable;
|
|
1964
3865
|
logger.info("");
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
3866
|
+
logger.info(summary({ pass: editable, warn: notEditable }));
|
|
3867
|
+
logger.info(chalk8.dim(" Open a page in the editor with `eds ue open <path>`."));
|
|
3868
|
+
}
|
|
3869
|
+
async function ueOpen(pagePath, options = {}) {
|
|
3870
|
+
const root = findProjectRoot() ?? process.cwd();
|
|
3871
|
+
const pPath = pagePath ?? options.path ?? "/";
|
|
3872
|
+
let host = options.url;
|
|
3873
|
+
if (!host) {
|
|
3874
|
+
const remote = await gitRemote(root);
|
|
3875
|
+
const parsed = remote ? parseRemote(remote) : null;
|
|
3876
|
+
if (!parsed) {
|
|
3877
|
+
logger.error("Couldn't determine the preview host from git.");
|
|
3878
|
+
logger.info(
|
|
3879
|
+
chalk8.dim(
|
|
3880
|
+
" Pass it explicitly: `eds ue open <path> --url main--<repo>--<owner>.aem.page`"
|
|
3881
|
+
)
|
|
3882
|
+
);
|
|
3883
|
+
process.exitCode = 1;
|
|
3884
|
+
return;
|
|
1973
3885
|
}
|
|
3886
|
+
host = previewHost(parsed.owner, parsed.repo, options.ref ?? "main");
|
|
1974
3887
|
}
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
);
|
|
3888
|
+
const link2 = editorLink({ host, path: pPath, org: options.org });
|
|
3889
|
+
logger.info(heading("Universal Editor"));
|
|
3890
|
+
logger.info(accentLine("page", `${host}${pPath.startsWith("/") ? "" : "/"}${pPath}`));
|
|
3891
|
+
logger.info(accentLine("open", link2));
|
|
3892
|
+
if (options.open !== false) await openUrl2(link2);
|
|
3893
|
+
else logger.info(chalk8.dim(" (use without --no-open to launch the browser)"));
|
|
1978
3894
|
}
|
|
1979
|
-
var
|
|
1980
|
-
|
|
1981
|
-
"src/commands/migrate.ts"() {
|
|
3895
|
+
var init_ue3 = __esm({
|
|
3896
|
+
"src/commands/ue.ts"() {
|
|
1982
3897
|
"use strict";
|
|
1983
3898
|
init_esm_shims();
|
|
1984
3899
|
init_logger();
|
|
1985
|
-
init_clientlib();
|
|
1986
|
-
init_component();
|
|
1987
3900
|
init_project();
|
|
3901
|
+
init_check();
|
|
3902
|
+
init_link();
|
|
1988
3903
|
init_ui();
|
|
1989
|
-
slug2 = (s) => s.trim().toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
1990
3904
|
}
|
|
1991
3905
|
});
|
|
1992
3906
|
|
|
@@ -1996,16 +3910,16 @@ __export(create_exports, {
|
|
|
1996
3910
|
createBlock: () => createBlock,
|
|
1997
3911
|
ueModelFor: () => ueModelFor
|
|
1998
3912
|
});
|
|
1999
|
-
import { existsSync as
|
|
2000
|
-
import { mkdir as
|
|
2001
|
-
import
|
|
2002
|
-
import
|
|
3913
|
+
import { existsSync as existsSync19 } from "fs";
|
|
3914
|
+
import { mkdir as mkdir7, writeFile as writeFile14 } from "fs/promises";
|
|
3915
|
+
import path22 from "path";
|
|
3916
|
+
import chalk9 from "chalk";
|
|
2003
3917
|
function titleCase(name) {
|
|
2004
3918
|
return name.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
2005
3919
|
}
|
|
2006
3920
|
function ueModelFor(name) {
|
|
2007
3921
|
const title = titleCase(name);
|
|
2008
|
-
const
|
|
3922
|
+
const model2 = {
|
|
2009
3923
|
definitions: [
|
|
2010
3924
|
{
|
|
2011
3925
|
title,
|
|
@@ -2058,7 +3972,7 @@ function ueModelFor(name) {
|
|
|
2058
3972
|
],
|
|
2059
3973
|
filters: []
|
|
2060
3974
|
};
|
|
2061
|
-
return `${JSON.stringify(
|
|
3975
|
+
return `${JSON.stringify(model2, null, 2)}
|
|
2062
3976
|
`;
|
|
2063
3977
|
}
|
|
2064
3978
|
async function createBlock(name, options = {}) {
|
|
@@ -2077,13 +3991,13 @@ async function createBlock(name, options = {}) {
|
|
|
2077
3991
|
process.exitCode = 1;
|
|
2078
3992
|
return;
|
|
2079
3993
|
}
|
|
2080
|
-
const blocksDir =
|
|
2081
|
-
if (
|
|
3994
|
+
const blocksDir = path22.join(projectRoot, "blocks", name);
|
|
3995
|
+
if (existsSync19(blocksDir)) {
|
|
2082
3996
|
logger.error(`Block "${name}" already exists at ${blocksDir}`);
|
|
2083
3997
|
process.exitCode = 1;
|
|
2084
3998
|
return;
|
|
2085
3999
|
}
|
|
2086
|
-
await
|
|
4000
|
+
await mkdir7(blocksDir, { recursive: true });
|
|
2087
4001
|
const jsContent = `export default function decorate(block) {
|
|
2088
4002
|
// TODO: implement ${name} block
|
|
2089
4003
|
}
|
|
@@ -2092,14 +4006,14 @@ async function createBlock(name, options = {}) {
|
|
|
2092
4006
|
/* TODO: style ${name} block */
|
|
2093
4007
|
}
|
|
2094
4008
|
`;
|
|
2095
|
-
await
|
|
2096
|
-
await
|
|
4009
|
+
await writeFile14(path22.join(blocksDir, `${name}.js`), jsContent);
|
|
4010
|
+
await writeFile14(path22.join(blocksDir, `${name}.css`), cssContent);
|
|
2097
4011
|
const withUe = options.ueModel !== false;
|
|
2098
4012
|
if (withUe) {
|
|
2099
|
-
await
|
|
4013
|
+
await writeFile14(path22.join(blocksDir, `_${name}.json`), ueModelFor(name));
|
|
2100
4014
|
}
|
|
2101
4015
|
logger.success(
|
|
2102
|
-
`Block "${name}" created at ${
|
|
4016
|
+
`Block "${name}" created at ${chalk9.cyan(path22.relative(projectRoot, blocksDir))}${withUe ? chalk9.dim(" (+ Universal Editor model)") : ""}`
|
|
2103
4017
|
);
|
|
2104
4018
|
}
|
|
2105
4019
|
var init_create = __esm({
|
|
@@ -2113,10 +4027,10 @@ var init_create = __esm({
|
|
|
2113
4027
|
});
|
|
2114
4028
|
|
|
2115
4029
|
// src/lib/agents/detect.ts
|
|
2116
|
-
import { existsSync as
|
|
2117
|
-
import { readFile as
|
|
4030
|
+
import { existsSync as existsSync20 } from "fs";
|
|
4031
|
+
import { readFile as readFile14 } from "fs/promises";
|
|
2118
4032
|
import { homedir } from "os";
|
|
2119
|
-
import
|
|
4033
|
+
import path23 from "path";
|
|
2120
4034
|
async function whichBinary(name) {
|
|
2121
4035
|
const { execaCommand } = await import("execa");
|
|
2122
4036
|
try {
|
|
@@ -2144,8 +4058,8 @@ function findFigmaServerKey(node) {
|
|
|
2144
4058
|
async function findFigmaMcpServer(configPaths) {
|
|
2145
4059
|
for (const configPath of configPaths) {
|
|
2146
4060
|
try {
|
|
2147
|
-
if (!
|
|
2148
|
-
const content = await
|
|
4061
|
+
if (!existsSync20(configPath)) continue;
|
|
4062
|
+
const content = await readFile14(configPath, "utf-8");
|
|
2149
4063
|
try {
|
|
2150
4064
|
const key = findFigmaServerKey(JSON.parse(content));
|
|
2151
4065
|
if (key) return key;
|
|
@@ -2167,7 +4081,7 @@ async function detectAgent(preferred) {
|
|
|
2167
4081
|
return {
|
|
2168
4082
|
type: def.type,
|
|
2169
4083
|
path: binPath,
|
|
2170
|
-
configDir:
|
|
4084
|
+
configDir: existsSync20(def.configDir) ? def.configDir : void 0,
|
|
2171
4085
|
hasFigmaMcp: !!figmaMcpServerName,
|
|
2172
4086
|
figmaMcpServerName
|
|
2173
4087
|
};
|
|
@@ -2182,7 +4096,7 @@ async function detectAgent(preferred) {
|
|
|
2182
4096
|
return {
|
|
2183
4097
|
type: def.type,
|
|
2184
4098
|
path: binPath,
|
|
2185
|
-
configDir:
|
|
4099
|
+
configDir: existsSync20(def.configDir) ? def.configDir : void 0,
|
|
2186
4100
|
hasFigmaMcp: !!figmaMcpServerName,
|
|
2187
4101
|
figmaMcpServerName
|
|
2188
4102
|
};
|
|
@@ -2199,7 +4113,7 @@ async function detectAllAgents() {
|
|
|
2199
4113
|
agents.push({
|
|
2200
4114
|
type: def.type,
|
|
2201
4115
|
path: binPath,
|
|
2202
|
-
configDir:
|
|
4116
|
+
configDir: existsSync20(def.configDir) ? def.configDir : void 0,
|
|
2203
4117
|
hasFigmaMcp: !!figmaMcpServerName,
|
|
2204
4118
|
figmaMcpServerName
|
|
2205
4119
|
});
|
|
@@ -2217,22 +4131,22 @@ var init_detect = __esm({
|
|
|
2217
4131
|
{
|
|
2218
4132
|
type: "claude",
|
|
2219
4133
|
binary: "claude",
|
|
2220
|
-
configDir:
|
|
4134
|
+
configDir: path23.join(homedir(), ".claude"),
|
|
2221
4135
|
mcpConfigPaths: [
|
|
2222
|
-
|
|
2223
|
-
|
|
4136
|
+
path23.join(homedir(), ".claude", "plugins.json"),
|
|
4137
|
+
path23.join(homedir(), ".claude.json")
|
|
2224
4138
|
]
|
|
2225
4139
|
},
|
|
2226
4140
|
{
|
|
2227
4141
|
type: "cursor",
|
|
2228
4142
|
binary: "cursor",
|
|
2229
|
-
configDir:
|
|
2230
|
-
mcpConfigPaths: [
|
|
4143
|
+
configDir: path23.join(homedir(), ".cursor"),
|
|
4144
|
+
mcpConfigPaths: [path23.join(homedir(), ".cursor", "mcp.json")]
|
|
2231
4145
|
},
|
|
2232
4146
|
{
|
|
2233
4147
|
type: "codex",
|
|
2234
4148
|
binary: "codex",
|
|
2235
|
-
configDir:
|
|
4149
|
+
configDir: path23.join(homedir(), ".codex"),
|
|
2236
4150
|
mcpConfigPaths: []
|
|
2237
4151
|
}
|
|
2238
4152
|
];
|
|
@@ -2240,27 +4154,27 @@ var init_detect = __esm({
|
|
|
2240
4154
|
});
|
|
2241
4155
|
|
|
2242
4156
|
// src/lib/agents/invoke.ts
|
|
2243
|
-
import { mkdir as
|
|
4157
|
+
import { mkdir as mkdir8, readFile as readFile15, writeFile as writeFile15 } from "fs/promises";
|
|
2244
4158
|
import { tmpdir } from "os";
|
|
2245
|
-
import
|
|
4159
|
+
import path24 from "path";
|
|
2246
4160
|
import readline from "readline";
|
|
2247
|
-
import
|
|
4161
|
+
import chalk10 from "chalk";
|
|
2248
4162
|
async function savePromptToFile(prompt, projectRoot) {
|
|
2249
|
-
const base = projectRoot ?
|
|
2250
|
-
const dir =
|
|
2251
|
-
await
|
|
4163
|
+
const base = projectRoot ? path24.join(projectRoot, "tmp") : tmpdir();
|
|
4164
|
+
const dir = path24.join(base, "eds-cli");
|
|
4165
|
+
await mkdir8(dir, { recursive: true });
|
|
2252
4166
|
const filename = `from-design-${Date.now()}.md`;
|
|
2253
|
-
const filePath =
|
|
2254
|
-
await
|
|
4167
|
+
const filePath = path24.join(dir, filename);
|
|
4168
|
+
await writeFile15(filePath, prompt, "utf-8");
|
|
2255
4169
|
return filePath;
|
|
2256
4170
|
}
|
|
2257
4171
|
async function invokeAgent(agent, promptFile, cwd) {
|
|
2258
4172
|
const { execa } = await import("execa");
|
|
2259
|
-
const prompt = await
|
|
4173
|
+
const prompt = await readFile15(promptFile, "utf-8");
|
|
2260
4174
|
try {
|
|
2261
4175
|
switch (agent.type) {
|
|
2262
4176
|
case "claude": {
|
|
2263
|
-
logger.info(`Running Claude Code ${
|
|
4177
|
+
logger.info(`Running Claude Code ${chalk10.dim("- live log:")}`);
|
|
2264
4178
|
const figmaServers = [
|
|
2265
4179
|
...agent.figmaMcpServerName ? [agent.figmaMcpServerName] : [],
|
|
2266
4180
|
"figma-sse",
|
|
@@ -2323,10 +4237,10 @@ async function renderClaudeStream(sub, cwd) {
|
|
|
2323
4237
|
const ora4 = (await import("ora")).default;
|
|
2324
4238
|
const start = Date.now();
|
|
2325
4239
|
const elapsed = () => `${Math.round((Date.now() - start) / 1e3)}s`;
|
|
2326
|
-
const spinner = ora4({ text: `thinking ${
|
|
4240
|
+
const spinner = ora4({ text: `thinking ${chalk10.dim(elapsed())}`, color: "yellow" }).start();
|
|
2327
4241
|
let activity = "thinking";
|
|
2328
4242
|
const timer = setInterval(() => {
|
|
2329
|
-
spinner.text = `${activity} ${
|
|
4243
|
+
spinner.text = `${activity} ${chalk10.dim(elapsed())}`;
|
|
2330
4244
|
}, 300);
|
|
2331
4245
|
const pending = /* @__PURE__ */ new Map();
|
|
2332
4246
|
const emit = (line) => {
|
|
@@ -2352,7 +4266,7 @@ async function renderClaudeStream(sub, cwd) {
|
|
|
2352
4266
|
for (const block2 of content) {
|
|
2353
4267
|
if (block2.type === "text" && "text" in block2) {
|
|
2354
4268
|
const first = block2.text.trim().split("\n")[0].slice(0, 100);
|
|
2355
|
-
if (first) emit(
|
|
4269
|
+
if (first) emit(chalk10.dim(first));
|
|
2356
4270
|
} else if (block2.type === "tool_use" && "name" in block2) {
|
|
2357
4271
|
const id = "id" in block2 && block2.id ? block2.id : `${Math.random()}`;
|
|
2358
4272
|
pending.set(id, {
|
|
@@ -2368,8 +4282,8 @@ async function renderClaudeStream(sub, cwd) {
|
|
|
2368
4282
|
const p = pending.get(block2.tool_use_id);
|
|
2369
4283
|
if (p) {
|
|
2370
4284
|
const dur = `${((Date.now() - p.start) / 1e3).toFixed(1)}s`;
|
|
2371
|
-
const failed = "is_error" in block2 && block2.is_error ? ` ${
|
|
2372
|
-
emit(`${p.label} ${
|
|
4285
|
+
const failed = "is_error" in block2 && block2.is_error ? ` ${chalk10.red("failed")}` : "";
|
|
4286
|
+
emit(`${p.label} ${chalk10.dim(dur)}${failed}`);
|
|
2373
4287
|
pending.delete(block2.tool_use_id);
|
|
2374
4288
|
}
|
|
2375
4289
|
}
|
|
@@ -2381,9 +4295,9 @@ async function renderClaudeStream(sub, cwd) {
|
|
|
2381
4295
|
}
|
|
2382
4296
|
}
|
|
2383
4297
|
}
|
|
2384
|
-
for (const p of pending.values()) emit(`${p.label} ${
|
|
4298
|
+
for (const p of pending.values()) emit(`${p.label} ${chalk10.dim("\u2026")}`);
|
|
2385
4299
|
clearInterval(timer);
|
|
2386
|
-
if (result) spinner.succeed(`${brand.accent("agent finished")} ${
|
|
4300
|
+
if (result) spinner.succeed(`${brand.accent("agent finished")} ${chalk10.dim(result)}`);
|
|
2387
4301
|
else spinner.stop();
|
|
2388
4302
|
} catch (err) {
|
|
2389
4303
|
clearInterval(timer);
|
|
@@ -2396,22 +4310,22 @@ function toolLabel(name, input, cwd) {
|
|
|
2396
4310
|
const rel = (key) => {
|
|
2397
4311
|
const p = input?.[key];
|
|
2398
4312
|
if (typeof p !== "string") return "";
|
|
2399
|
-
return p.startsWith(cwd) ?
|
|
4313
|
+
return p.startsWith(cwd) ? path24.relative(cwd, p) : path24.basename(p);
|
|
2400
4314
|
};
|
|
2401
4315
|
if (name.includes("figma")) {
|
|
2402
4316
|
const short = name.split("__").pop() ?? name;
|
|
2403
|
-
return `${icon.figma} ${
|
|
4317
|
+
return `${icon.figma} ${chalk10.bold(short)} ${chalk10.dim("Figma MCP")}`;
|
|
2404
4318
|
}
|
|
2405
4319
|
switch (name) {
|
|
2406
4320
|
case "Write":
|
|
2407
4321
|
case "Edit":
|
|
2408
4322
|
case "NotebookEdit":
|
|
2409
|
-
return `${brand.accent("\u270E")} ${
|
|
4323
|
+
return `${brand.accent("\u270E")} ${chalk10.cyan(rel("file_path"))}`;
|
|
2410
4324
|
case "Read":
|
|
2411
|
-
return
|
|
4325
|
+
return chalk10.dim(`\xB7 read ${rel("file_path")}`);
|
|
2412
4326
|
case "Bash": {
|
|
2413
4327
|
const cmd = typeof input?.command === "string" ? input.command.split("\n")[0].slice(0, 70) : "";
|
|
2414
|
-
return
|
|
4328
|
+
return chalk10.dim(`$ ${cmd}`);
|
|
2415
4329
|
}
|
|
2416
4330
|
default:
|
|
2417
4331
|
return `${brand.blue("\u2192")} ${name}`;
|
|
@@ -2427,13 +4341,13 @@ var init_invoke = __esm({
|
|
|
2427
4341
|
});
|
|
2428
4342
|
|
|
2429
4343
|
// src/lib/config.ts
|
|
2430
|
-
import { existsSync as
|
|
2431
|
-
import { readFile as
|
|
4344
|
+
import { existsSync as existsSync21 } from "fs";
|
|
4345
|
+
import { readFile as readFile16 } from "fs/promises";
|
|
2432
4346
|
import { homedir as homedir2 } from "os";
|
|
2433
|
-
import
|
|
4347
|
+
import path25 from "path";
|
|
2434
4348
|
async function loadJsonFile(filePath) {
|
|
2435
4349
|
try {
|
|
2436
|
-
const content = await
|
|
4350
|
+
const content = await readFile16(filePath, "utf-8");
|
|
2437
4351
|
return JSON.parse(content);
|
|
2438
4352
|
} catch {
|
|
2439
4353
|
return void 0;
|
|
@@ -2441,7 +4355,7 @@ async function loadJsonFile(filePath) {
|
|
|
2441
4355
|
}
|
|
2442
4356
|
async function loadConfig(projectRoot) {
|
|
2443
4357
|
const globalRaw = await loadJsonFile(GLOBAL_CONFIG_PATH);
|
|
2444
|
-
const projectRaw = projectRoot ? await loadJsonFile(
|
|
4358
|
+
const projectRaw = projectRoot ? await loadJsonFile(path25.join(projectRoot, PROJECT_CONFIG_NAME)) : void 0;
|
|
2445
4359
|
const merged = {
|
|
2446
4360
|
...typeof globalRaw === "object" && globalRaw !== null ? globalRaw : {},
|
|
2447
4361
|
...typeof projectRaw === "object" && projectRaw !== null ? projectRaw : {}
|
|
@@ -2460,7 +4374,7 @@ function globalConfigPath() {
|
|
|
2460
4374
|
return GLOBAL_CONFIG_PATH;
|
|
2461
4375
|
}
|
|
2462
4376
|
function globalConfigExists() {
|
|
2463
|
-
return
|
|
4377
|
+
return existsSync21(GLOBAL_CONFIG_PATH);
|
|
2464
4378
|
}
|
|
2465
4379
|
var GLOBAL_CONFIG_DIR, GLOBAL_CONFIG_PATH, PROJECT_CONFIG_NAME;
|
|
2466
4380
|
var init_config = __esm({
|
|
@@ -2469,8 +4383,8 @@ var init_config = __esm({
|
|
|
2469
4383
|
init_esm_shims();
|
|
2470
4384
|
init_logger();
|
|
2471
4385
|
init_schemas();
|
|
2472
|
-
GLOBAL_CONFIG_DIR =
|
|
2473
|
-
GLOBAL_CONFIG_PATH =
|
|
4386
|
+
GLOBAL_CONFIG_DIR = path25.join(homedir2(), ".eds");
|
|
4387
|
+
GLOBAL_CONFIG_PATH = path25.join(GLOBAL_CONFIG_DIR, "config.json");
|
|
2474
4388
|
PROJECT_CONFIG_NAME = ".edsrc.json";
|
|
2475
4389
|
}
|
|
2476
4390
|
});
|
|
@@ -2520,9 +4434,9 @@ var init_node_id = __esm({
|
|
|
2520
4434
|
});
|
|
2521
4435
|
|
|
2522
4436
|
// src/lib/figma/prompt-builder.ts
|
|
2523
|
-
import { existsSync as
|
|
2524
|
-
import { readFile as
|
|
2525
|
-
import
|
|
4437
|
+
import { existsSync as existsSync22 } from "fs";
|
|
4438
|
+
import { readFile as readFile17 } from "fs/promises";
|
|
4439
|
+
import path26 from "path";
|
|
2526
4440
|
function contentSourceGuidance(source, cfHint, blockName) {
|
|
2527
4441
|
if (!source || source === "document" || source === "ue") return "";
|
|
2528
4442
|
const q = cfHint ? ` (model / persisted query: \`${cfHint}\`)` : "";
|
|
@@ -2651,6 +4565,9 @@ Rules:
|
|
|
2651
4565
|
- **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.
|
|
2652
4566
|
|
|
2653
4567
|
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\`.
|
|
4568
|
+
|
|
4569
|
+
**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:
|
|
4570
|
+
${PARTIALS.map((p) => `- \`${p.id}\` \u2014 ${p.label}`).join("\n")}
|
|
2654
4571
|
` : "";
|
|
2655
4572
|
return `# Pixel-Perfect EDS Block from ${providerName}
|
|
2656
4573
|
|
|
@@ -2734,10 +4651,10 @@ ${tokensHint}
|
|
|
2734
4651
|
`;
|
|
2735
4652
|
}
|
|
2736
4653
|
async function getTokensHint(projectRoot) {
|
|
2737
|
-
const stylesPath =
|
|
2738
|
-
if (!
|
|
4654
|
+
const stylesPath = path26.join(projectRoot, "styles", "styles.css");
|
|
4655
|
+
if (!existsSync22(stylesPath)) return "";
|
|
2739
4656
|
try {
|
|
2740
|
-
const css = await
|
|
4657
|
+
const css = await readFile17(stylesPath, "utf-8");
|
|
2741
4658
|
const rootMatch = css.match(/:root\s*\{([^}]+)\}/);
|
|
2742
4659
|
if (!rootMatch) return "";
|
|
2743
4660
|
const props = rootMatch[1].split("\n").filter((line) => line.trim().startsWith("--")).map((line) => line.trim()).join("\n ");
|
|
@@ -2762,6 +4679,7 @@ var init_prompt_builder = __esm({
|
|
|
2762
4679
|
"src/lib/figma/prompt-builder.ts"() {
|
|
2763
4680
|
"use strict";
|
|
2764
4681
|
init_esm_shims();
|
|
4682
|
+
init_partials();
|
|
2765
4683
|
PROMPT_VERSION = "0.5.0";
|
|
2766
4684
|
PROVIDER_NAMES = {
|
|
2767
4685
|
figma: "Figma",
|
|
@@ -2878,11 +4796,11 @@ var init_harness = __esm({
|
|
|
2878
4796
|
});
|
|
2879
4797
|
|
|
2880
4798
|
// src/lib/preview/server.ts
|
|
2881
|
-
import { createReadStream, existsSync as
|
|
4799
|
+
import { createReadStream, existsSync as existsSync23, statSync } from "fs";
|
|
2882
4800
|
import http from "http";
|
|
2883
|
-
import
|
|
4801
|
+
import path27 from "path";
|
|
2884
4802
|
function startServer(root, routes, port, host = "127.0.0.1") {
|
|
2885
|
-
const resolvedRoot =
|
|
4803
|
+
const resolvedRoot = path27.resolve(root);
|
|
2886
4804
|
const clients = /* @__PURE__ */ new Set();
|
|
2887
4805
|
const server = http.createServer((req, res) => {
|
|
2888
4806
|
const urlPath = decodeURIComponent((req.url ?? "/").split("?")[0]);
|
|
@@ -2908,19 +4826,19 @@ function startServer(root, routes, port, host = "127.0.0.1") {
|
|
|
2908
4826
|
res.end(route.body);
|
|
2909
4827
|
return;
|
|
2910
4828
|
}
|
|
2911
|
-
const filePath =
|
|
4829
|
+
const filePath = path27.join(resolvedRoot, urlPath);
|
|
2912
4830
|
if (!filePath.startsWith(resolvedRoot)) {
|
|
2913
4831
|
res.writeHead(403);
|
|
2914
4832
|
res.end("Forbidden");
|
|
2915
4833
|
return;
|
|
2916
4834
|
}
|
|
2917
|
-
if (!
|
|
4835
|
+
if (!existsSync23(filePath) || !statSync(filePath).isFile()) {
|
|
2918
4836
|
res.writeHead(404, { "content-type": "text/plain" });
|
|
2919
4837
|
res.end(`Not found: ${urlPath}`);
|
|
2920
4838
|
return;
|
|
2921
4839
|
}
|
|
2922
4840
|
res.writeHead(200, {
|
|
2923
|
-
"content-type": MIME[
|
|
4841
|
+
"content-type": MIME[path27.extname(filePath).toLowerCase()] ?? "application/octet-stream"
|
|
2924
4842
|
});
|
|
2925
4843
|
createReadStream(filePath).pipe(res);
|
|
2926
4844
|
});
|
|
@@ -2984,10 +4902,10 @@ var preview_exports = {};
|
|
|
2984
4902
|
__export(preview_exports, {
|
|
2985
4903
|
previewBlock: () => previewBlock
|
|
2986
4904
|
});
|
|
2987
|
-
import { existsSync as
|
|
2988
|
-
import { readFile as
|
|
2989
|
-
import
|
|
2990
|
-
import
|
|
4905
|
+
import { existsSync as existsSync24, watch } from "fs";
|
|
4906
|
+
import { readFile as readFile18 } from "fs/promises";
|
|
4907
|
+
import path28 from "path";
|
|
4908
|
+
import chalk11 from "chalk";
|
|
2991
4909
|
function labelFor(width) {
|
|
2992
4910
|
if (width <= 480) return `${width} mobile`;
|
|
2993
4911
|
if (width <= 1024) return `${width} tablet`;
|
|
@@ -3039,30 +4957,30 @@ async function previewBlock(name, options = {}) {
|
|
|
3039
4957
|
process.exitCode = 1;
|
|
3040
4958
|
return;
|
|
3041
4959
|
}
|
|
3042
|
-
const blockDir =
|
|
3043
|
-
if (!
|
|
4960
|
+
const blockDir = path28.join(projectRoot, "blocks", name);
|
|
4961
|
+
if (!existsSync24(blockDir)) {
|
|
3044
4962
|
logger.error(`Block "${name}" not found at blocks/${name}/.`);
|
|
3045
4963
|
process.exitCode = 1;
|
|
3046
4964
|
return;
|
|
3047
4965
|
}
|
|
3048
|
-
if (!
|
|
4966
|
+
if (!existsSync24(path28.join(blockDir, `${name}.css`)) && !existsSync24(path28.join(blockDir, `${name}.js`))) {
|
|
3049
4967
|
logger.error(`Block "${name}" has no ${name}.css / ${name}.js to preview.`);
|
|
3050
4968
|
process.exitCode = 1;
|
|
3051
4969
|
return;
|
|
3052
4970
|
}
|
|
3053
4971
|
let meta = null;
|
|
3054
|
-
const metaPath =
|
|
3055
|
-
if (
|
|
4972
|
+
const metaPath = path28.join(blockDir, ".eds-meta.json");
|
|
4973
|
+
if (existsSync24(metaPath)) {
|
|
3056
4974
|
try {
|
|
3057
|
-
meta = JSON.parse(await
|
|
4975
|
+
meta = JSON.parse(await readFile18(metaPath, "utf-8"));
|
|
3058
4976
|
} catch {
|
|
3059
4977
|
}
|
|
3060
4978
|
}
|
|
3061
|
-
const samplePath =
|
|
4979
|
+
const samplePath = path28.join(blockDir, `_${name}.preview.html`);
|
|
3062
4980
|
let sampleContent;
|
|
3063
4981
|
let placeholder = false;
|
|
3064
|
-
if (
|
|
3065
|
-
sampleContent = await
|
|
4982
|
+
if (existsSync24(samplePath)) {
|
|
4983
|
+
sampleContent = await readFile18(samplePath, "utf-8");
|
|
3066
4984
|
} else {
|
|
3067
4985
|
sampleContent = defaultSample(name);
|
|
3068
4986
|
placeholder = true;
|
|
@@ -3112,9 +5030,9 @@ async function previewBlock(name, options = {}) {
|
|
|
3112
5030
|
reloadTimer = setTimeout(() => server.reload(), 150);
|
|
3113
5031
|
});
|
|
3114
5032
|
logger.info("");
|
|
3115
|
-
logger.info(` ${
|
|
5033
|
+
logger.info(` ${chalk11.bold("\u279C")} ${chalk11.cyan(server.url)}`);
|
|
3116
5034
|
logger.info(statusLine("pass", "live reload", "edits to the block refresh the browser"));
|
|
3117
|
-
logger.info(
|
|
5035
|
+
logger.info(chalk11.dim(" Press Ctrl+C to stop.\n"));
|
|
3118
5036
|
if (options.open !== false) await openInBrowser(server.url);
|
|
3119
5037
|
const stop = async () => {
|
|
3120
5038
|
watcher.close();
|
|
@@ -3143,10 +5061,10 @@ var from_design_exports = {};
|
|
|
3143
5061
|
__export(from_design_exports, {
|
|
3144
5062
|
blockFromDesign: () => blockFromDesign
|
|
3145
5063
|
});
|
|
3146
|
-
import { existsSync as
|
|
3147
|
-
import { readFile as
|
|
3148
|
-
import
|
|
3149
|
-
import
|
|
5064
|
+
import { existsSync as existsSync25 } from "fs";
|
|
5065
|
+
import { readFile as readFile19, writeFile as writeFile16 } from "fs/promises";
|
|
5066
|
+
import path29 from "path";
|
|
5067
|
+
import chalk12 from "chalk";
|
|
3150
5068
|
import ora from "ora";
|
|
3151
5069
|
async function previewRunning(port = 8777) {
|
|
3152
5070
|
try {
|
|
@@ -3296,7 +5214,7 @@ async function blockFromDesign(figmaUrlArg, optionsArg) {
|
|
|
3296
5214
|
try {
|
|
3297
5215
|
figma2 = parseFigmaUrl(designUrl);
|
|
3298
5216
|
spinner.succeed(
|
|
3299
|
-
`Parsed Figma URL ${
|
|
5217
|
+
`Parsed Figma URL ${chalk12.dim(figma2.nodeId ? `node ${figma2.nodeId}` : `file ${figma2.fileKey}`)}`
|
|
3300
5218
|
);
|
|
3301
5219
|
} catch (err) {
|
|
3302
5220
|
spinner.fail(err.message);
|
|
@@ -3304,7 +5222,7 @@ async function blockFromDesign(figmaUrlArg, optionsArg) {
|
|
|
3304
5222
|
return;
|
|
3305
5223
|
}
|
|
3306
5224
|
} else {
|
|
3307
|
-
logger.info(` ${icon.pass} ${providerName} design ${
|
|
5225
|
+
logger.info(` ${icon.pass} ${providerName} design ${chalk12.dim(designRef)}`);
|
|
3308
5226
|
}
|
|
3309
5227
|
const blockName = options.name || (figma2 ? inferBlockName(figma2.fileName) : "");
|
|
3310
5228
|
if (!kebabCaseRegex.test(blockName)) {
|
|
@@ -3327,20 +5245,20 @@ async function blockFromDesign(figmaUrlArg, optionsArg) {
|
|
|
3327
5245
|
screenshot: options.screenshot !== false
|
|
3328
5246
|
});
|
|
3329
5247
|
const promptFile = await savePromptToFile(prompt, projectRoot);
|
|
3330
|
-
const hasTokens =
|
|
5248
|
+
const hasTokens = existsSync25(path29.join(projectRoot, "styles", "styles.css"));
|
|
3331
5249
|
promptSpinner.succeed(
|
|
3332
|
-
hasTokens ? `Loaded design tokens ${
|
|
5250
|
+
hasTokens ? `Loaded design tokens ${chalk12.dim("styles/styles.css")}` : `Built prompt ${chalk12.dim("no project tokens found")}`
|
|
3333
5251
|
);
|
|
3334
|
-
logger.info(
|
|
5252
|
+
logger.info(chalk12.dim(` prompt \u2192 ${promptFile}`));
|
|
3335
5253
|
if (options.dryRun) {
|
|
3336
5254
|
logger.info("");
|
|
3337
|
-
logger.info(
|
|
3338
|
-
logger.info(`Prompt file: ${
|
|
3339
|
-
logger.info(`Block name: ${
|
|
5255
|
+
logger.info(chalk12.yellow("Dry run mode \u2014 prompt generated but agent not invoked."));
|
|
5256
|
+
logger.info(`Prompt file: ${chalk12.cyan(promptFile)}`);
|
|
5257
|
+
logger.info(`Block name: ${chalk12.cyan(blockName)}`);
|
|
3340
5258
|
logger.info(`Prompt version: ${PROMPT_VERSION}`);
|
|
3341
5259
|
logger.info("");
|
|
3342
5260
|
logger.info("Preview:");
|
|
3343
|
-
logger.info(
|
|
5261
|
+
logger.info(chalk12.dim("\u2500".repeat(60)));
|
|
3344
5262
|
console.log(prompt);
|
|
3345
5263
|
return;
|
|
3346
5264
|
}
|
|
@@ -3348,22 +5266,22 @@ async function blockFromDesign(figmaUrlArg, optionsArg) {
|
|
|
3348
5266
|
const preferredAgent = options.agent || config.agent;
|
|
3349
5267
|
if (options.agent === "none") {
|
|
3350
5268
|
logger.info("Agent skipped (--agent none). Use the prompt file manually:");
|
|
3351
|
-
logger.info(` ${
|
|
5269
|
+
logger.info(` ${chalk12.cyan(promptFile)}`);
|
|
3352
5270
|
return;
|
|
3353
5271
|
}
|
|
3354
5272
|
const agentSpinner = ora("Detecting AI agent...").start();
|
|
3355
5273
|
const agent = await detectAgent(preferredAgent);
|
|
3356
5274
|
if (!agent) {
|
|
3357
5275
|
agentSpinner.fail("No AI agent found (claude, cursor, or codex).");
|
|
3358
|
-
logger.info(`Install Claude Code: ${
|
|
3359
|
-
logger.info(`Or use the prompt file manually: ${
|
|
5276
|
+
logger.info(`Install Claude Code: ${chalk12.cyan("npm install -g @anthropic-ai/claude-code")}`);
|
|
5277
|
+
logger.info(`Or use the prompt file manually: ${chalk12.cyan(promptFile)}`);
|
|
3360
5278
|
process.exitCode = 1;
|
|
3361
5279
|
return;
|
|
3362
5280
|
}
|
|
3363
|
-
agentSpinner.succeed(`Agent ready ${
|
|
5281
|
+
agentSpinner.succeed(`Agent ready ${chalk12.cyan(agent.type)} ${chalk12.dim(agent.path)}`);
|
|
3364
5282
|
if (provider === "figma" && !agent.hasFigmaMcp) {
|
|
3365
5283
|
logger.warn(`Figma MCP may not be configured for ${agent.type}.`);
|
|
3366
|
-
logger.info(`Run ${
|
|
5284
|
+
logger.info(`Run ${chalk12.cyan("eds figma setup")} to configure it.`);
|
|
3367
5285
|
}
|
|
3368
5286
|
if (!options.yes) {
|
|
3369
5287
|
const { confirm } = await import("@inquirer/prompts");
|
|
@@ -3372,24 +5290,24 @@ async function blockFromDesign(figmaUrlArg, optionsArg) {
|
|
|
3372
5290
|
default: true
|
|
3373
5291
|
});
|
|
3374
5292
|
if (!proceed) {
|
|
3375
|
-
logger.info(`Prompt saved to: ${
|
|
5293
|
+
logger.info(`Prompt saved to: ${chalk12.cyan(promptFile)}`);
|
|
3376
5294
|
return;
|
|
3377
5295
|
}
|
|
3378
5296
|
}
|
|
3379
5297
|
logger.info(
|
|
3380
|
-
` ${
|
|
5298
|
+
` ${chalk12.dim("\u2192")} Handing off to ${chalk12.cyan(agent.type)} ${chalk12.dim(`via ${providerName} MCP`)}`
|
|
3381
5299
|
);
|
|
3382
5300
|
if (provider === "figma") {
|
|
3383
|
-
logger.info(
|
|
5301
|
+
logger.info(chalk12.dim(" get_design_context \xB7 get_variable_defs \xB7 get_screenshot"));
|
|
3384
5302
|
}
|
|
3385
5303
|
const success = await invokeAgent(agent, promptFile, projectRoot);
|
|
3386
5304
|
if (!success) {
|
|
3387
5305
|
process.exitCode = 1;
|
|
3388
5306
|
return;
|
|
3389
5307
|
}
|
|
3390
|
-
const blockDir =
|
|
5308
|
+
const blockDir = path29.join(projectRoot, "blocks", blockName);
|
|
3391
5309
|
const ok = await verifyGeneratedBlock(blockDir, blockName, options.ueModel !== false);
|
|
3392
|
-
if (
|
|
5310
|
+
if (existsSync25(blockDir)) {
|
|
3393
5311
|
const meta = {
|
|
3394
5312
|
provider,
|
|
3395
5313
|
designRef,
|
|
@@ -3399,7 +5317,7 @@ async function blockFromDesign(figmaUrlArg, optionsArg) {
|
|
|
3399
5317
|
promptVersion: PROMPT_VERSION,
|
|
3400
5318
|
agentUsed: agent.type
|
|
3401
5319
|
};
|
|
3402
|
-
await
|
|
5320
|
+
await writeFile16(path29.join(blockDir, ".eds-meta.json"), `${JSON.stringify(meta, null, 2)}
|
|
3403
5321
|
`);
|
|
3404
5322
|
}
|
|
3405
5323
|
if (!ok) {
|
|
@@ -3424,7 +5342,7 @@ async function blockFromDesign(figmaUrlArg, optionsArg) {
|
|
|
3424
5342
|
if (options.serve !== false) {
|
|
3425
5343
|
if (await previewRunning()) {
|
|
3426
5344
|
logger.info(
|
|
3427
|
-
|
|
5345
|
+
chalk12.dim(
|
|
3428
5346
|
" A preview server is already running at http://127.0.0.1:8777 \u2014 your change will refresh there."
|
|
3429
5347
|
)
|
|
3430
5348
|
);
|
|
@@ -3434,15 +5352,15 @@ async function blockFromDesign(figmaUrlArg, optionsArg) {
|
|
|
3434
5352
|
await previewBlock2(blockName, {});
|
|
3435
5353
|
}
|
|
3436
5354
|
} else {
|
|
3437
|
-
logger.info(` ${
|
|
5355
|
+
logger.info(` ${chalk12.dim("\u2192")} Next: ${chalk12.cyan(`eds block preview ${blockName}`)}`);
|
|
3438
5356
|
}
|
|
3439
5357
|
}
|
|
3440
5358
|
async function verifyGeneratedBlock(blockDir, blockName, withUeModel) {
|
|
3441
5359
|
const checks = [];
|
|
3442
|
-
const jsPath =
|
|
3443
|
-
const cssPath =
|
|
3444
|
-
const hasJs =
|
|
3445
|
-
const hasCss =
|
|
5360
|
+
const jsPath = path29.join(blockDir, `${blockName}.js`);
|
|
5361
|
+
const cssPath = path29.join(blockDir, `${blockName}.css`);
|
|
5362
|
+
const hasJs = existsSync25(jsPath);
|
|
5363
|
+
const hasCss = existsSync25(cssPath);
|
|
3446
5364
|
checks.push({
|
|
3447
5365
|
status: hasJs ? "pass" : "fail",
|
|
3448
5366
|
name: `${blockName}.js`,
|
|
@@ -3454,7 +5372,7 @@ async function verifyGeneratedBlock(blockDir, blockName, withUeModel) {
|
|
|
3454
5372
|
message: hasCss ? "created" : "missing \u2014 agent did not write the CSS file"
|
|
3455
5373
|
});
|
|
3456
5374
|
if (hasJs) {
|
|
3457
|
-
const js = await
|
|
5375
|
+
const js = await readFile19(jsPath, "utf-8");
|
|
3458
5376
|
checks.push({
|
|
3459
5377
|
status: /export\s+default/.test(js) ? "pass" : "fail",
|
|
3460
5378
|
name: "decorate export",
|
|
@@ -3469,7 +5387,7 @@ async function verifyGeneratedBlock(blockDir, blockName, withUeModel) {
|
|
|
3469
5387
|
}
|
|
3470
5388
|
}
|
|
3471
5389
|
if (hasCss) {
|
|
3472
|
-
const css = await
|
|
5390
|
+
const css = await readFile19(cssPath, "utf-8");
|
|
3473
5391
|
checks.push({
|
|
3474
5392
|
status: css.includes(`.${blockName}`) ? "pass" : "warn",
|
|
3475
5393
|
name: "scoped CSS",
|
|
@@ -3477,12 +5395,12 @@ async function verifyGeneratedBlock(blockDir, blockName, withUeModel) {
|
|
|
3477
5395
|
});
|
|
3478
5396
|
}
|
|
3479
5397
|
if (withUeModel) {
|
|
3480
|
-
const hasPerBlock =
|
|
3481
|
-
const modelsFile =
|
|
5398
|
+
const hasPerBlock = existsSync25(path29.join(blockDir, `_${blockName}.json`));
|
|
5399
|
+
const modelsFile = path29.join(blockDir, "..", "..", "component-models.json");
|
|
3482
5400
|
let hasAggregated = false;
|
|
3483
|
-
if (!hasPerBlock &&
|
|
5401
|
+
if (!hasPerBlock && existsSync25(modelsFile)) {
|
|
3484
5402
|
try {
|
|
3485
|
-
hasAggregated = (await
|
|
5403
|
+
hasAggregated = (await readFile19(modelsFile, "utf-8")).includes(`"${blockName}"`);
|
|
3486
5404
|
} catch {
|
|
3487
5405
|
}
|
|
3488
5406
|
}
|
|
@@ -3494,8 +5412,8 @@ async function verifyGeneratedBlock(blockDir, blockName, withUeModel) {
|
|
|
3494
5412
|
});
|
|
3495
5413
|
if (hasPerBlock && hasJs) {
|
|
3496
5414
|
try {
|
|
3497
|
-
const modelRaw = await
|
|
3498
|
-
const js = await
|
|
5415
|
+
const modelRaw = await readFile19(path29.join(blockDir, `_${blockName}.json`), "utf-8");
|
|
5416
|
+
const js = await readFile19(jsPath, "utf-8");
|
|
3499
5417
|
const isRepeatable = /"resourceType"\s*:\s*"[^"]*\/item"/.test(modelRaw) || /"filter"\s*:/.test(modelRaw) || /"multi"\s*:\s*true/.test(modelRaw);
|
|
3500
5418
|
const iterates = /\.children/.test(js) || /\bfor\s*\(/.test(js) || /\.forEach\s*\(/.test(js);
|
|
3501
5419
|
if (isRepeatable) {
|
|
@@ -3538,10 +5456,10 @@ var list_exports = {};
|
|
|
3538
5456
|
__export(list_exports, {
|
|
3539
5457
|
listBlocks: () => listBlocks
|
|
3540
5458
|
});
|
|
3541
|
-
import { existsSync as
|
|
3542
|
-
import { readFile as
|
|
3543
|
-
import
|
|
3544
|
-
import
|
|
5459
|
+
import { existsSync as existsSync26 } from "fs";
|
|
5460
|
+
import { readFile as readFile20, readdir as readdir4 } from "fs/promises";
|
|
5461
|
+
import path30 from "path";
|
|
5462
|
+
import chalk13 from "chalk";
|
|
3545
5463
|
async function listBlocks(options = {}) {
|
|
3546
5464
|
const projectRoot = findProjectRoot();
|
|
3547
5465
|
if (!projectRoot) {
|
|
@@ -3549,27 +5467,27 @@ async function listBlocks(options = {}) {
|
|
|
3549
5467
|
process.exitCode = 1;
|
|
3550
5468
|
return;
|
|
3551
5469
|
}
|
|
3552
|
-
const blocksDir =
|
|
3553
|
-
if (!
|
|
5470
|
+
const blocksDir = path30.join(projectRoot, "blocks");
|
|
5471
|
+
if (!existsSync26(blocksDir)) {
|
|
3554
5472
|
logger.info("No blocks/ directory found.");
|
|
3555
5473
|
return;
|
|
3556
5474
|
}
|
|
3557
|
-
const entries = await
|
|
5475
|
+
const entries = await readdir4(blocksDir, { withFileTypes: true });
|
|
3558
5476
|
const blocks = [];
|
|
3559
5477
|
for (const entry of entries) {
|
|
3560
5478
|
if (!entry.isDirectory()) continue;
|
|
3561
5479
|
const name = entry.name;
|
|
3562
|
-
const blockDir =
|
|
5480
|
+
const blockDir = path30.join(blocksDir, name);
|
|
3563
5481
|
const info = {
|
|
3564
5482
|
name,
|
|
3565
|
-
hasJs:
|
|
3566
|
-
hasCss:
|
|
3567
|
-
hasUeModel:
|
|
5483
|
+
hasJs: existsSync26(path30.join(blockDir, `${name}.js`)),
|
|
5484
|
+
hasCss: existsSync26(path30.join(blockDir, `${name}.css`)),
|
|
5485
|
+
hasUeModel: existsSync26(path30.join(blockDir, `_${name}.json`))
|
|
3568
5486
|
};
|
|
3569
|
-
const metaFile =
|
|
3570
|
-
if (
|
|
5487
|
+
const metaFile = path30.join(blockDir, ".eds-meta.json");
|
|
5488
|
+
if (existsSync26(metaFile)) {
|
|
3571
5489
|
try {
|
|
3572
|
-
const meta = JSON.parse(await
|
|
5490
|
+
const meta = JSON.parse(await readFile20(metaFile, "utf-8"));
|
|
3573
5491
|
info.figmaSource = {
|
|
3574
5492
|
fileKey: meta.figmaFileKey,
|
|
3575
5493
|
nodeId: meta.figmaNodeId,
|
|
@@ -3592,17 +5510,17 @@ async function listBlocks(options = {}) {
|
|
|
3592
5510
|
const nameWidth = columnWidth(blocks.map((b) => b.name));
|
|
3593
5511
|
for (const b of blocks) {
|
|
3594
5512
|
const badges = [
|
|
3595
|
-
b.hasJs ? brand.ok("js") :
|
|
3596
|
-
b.hasCss ? brand.ok("css") :
|
|
5513
|
+
b.hasJs ? brand.ok("js") : chalk13.dim.strikethrough("js"),
|
|
5514
|
+
b.hasCss ? brand.ok("css") : chalk13.dim.strikethrough("css")
|
|
3597
5515
|
];
|
|
3598
5516
|
if (b.hasUeModel) badges.push(brand.blue("ue-model"));
|
|
3599
5517
|
if (b.figmaSource) badges.push(`${icon.figma} ${brand.dotPurple("figma")}`);
|
|
3600
|
-
const name = padEndVisible(
|
|
3601
|
-
logger.info(` ${name} ${
|
|
5518
|
+
const name = padEndVisible(chalk13.bold(b.name), nameWidth);
|
|
5519
|
+
logger.info(` ${name} ${chalk13.dim("[")}${badges.join(" ")}${chalk13.dim("]")}`);
|
|
3602
5520
|
if (b.figmaSource) {
|
|
3603
5521
|
const synced = new Date(b.figmaSource.lastSyncedAt).toLocaleDateString();
|
|
3604
5522
|
logger.info(
|
|
3605
|
-
` ${" ".repeat(nameWidth)} ${icon.arrow} ${
|
|
5523
|
+
` ${" ".repeat(nameWidth)} ${icon.arrow} ${chalk13.dim(
|
|
3606
5524
|
`figma:${b.figmaSource.fileKey} \xB7 synced ${synced}`
|
|
3607
5525
|
)}`
|
|
3608
5526
|
);
|
|
@@ -3782,10 +5700,10 @@ var integrate_exports = {};
|
|
|
3782
5700
|
__export(integrate_exports, {
|
|
3783
5701
|
integrate: () => integrate
|
|
3784
5702
|
});
|
|
3785
|
-
import { existsSync as
|
|
3786
|
-
import { mkdir as
|
|
3787
|
-
import
|
|
3788
|
-
import
|
|
5703
|
+
import { existsSync as existsSync27 } from "fs";
|
|
5704
|
+
import { mkdir as mkdir9, readFile as readFile21, writeFile as writeFile17 } from "fs/promises";
|
|
5705
|
+
import path31 from "path";
|
|
5706
|
+
import chalk14 from "chalk";
|
|
3789
5707
|
async function integrate(type) {
|
|
3790
5708
|
const projectRoot = findProjectRoot();
|
|
3791
5709
|
if (!projectRoot) {
|
|
@@ -3808,16 +5726,16 @@ async function integrate(type) {
|
|
|
3808
5726
|
return;
|
|
3809
5727
|
}
|
|
3810
5728
|
const values = {};
|
|
3811
|
-
for (const
|
|
3812
|
-
const answer = await input({ message: ` ${
|
|
3813
|
-
values[
|
|
5729
|
+
for (const f2 of integration.fields) {
|
|
5730
|
+
const answer = await input({ message: ` ${f2.message}`, default: f2.placeholder });
|
|
5731
|
+
values[f2.name] = answer.trim();
|
|
3814
5732
|
}
|
|
3815
5733
|
const modulePaths = [];
|
|
3816
5734
|
if (integration.module) {
|
|
3817
5735
|
for (const mod of integration.module(values)) {
|
|
3818
|
-
const abs =
|
|
3819
|
-
await
|
|
3820
|
-
await
|
|
5736
|
+
const abs = path31.join(projectRoot, mod.file);
|
|
5737
|
+
await mkdir9(path31.dirname(abs), { recursive: true });
|
|
5738
|
+
await writeFile17(abs, mod.content);
|
|
3821
5739
|
modulePaths.push(mod.file);
|
|
3822
5740
|
}
|
|
3823
5741
|
}
|
|
@@ -3825,26 +5743,26 @@ async function integrate(type) {
|
|
|
3825
5743
|
// ${integration.name} (eds integrate)
|
|
3826
5744
|
${integration.code(values)}
|
|
3827
5745
|
` : wrapBlock(integration.name, integration.code(values));
|
|
3828
|
-
const delayedPath =
|
|
3829
|
-
await
|
|
3830
|
-
const existing =
|
|
3831
|
-
await
|
|
5746
|
+
const delayedPath = path31.join(projectRoot, "scripts", "delayed.js");
|
|
5747
|
+
await mkdir9(path31.dirname(delayedPath), { recursive: true });
|
|
5748
|
+
const existing = existsSync27(delayedPath) ? await readFile21(delayedPath, "utf-8") : DELAYED_HEADER;
|
|
5749
|
+
await writeFile17(delayedPath, `${existing.replace(/\s*$/, "")}
|
|
3832
5750
|
${block2}`);
|
|
3833
5751
|
logger.info(heading("Added"));
|
|
3834
5752
|
for (const p of modulePaths) logger.info(accentLine(integration.name, `wrote ${p}`));
|
|
3835
5753
|
logger.info(accentLine(integration.name, "wired into scripts/delayed.js"));
|
|
3836
5754
|
logger.info("");
|
|
3837
5755
|
logger.info(
|
|
3838
|
-
|
|
5756
|
+
chalk14.dim(" Loads after LCP (delayed phase) - zero impact on your Core Web Vitals.")
|
|
3839
5757
|
);
|
|
3840
5758
|
if (integration.id === "gtm" || integration.id === "ga4") {
|
|
3841
5759
|
logger.info(
|
|
3842
|
-
|
|
5760
|
+
chalk14.dim(" Tip: gate analytics behind user consent (add the Cookiebot integration).")
|
|
3843
5761
|
);
|
|
3844
5762
|
}
|
|
3845
5763
|
if (integration.id === "chat") {
|
|
3846
5764
|
logger.info(
|
|
3847
|
-
|
|
5765
|
+
chalk14.dim(" The widget loads only when the visitor clicks the button (facade pattern).")
|
|
3848
5766
|
);
|
|
3849
5767
|
}
|
|
3850
5768
|
logger.info("");
|
|
@@ -3870,9 +5788,9 @@ var setup_exports = {};
|
|
|
3870
5788
|
__export(setup_exports, {
|
|
3871
5789
|
figmaSetup: () => figmaSetup
|
|
3872
5790
|
});
|
|
3873
|
-
import { existsSync as
|
|
3874
|
-
import { mkdir as
|
|
3875
|
-
import
|
|
5791
|
+
import { existsSync as existsSync28 } from "fs";
|
|
5792
|
+
import { mkdir as mkdir10, writeFile as writeFile18 } from "fs/promises";
|
|
5793
|
+
import chalk15 from "chalk";
|
|
3876
5794
|
async function figmaSetup() {
|
|
3877
5795
|
const { select, confirm } = await import("@inquirer/prompts");
|
|
3878
5796
|
logger.logoOnce(logo());
|
|
@@ -3882,15 +5800,15 @@ async function figmaSetup() {
|
|
|
3882
5800
|
let selectedAgent;
|
|
3883
5801
|
if (agents.length === 0) {
|
|
3884
5802
|
logger.warn("No AI agents found (claude, cursor, codex).");
|
|
3885
|
-
logger.info(`Install Claude Code: ${
|
|
5803
|
+
logger.info(`Install Claude Code: ${chalk15.cyan("npm install -g @anthropic-ai/claude-code")}`);
|
|
3886
5804
|
selectedAgent = "none";
|
|
3887
5805
|
} else {
|
|
3888
|
-
logger.success(`Found: ${agents.map((a) =>
|
|
5806
|
+
logger.success(`Found: ${agents.map((a) => chalk15.cyan(a.type)).join(", ")}`);
|
|
3889
5807
|
selectedAgent = await select({
|
|
3890
5808
|
message: "Which agent do you want to use with eds-cli?",
|
|
3891
5809
|
choices: [
|
|
3892
5810
|
...agents.map((a) => ({
|
|
3893
|
-
name: `${a.type}${a.hasFigmaMcp ?
|
|
5811
|
+
name: `${a.type}${a.hasFigmaMcp ? chalk15.green(" (Figma MCP detected)") : ""}`,
|
|
3894
5812
|
value: a.type
|
|
3895
5813
|
})),
|
|
3896
5814
|
{ name: "None (manual mode)", value: "none" }
|
|
@@ -3906,17 +5824,17 @@ async function figmaSetup() {
|
|
|
3906
5824
|
switch (selectedAgent) {
|
|
3907
5825
|
case "claude":
|
|
3908
5826
|
logger.info(
|
|
3909
|
-
` ${
|
|
5827
|
+
` ${chalk15.cyan("claude mcp add --transport sse figma-sse https://mcp.figma.com/sse")}`
|
|
3910
5828
|
);
|
|
3911
5829
|
logger.info(" or for Figma Desktop app:");
|
|
3912
5830
|
logger.info(
|
|
3913
|
-
` ${
|
|
5831
|
+
` ${chalk15.cyan("claude mcp add --transport http figma-desktop http://127.0.0.1:3845/mcp")}`
|
|
3914
5832
|
);
|
|
3915
5833
|
break;
|
|
3916
5834
|
case "cursor":
|
|
3917
|
-
logger.info(` Add to ${
|
|
5835
|
+
logger.info(` Add to ${chalk15.cyan("~/.cursor/mcp.json")}:`);
|
|
3918
5836
|
logger.info(
|
|
3919
|
-
|
|
5837
|
+
chalk15.dim(
|
|
3920
5838
|
JSON.stringify(
|
|
3921
5839
|
{
|
|
3922
5840
|
mcpServers: {
|
|
@@ -3941,7 +5859,7 @@ async function figmaSetup() {
|
|
|
3941
5859
|
}
|
|
3942
5860
|
}
|
|
3943
5861
|
logger.info(
|
|
3944
|
-
|
|
5862
|
+
chalk15.dim(
|
|
3945
5863
|
"\nNote: Figma MCP requires a Figma seat (Dev or Full on paid plans for desktop server)."
|
|
3946
5864
|
)
|
|
3947
5865
|
);
|
|
@@ -3951,22 +5869,22 @@ async function figmaSetup() {
|
|
|
3951
5869
|
});
|
|
3952
5870
|
if (shouldSave) {
|
|
3953
5871
|
const configDir = globalConfigDir();
|
|
3954
|
-
if (!
|
|
3955
|
-
await
|
|
5872
|
+
if (!existsSync28(configDir)) {
|
|
5873
|
+
await mkdir10(configDir, { recursive: true });
|
|
3956
5874
|
}
|
|
3957
5875
|
const config = {};
|
|
3958
5876
|
if (selectedAgent !== "none") {
|
|
3959
5877
|
config.agent = selectedAgent;
|
|
3960
5878
|
}
|
|
3961
|
-
await
|
|
5879
|
+
await writeFile18(globalConfigPath(), `${JSON.stringify(config, null, 2)}
|
|
3962
5880
|
`, {
|
|
3963
5881
|
mode: 384
|
|
3964
5882
|
});
|
|
3965
|
-
logger.success(`Config saved to ${
|
|
5883
|
+
logger.success(`Config saved to ${chalk15.cyan(globalConfigPath())}`);
|
|
3966
5884
|
}
|
|
3967
|
-
logger.info(
|
|
5885
|
+
logger.info(chalk15.bold("\nSetup complete!"));
|
|
3968
5886
|
if (selectedAgent !== "none") {
|
|
3969
|
-
logger.info(`Try: ${
|
|
5887
|
+
logger.info(`Try: ${chalk15.cyan('eds block from-design "<figma-url>" --dry-run')}`);
|
|
3970
5888
|
}
|
|
3971
5889
|
}
|
|
3972
5890
|
var init_setup = __esm({
|
|
@@ -3985,10 +5903,10 @@ var doctor_exports = {};
|
|
|
3985
5903
|
__export(doctor_exports, {
|
|
3986
5904
|
doctor: () => doctor
|
|
3987
5905
|
});
|
|
3988
|
-
import { existsSync as
|
|
3989
|
-
import { readFile as
|
|
3990
|
-
import
|
|
3991
|
-
import { parse as
|
|
5906
|
+
import { existsSync as existsSync29 } from "fs";
|
|
5907
|
+
import { readFile as readFile22, readdir as readdir5 } from "fs/promises";
|
|
5908
|
+
import path32 from "path";
|
|
5909
|
+
import { parse as parseYaml2 } from "yaml";
|
|
3992
5910
|
async function doctor(options = {}) {
|
|
3993
5911
|
const projectRoot = findProjectRoot();
|
|
3994
5912
|
if (!projectRoot) {
|
|
@@ -4009,8 +5927,8 @@ async function doctor(options = {}) {
|
|
|
4009
5927
|
});
|
|
4010
5928
|
if (project.hasFstab) {
|
|
4011
5929
|
try {
|
|
4012
|
-
const content = await
|
|
4013
|
-
|
|
5930
|
+
const content = await readFile22(path32.join(projectRoot, "fstab.yaml"), "utf-8");
|
|
5931
|
+
parseYaml2(content);
|
|
4014
5932
|
results.push({ name: "fstab.yaml valid YAML", status: "pass", message: "Valid" });
|
|
4015
5933
|
} catch {
|
|
4016
5934
|
results.push({
|
|
@@ -4031,28 +5949,28 @@ async function doctor(options = {}) {
|
|
|
4031
5949
|
message: project.hasBlocks ? "Found" : "Missing - no blocks yet"
|
|
4032
5950
|
});
|
|
4033
5951
|
if (project.hasBlocks) {
|
|
4034
|
-
const blocksDir =
|
|
4035
|
-
const entries = await
|
|
5952
|
+
const blocksDir = path32.join(projectRoot, "blocks");
|
|
5953
|
+
const entries = await readdir5(blocksDir, { withFileTypes: true });
|
|
4036
5954
|
let aggregatedModels = "";
|
|
4037
|
-
const aggregatedPath =
|
|
4038
|
-
if (
|
|
5955
|
+
const aggregatedPath = path32.join(projectRoot, "component-models.json");
|
|
5956
|
+
if (existsSync29(aggregatedPath)) {
|
|
4039
5957
|
try {
|
|
4040
|
-
aggregatedModels = await
|
|
5958
|
+
aggregatedModels = await readFile22(aggregatedPath, "utf-8");
|
|
4041
5959
|
} catch {
|
|
4042
5960
|
}
|
|
4043
5961
|
}
|
|
4044
5962
|
for (const entry of entries) {
|
|
4045
5963
|
if (!entry.isDirectory()) continue;
|
|
4046
5964
|
const blockName = entry.name;
|
|
4047
|
-
const jsFile =
|
|
4048
|
-
const hasJs =
|
|
5965
|
+
const jsFile = path32.join(blocksDir, blockName, `${blockName}.js`);
|
|
5966
|
+
const hasJs = existsSync29(jsFile);
|
|
4049
5967
|
results.push({
|
|
4050
5968
|
name: `blocks/${blockName} has JS`,
|
|
4051
5969
|
status: hasJs ? "pass" : "warn",
|
|
4052
5970
|
message: hasJs ? "Found" : `Missing ${blockName}.js`
|
|
4053
5971
|
});
|
|
4054
5972
|
if (hasJs) {
|
|
4055
|
-
const jsContent = await
|
|
5973
|
+
const jsContent = await readFile22(jsFile, "utf-8");
|
|
4056
5974
|
if (jsContent.includes("console.log")) {
|
|
4057
5975
|
results.push({
|
|
4058
5976
|
name: `blocks/${blockName} no console.log`,
|
|
@@ -4061,17 +5979,17 @@ async function doctor(options = {}) {
|
|
|
4061
5979
|
});
|
|
4062
5980
|
}
|
|
4063
5981
|
}
|
|
4064
|
-
const hasPerBlockModel =
|
|
5982
|
+
const hasPerBlockModel = existsSync29(path32.join(blocksDir, blockName, `_${blockName}.json`));
|
|
4065
5983
|
const hasAggregatedModel = aggregatedModels.includes(`"${blockName}"`);
|
|
4066
5984
|
results.push({
|
|
4067
5985
|
name: `blocks/${blockName} UE model`,
|
|
4068
5986
|
status: hasPerBlockModel || hasAggregatedModel ? "pass" : "warn",
|
|
4069
5987
|
message: hasPerBlockModel || hasAggregatedModel ? hasPerBlockModel ? `_${blockName}.json` : "in component-models.json" : "no Universal Editor model - authors can't edit this block in UE"
|
|
4070
5988
|
});
|
|
4071
|
-
const metaFile =
|
|
4072
|
-
if (
|
|
5989
|
+
const metaFile = path32.join(blocksDir, blockName, ".eds-meta.json");
|
|
5990
|
+
if (existsSync29(metaFile)) {
|
|
4073
5991
|
try {
|
|
4074
|
-
const meta = JSON.parse(await
|
|
5992
|
+
const meta = JSON.parse(await readFile22(metaFile, "utf-8"));
|
|
4075
5993
|
const syncDate = new Date(meta.lastSyncedAt);
|
|
4076
5994
|
const daysSinceSync = Math.floor(
|
|
4077
5995
|
(Date.now() - syncDate.getTime()) / (1e3 * 60 * 60 * 24)
|
|
@@ -4091,11 +6009,11 @@ async function doctor(options = {}) {
|
|
|
4091
6009
|
}
|
|
4092
6010
|
}
|
|
4093
6011
|
}
|
|
4094
|
-
const queryFile =
|
|
4095
|
-
if (
|
|
6012
|
+
const queryFile = path32.join(projectRoot, "helix-query.yaml");
|
|
6013
|
+
if (existsSync29(queryFile)) {
|
|
4096
6014
|
try {
|
|
4097
|
-
const content = await
|
|
4098
|
-
|
|
6015
|
+
const content = await readFile22(queryFile, "utf-8");
|
|
6016
|
+
parseYaml2(content);
|
|
4099
6017
|
results.push({ name: "helix-query.yaml valid", status: "pass", message: "Valid YAML" });
|
|
4100
6018
|
} catch {
|
|
4101
6019
|
results.push({
|
|
@@ -4151,10 +6069,10 @@ var audit_loading_exports = {};
|
|
|
4151
6069
|
__export(audit_loading_exports, {
|
|
4152
6070
|
auditLoading: () => auditLoading
|
|
4153
6071
|
});
|
|
4154
|
-
import { existsSync as
|
|
4155
|
-
import { readFile as
|
|
4156
|
-
import
|
|
4157
|
-
import
|
|
6072
|
+
import { existsSync as existsSync30 } from "fs";
|
|
6073
|
+
import { readFile as readFile23, readdir as readdir6, stat as stat3 } from "fs/promises";
|
|
6074
|
+
import path33 from "path";
|
|
6075
|
+
import chalk16 from "chalk";
|
|
4158
6076
|
async function auditLoading(options = {}) {
|
|
4159
6077
|
const projectRoot = findProjectRoot();
|
|
4160
6078
|
if (!projectRoot) {
|
|
@@ -4187,8 +6105,8 @@ async function auditLoading(options = {}) {
|
|
|
4187
6105
|
printResults(results);
|
|
4188
6106
|
}
|
|
4189
6107
|
async function checkHeadHtml(root, results) {
|
|
4190
|
-
const filePath =
|
|
4191
|
-
if (!
|
|
6108
|
+
const filePath = path33.join(root, "head.html");
|
|
6109
|
+
if (!existsSync30(filePath)) {
|
|
4192
6110
|
results.push({
|
|
4193
6111
|
rule: "head.html exists",
|
|
4194
6112
|
status: "warn",
|
|
@@ -4196,7 +6114,7 @@ async function checkHeadHtml(root, results) {
|
|
|
4196
6114
|
});
|
|
4197
6115
|
return;
|
|
4198
6116
|
}
|
|
4199
|
-
const content = await
|
|
6117
|
+
const content = await readFile23(filePath, "utf-8");
|
|
4200
6118
|
const lines = content.split("\n");
|
|
4201
6119
|
for (let i = 0; i < lines.length; i++) {
|
|
4202
6120
|
const line = lines[i];
|
|
@@ -4268,8 +6186,8 @@ async function checkHeadHtml(root, results) {
|
|
|
4268
6186
|
}
|
|
4269
6187
|
}
|
|
4270
6188
|
async function checkScriptsJs(root, results) {
|
|
4271
|
-
const filePath =
|
|
4272
|
-
if (!
|
|
6189
|
+
const filePath = path33.join(root, "scripts", "scripts.js");
|
|
6190
|
+
if (!existsSync30(filePath)) {
|
|
4273
6191
|
results.push({
|
|
4274
6192
|
rule: "scripts/scripts.js exists",
|
|
4275
6193
|
status: "warn",
|
|
@@ -4277,7 +6195,7 @@ async function checkScriptsJs(root, results) {
|
|
|
4277
6195
|
});
|
|
4278
6196
|
return;
|
|
4279
6197
|
}
|
|
4280
|
-
const content = await
|
|
6198
|
+
const content = await readFile23(filePath, "utf-8");
|
|
4281
6199
|
const lines = content.split("\n");
|
|
4282
6200
|
const hasLoadEager = /function\s+loadEager|loadEager\s*=/i.test(content);
|
|
4283
6201
|
const hasLoadLazy = /function\s+loadLazy|loadLazy\s*=/i.test(content);
|
|
@@ -4355,8 +6273,8 @@ async function checkScriptsJs(root, results) {
|
|
|
4355
6273
|
}
|
|
4356
6274
|
}
|
|
4357
6275
|
async function checkStylesCss(root, results) {
|
|
4358
|
-
const filePath =
|
|
4359
|
-
if (!
|
|
6276
|
+
const filePath = path33.join(root, "styles", "styles.css");
|
|
6277
|
+
if (!existsSync30(filePath)) return;
|
|
4360
6278
|
const stats = await stat3(filePath);
|
|
4361
6279
|
const sizeKb = stats.size / 1024;
|
|
4362
6280
|
results.push({
|
|
@@ -4365,7 +6283,7 @@ async function checkStylesCss(root, results) {
|
|
|
4365
6283
|
message: `${sizeKb.toFixed(1)} KB${sizeKb > 40 ? " \u2014 large CSS competes with 100KB LCP budget" : ""}`,
|
|
4366
6284
|
file: "styles/styles.css"
|
|
4367
6285
|
});
|
|
4368
|
-
const content = await
|
|
6286
|
+
const content = await readFile23(filePath, "utf-8");
|
|
4369
6287
|
if (/@import\s/.test(content)) {
|
|
4370
6288
|
results.push({
|
|
4371
6289
|
rule: "No @import in styles.css",
|
|
@@ -4376,8 +6294,8 @@ async function checkStylesCss(root, results) {
|
|
|
4376
6294
|
}
|
|
4377
6295
|
}
|
|
4378
6296
|
async function checkDelayedJs(root, results) {
|
|
4379
|
-
const filePath =
|
|
4380
|
-
if (!
|
|
6297
|
+
const filePath = path33.join(root, "scripts", "delayed.js");
|
|
6298
|
+
if (!existsSync30(filePath)) {
|
|
4381
6299
|
results.push({
|
|
4382
6300
|
rule: "scripts/delayed.js exists",
|
|
4383
6301
|
status: "info",
|
|
@@ -4385,7 +6303,7 @@ async function checkDelayedJs(root, results) {
|
|
|
4385
6303
|
});
|
|
4386
6304
|
return;
|
|
4387
6305
|
}
|
|
4388
|
-
const content = await
|
|
6306
|
+
const content = await readFile23(filePath, "utf-8");
|
|
4389
6307
|
const stripped = content.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "").trim();
|
|
4390
6308
|
if (!stripped) {
|
|
4391
6309
|
results.push({
|
|
@@ -4397,15 +6315,15 @@ async function checkDelayedJs(root, results) {
|
|
|
4397
6315
|
}
|
|
4398
6316
|
}
|
|
4399
6317
|
async function checkBlocks(root, results) {
|
|
4400
|
-
const blocksDir =
|
|
4401
|
-
if (!
|
|
4402
|
-
const entries = await
|
|
6318
|
+
const blocksDir = path33.join(root, "blocks");
|
|
6319
|
+
if (!existsSync30(blocksDir)) return;
|
|
6320
|
+
const entries = await readdir6(blocksDir, { withFileTypes: true });
|
|
4403
6321
|
for (const entry of entries) {
|
|
4404
6322
|
if (!entry.isDirectory()) continue;
|
|
4405
6323
|
const blockName = entry.name;
|
|
4406
|
-
const jsFile =
|
|
4407
|
-
if (!
|
|
4408
|
-
const content = await
|
|
6324
|
+
const jsFile = path33.join(blocksDir, blockName, `${blockName}.js`);
|
|
6325
|
+
if (!existsSync30(jsFile)) continue;
|
|
6326
|
+
const content = await readFile23(jsFile, "utf-8");
|
|
4409
6327
|
const lines = content.split("\n");
|
|
4410
6328
|
const heavyLibs = [
|
|
4411
6329
|
"jquery",
|
|
@@ -4455,9 +6373,9 @@ async function checkBlocks(root, results) {
|
|
|
4455
6373
|
for (const entry of entries) {
|
|
4456
6374
|
if (!entry.isDirectory()) continue;
|
|
4457
6375
|
const blockName = entry.name;
|
|
4458
|
-
const cssFile =
|
|
4459
|
-
if (!
|
|
4460
|
-
const content = await
|
|
6376
|
+
const cssFile = path33.join(blocksDir, blockName, `${blockName}.css`);
|
|
6377
|
+
if (!existsSync30(cssFile)) continue;
|
|
6378
|
+
const content = await readFile23(cssFile, "utf-8");
|
|
4461
6379
|
const selectorPattern = /^([a-z][a-z0-9-]*)\s*\{/gm;
|
|
4462
6380
|
for (const selectorMatch of content.matchAll(selectorPattern)) {
|
|
4463
6381
|
const selector = selectorMatch[1];
|
|
@@ -4473,8 +6391,8 @@ async function checkBlocks(root, results) {
|
|
|
4473
6391
|
}
|
|
4474
6392
|
}
|
|
4475
6393
|
async function checkAemJs(root, results) {
|
|
4476
|
-
const filePath =
|
|
4477
|
-
if (!
|
|
6394
|
+
const filePath = path33.join(root, "scripts", "aem.js");
|
|
6395
|
+
if (!existsSync30(filePath)) {
|
|
4478
6396
|
results.push({
|
|
4479
6397
|
rule: "scripts/aem.js exists",
|
|
4480
6398
|
status: "warn",
|
|
@@ -4512,12 +6430,12 @@ function printResults(results) {
|
|
|
4512
6430
|
grouped.set(key, bucket);
|
|
4513
6431
|
}
|
|
4514
6432
|
for (const [file, fileResults] of grouped) {
|
|
4515
|
-
logger.info(` ${icon.bullet} ${
|
|
6433
|
+
logger.info(` ${icon.bullet} ${chalk16.underline(file)}`);
|
|
4516
6434
|
for (const r of fileResults) {
|
|
4517
|
-
const loc = r.line ?
|
|
6435
|
+
const loc = r.line ? chalk16.dim(`:${r.line}`) : "";
|
|
4518
6436
|
logger.info(` ${statusIcon(r.status)} ${r.rule}${loc}`);
|
|
4519
6437
|
if (r.status !== "pass") {
|
|
4520
|
-
logger.info(
|
|
6438
|
+
logger.info(chalk16.dim(` ${r.message}`));
|
|
4521
6439
|
}
|
|
4522
6440
|
}
|
|
4523
6441
|
logger.info("");
|
|
@@ -4546,14 +6464,14 @@ var audit_security_exports = {};
|
|
|
4546
6464
|
__export(audit_security_exports, {
|
|
4547
6465
|
auditSecurity: () => auditSecurity
|
|
4548
6466
|
});
|
|
4549
|
-
import { existsSync as
|
|
4550
|
-
import { readFile as
|
|
4551
|
-
import
|
|
4552
|
-
import
|
|
6467
|
+
import { existsSync as existsSync31 } from "fs";
|
|
6468
|
+
import { readFile as readFile24, readdir as readdir7 } from "fs/promises";
|
|
6469
|
+
import path34 from "path";
|
|
6470
|
+
import chalk17 from "chalk";
|
|
4553
6471
|
async function* walk2(dir) {
|
|
4554
|
-
const entries = await
|
|
6472
|
+
const entries = await readdir7(dir, { withFileTypes: true });
|
|
4555
6473
|
for (const entry of entries) {
|
|
4556
|
-
const full =
|
|
6474
|
+
const full = path34.join(dir, entry.name);
|
|
4557
6475
|
if (entry.isDirectory()) {
|
|
4558
6476
|
if (IGNORE_DIRS.has(entry.name)) continue;
|
|
4559
6477
|
yield* walk2(full);
|
|
@@ -4587,11 +6505,11 @@ function scanContent(content, relFile, ext) {
|
|
|
4587
6505
|
return findings;
|
|
4588
6506
|
}
|
|
4589
6507
|
async function auditDependencies(root) {
|
|
4590
|
-
if (!
|
|
6508
|
+
if (!existsSync31(path34.join(root, "package.json"))) {
|
|
4591
6509
|
return { status: "skipped", note: "no package.json" };
|
|
4592
6510
|
}
|
|
4593
|
-
const hasLock =
|
|
4594
|
-
if (!hasLock && !
|
|
6511
|
+
const hasLock = existsSync31(path34.join(root, "package-lock.json")) || existsSync31(path34.join(root, "npm-shrinkwrap.json"));
|
|
6512
|
+
if (!hasLock && !existsSync31(path34.join(root, "node_modules"))) {
|
|
4595
6513
|
return { status: "skipped", note: "no package-lock.json / node_modules \u2014 run npm install" };
|
|
4596
6514
|
}
|
|
4597
6515
|
try {
|
|
@@ -4623,15 +6541,15 @@ async function auditSecurity(options = {}) {
|
|
|
4623
6541
|
}
|
|
4624
6542
|
const findings = [];
|
|
4625
6543
|
for await (const file of walk2(projectRoot)) {
|
|
4626
|
-
const ext =
|
|
6544
|
+
const ext = path34.extname(file).slice(1).toLowerCase();
|
|
4627
6545
|
if (!SCAN_EXT.has(ext)) continue;
|
|
4628
|
-
const rel =
|
|
6546
|
+
const rel = path34.relative(projectRoot, file);
|
|
4629
6547
|
if (rel.endsWith("aem.js") || rel.endsWith("lib-franklin.js")) continue;
|
|
4630
|
-
const content = await
|
|
6548
|
+
const content = await readFile24(file, "utf-8");
|
|
4631
6549
|
findings.push(...scanContent(content, rel, ext));
|
|
4632
6550
|
}
|
|
4633
6551
|
const deps = await auditDependencies(projectRoot);
|
|
4634
|
-
const bySeverity = (s) => findings.filter((
|
|
6552
|
+
const bySeverity = (s) => findings.filter((f2) => f2.severity === s);
|
|
4635
6553
|
const codeCounts = {
|
|
4636
6554
|
critical: bySeverity("critical").length,
|
|
4637
6555
|
high: bySeverity("high").length,
|
|
@@ -4673,11 +6591,11 @@ async function auditSecurity(options = {}) {
|
|
|
4673
6591
|
const sorted = [...findings].sort(
|
|
4674
6592
|
(a, b) => SEVERITY_ORDER.indexOf(a.severity) - SEVERITY_ORDER.indexOf(b.severity)
|
|
4675
6593
|
);
|
|
4676
|
-
for (const
|
|
4677
|
-
const meta = SEVERITY_META[
|
|
4678
|
-
const loc =
|
|
4679
|
-
logger.info(` ${statusIcon(meta.status)} ${
|
|
4680
|
-
logger.info(
|
|
6594
|
+
for (const f2 of sorted) {
|
|
6595
|
+
const meta = SEVERITY_META[f2.severity];
|
|
6596
|
+
const loc = chalk17.dim(`${f2.file}:${f2.line}`);
|
|
6597
|
+
logger.info(` ${statusIcon(meta.status)} ${chalk17.bold(meta.label)} ${f2.message} ${loc}`);
|
|
6598
|
+
logger.info(chalk17.dim(` ${f2.snippet}`));
|
|
4681
6599
|
}
|
|
4682
6600
|
}
|
|
4683
6601
|
const summaryLine = summary({
|
|
@@ -4884,7 +6802,7 @@ __export(preview_exports2, {
|
|
|
4884
6802
|
preview: () => preview,
|
|
4885
6803
|
resolveAdminConfig: () => resolveAdminConfig
|
|
4886
6804
|
});
|
|
4887
|
-
import
|
|
6805
|
+
import chalk18 from "chalk";
|
|
4888
6806
|
import ora2 from "ora";
|
|
4889
6807
|
async function preview(paths, options) {
|
|
4890
6808
|
const projectRoot = findProjectRoot();
|
|
@@ -4897,7 +6815,7 @@ async function preview(paths, options) {
|
|
|
4897
6815
|
let hasErrors = false;
|
|
4898
6816
|
for (const r of results) {
|
|
4899
6817
|
if (r.ok) {
|
|
4900
|
-
logger.success(`${r.path} \u2192 ${
|
|
6818
|
+
logger.success(`${r.path} \u2192 ${chalk18.cyan(r.previewUrl || "preview updated")}`);
|
|
4901
6819
|
} else {
|
|
4902
6820
|
logger.error(`${r.path} \u2014 ${r.message}`);
|
|
4903
6821
|
hasErrors = true;
|
|
@@ -4911,7 +6829,7 @@ function resolveAdminConfig(config, options) {
|
|
|
4911
6829
|
const ref = options.ref || config.admin?.ref || "main";
|
|
4912
6830
|
if (!org || !site) {
|
|
4913
6831
|
logger.error("Missing --org and --site. Set them via flags or in .edsrc.json:");
|
|
4914
|
-
logger.info(
|
|
6832
|
+
logger.info(chalk18.dim(' { "admin": { "org": "my-org", "site": "my-site" } }'));
|
|
4915
6833
|
process.exitCode = 1;
|
|
4916
6834
|
return void 0;
|
|
4917
6835
|
}
|
|
@@ -4933,7 +6851,7 @@ var publish_exports = {};
|
|
|
4933
6851
|
__export(publish_exports, {
|
|
4934
6852
|
publish: () => publish
|
|
4935
6853
|
});
|
|
4936
|
-
import
|
|
6854
|
+
import chalk19 from "chalk";
|
|
4937
6855
|
import ora3 from "ora";
|
|
4938
6856
|
async function publish(paths, options) {
|
|
4939
6857
|
const projectRoot = findProjectRoot();
|
|
@@ -4946,7 +6864,7 @@ async function publish(paths, options) {
|
|
|
4946
6864
|
let hasErrors = false;
|
|
4947
6865
|
for (const r of results) {
|
|
4948
6866
|
if (r.ok) {
|
|
4949
|
-
logger.success(`${r.path} \u2192 ${
|
|
6867
|
+
logger.success(`${r.path} \u2192 ${chalk19.cyan(r.liveUrl || "published")}`);
|
|
4950
6868
|
} else {
|
|
4951
6869
|
logger.error(`${r.path} \u2014 ${r.message}`);
|
|
4952
6870
|
hasErrors = true;
|
|
@@ -5045,13 +6963,13 @@ var menu_exports = {};
|
|
|
5045
6963
|
__export(menu_exports, {
|
|
5046
6964
|
mainMenu: () => mainMenu
|
|
5047
6965
|
});
|
|
5048
|
-
import { existsSync as
|
|
5049
|
-
import { readdir as
|
|
5050
|
-
import
|
|
6966
|
+
import { existsSync as existsSync32 } from "fs";
|
|
6967
|
+
import { readdir as readdir8 } from "fs/promises";
|
|
6968
|
+
import path35 from "path";
|
|
5051
6969
|
async function listBlockNames(projectRoot) {
|
|
5052
|
-
const dir =
|
|
5053
|
-
if (!
|
|
5054
|
-
const entries = await
|
|
6970
|
+
const dir = path35.join(projectRoot, "blocks");
|
|
6971
|
+
if (!existsSync32(dir)) return [];
|
|
6972
|
+
const entries = await readdir8(dir, { withFileTypes: true });
|
|
5055
6973
|
return entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
5056
6974
|
}
|
|
5057
6975
|
async function mainMenu() {
|
|
@@ -5061,6 +6979,8 @@ async function mainMenu() {
|
|
|
5061
6979
|
message: "Choose an action",
|
|
5062
6980
|
choices: [
|
|
5063
6981
|
{ name: "Set up this project (authoring model, CI, naming)", value: "init" },
|
|
6982
|
+
{ name: "Generate a page template (initial content)", value: "template" },
|
|
6983
|
+
{ name: "Create a disposable sandbox repo on GitHub", value: "sandbox" },
|
|
5064
6984
|
{
|
|
5065
6985
|
name: "Generate a block from a design (Figma/Stitch/Canva/Sketch/Framer)",
|
|
5066
6986
|
value: "from-design"
|
|
@@ -5069,7 +6989,10 @@ async function mainMenu() {
|
|
|
5069
6989
|
{ name: "Scaffold Universal Editor components", value: "scaffold" },
|
|
5070
6990
|
{ name: "Create an empty block", value: "create" },
|
|
5071
6991
|
{ name: "Add a third-party integration (GTM, chat, consent\u2026)", value: "integrate" },
|
|
6992
|
+
{ name: "Add fields to a block model (partials)", value: "model" },
|
|
5072
6993
|
{ name: "Instrument a block for analytics (dataLayer)", value: "track" },
|
|
6994
|
+
{ name: "Add JSON-LD structured data to a block", value: "schema" },
|
|
6995
|
+
{ name: "Check Universal Editor readiness / open the editor", value: "ue" },
|
|
5073
6996
|
{ name: "Preview a block in the browser", value: "preview-block" },
|
|
5074
6997
|
{ name: "Preview / publish pages (Admin API)", value: "admin" },
|
|
5075
6998
|
{ name: "Audit the project", value: "audit" },
|
|
@@ -5082,6 +7005,24 @@ async function mainMenu() {
|
|
|
5082
7005
|
await initProject2({});
|
|
5083
7006
|
return;
|
|
5084
7007
|
}
|
|
7008
|
+
case "template": {
|
|
7009
|
+
const name = await input({
|
|
7010
|
+
message: "Template name",
|
|
7011
|
+
validate: (v) => !!v.trim() || "Required"
|
|
7012
|
+
});
|
|
7013
|
+
const { templateNew: templateNew2 } = await Promise.resolve().then(() => (init_template(), template_exports));
|
|
7014
|
+
await templateNew2(name.trim(), {});
|
|
7015
|
+
return;
|
|
7016
|
+
}
|
|
7017
|
+
case "sandbox": {
|
|
7018
|
+
const name = await input({
|
|
7019
|
+
message: "Sandbox repo name",
|
|
7020
|
+
validate: (v) => /^[a-z0-9][a-z0-9._-]*$/i.test(v.trim()) || "Use a valid repo name"
|
|
7021
|
+
});
|
|
7022
|
+
const { sandboxNew: sandboxNew2 } = await Promise.resolve().then(() => (init_sandbox(), sandbox_exports));
|
|
7023
|
+
await sandboxNew2(name.trim(), {});
|
|
7024
|
+
return;
|
|
7025
|
+
}
|
|
5085
7026
|
case "from-design": {
|
|
5086
7027
|
const { blockFromDesign: blockFromDesign2 } = await Promise.resolve().then(() => (init_from_design(), from_design_exports));
|
|
5087
7028
|
await blockFromDesign2(void 0, {});
|
|
@@ -5130,6 +7071,11 @@ async function mainMenu() {
|
|
|
5130
7071
|
await previewBlock2(name, {});
|
|
5131
7072
|
return;
|
|
5132
7073
|
}
|
|
7074
|
+
case "model": {
|
|
7075
|
+
const { modelAdd: modelAdd2 } = await Promise.resolve().then(() => (init_model(), model_exports));
|
|
7076
|
+
await modelAdd2(void 0, [], {});
|
|
7077
|
+
return;
|
|
7078
|
+
}
|
|
5133
7079
|
case "track": {
|
|
5134
7080
|
const projectRoot = findProjectRoot();
|
|
5135
7081
|
if (!projectRoot) {
|
|
@@ -5149,6 +7095,43 @@ async function mainMenu() {
|
|
|
5149
7095
|
await trackBlock2(name, {});
|
|
5150
7096
|
return;
|
|
5151
7097
|
}
|
|
7098
|
+
case "schema": {
|
|
7099
|
+
const projectRoot = findProjectRoot();
|
|
7100
|
+
if (!projectRoot) {
|
|
7101
|
+
logger.error("Not inside an EDS project (no EDS project markers found).");
|
|
7102
|
+
return;
|
|
7103
|
+
}
|
|
7104
|
+
const blocks = await listBlockNames(projectRoot);
|
|
7105
|
+
if (blocks.length === 0) {
|
|
7106
|
+
logger.warn("No blocks found in blocks/.");
|
|
7107
|
+
return;
|
|
7108
|
+
}
|
|
7109
|
+
const name = await select({
|
|
7110
|
+
message: "Which block?",
|
|
7111
|
+
choices: blocks.map((b) => ({ name: b, value: b }))
|
|
7112
|
+
});
|
|
7113
|
+
const { schemaBlock: schemaBlock2 } = await Promise.resolve().then(() => (init_schema(), schema_exports));
|
|
7114
|
+
await schemaBlock2(name, {});
|
|
7115
|
+
return;
|
|
7116
|
+
}
|
|
7117
|
+
case "ue": {
|
|
7118
|
+
const which = await select({
|
|
7119
|
+
message: "Universal Editor:",
|
|
7120
|
+
choices: [
|
|
7121
|
+
{ name: "Check readiness (which blocks are editable)", value: "check" },
|
|
7122
|
+
{ name: "Open a page in the editor", value: "open" }
|
|
7123
|
+
]
|
|
7124
|
+
});
|
|
7125
|
+
if (which === "check") {
|
|
7126
|
+
const { ueCheck: ueCheck2 } = await Promise.resolve().then(() => (init_ue3(), ue_exports));
|
|
7127
|
+
await ueCheck2();
|
|
7128
|
+
} else {
|
|
7129
|
+
const pagePath = await input({ message: "Page path", default: "/" });
|
|
7130
|
+
const { ueOpen: ueOpen2 } = await Promise.resolve().then(() => (init_ue3(), ue_exports));
|
|
7131
|
+
await ueOpen2(pagePath.trim() || "/", {});
|
|
7132
|
+
}
|
|
7133
|
+
return;
|
|
7134
|
+
}
|
|
5152
7135
|
case "admin": {
|
|
5153
7136
|
const { adminWizard: adminWizard2 } = await Promise.resolve().then(() => (init_admin(), admin_exports));
|
|
5154
7137
|
await adminWizard2();
|
|
@@ -5194,13 +7177,13 @@ var init_menu = __esm({
|
|
|
5194
7177
|
|
|
5195
7178
|
// src/bin/eds.ts
|
|
5196
7179
|
init_esm_shims();
|
|
5197
|
-
import
|
|
7180
|
+
import chalk20 from "chalk";
|
|
5198
7181
|
import { Command } from "commander";
|
|
5199
7182
|
|
|
5200
7183
|
// package.json
|
|
5201
7184
|
var package_default = {
|
|
5202
7185
|
name: "aem-eds-kit",
|
|
5203
|
-
version: "0.4.
|
|
7186
|
+
version: "0.4.22",
|
|
5204
7187
|
description: "CLI for AEM Edge Delivery Services with Figma MCP integration",
|
|
5205
7188
|
type: "module",
|
|
5206
7189
|
engines: {
|
|
@@ -5276,10 +7259,40 @@ program.name("eds").description("CLI for AEM Edge Delivery Services with Figma i
|
|
|
5276
7259
|
const opts = thisCommand.opts();
|
|
5277
7260
|
if (opts.verbose) logger.setLevel("debug");
|
|
5278
7261
|
if (opts.quiet) logger.setQuiet(true);
|
|
5279
|
-
if (opts.color === false)
|
|
7262
|
+
if (opts.color === false) chalk20.level = 0;
|
|
5280
7263
|
});
|
|
5281
7264
|
program.addHelpText("beforeAll", () => logo());
|
|
5282
|
-
program.command("
|
|
7265
|
+
var sandbox = program.command("sandbox").description("Disposable EDS sandbox repos on GitHub");
|
|
7266
|
+
sandbox.command("new <name>").description("Create a disposable EDS repo from a boilerplate (GitHub auth, no gh required)").option("--org <org>", "Create under a GitHub org (else your account)").option("--boilerplate <repo>", "Template repo (default adobe/aem-boilerplate)").option("--ue", "Use the crosswalk / Universal Editor boilerplate").option("--private", "Create a private repo").option("--yes", "Skip the guided prompts (use flags/defaults)").action(async (name, options) => {
|
|
7267
|
+
const { sandboxNew: sandboxNew2 } = await Promise.resolve().then(() => (init_sandbox(), sandbox_exports));
|
|
7268
|
+
await sandboxNew2(name, options);
|
|
7269
|
+
});
|
|
7270
|
+
sandbox.command("login").description("Authorize GitHub (OAuth device flow or a token) - no gh needed").action(async () => {
|
|
7271
|
+
const { sandboxLogin: sandboxLogin2 } = await Promise.resolve().then(() => (init_sandbox(), sandbox_exports));
|
|
7272
|
+
await sandboxLogin2();
|
|
7273
|
+
});
|
|
7274
|
+
sandbox.command("logout").description("Remove the stored GitHub token").action(async () => {
|
|
7275
|
+
const { sandboxLogout: sandboxLogout2 } = await Promise.resolve().then(() => (init_sandbox(), sandbox_exports));
|
|
7276
|
+
await sandboxLogout2();
|
|
7277
|
+
});
|
|
7278
|
+
sandbox.command("list").description("List your EDS sandboxes (repos tagged eds-sandbox)").action(async () => {
|
|
7279
|
+
const { sandboxList: sandboxList2 } = await Promise.resolve().then(() => (init_sandbox(), sandbox_exports));
|
|
7280
|
+
await sandboxList2();
|
|
7281
|
+
});
|
|
7282
|
+
sandbox.command("rm <name>").description("Delete a sandbox repo (destructive)").option("--org <org>", "Owner org (else your account)").option("--yes", "Skip the confirmation").action(async (name, options) => {
|
|
7283
|
+
const { sandboxRm: sandboxRm2 } = await Promise.resolve().then(() => (init_sandbox(), sandbox_exports));
|
|
7284
|
+
await sandboxRm2(name, options);
|
|
7285
|
+
});
|
|
7286
|
+
var template = program.command("template").description("Generate page templates / initial content");
|
|
7287
|
+
template.command("new <name>").description("Generate EDS page initial content (sections + blocks + metadata), DA push optional").option("--path <path>", "Content path (default: /templates/<name>)").option("--title <title>", "Page title").option("--description <text>", "Page description").option(
|
|
7288
|
+
"--blocks <list>",
|
|
7289
|
+
"Comma-separated block ids to seed",
|
|
7290
|
+
(v) => v.split(",").map((b) => b.trim())
|
|
7291
|
+
).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) => {
|
|
7292
|
+
const { templateNew: templateNew2 } = await Promise.resolve().then(() => (init_template(), template_exports));
|
|
7293
|
+
await templateNew2(name, options);
|
|
7294
|
+
});
|
|
7295
|
+
program.command("init").description("Set up a freshly cloned EDS boilerplate for your authoring model (interactive)").option("--name <name>", "Project name (kebab-case)").option("--authoring <model>", "Authoring model: ue | da | gdrive | sharepoint").option("--mountpoint <url>", "Content source URL (document models)").option("--org <org>", "DA org (authoring=da)").option("--site <site>", "DA site (authoring=da)").option("--no-ci", "Skip the GitHub Actions CI workflow").option("--no-env", "Skip the local-dev .env (aem up config)").option("--no-xwalk", "Skip xwalk.json (UE multi-field)").option("--yes", "Skip prompts; apply with flags/defaults").action(async (options) => {
|
|
5283
7296
|
const { initProject: initProject2 } = await Promise.resolve().then(() => (init_init(), init_exports));
|
|
5284
7297
|
await initProject2(options);
|
|
5285
7298
|
});
|
|
@@ -5288,11 +7301,30 @@ migrate.command("component [dir]").description("Migrate a classic AEM component'
|
|
|
5288
7301
|
const { migrateComponent: migrateComponent2 } = await Promise.resolve().then(() => (init_migrate(), migrate_exports));
|
|
5289
7302
|
await migrateComponent2(dir, options);
|
|
5290
7303
|
});
|
|
7304
|
+
var model = program.command("model").description("Compose Universal Editor models");
|
|
7305
|
+
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) => {
|
|
7306
|
+
const { modelAdd: modelAdd2 } = await Promise.resolve().then(() => (init_model(), model_exports));
|
|
7307
|
+
await modelAdd2(blockName, partials, options);
|
|
7308
|
+
});
|
|
7309
|
+
var schema = program.command("schema").description("Structured data (JSON-LD) for blocks");
|
|
7310
|
+
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) => {
|
|
7311
|
+
const { schemaBlock: schemaBlock2 } = await Promise.resolve().then(() => (init_schema(), schema_exports));
|
|
7312
|
+
await schemaBlock2(name, options);
|
|
7313
|
+
});
|
|
5291
7314
|
var track = program.command("track").description("Instrument blocks for analytics");
|
|
5292
7315
|
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) => {
|
|
5293
7316
|
const { trackBlock: trackBlock2 } = await Promise.resolve().then(() => (init_track2(), track_exports));
|
|
5294
7317
|
await trackBlock2(name, options);
|
|
5295
7318
|
});
|
|
7319
|
+
var ue = program.command("ue").description("Universal Editor - readiness check + editor deep link");
|
|
7320
|
+
ue.command("check").description("Check whether blocks are Universal-Editor-ready (offline)").action(async () => {
|
|
7321
|
+
const { ueCheck: ueCheck2 } = await Promise.resolve().then(() => (init_ue3(), ue_exports));
|
|
7322
|
+
await ueCheck2();
|
|
7323
|
+
});
|
|
7324
|
+
ue.command("open [path]").description("Open a page in the hosted Universal Editor (preview host from git)").option("--org <slug>", "IMS org slug (skips the org picker)").option("--ref <branch>", "Branch/ref for the preview host (default: main)").option("--url <host>", "Override the delivery host (e.g. main--repo--owner.aem.page)").option("--no-open", "Print the link without opening the browser").action(async (pagePath, options) => {
|
|
7325
|
+
const { ueOpen: ueOpen2 } = await Promise.resolve().then(() => (init_ue3(), ue_exports));
|
|
7326
|
+
await ueOpen2(pagePath, options);
|
|
7327
|
+
});
|
|
5296
7328
|
var block = program.command("block").description("Manage EDS blocks");
|
|
5297
7329
|
block.command("create <name>").description("Scaffold a new EDS block (with Universal Editor model by default)").option("--no-ue-model", "Skip the Universal Editor model (generated by default)").action(async (name, options) => {
|
|
5298
7330
|
const { createBlock: createBlock2 } = await Promise.resolve().then(() => (init_create(), create_exports));
|
|
@@ -5328,6 +7360,18 @@ scaffold.command("ci").description("Scaffold a GitHub Actions workflow (doctor +
|
|
|
5328
7360
|
const { scaffoldCi: scaffoldCi2 } = await Promise.resolve().then(() => (init_scaffold(), scaffold_exports));
|
|
5329
7361
|
await scaffoldCi2();
|
|
5330
7362
|
});
|
|
7363
|
+
scaffold.command("helpers [names...]").description("Scaffold scripts/utils.js with chosen EDS helpers (interactive picker)").action(async (names) => {
|
|
7364
|
+
const { scaffoldHelpers: scaffoldHelpers2 } = await Promise.resolve().then(() => (init_scaffold(), scaffold_exports));
|
|
7365
|
+
await scaffoldHelpers2(names);
|
|
7366
|
+
});
|
|
7367
|
+
scaffold.command("env").description("Write a local-dev .env for `aem up` (AEM_OPEN / AEM_PORT / AEM_PAGES_URL)").option("--open <path>", "AEM_OPEN - path opened on start (default: /)").option("--port <port>", "AEM_PORT - proxy port (default: 3007)").option("--pages-url <url>", "AEM_PAGES_URL - content origin (default: from git remote)").option("--yes", "Skip prompts; use flags/defaults").action(async (options) => {
|
|
7368
|
+
const { scaffoldEnv: scaffoldEnv2 } = await Promise.resolve().then(() => (init_scaffold(), scaffold_exports));
|
|
7369
|
+
await scaffoldEnv2(options);
|
|
7370
|
+
});
|
|
7371
|
+
scaffold.command("xwalk").description("Write xwalk.json enabling Universal Editor multi-field support").action(async () => {
|
|
7372
|
+
const { scaffoldXwalk: scaffoldXwalk2 } = await Promise.resolve().then(() => (init_scaffold(), scaffold_exports));
|
|
7373
|
+
await scaffoldXwalk2();
|
|
7374
|
+
});
|
|
5331
7375
|
program.command("integrate [type]").description("Add a third-party service (GTM, GA4, chat, consent) to scripts/delayed.js").action(async (type) => {
|
|
5332
7376
|
const { integrate: integrate2 } = await Promise.resolve().then(() => (init_integrate(), integrate_exports));
|
|
5333
7377
|
await integrate2(type);
|