aem-eds-kit 0.4.2 → 0.4.4

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 CHANGED
@@ -6,6 +6,24 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.4.4] - 2026-10-07
10
+
11
+ ### Added
12
+ - **`eds scaffold ci`** - generate a GitHub Actions workflow (`.github/workflows/eds.yml`)
13
+ that runs the project's own `lint`/`build` (if present) plus the eds audits
14
+ (`doctor`, `audit loading`, `audit security`) as a PR gatekeeper. Also offered
15
+ in the interactive `eds scaffold` picker. Deterministic, no agent.
16
+
17
+ ## [0.4.3] - 2026-10-06
18
+
19
+ ### Changed
20
+ - CLI: a blinking accent caret now trails the animated logo line at the
21
+ interactive entry points, matching the landing terminal.
22
+
23
+ ### Fixed
24
+ - Landing hero headline no longer wraps "Design frame in." onto three lines
25
+ (tightened the clamp + tracking so each clause stays on one line).
26
+
9
27
  ## [0.4.2] - 2026-10-06
10
28
 
11
29
  ### Fixed
@@ -114,7 +132,9 @@ harness, gradient ANSI logo. Not published (superseded by 0.3.0).
114
132
  Initial CLI scaffold: `block create`, `block from-figma`, `block list`,
115
133
  `figma setup`, `doctor`, `preview`, `publish`. Not published.
116
134
 
117
- [Unreleased]: https://github.com/piotrgodycki/aem-eds-kit/compare/v0.4.2...HEAD
135
+ [Unreleased]: https://github.com/piotrgodycki/aem-eds-kit/compare/v0.4.4...HEAD
136
+ [0.4.4]: https://github.com/piotrgodycki/aem-eds-kit/releases/tag/v0.4.4
137
+ [0.4.3]: https://github.com/piotrgodycki/aem-eds-kit/releases/tag/v0.4.3
118
138
  [0.4.2]: https://github.com/piotrgodycki/aem-eds-kit/releases/tag/v0.4.2
119
139
  [0.4.1]: https://github.com/piotrgodycki/aem-eds-kit/releases/tag/v0.4.1
120
140
  [0.4.0]: https://github.com/piotrgodycki/aem-eds-kit/releases/tag/v0.4.0
package/README.md CHANGED
@@ -136,6 +136,14 @@ eds scaffold ue
136
136
  eds scaffold blocks # all, or: eds scaffold blocks hero cards
137
137
  ```
138
138
 
139
+ ### `eds scaffold ci`
140
+
141
+ Generate a GitHub Actions workflow (`.github/workflows/eds.yml`) that gatekeeps every PR and push to `main`: it installs deps, runs the project's own `lint`/`build` (if defined), then the eds audits - `doctor`, `audit loading`, `audit security` - which exit non-zero on problems. No agent, deterministic. Also offered in the interactive `eds scaffold` picker.
142
+
143
+ ```bash
144
+ eds scaffold ci
145
+ ```
146
+
139
147
  ### `eds integrate [type]`
140
148
 
141
149
  Add a third-party service to `scripts/delayed.js` (the EDS-correct place - delayed phase, after LCP, zero CWV impact). Chat widgets use the facade pattern.
package/dist/bin/eds.js CHANGED
@@ -247,11 +247,16 @@ var init_logger = __esm({
247
247
  ${logoArt()}
248
248
 
249
249
  `);
250
- const frames = 16;
251
- for (let step = 0; step < frames; step++) {
252
- out.write(`\r\x1B[K ${brandLine(dotsFrame(step), tagline)}`);
250
+ const caret = (on) => on ? brand.accent("\u258C") : " ";
251
+ const sweep = 14;
252
+ for (let step = 0; step < sweep; step++) {
253
+ out.write(`\r\x1B[K ${brandLine(dotsFrame(step), tagline)} ${caret(step % 2 === 0)}`);
253
254
  await new Promise((r) => setTimeout(r, 70));
254
255
  }
256
+ for (let b = 0; b < 4; b++) {
257
+ out.write(`\r\x1B[K ${brandLine(dotsStatic(), tagline)} ${caret(b % 2 === 0)}`);
258
+ await new Promise((r) => setTimeout(r, 200));
259
+ }
255
260
  out.write(`\r\x1B[K ${brandLine(dotsStatic(), tagline)}
256
261
  `);
257
262
  },
@@ -2277,6 +2282,70 @@ export default function decorate(block) {
2277
2282
  }
2278
2283
  });
2279
2284
 
2285
+ // src/lib/scaffold/ci.ts
2286
+ import { existsSync as existsSync10 } from "fs";
2287
+ import { mkdir as mkdir3, writeFile as writeFile4 } from "fs/promises";
2288
+ import path12 from "path";
2289
+ async function writeCiWorkflow(projectRoot) {
2290
+ const file = path12.join(projectRoot, ".github", "workflows", "eds.yml");
2291
+ if (existsSync10(file)) return { path: CI_WORKFLOW_PATH, created: false };
2292
+ await mkdir3(path12.dirname(file), { recursive: true });
2293
+ await writeFile4(file, CI_WORKFLOW);
2294
+ return { path: CI_WORKFLOW_PATH, created: true };
2295
+ }
2296
+ var CI_WORKFLOW_PATH, CI_WORKFLOW;
2297
+ var init_ci = __esm({
2298
+ "src/lib/scaffold/ci.ts"() {
2299
+ "use strict";
2300
+ init_esm_shims();
2301
+ CI_WORKFLOW_PATH = ".github/workflows/eds.yml";
2302
+ CI_WORKFLOW = `name: EDS checks
2303
+
2304
+ # Generated by aem-eds-kit (\`eds scaffold ci\`). Runs the project's own
2305
+ # lint/build plus the eds audits as a gatekeeper on every PR and push to main.
2306
+ on:
2307
+ pull_request:
2308
+ push:
2309
+ branches: [main]
2310
+
2311
+ jobs:
2312
+ checks:
2313
+ runs-on: ubuntu-latest
2314
+ steps:
2315
+ - uses: actions/checkout@v4
2316
+ - uses: actions/setup-node@v4
2317
+ with:
2318
+ node-version: 20
2319
+
2320
+ # Install project dependencies when the repo has them.
2321
+ - name: Install dependencies
2322
+ run: |
2323
+ if [ -f package-lock.json ]; then
2324
+ npm ci
2325
+ elif [ -f package.json ]; then
2326
+ npm install
2327
+ fi
2328
+
2329
+ # The project's own scripts, if defined (skipped otherwise).
2330
+ - name: Lint
2331
+ if: hashFiles('package.json') != ''
2332
+ run: npm run lint --if-present
2333
+ - name: Build
2334
+ if: hashFiles('package.json') != ''
2335
+ run: npm run build --if-present
2336
+
2337
+ # aem-eds-kit audits - run via npx, no install needed. Each exits
2338
+ # non-zero on failures, so a bad PR fails the check.
2339
+ - name: Project health (doctor)
2340
+ run: npx --yes aem-eds-kit@latest doctor
2341
+ - name: Loading & LCP budget
2342
+ run: npx --yes aem-eds-kit@latest audit loading
2343
+ - name: Security & exploit scan
2344
+ run: npx --yes aem-eds-kit@latest audit security
2345
+ `;
2346
+ }
2347
+ });
2348
+
2280
2349
  // src/lib/scaffold/ue.ts
2281
2350
  function fieldReferenceFields() {
2282
2351
  return [
@@ -2538,14 +2607,15 @@ export default function decorate(block) {
2538
2607
  var scaffold_exports = {};
2539
2608
  __export(scaffold_exports, {
2540
2609
  scaffoldBlocks: () => scaffoldBlocks,
2610
+ scaffoldCi: () => scaffoldCi,
2541
2611
  scaffoldInteractive: () => scaffoldInteractive,
2542
2612
  scaffoldUe: () => scaffoldUe
2543
2613
  });
2544
- import { existsSync as existsSync10 } from "fs";
2545
- import { mkdir as mkdir3, readFile as readFile8, writeFile as writeFile4 } from "fs/promises";
2546
- import path12 from "path";
2614
+ import { existsSync as existsSync11 } from "fs";
2615
+ import { mkdir as mkdir4, readFile as readFile8, writeFile as writeFile5 } from "fs/promises";
2616
+ import path13 from "path";
2547
2617
  async function readJson(file) {
2548
- if (!existsSync10(file)) return null;
2618
+ if (!existsSync11(file)) return null;
2549
2619
  try {
2550
2620
  return JSON.parse(await readFile8(file, "utf-8"));
2551
2621
  } catch {
@@ -2553,7 +2623,7 @@ async function readJson(file) {
2553
2623
  }
2554
2624
  }
2555
2625
  async function writeJson(file, data) {
2556
- await writeFile4(file, `${JSON.stringify(data, null, 2)}
2626
+ await writeFile5(file, `${JSON.stringify(data, null, 2)}
2557
2627
  `);
2558
2628
  }
2559
2629
  async function scaffoldUe() {
@@ -2565,15 +2635,15 @@ async function scaffoldUe() {
2565
2635
  }
2566
2636
  logger.logoOnce(logo("Scaffold - Universal Editor"));
2567
2637
  const created = [];
2568
- const blockDir = path12.join(projectRoot, "blocks", "field-reference");
2569
- await mkdir3(blockDir, { recursive: true });
2570
- await writeFile4(path12.join(blockDir, "field-reference.js"), FIELD_REFERENCE_JS);
2571
- await writeFile4(path12.join(blockDir, "field-reference.css"), FIELD_REFERENCE_CSS);
2572
- await writeFile4(path12.join(blockDir, "_field-reference.json"), fieldReferenceModelFile());
2638
+ const blockDir = path13.join(projectRoot, "blocks", "field-reference");
2639
+ await mkdir4(blockDir, { recursive: true });
2640
+ await writeFile5(path13.join(blockDir, "field-reference.js"), FIELD_REFERENCE_JS);
2641
+ await writeFile5(path13.join(blockDir, "field-reference.css"), FIELD_REFERENCE_CSS);
2642
+ await writeFile5(path13.join(blockDir, "_field-reference.json"), fieldReferenceModelFile());
2573
2643
  created.push("blocks/field-reference/ (all 17 fields + multifield)");
2574
- const defFile = path12.join(projectRoot, "component-definition.json");
2575
- const modelsFile = path12.join(projectRoot, "component-models.json");
2576
- const filtersFile = path12.join(projectRoot, "component-filters.json");
2644
+ const defFile = path13.join(projectRoot, "component-definition.json");
2645
+ const modelsFile = path13.join(projectRoot, "component-models.json");
2646
+ const filtersFile = path13.join(projectRoot, "component-filters.json");
2577
2647
  const blockDefs = [
2578
2648
  {
2579
2649
  title: "Field Reference",
@@ -2636,18 +2706,18 @@ async function scaffoldBlocks(names) {
2636
2706
  const createdIds = [];
2637
2707
  const skipped = [];
2638
2708
  for (const b of selected) {
2639
- const dir = path12.join(projectRoot, "blocks", b.id);
2640
- if (existsSync10(dir)) {
2709
+ const dir = path13.join(projectRoot, "blocks", b.id);
2710
+ if (existsSync11(dir)) {
2641
2711
  skipped.push(b.id);
2642
2712
  continue;
2643
2713
  }
2644
- await mkdir3(dir, { recursive: true });
2645
- await writeFile4(path12.join(dir, `${b.id}.js`), b.js);
2646
- await writeFile4(path12.join(dir, `${b.id}.css`), b.css);
2647
- await writeFile4(path12.join(dir, `_${b.id}.json`), b.model);
2714
+ await mkdir4(dir, { recursive: true });
2715
+ await writeFile5(path13.join(dir, `${b.id}.js`), b.js);
2716
+ await writeFile5(path13.join(dir, `${b.id}.css`), b.css);
2717
+ await writeFile5(path13.join(dir, `_${b.id}.json`), b.model);
2648
2718
  createdIds.push(b.id);
2649
2719
  }
2650
- const filtersFile = path12.join(projectRoot, "component-filters.json");
2720
+ const filtersFile = path13.join(projectRoot, "component-filters.json");
2651
2721
  const filters = mergeSectionFilter(
2652
2722
  await readJson(filtersFile),
2653
2723
  selected.map((b) => b.id)
@@ -2660,6 +2730,28 @@ async function scaffoldBlocks(names) {
2660
2730
  logger.info("");
2661
2731
  logger.info(box([`${createdIds.length} block(s) scaffolded + registered`]));
2662
2732
  }
2733
+ async function scaffoldCi() {
2734
+ const projectRoot = findProjectRoot();
2735
+ if (!projectRoot) {
2736
+ logger.error("Not inside an EDS project (no fstab.yaml found).");
2737
+ process.exitCode = 1;
2738
+ return;
2739
+ }
2740
+ logger.logoOnce(logo("Scaffold - CI workflow"));
2741
+ const res = await writeCiWorkflow(projectRoot);
2742
+ logger.info(heading("GitHub Actions"));
2743
+ if (res.created) {
2744
+ logger.info(accentLine(res.path, "doctor + audit loading + audit security + lint/build"));
2745
+ } else {
2746
+ logger.info(statusLine("warn", res.path, "already exists - skipped"));
2747
+ }
2748
+ logger.info("");
2749
+ logger.info(
2750
+ box([
2751
+ res.created ? "CI workflow scaffolded - commit it and it runs on every PR" : "CI workflow left untouched"
2752
+ ])
2753
+ );
2754
+ }
2663
2755
  async function scaffoldInteractive() {
2664
2756
  const { select, checkbox } = await import("@inquirer/prompts");
2665
2757
  logger.logoOnce(logo("Scaffold"));
@@ -2668,7 +2760,8 @@ async function scaffoldInteractive() {
2668
2760
  choices: [
2669
2761
  { name: "Universal Editor config + field-reference (all 17 field types)", value: "ue" },
2670
2762
  { name: "Standard blocks (hero, cards, columns, accordion, embed)", value: "blocks" },
2671
- { name: "Both", value: "both" }
2763
+ { name: "GitHub Actions CI (doctor + audits + lint/build)", value: "ci" },
2764
+ { name: "UE config + standard blocks", value: "both" }
2672
2765
  ]
2673
2766
  });
2674
2767
  if (what === "ue" || what === "both") await scaffoldUe();
@@ -2679,6 +2772,7 @@ async function scaffoldInteractive() {
2679
2772
  });
2680
2773
  await scaffoldBlocks(picked);
2681
2774
  }
2775
+ if (what === "ci") await scaffoldCi();
2682
2776
  }
2683
2777
  var init_scaffold = __esm({
2684
2778
  "src/commands/scaffold.ts"() {
@@ -2687,6 +2781,7 @@ var init_scaffold = __esm({
2687
2781
  init_logger();
2688
2782
  init_project();
2689
2783
  init_blocks();
2784
+ init_ci();
2690
2785
  init_ue();
2691
2786
  init_ui();
2692
2787
  }
@@ -2791,9 +2886,9 @@ var integrate_exports = {};
2791
2886
  __export(integrate_exports, {
2792
2887
  integrate: () => integrate
2793
2888
  });
2794
- import { existsSync as existsSync11 } from "fs";
2795
- import { mkdir as mkdir4, readFile as readFile9, writeFile as writeFile5 } from "fs/promises";
2796
- import path13 from "path";
2889
+ import { existsSync as existsSync12 } from "fs";
2890
+ import { mkdir as mkdir5, readFile as readFile9, writeFile as writeFile6 } from "fs/promises";
2891
+ import path14 from "path";
2797
2892
  import chalk8 from "chalk";
2798
2893
  async function integrate(type) {
2799
2894
  const projectRoot = findProjectRoot();
@@ -2822,10 +2917,10 @@ async function integrate(type) {
2822
2917
  values[f.name] = answer.trim();
2823
2918
  }
2824
2919
  const block2 = wrapBlock(integration.name, integration.code(values));
2825
- const delayedPath = path13.join(projectRoot, "scripts", "delayed.js");
2826
- await mkdir4(path13.dirname(delayedPath), { recursive: true });
2827
- const existing = existsSync11(delayedPath) ? await readFile9(delayedPath, "utf-8") : DELAYED_HEADER;
2828
- await writeFile5(delayedPath, `${existing.replace(/\s*$/, "")}
2920
+ const delayedPath = path14.join(projectRoot, "scripts", "delayed.js");
2921
+ await mkdir5(path14.dirname(delayedPath), { recursive: true });
2922
+ const existing = existsSync12(delayedPath) ? await readFile9(delayedPath, "utf-8") : DELAYED_HEADER;
2923
+ await writeFile6(delayedPath, `${existing.replace(/\s*$/, "")}
2829
2924
  ${block2}`);
2830
2925
  logger.info(heading("Added"));
2831
2926
  logger.info(accentLine(integration.name, "\u2192 scripts/delayed.js"));
@@ -2866,8 +2961,8 @@ var setup_exports = {};
2866
2961
  __export(setup_exports, {
2867
2962
  figmaSetup: () => figmaSetup
2868
2963
  });
2869
- import { existsSync as existsSync12 } from "fs";
2870
- import { mkdir as mkdir5, writeFile as writeFile6 } from "fs/promises";
2964
+ import { existsSync as existsSync13 } from "fs";
2965
+ import { mkdir as mkdir6, writeFile as writeFile7 } from "fs/promises";
2871
2966
  import chalk9 from "chalk";
2872
2967
  async function figmaSetup() {
2873
2968
  const { select, confirm } = await import("@inquirer/prompts");
@@ -2947,14 +3042,14 @@ async function figmaSetup() {
2947
3042
  });
2948
3043
  if (shouldSave) {
2949
3044
  const configDir = globalConfigDir();
2950
- if (!existsSync12(configDir)) {
2951
- await mkdir5(configDir, { recursive: true });
3045
+ if (!existsSync13(configDir)) {
3046
+ await mkdir6(configDir, { recursive: true });
2952
3047
  }
2953
3048
  const config = {};
2954
3049
  if (selectedAgent !== "none") {
2955
3050
  config.agent = selectedAgent;
2956
3051
  }
2957
- await writeFile6(globalConfigPath(), `${JSON.stringify(config, null, 2)}
3052
+ await writeFile7(globalConfigPath(), `${JSON.stringify(config, null, 2)}
2958
3053
  `, {
2959
3054
  mode: 384
2960
3055
  });
@@ -2981,9 +3076,9 @@ var doctor_exports = {};
2981
3076
  __export(doctor_exports, {
2982
3077
  doctor: () => doctor
2983
3078
  });
2984
- import { existsSync as existsSync13 } from "fs";
3079
+ import { existsSync as existsSync14 } from "fs";
2985
3080
  import { readFile as readFile10, readdir as readdir2 } from "fs/promises";
2986
- import path14 from "path";
3081
+ import path15 from "path";
2987
3082
  import { parse as parseYaml } from "yaml";
2988
3083
  async function doctor(options = {}) {
2989
3084
  const projectRoot = findProjectRoot();
@@ -3005,7 +3100,7 @@ async function doctor(options = {}) {
3005
3100
  });
3006
3101
  if (project.hasFstab) {
3007
3102
  try {
3008
- const content = await readFile10(path14.join(projectRoot, "fstab.yaml"), "utf-8");
3103
+ const content = await readFile10(path15.join(projectRoot, "fstab.yaml"), "utf-8");
3009
3104
  parseYaml(content);
3010
3105
  results.push({ name: "fstab.yaml valid YAML", status: "pass", message: "Valid" });
3011
3106
  } catch {
@@ -3027,11 +3122,11 @@ async function doctor(options = {}) {
3027
3122
  message: project.hasBlocks ? "Found" : "Missing \u2014 no blocks yet"
3028
3123
  });
3029
3124
  if (project.hasBlocks) {
3030
- const blocksDir = path14.join(projectRoot, "blocks");
3125
+ const blocksDir = path15.join(projectRoot, "blocks");
3031
3126
  const entries = await readdir2(blocksDir, { withFileTypes: true });
3032
3127
  let aggregatedModels = "";
3033
- const aggregatedPath = path14.join(projectRoot, "component-models.json");
3034
- if (existsSync13(aggregatedPath)) {
3128
+ const aggregatedPath = path15.join(projectRoot, "component-models.json");
3129
+ if (existsSync14(aggregatedPath)) {
3035
3130
  try {
3036
3131
  aggregatedModels = await readFile10(aggregatedPath, "utf-8");
3037
3132
  } catch {
@@ -3040,8 +3135,8 @@ async function doctor(options = {}) {
3040
3135
  for (const entry of entries) {
3041
3136
  if (!entry.isDirectory()) continue;
3042
3137
  const blockName = entry.name;
3043
- const jsFile = path14.join(blocksDir, blockName, `${blockName}.js`);
3044
- const hasJs = existsSync13(jsFile);
3138
+ const jsFile = path15.join(blocksDir, blockName, `${blockName}.js`);
3139
+ const hasJs = existsSync14(jsFile);
3045
3140
  results.push({
3046
3141
  name: `blocks/${blockName} has JS`,
3047
3142
  status: hasJs ? "pass" : "warn",
@@ -3057,15 +3152,15 @@ async function doctor(options = {}) {
3057
3152
  });
3058
3153
  }
3059
3154
  }
3060
- const hasPerBlockModel = existsSync13(path14.join(blocksDir, blockName, `_${blockName}.json`));
3155
+ const hasPerBlockModel = existsSync14(path15.join(blocksDir, blockName, `_${blockName}.json`));
3061
3156
  const hasAggregatedModel = aggregatedModels.includes(`"${blockName}"`);
3062
3157
  results.push({
3063
3158
  name: `blocks/${blockName} UE model`,
3064
3159
  status: hasPerBlockModel || hasAggregatedModel ? "pass" : "warn",
3065
3160
  message: hasPerBlockModel || hasAggregatedModel ? hasPerBlockModel ? `_${blockName}.json` : "in component-models.json" : "no Universal Editor model \u2014 authors can't edit this block in UE"
3066
3161
  });
3067
- const metaFile = path14.join(blocksDir, blockName, ".eds-meta.json");
3068
- if (existsSync13(metaFile)) {
3162
+ const metaFile = path15.join(blocksDir, blockName, ".eds-meta.json");
3163
+ if (existsSync14(metaFile)) {
3069
3164
  try {
3070
3165
  const meta = JSON.parse(await readFile10(metaFile, "utf-8"));
3071
3166
  const syncDate = new Date(meta.lastSyncedAt);
@@ -3087,8 +3182,8 @@ async function doctor(options = {}) {
3087
3182
  }
3088
3183
  }
3089
3184
  }
3090
- const queryFile = path14.join(projectRoot, "helix-query.yaml");
3091
- if (existsSync13(queryFile)) {
3185
+ const queryFile = path15.join(projectRoot, "helix-query.yaml");
3186
+ if (existsSync14(queryFile)) {
3092
3187
  try {
3093
3188
  const content = await readFile10(queryFile, "utf-8");
3094
3189
  parseYaml(content);
@@ -3147,9 +3242,9 @@ var audit_loading_exports = {};
3147
3242
  __export(audit_loading_exports, {
3148
3243
  auditLoading: () => auditLoading
3149
3244
  });
3150
- import { existsSync as existsSync14 } from "fs";
3245
+ import { existsSync as existsSync15 } from "fs";
3151
3246
  import { readFile as readFile11, readdir as readdir3, stat as stat3 } from "fs/promises";
3152
- import path15 from "path";
3247
+ import path16 from "path";
3153
3248
  import chalk10 from "chalk";
3154
3249
  async function auditLoading(options = {}) {
3155
3250
  const projectRoot = findProjectRoot();
@@ -3183,8 +3278,8 @@ async function auditLoading(options = {}) {
3183
3278
  printResults(results);
3184
3279
  }
3185
3280
  async function checkHeadHtml(root, results) {
3186
- const filePath = path15.join(root, "head.html");
3187
- if (!existsSync14(filePath)) {
3281
+ const filePath = path16.join(root, "head.html");
3282
+ if (!existsSync15(filePath)) {
3188
3283
  results.push({
3189
3284
  rule: "head.html exists",
3190
3285
  status: "warn",
@@ -3264,8 +3359,8 @@ async function checkHeadHtml(root, results) {
3264
3359
  }
3265
3360
  }
3266
3361
  async function checkScriptsJs(root, results) {
3267
- const filePath = path15.join(root, "scripts", "scripts.js");
3268
- if (!existsSync14(filePath)) {
3362
+ const filePath = path16.join(root, "scripts", "scripts.js");
3363
+ if (!existsSync15(filePath)) {
3269
3364
  results.push({
3270
3365
  rule: "scripts/scripts.js exists",
3271
3366
  status: "warn",
@@ -3351,8 +3446,8 @@ async function checkScriptsJs(root, results) {
3351
3446
  }
3352
3447
  }
3353
3448
  async function checkStylesCss(root, results) {
3354
- const filePath = path15.join(root, "styles", "styles.css");
3355
- if (!existsSync14(filePath)) return;
3449
+ const filePath = path16.join(root, "styles", "styles.css");
3450
+ if (!existsSync15(filePath)) return;
3356
3451
  const stats = await stat3(filePath);
3357
3452
  const sizeKb = stats.size / 1024;
3358
3453
  results.push({
@@ -3372,8 +3467,8 @@ async function checkStylesCss(root, results) {
3372
3467
  }
3373
3468
  }
3374
3469
  async function checkDelayedJs(root, results) {
3375
- const filePath = path15.join(root, "scripts", "delayed.js");
3376
- if (!existsSync14(filePath)) {
3470
+ const filePath = path16.join(root, "scripts", "delayed.js");
3471
+ if (!existsSync15(filePath)) {
3377
3472
  results.push({
3378
3473
  rule: "scripts/delayed.js exists",
3379
3474
  status: "info",
@@ -3393,14 +3488,14 @@ async function checkDelayedJs(root, results) {
3393
3488
  }
3394
3489
  }
3395
3490
  async function checkBlocks(root, results) {
3396
- const blocksDir = path15.join(root, "blocks");
3397
- if (!existsSync14(blocksDir)) return;
3491
+ const blocksDir = path16.join(root, "blocks");
3492
+ if (!existsSync15(blocksDir)) return;
3398
3493
  const entries = await readdir3(blocksDir, { withFileTypes: true });
3399
3494
  for (const entry of entries) {
3400
3495
  if (!entry.isDirectory()) continue;
3401
3496
  const blockName = entry.name;
3402
- const jsFile = path15.join(blocksDir, blockName, `${blockName}.js`);
3403
- if (!existsSync14(jsFile)) continue;
3497
+ const jsFile = path16.join(blocksDir, blockName, `${blockName}.js`);
3498
+ if (!existsSync15(jsFile)) continue;
3404
3499
  const content = await readFile11(jsFile, "utf-8");
3405
3500
  const lines = content.split("\n");
3406
3501
  const heavyLibs = [
@@ -3451,8 +3546,8 @@ async function checkBlocks(root, results) {
3451
3546
  for (const entry of entries) {
3452
3547
  if (!entry.isDirectory()) continue;
3453
3548
  const blockName = entry.name;
3454
- const cssFile = path15.join(blocksDir, blockName, `${blockName}.css`);
3455
- if (!existsSync14(cssFile)) continue;
3549
+ const cssFile = path16.join(blocksDir, blockName, `${blockName}.css`);
3550
+ if (!existsSync15(cssFile)) continue;
3456
3551
  const content = await readFile11(cssFile, "utf-8");
3457
3552
  const selectorPattern = /^([a-z][a-z0-9-]*)\s*\{/gm;
3458
3553
  for (const selectorMatch of content.matchAll(selectorPattern)) {
@@ -3469,8 +3564,8 @@ async function checkBlocks(root, results) {
3469
3564
  }
3470
3565
  }
3471
3566
  async function checkAemJs(root, results) {
3472
- const filePath = path15.join(root, "scripts", "aem.js");
3473
- if (!existsSync14(filePath)) {
3567
+ const filePath = path16.join(root, "scripts", "aem.js");
3568
+ if (!existsSync15(filePath)) {
3474
3569
  results.push({
3475
3570
  rule: "scripts/aem.js exists",
3476
3571
  status: "warn",
@@ -3542,14 +3637,14 @@ var audit_security_exports = {};
3542
3637
  __export(audit_security_exports, {
3543
3638
  auditSecurity: () => auditSecurity
3544
3639
  });
3545
- import { existsSync as existsSync15 } from "fs";
3640
+ import { existsSync as existsSync16 } from "fs";
3546
3641
  import { readFile as readFile12, readdir as readdir4 } from "fs/promises";
3547
- import path16 from "path";
3642
+ import path17 from "path";
3548
3643
  import chalk11 from "chalk";
3549
3644
  async function* walk(dir) {
3550
3645
  const entries = await readdir4(dir, { withFileTypes: true });
3551
3646
  for (const entry of entries) {
3552
- const full = path16.join(dir, entry.name);
3647
+ const full = path17.join(dir, entry.name);
3553
3648
  if (entry.isDirectory()) {
3554
3649
  if (IGNORE_DIRS.has(entry.name)) continue;
3555
3650
  yield* walk(full);
@@ -3583,11 +3678,11 @@ function scanContent(content, relFile, ext) {
3583
3678
  return findings;
3584
3679
  }
3585
3680
  async function auditDependencies(root) {
3586
- if (!existsSync15(path16.join(root, "package.json"))) {
3681
+ if (!existsSync16(path17.join(root, "package.json"))) {
3587
3682
  return { status: "skipped", note: "no package.json" };
3588
3683
  }
3589
- const hasLock = existsSync15(path16.join(root, "package-lock.json")) || existsSync15(path16.join(root, "npm-shrinkwrap.json"));
3590
- if (!hasLock && !existsSync15(path16.join(root, "node_modules"))) {
3684
+ const hasLock = existsSync16(path17.join(root, "package-lock.json")) || existsSync16(path17.join(root, "npm-shrinkwrap.json"));
3685
+ if (!hasLock && !existsSync16(path17.join(root, "node_modules"))) {
3591
3686
  return { status: "skipped", note: "no package-lock.json / node_modules \u2014 run npm install" };
3592
3687
  }
3593
3688
  try {
@@ -3619,9 +3714,9 @@ async function auditSecurity(options = {}) {
3619
3714
  }
3620
3715
  const findings = [];
3621
3716
  for await (const file of walk(projectRoot)) {
3622
- const ext = path16.extname(file).slice(1).toLowerCase();
3717
+ const ext = path17.extname(file).slice(1).toLowerCase();
3623
3718
  if (!SCAN_EXT.has(ext)) continue;
3624
- const rel = path16.relative(projectRoot, file);
3719
+ const rel = path17.relative(projectRoot, file);
3625
3720
  if (rel.endsWith("aem.js") || rel.endsWith("lib-franklin.js")) continue;
3626
3721
  const content = await readFile12(file, "utf-8");
3627
3722
  findings.push(...scanContent(content, rel, ext));
@@ -4041,12 +4136,12 @@ var menu_exports = {};
4041
4136
  __export(menu_exports, {
4042
4137
  mainMenu: () => mainMenu
4043
4138
  });
4044
- import { existsSync as existsSync16 } from "fs";
4139
+ import { existsSync as existsSync17 } from "fs";
4045
4140
  import { readdir as readdir5 } from "fs/promises";
4046
- import path17 from "path";
4141
+ import path18 from "path";
4047
4142
  async function listBlockNames(projectRoot) {
4048
- const dir = path17.join(projectRoot, "blocks");
4049
- if (!existsSync16(dir)) return [];
4143
+ const dir = path18.join(projectRoot, "blocks");
4144
+ if (!existsSync17(dir)) return [];
4050
4145
  const entries = await readdir5(dir, { withFileTypes: true });
4051
4146
  return entries.filter((e) => e.isDirectory()).map((e) => e.name);
4052
4147
  }
@@ -4161,7 +4256,7 @@ import { Command } from "commander";
4161
4256
  // package.json
4162
4257
  var package_default = {
4163
4258
  name: "aem-eds-kit",
4164
- version: "0.4.2",
4259
+ version: "0.4.4",
4165
4260
  description: "CLI for AEM Edge Delivery Services with Figma MCP integration",
4166
4261
  type: "module",
4167
4262
  engines: {
@@ -4270,6 +4365,10 @@ scaffold.command("blocks [names...]").description("Scaffold standard blocks (her
4270
4365
  const { scaffoldBlocks: scaffoldBlocks2 } = await Promise.resolve().then(() => (init_scaffold(), scaffold_exports));
4271
4366
  await scaffoldBlocks2(names);
4272
4367
  });
4368
+ scaffold.command("ci").description("Scaffold a GitHub Actions workflow (doctor + audits + project lint/build)").action(async () => {
4369
+ const { scaffoldCi: scaffoldCi2 } = await Promise.resolve().then(() => (init_scaffold(), scaffold_exports));
4370
+ await scaffoldCi2();
4371
+ });
4273
4372
  program.command("integrate [type]").description("Add a third-party service (GTM, GA4, chat, consent) to scripts/delayed.js").action(async (type) => {
4274
4373
  const { integrate: integrate2 } = await Promise.resolve().then(() => (init_integrate(), integrate_exports));
4275
4374
  await integrate2(type);