@duffcloudservices/cli 0.3.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ import { Command } from "commander";
5
5
  import chalk8 from "chalk";
6
6
 
7
7
  // package.json
8
- var version = "0.3.2";
8
+ var version = "0.4.0";
9
9
 
10
10
  // src/commands/auth.ts
11
11
  import chalk2 from "chalk";
@@ -17,64 +17,375 @@ import chalk from "chalk";
17
17
 
18
18
  // src/auth/credentials.ts
19
19
  import Conf from "conf";
20
- var config = new Conf({
21
- projectName: "dcs-cli",
22
- schema: {
23
- auth: {
24
- type: "object",
25
- properties: {
26
- accessToken: { type: "string" },
27
- refreshToken: { type: "string" },
28
- expiresAt: { type: "number" },
29
- email: { type: "string" },
30
- userId: { type: "string" }
20
+
21
+ // src/auth/keychain/run.ts
22
+ import { spawnSync } from "child_process";
23
+ function run(cmd, args, opts = {}) {
24
+ const options = {
25
+ encoding: "utf8",
26
+ env: opts.env ? { ...process.env, ...opts.env } : process.env,
27
+ windowsHide: true
28
+ };
29
+ if (opts.input !== void 0) options.input = opts.input;
30
+ const res = spawnSync(cmd, args, options);
31
+ if (res.error) {
32
+ return { ok: false, code: null, stdout: "", stderr: String(res.error), spawnError: true };
33
+ }
34
+ return {
35
+ ok: res.status === 0,
36
+ code: res.status,
37
+ stdout: typeof res.stdout === "string" ? res.stdout : "",
38
+ stderr: typeof res.stderr === "string" ? res.stderr : "",
39
+ spawnError: false
40
+ };
41
+ }
42
+ function commandExists(cmd, probeArgs) {
43
+ const res = run(cmd, probeArgs);
44
+ return !res.spawnError;
45
+ }
46
+
47
+ // src/auth/keychain/types.ts
48
+ var SERVICE = "dcs-cli";
49
+
50
+ // src/auth/keychain/macos.ts
51
+ var macosKeychain = {
52
+ name: "macos-keychain",
53
+ isAvailable() {
54
+ return process.platform === "darwin" && commandExists("security", ["help"]);
55
+ },
56
+ get(account) {
57
+ const res = run("security", [
58
+ "find-generic-password",
59
+ "-s",
60
+ SERVICE,
61
+ "-a",
62
+ account,
63
+ "-w"
64
+ ]);
65
+ if (!res.ok) return null;
66
+ return res.stdout.replace(/\n$/, "");
67
+ },
68
+ set(account, secret) {
69
+ const res = run("security", [
70
+ "add-generic-password",
71
+ "-s",
72
+ SERVICE,
73
+ "-a",
74
+ account,
75
+ "-w",
76
+ secret,
77
+ "-U"
78
+ ]);
79
+ if (!res.ok) {
80
+ throw new Error(`macOS Keychain write failed: ${res.stderr.trim() || res.code}`);
81
+ }
82
+ },
83
+ delete(account) {
84
+ run("security", ["delete-generic-password", "-s", SERVICE, "-a", account]);
85
+ }
86
+ };
87
+
88
+ // src/auth/keychain/libsecret.ts
89
+ var libsecretKeychain = {
90
+ name: "libsecret",
91
+ isAvailable() {
92
+ return process.platform === "linux" && commandExists("secret-tool", ["--version"]);
93
+ },
94
+ get(account) {
95
+ const res = run("secret-tool", ["lookup", "service", SERVICE, "account", account]);
96
+ if (!res.ok) return null;
97
+ return res.stdout.replace(/\n$/, "");
98
+ },
99
+ set(account, secret) {
100
+ const res = run(
101
+ "secret-tool",
102
+ ["store", "--label", `${SERVICE}:${account}`, "service", SERVICE, "account", account],
103
+ { input: secret }
104
+ );
105
+ if (!res.ok) {
106
+ throw new Error(`libsecret write failed: ${res.stderr.trim() || res.code}`);
107
+ }
108
+ },
109
+ delete(account) {
110
+ run("secret-tool", ["clear", "service", SERVICE, "account", account]);
111
+ }
112
+ };
113
+
114
+ // src/auth/keychain/windows.ts
115
+ import fs3 from "fs";
116
+
117
+ // src/auth/keychain/paths.ts
118
+ import os from "os";
119
+ import path from "path";
120
+ import fs from "fs";
121
+ var APP_DIR = "dcs-cli-nodejs";
122
+ function configDir() {
123
+ const override2 = process.env.DCS_CLI_CONFIG_DIR;
124
+ if (override2) return override2;
125
+ if (process.platform === "win32") {
126
+ const base2 = process.env.APPDATA || path.join(os.homedir(), "AppData", "Roaming");
127
+ return path.join(base2, APP_DIR);
128
+ }
129
+ if (process.platform === "darwin") {
130
+ return path.join(os.homedir(), "Library", "Application Support", APP_DIR);
131
+ }
132
+ const base = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config");
133
+ return path.join(base, APP_DIR);
134
+ }
135
+ function secretFile(account, ext) {
136
+ const safe = account.replace(/[^a-zA-Z0-9._-]/g, "_");
137
+ return path.join(configDir(), `${safe}.${ext}`);
138
+ }
139
+ function ensureConfigDir() {
140
+ const dir = configDir();
141
+ fs.mkdirSync(dir, { recursive: true });
142
+ return dir;
143
+ }
144
+
145
+ // src/auth/keychain/encrypted-file.ts
146
+ import fs2 from "fs";
147
+ import os2 from "os";
148
+ import crypto from "crypto";
149
+ var EXT = "enc";
150
+ var MAGIC = "DCSK1";
151
+ var STATIC_SALT = Buffer.from("dcs-cli-keychain-v1-salt", "utf8");
152
+ function deriveKey() {
153
+ let username = "unknown";
154
+ try {
155
+ username = os2.userInfo().username;
156
+ } catch {
157
+ }
158
+ const material = `${username}\0${os2.hostname()}\0dcs-cli`;
159
+ return crypto.scryptSync(material, STATIC_SALT, 32);
160
+ }
161
+ var encryptedFile = {
162
+ name: "encrypted-file",
163
+ isAvailable() {
164
+ return true;
165
+ },
166
+ get(account) {
167
+ let raw;
168
+ try {
169
+ raw = fs2.readFileSync(secretFile(account, EXT), "utf8").trim();
170
+ } catch {
171
+ return null;
172
+ }
173
+ const parts = raw.split(":");
174
+ if (parts.length !== 4 || parts[0] !== MAGIC) return null;
175
+ try {
176
+ const iv = Buffer.from(parts[1], "base64");
177
+ const tag = Buffer.from(parts[2], "base64");
178
+ const ciphertext = Buffer.from(parts[3], "base64");
179
+ const decipher = crypto.createDecipheriv("aes-256-gcm", deriveKey(), iv);
180
+ decipher.setAuthTag(tag);
181
+ const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
182
+ return plaintext.toString("utf8");
183
+ } catch {
184
+ return null;
185
+ }
186
+ },
187
+ set(account, secret) {
188
+ ensureConfigDir();
189
+ const iv = crypto.randomBytes(12);
190
+ const cipher = crypto.createCipheriv("aes-256-gcm", deriveKey(), iv);
191
+ const ciphertext = Buffer.concat([cipher.update(secret, "utf8"), cipher.final()]);
192
+ const tag = cipher.getAuthTag();
193
+ const envelope = [
194
+ MAGIC,
195
+ iv.toString("base64"),
196
+ tag.toString("base64"),
197
+ ciphertext.toString("base64")
198
+ ].join(":");
199
+ fs2.writeFileSync(secretFile(account, EXT), envelope, { encoding: "utf8", mode: 384 });
200
+ },
201
+ delete(account) {
202
+ try {
203
+ fs2.unlinkSync(secretFile(account, EXT));
204
+ } catch {
205
+ }
206
+ }
207
+ };
208
+
209
+ // src/auth/keychain/windows.ts
210
+ var EXT2 = "dpapi";
211
+ var PROTECT = "$ErrorActionPreference='Stop'; Add-Type -AssemblyName System.Security; $b=[Text.Encoding]::UTF8.GetBytes($env:DCS_KEYCHAIN_SECRET); $p=[Security.Cryptography.ProtectedData]::Protect($b,$null,'CurrentUser'); [Convert]::ToBase64String($p)";
212
+ var UNPROTECT = "$ErrorActionPreference='Stop'; Add-Type -AssemblyName System.Security; $p=[Convert]::FromBase64String($env:DCS_KEYCHAIN_ENC); $b=[Security.Cryptography.ProtectedData]::Unprotect($p,$null,'CurrentUser'); [Text.Encoding]::UTF8.GetString($b)";
213
+ function powershell(script, env) {
214
+ return run("powershell", ["-NoProfile", "-NonInteractive", "-Command", script], { env });
215
+ }
216
+ function deleteDpapiSidecar(account) {
217
+ try {
218
+ fs3.unlinkSync(secretFile(account, EXT2));
219
+ } catch {
220
+ }
221
+ }
222
+ var dpapiUnavailable = false;
223
+ var windowsDpapi = {
224
+ name: "windows-dpapi",
225
+ isAvailable() {
226
+ return process.platform === "win32" && !dpapiUnavailable;
227
+ },
228
+ get(account) {
229
+ if (dpapiUnavailable) return encryptedFile.get(account);
230
+ let enc;
231
+ try {
232
+ enc = fs3.readFileSync(secretFile(account, EXT2), "utf8").trim();
233
+ } catch {
234
+ return encryptedFile.get(account);
235
+ }
236
+ if (!enc) return null;
237
+ const res = powershell(UNPROTECT, { DCS_KEYCHAIN_ENC: enc });
238
+ if (!res.ok) {
239
+ if (res.spawnError) {
240
+ dpapiUnavailable = true;
241
+ return encryptedFile.get(account);
31
242
  }
243
+ return null;
244
+ }
245
+ return res.stdout.replace(/\r?\n$/, "");
246
+ },
247
+ set(account, secret) {
248
+ if (dpapiUnavailable) {
249
+ encryptedFile.set(account, secret);
250
+ deleteDpapiSidecar(account);
251
+ return;
252
+ }
253
+ ensureConfigDir();
254
+ const res = powershell(PROTECT, { DCS_KEYCHAIN_SECRET: secret });
255
+ if (!res.ok || !res.stdout.trim()) {
256
+ dpapiUnavailable = true;
257
+ encryptedFile.set(account, secret);
258
+ deleteDpapiSidecar(account);
259
+ return;
260
+ }
261
+ fs3.writeFileSync(secretFile(account, EXT2), res.stdout.trim(), { encoding: "utf8", mode: 384 });
262
+ encryptedFile.delete(account);
263
+ },
264
+ delete(account) {
265
+ deleteDpapiSidecar(account);
266
+ encryptedFile.delete(account);
267
+ }
268
+ };
269
+
270
+ // src/auth/keychain/index.ts
271
+ var CANDIDATES = [macosKeychain, libsecretKeychain, windowsDpapi];
272
+ var resolved;
273
+ var override = null;
274
+ function resolve() {
275
+ for (const backend of CANDIDATES) {
276
+ try {
277
+ if (backend.isAvailable()) return backend;
278
+ } catch {
32
279
  }
33
280
  }
34
- });
35
- function storeCredentials(credentials) {
36
- config.set("auth", credentials);
281
+ return encryptedFile;
282
+ }
283
+ function getKeychain() {
284
+ if (override) return override;
285
+ if (!resolved) resolved = resolve();
286
+ return resolved;
287
+ }
288
+
289
+ // src/auth/credentials.ts
290
+ var ACCOUNT = "auth";
291
+ function defaultLegacyStore() {
292
+ const config = new Conf({ projectName: "dcs-cli" });
293
+ return {
294
+ read: () => config.get("auth"),
295
+ clear: () => {
296
+ if (config.has("auth")) config.delete("auth");
297
+ }
298
+ };
37
299
  }
38
- function getCredentials() {
39
- return config.get("auth");
300
+ var legacyStore;
301
+ function getLegacyStore() {
302
+ if (!legacyStore) legacyStore = defaultLegacyStore();
303
+ return legacyStore;
304
+ }
305
+ var cache;
306
+ function load() {
307
+ if (cache !== void 0) return cache;
308
+ const raw = getKeychain().get(ACCOUNT);
309
+ if (raw) {
310
+ try {
311
+ cache = JSON.parse(raw);
312
+ return cache;
313
+ } catch {
314
+ getKeychain().delete(ACCOUNT);
315
+ cache = null;
316
+ return cache;
317
+ }
318
+ }
319
+ const legacy = getLegacyStore().read();
320
+ if (legacy) {
321
+ getKeychain().set(ACCOUNT, JSON.stringify(legacy));
322
+ getLegacyStore().clear();
323
+ cache = legacy;
324
+ return cache;
325
+ }
326
+ cache = null;
327
+ return cache;
328
+ }
329
+ function persist(creds) {
330
+ getKeychain().set(ACCOUNT, JSON.stringify(creds));
331
+ cache = creds;
332
+ getLegacyStore().clear();
333
+ }
334
+ function storeCredentials(credentials) {
335
+ persist(credentials);
40
336
  }
41
337
  function clearCredentials() {
42
- config.delete("auth");
338
+ getKeychain().delete(ACCOUNT);
339
+ getLegacyStore().clear();
340
+ cache = null;
43
341
  }
44
342
  function isAuthenticated() {
45
- const creds = getCredentials();
343
+ const creds = load();
46
344
  if (!creds) return false;
47
345
  const bufferMs = 5 * 60 * 1e3;
48
346
  return creds.expiresAt > Date.now() + bufferMs;
49
347
  }
348
+ function hasStoredSession() {
349
+ return load() !== null;
350
+ }
50
351
  function getCurrentUserEmail() {
51
- return getCredentials()?.email;
352
+ return load()?.email;
52
353
  }
53
354
  function getAccessToken() {
54
- const creds = getCredentials();
55
- if (!creds) return void 0;
56
- return creds.accessToken;
355
+ return load()?.accessToken;
57
356
  }
58
357
  function getRefreshToken() {
59
- return getCredentials()?.refreshToken;
358
+ return load()?.refreshToken;
60
359
  }
61
360
  function updateAccessToken(accessToken, expiresIn) {
62
- const creds = getCredentials();
361
+ const creds = load();
63
362
  if (!creds) return;
64
- config.set("auth", {
363
+ persist({
65
364
  ...creds,
66
365
  accessToken,
67
366
  expiresAt: Date.now() + expiresIn * 1e3
68
367
  });
69
368
  }
70
- function getConfigPath() {
71
- return config.path;
369
+ function getStorageBackendName() {
370
+ return getKeychain().name;
72
371
  }
73
372
 
74
373
  // src/auth/device-flow.ts
75
374
  var DEFAULT_API_URL = "https://portal.duffcloudservices.com";
375
+ var RefreshTokenRevokedError = class extends Error {
376
+ constructor(message = "Refresh token was rejected (expired or revoked).") {
377
+ super(message);
378
+ this.name = "RefreshTokenRevokedError";
379
+ }
380
+ };
381
+ var ReauthRequiredError = class extends Error {
382
+ constructor(message = "Your CLI session expired or was revoked. Run `dcs login` to sign in again.") {
383
+ super(message);
384
+ this.name = "ReauthRequiredError";
385
+ }
386
+ };
76
387
  function sleep(ms) {
77
- return new Promise((resolve) => setTimeout(resolve, ms));
388
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
78
389
  }
79
390
  async function startDeviceAuth(options = {}) {
80
391
  const apiUrl = options.apiUrl || DEFAULT_API_URL;
@@ -201,8 +512,11 @@ async function refreshAccessToken(refreshToken, options = {}) {
201
512
  },
202
513
  body: JSON.stringify({ refresh_token: refreshToken })
203
514
  });
515
+ if (response.status === 401) {
516
+ throw new RefreshTokenRevokedError();
517
+ }
204
518
  if (!response.ok) {
205
- throw new Error("Failed to refresh access token. Please login again.");
519
+ throw new Error(`Failed to refresh access token (HTTP ${response.status}).`);
206
520
  }
207
521
  const data = await response.json();
208
522
  return {
@@ -234,7 +548,7 @@ async function whoamiCommand() {
234
548
  return;
235
549
  }
236
550
  console.log("Logged in as:", chalk2.green(email));
237
- console.log(chalk2.dim("Config file:"), chalk2.dim(getConfigPath()));
551
+ console.log(chalk2.dim("Token storage:"), chalk2.dim(getStorageBackendName()));
238
552
  }
239
553
 
240
554
  // src/commands/sites.ts
@@ -248,36 +562,59 @@ var PortalClient = class {
248
562
  constructor(options = {}) {
249
563
  this.apiUrl = options.apiUrl || process.env.DCS_API_URL || DEFAULT_API_URL2;
250
564
  }
565
+ /**
566
+ * Refresh the session using the stored refresh token. Exactly one attempt —
567
+ * never a retry loop. On a revoked/expired refresh token the dead local
568
+ * session is dropped and a clean re-auth is demanded; transient failures
569
+ * (network/5xx/429) keep the session and surface a retryable error.
570
+ */
571
+ async refreshSession() {
572
+ const refreshToken = getRefreshToken();
573
+ try {
574
+ const { accessToken: newToken, expiresIn } = await refreshAccessToken(refreshToken, {
575
+ apiUrl: this.apiUrl
576
+ });
577
+ updateAccessToken(newToken, expiresIn);
578
+ return newToken;
579
+ } catch (error) {
580
+ if (error instanceof RefreshTokenRevokedError) {
581
+ clearCredentials();
582
+ throw new ReauthRequiredError();
583
+ }
584
+ throw new Error(
585
+ `Could not reach the DCS Portal to refresh your session: ${error instanceof Error ? error.message : String(error)}`
586
+ );
587
+ }
588
+ }
251
589
  /**
252
590
  * Make an authenticated API request.
253
591
  */
254
- async request(path5, options = {}) {
592
+ async request(path6, options = {}) {
255
593
  let accessToken = getAccessToken();
594
+ let refreshedThisRequest = false;
256
595
  if (!isAuthenticated() && getRefreshToken()) {
257
- const refreshToken = getRefreshToken();
258
- try {
259
- const { accessToken: newToken, expiresIn } = await refreshAccessToken(refreshToken, {
260
- apiUrl: this.apiUrl
261
- });
262
- updateAccessToken(newToken, expiresIn);
263
- accessToken = newToken;
264
- } catch {
265
- throw new Error("Session expired. Please login again with `dcs login`.");
266
- }
596
+ accessToken = await this.refreshSession();
597
+ refreshedThisRequest = true;
267
598
  }
268
599
  if (!accessToken) {
269
- throw new Error("Not authenticated. Please login with `dcs login`.");
600
+ throw new ReauthRequiredError("Not authenticated. Run `dcs login` to sign in.");
270
601
  }
271
- const response = await fetch(`${this.apiUrl}${path5}`, {
602
+ const send = (token) => fetch(`${this.apiUrl}${path6}`, {
272
603
  ...options,
273
604
  headers: {
274
605
  ...options.headers,
275
- Authorization: `Bearer ${accessToken}`,
606
+ Authorization: `Bearer ${token}`,
276
607
  "Content-Type": "application/json"
277
608
  }
278
609
  });
610
+ let response = await send(accessToken);
611
+ if (response.status === 401 && !refreshedThisRequest && getRefreshToken()) {
612
+ const newToken = await this.refreshSession();
613
+ response = await send(newToken);
614
+ }
279
615
  if (response.status === 401) {
280
- throw new Error("Session expired. Please login again with `dcs login`.");
616
+ clearCredentials();
617
+ throw new ReauthRequiredError();
281
618
  }
282
619
  if (!response.ok) {
283
620
  const error = await response.text();
@@ -340,7 +677,7 @@ function getPortalClient(options) {
340
677
 
341
678
  // src/commands/sites.ts
342
679
  async function listSitesCommand() {
343
- if (!isAuthenticated()) {
680
+ if (!hasStoredSession()) {
344
681
  console.log(chalk3.yellow("Not logged in."));
345
682
  console.log("Run", chalk3.cyan("dcs login"), "to authenticate.");
346
683
  process.exit(1);
@@ -387,7 +724,7 @@ async function listSitesCommand() {
387
724
  }
388
725
  }
389
726
  async function showSiteCommand(slug) {
390
- if (!isAuthenticated()) {
727
+ if (!hasStoredSession()) {
391
728
  console.log(chalk3.yellow("Not logged in."));
392
729
  console.log("Run", chalk3.cyan("dcs login"), "to authenticate.");
393
730
  process.exit(1);
@@ -435,8 +772,8 @@ function displaySiteDetails(site) {
435
772
  }
436
773
 
437
774
  // src/commands/init.ts
438
- import fs from "fs/promises";
439
- import path from "path";
775
+ import fs4 from "fs/promises";
776
+ import path2 from "path";
440
777
  import chalk4 from "chalk";
441
778
  import ora2 from "ora";
442
779
 
@@ -475,8 +812,9 @@ jobs:
475
812
  deploy:
476
813
  name: Deploy to Azure Static Web App
477
814
  runs-on: ubuntu-latest
478
- # Skip deployment on branch creation events
479
- if: github.event.before != '0000000000000000000000000000000000000000'
815
+ # Skip deployment on branch creation events, but always run manual dispatches
816
+ # (workflow_dispatch has no github.event.before).
817
+ if: github.event_name == 'workflow_dispatch' || github.event.before != '0000000000000000000000000000000000000000'
480
818
 
481
819
  steps:
482
820
  - name: Determine deployment environment
@@ -564,10 +902,15 @@ jobs:
564
902
  SITE_SLUG="\${{ steps.config.outputs.site_slug }}"
565
903
  PORTAL_API_URL="\${{ steps.config.outputs.portal_api_url }}"
566
904
 
905
+ # Deployment environment: "preview" lets the server issue the token for a
906
+ # dedicated preview SWA when PortalSites.PreviewStaticWebAppResourceId is set.
907
+ ENVIRONMENT="\${{ steps.environment.outputs.swa_environment }}"
908
+ ENVIRONMENT="\${ENVIRONMENT:-production}"
909
+
567
910
  RESPONSE=$(curl -s -X POST "\${PORTAL_API_URL}/api/v1/sites/deployment-tokens" \\
568
911
  -H "Authorization: Bearer $ACCESS_TOKEN" \\
569
912
  -H "Content-Type: application/json" \\
570
- -d "{\\"siteName\\": \\"$SITE_NAME\\", \\"siteSlug\\": \\"$SITE_SLUG\\"}")
913
+ -d "{\\"siteName\\": \\"$SITE_NAME\\", \\"siteSlug\\": \\"$SITE_SLUG\\", \\"environment\\": \\"$ENVIRONMENT\\"}")
571
914
 
572
915
  SWA_TOKEN=$(echo $RESPONSE | jq -r .swaToken)
573
916
 
@@ -638,7 +981,7 @@ jobs:
638
981
  // src/commands/init.ts
639
982
  async function initCommand(options) {
640
983
  const { siteSlug, siteName, framework = "vue", target = ".", dryRun = false, force = false } = options;
641
- if (!isAuthenticated()) {
984
+ if (!hasStoredSession()) {
642
985
  console.log(chalk4.yellow("Not logged in."));
643
986
  console.log("Run", chalk4.cyan("dcs login"), "to authenticate.");
644
987
  process.exit(1);
@@ -669,7 +1012,7 @@ async function initCommand(options) {
669
1012
  }
670
1013
  process.exit(1);
671
1014
  }
672
- const targetDir = path.resolve(target);
1015
+ const targetDir = path2.resolve(target);
673
1016
  console.log(chalk4.dim(`Target directory: ${targetDir}`));
674
1017
  const generatedFiles = [];
675
1018
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
@@ -721,21 +1064,21 @@ async function initCommand(options) {
721
1064
  console.log();
722
1065
  }
723
1066
  async function writeFile(targetDir, relativePath, content, options) {
724
- const fullPath = path.join(targetDir, relativePath);
1067
+ const fullPath = path2.join(targetDir, relativePath);
725
1068
  if (options.dryRun) {
726
1069
  console.log(chalk4.dim(`Would create: ${relativePath}`));
727
1070
  return relativePath;
728
1071
  }
729
1072
  try {
730
- await fs.access(fullPath);
1073
+ await fs4.access(fullPath);
731
1074
  if (!options.force) {
732
1075
  console.log(chalk4.yellow(`Skipped (exists): ${relativePath}`));
733
1076
  return relativePath;
734
1077
  }
735
1078
  } catch {
736
1079
  }
737
- await fs.mkdir(path.dirname(fullPath), { recursive: true });
738
- await fs.writeFile(fullPath, content, "utf8");
1080
+ await fs4.mkdir(path2.dirname(fullPath), { recursive: true });
1081
+ await fs4.writeFile(fullPath, content, "utf8");
739
1082
  return relativePath;
740
1083
  }
741
1084
  function generateSiteYaml(data) {
@@ -1346,19 +1689,19 @@ After completing this plan:
1346
1689
  }
1347
1690
 
1348
1691
  // src/commands/validate.ts
1349
- import fs2 from "fs/promises";
1350
- import path2 from "path";
1692
+ import fs5 from "fs/promises";
1693
+ import path3 from "path";
1351
1694
  import chalk5 from "chalk";
1352
1695
  import yaml from "js-yaml";
1353
1696
  async function validateCommand(options) {
1354
1697
  const { target = ".", fix = false, verbose = false } = options;
1355
- const targetDir = path2.resolve(target);
1356
- const dcsDir = path2.join(targetDir, ".dcs");
1698
+ const targetDir = path3.resolve(target);
1699
+ const dcsDir = path3.join(targetDir, ".dcs");
1357
1700
  console.log(chalk5.bold("Validating DCS configuration..."));
1358
1701
  console.log(chalk5.dim(`Directory: ${targetDir}`));
1359
1702
  console.log();
1360
1703
  try {
1361
- await fs2.access(dcsDir);
1704
+ await fs5.access(dcsDir);
1362
1705
  } catch {
1363
1706
  console.log(chalk5.red("No .dcs directory found."));
1364
1707
  console.log("Run", chalk5.cyan("dcs init"), "to create DCS configuration.");
@@ -1404,7 +1747,7 @@ async function validateCommand(options) {
1404
1747
  }
1405
1748
  }
1406
1749
  async function validateSiteYaml(dcsDir, verbose) {
1407
- const filePath = path2.join(dcsDir, "site.yaml");
1750
+ const filePath = path3.join(dcsDir, "site.yaml");
1408
1751
  const result = {
1409
1752
  file: ".dcs/site.yaml",
1410
1753
  valid: true,
@@ -1412,28 +1755,28 @@ async function validateSiteYaml(dcsDir, verbose) {
1412
1755
  warnings: []
1413
1756
  };
1414
1757
  try {
1415
- const content = await fs2.readFile(filePath, "utf8");
1416
- const config2 = yaml.load(content);
1417
- if (!config2.site_name) {
1758
+ const content = await fs5.readFile(filePath, "utf8");
1759
+ const config = yaml.load(content);
1760
+ if (!config.site_name) {
1418
1761
  result.errors.push("Missing required field: site_name");
1419
1762
  result.valid = false;
1420
1763
  }
1421
- if (!config2.site_slug) {
1764
+ if (!config.site_slug) {
1422
1765
  result.errors.push("Missing required field: site_slug");
1423
1766
  result.valid = false;
1424
- } else if (!/^[a-z0-9-]+$/.test(config2.site_slug)) {
1767
+ } else if (!/^[a-z0-9-]+$/.test(config.site_slug)) {
1425
1768
  result.errors.push("site_slug must be lowercase alphanumeric with hyphens only");
1426
1769
  result.valid = false;
1427
1770
  }
1428
- if (!config2.swa_resource_id) {
1771
+ if (!config.swa_resource_id) {
1429
1772
  result.warnings.push("swa_resource_id is not set (required for deployment)");
1430
1773
  }
1431
- if (!config2.production_url) {
1774
+ if (!config.production_url) {
1432
1775
  result.warnings.push("production_url is not set");
1433
1776
  }
1434
1777
  if (verbose) {
1435
- console.log(chalk5.dim(` site_name: ${config2.site_name}`));
1436
- console.log(chalk5.dim(` site_slug: ${config2.site_slug}`));
1778
+ console.log(chalk5.dim(` site_name: ${config.site_name}`));
1779
+ console.log(chalk5.dim(` site_slug: ${config.site_slug}`));
1437
1780
  }
1438
1781
  } catch (error) {
1439
1782
  if (error.code === "ENOENT") {
@@ -1446,7 +1789,7 @@ async function validateSiteYaml(dcsDir, verbose) {
1446
1789
  return result;
1447
1790
  }
1448
1791
  async function validatePagesYaml(dcsDir, verbose) {
1449
- const filePath = path2.join(dcsDir, "pages.yaml");
1792
+ const filePath = path3.join(dcsDir, "pages.yaml");
1450
1793
  const result = {
1451
1794
  file: ".dcs/pages.yaml",
1452
1795
  valid: true,
@@ -1454,17 +1797,17 @@ async function validatePagesYaml(dcsDir, verbose) {
1454
1797
  warnings: []
1455
1798
  };
1456
1799
  try {
1457
- const content = await fs2.readFile(filePath, "utf8");
1458
- const config2 = yaml.load(content);
1459
- if (config2.version === void 0) {
1800
+ const content = await fs5.readFile(filePath, "utf8");
1801
+ const config = yaml.load(content);
1802
+ if (config.version === void 0) {
1460
1803
  result.errors.push("Missing required field: version");
1461
1804
  result.valid = false;
1462
1805
  }
1463
- if (!config2.siteSlug) {
1806
+ if (!config.siteSlug) {
1464
1807
  result.errors.push("Missing required field: siteSlug");
1465
1808
  result.valid = false;
1466
1809
  }
1467
- const pages = config2.pages;
1810
+ const pages = config.pages;
1468
1811
  if (!pages || !Array.isArray(pages)) {
1469
1812
  result.errors.push("Missing or invalid pages array");
1470
1813
  result.valid = false;
@@ -1502,7 +1845,7 @@ async function validatePagesYaml(dcsDir, verbose) {
1502
1845
  return result;
1503
1846
  }
1504
1847
  async function validateContentYaml(dcsDir, verbose) {
1505
- const filePath = path2.join(dcsDir, "content.yaml");
1848
+ const filePath = path3.join(dcsDir, "content.yaml");
1506
1849
  const result = {
1507
1850
  file: ".dcs/content.yaml",
1508
1851
  valid: true,
@@ -1510,23 +1853,23 @@ async function validateContentYaml(dcsDir, verbose) {
1510
1853
  warnings: []
1511
1854
  };
1512
1855
  try {
1513
- const content = await fs2.readFile(filePath, "utf8");
1514
- const config2 = yaml.load(content);
1515
- if (config2.version === void 0) {
1856
+ const content = await fs5.readFile(filePath, "utf8");
1857
+ const config = yaml.load(content);
1858
+ if (config.version === void 0) {
1516
1859
  result.errors.push("Missing required field: version");
1517
1860
  result.valid = false;
1518
1861
  }
1519
- if (config2.global !== void 0 && typeof config2.global !== "object") {
1862
+ if (config.global !== void 0 && typeof config.global !== "object") {
1520
1863
  result.errors.push("global must be an object");
1521
1864
  result.valid = false;
1522
1865
  }
1523
- if (config2.pages !== void 0 && typeof config2.pages !== "object") {
1866
+ if (config.pages !== void 0 && typeof config.pages !== "object") {
1524
1867
  result.errors.push("pages must be an object");
1525
1868
  result.valid = false;
1526
1869
  }
1527
1870
  if (verbose) {
1528
- const globalKeys = config2.global ? Object.keys(config2.global).length : 0;
1529
- const pageCount = config2.pages ? Object.keys(config2.pages).length : 0;
1871
+ const globalKeys = config.global ? Object.keys(config.global).length : 0;
1872
+ const pageCount = config.pages ? Object.keys(config.pages).length : 0;
1530
1873
  console.log(chalk5.dim(` ${globalKeys} global keys, ${pageCount} page sections`));
1531
1874
  }
1532
1875
  } catch (error) {
@@ -1540,7 +1883,7 @@ async function validateContentYaml(dcsDir, verbose) {
1540
1883
  return result;
1541
1884
  }
1542
1885
  async function validateSeoYaml(dcsDir, verbose) {
1543
- const filePath = path2.join(dcsDir, "seo.yaml");
1886
+ const filePath = path3.join(dcsDir, "seo.yaml");
1544
1887
  const result = {
1545
1888
  file: ".dcs/seo.yaml",
1546
1889
  valid: true,
@@ -1548,13 +1891,13 @@ async function validateSeoYaml(dcsDir, verbose) {
1548
1891
  warnings: []
1549
1892
  };
1550
1893
  try {
1551
- const content = await fs2.readFile(filePath, "utf8");
1552
- const config2 = yaml.load(content);
1553
- if (config2.version === void 0) {
1894
+ const content = await fs5.readFile(filePath, "utf8");
1895
+ const config = yaml.load(content);
1896
+ if (config.version === void 0) {
1554
1897
  result.errors.push("Missing required field: version");
1555
1898
  result.valid = false;
1556
1899
  }
1557
- const global = config2.global;
1900
+ const global = config.global;
1558
1901
  if (!global) {
1559
1902
  result.errors.push("Missing required section: global");
1560
1903
  result.valid = false;
@@ -1569,7 +1912,7 @@ async function validateSeoYaml(dcsDir, verbose) {
1569
1912
  result.warnings.push("global.defaultDescription is not set");
1570
1913
  }
1571
1914
  }
1572
- const pages = config2.pages;
1915
+ const pages = config.pages;
1573
1916
  if (pages && typeof pages === "object") {
1574
1917
  const pageKeys = Object.keys(pages);
1575
1918
  if (!pageKeys.includes("home")) {
@@ -1591,20 +1934,20 @@ async function validateSeoYaml(dcsDir, verbose) {
1591
1934
  }
1592
1935
 
1593
1936
  // src/commands/plans.ts
1594
- import fs3 from "fs/promises";
1595
- import path3 from "path";
1937
+ import fs6 from "fs/promises";
1938
+ import path4 from "path";
1596
1939
  import chalk6 from "chalk";
1597
1940
  import yaml2 from "js-yaml";
1598
1941
  async function plansCommand(options) {
1599
1942
  const { target = ".", force = false } = options;
1600
- const targetDir = path3.resolve(target);
1601
- const dcsDir = path3.join(targetDir, ".dcs");
1602
- const plansDir = path3.join(targetDir, ".plans");
1943
+ const targetDir = path4.resolve(target);
1944
+ const dcsDir = path4.join(targetDir, ".dcs");
1945
+ const plansDir = path4.join(targetDir, ".plans");
1603
1946
  console.log(chalk6.bold("Generating DCS integration plans..."));
1604
1947
  console.log(chalk6.dim(`Directory: ${targetDir}`));
1605
1948
  console.log();
1606
1949
  try {
1607
- await fs3.access(dcsDir);
1950
+ await fs6.access(dcsDir);
1608
1951
  } catch {
1609
1952
  console.log(chalk6.red("No .dcs directory found."));
1610
1953
  console.log("Run", chalk6.cyan("dcs init"), "first to create DCS configuration.");
@@ -1613,7 +1956,7 @@ async function plansCommand(options) {
1613
1956
  let siteName = "Customer Site";
1614
1957
  let framework = "vue";
1615
1958
  try {
1616
- const siteYaml = await fs3.readFile(path3.join(dcsDir, "site.yaml"), "utf8");
1959
+ const siteYaml = await fs6.readFile(path4.join(dcsDir, "site.yaml"), "utf8");
1617
1960
  const siteConfig = yaml2.load(siteYaml);
1618
1961
  siteName = siteConfig.site_name || siteName;
1619
1962
  const metadata = siteConfig.metadata;
@@ -1629,19 +1972,19 @@ async function plansCommand(options) {
1629
1972
  { name: "04-capture-snapshots.md", content: generateSnapshotsPlan(siteName) },
1630
1973
  { name: "05-verify-deployment.md", content: generateVerifyPlan(siteName) }
1631
1974
  ];
1632
- await fs3.mkdir(plansDir, { recursive: true });
1975
+ await fs6.mkdir(plansDir, { recursive: true });
1633
1976
  const generatedFiles = [];
1634
1977
  for (const plan of plans) {
1635
- const filePath = path3.join(plansDir, plan.name);
1978
+ const filePath = path4.join(plansDir, plan.name);
1636
1979
  try {
1637
- await fs3.access(filePath);
1980
+ await fs6.access(filePath);
1638
1981
  if (!force) {
1639
1982
  console.log(chalk6.yellow(`Skipped (exists): .plans/${plan.name}`));
1640
1983
  continue;
1641
1984
  }
1642
1985
  } catch {
1643
1986
  }
1644
- await fs3.writeFile(filePath, plan.content, "utf8");
1987
+ await fs6.writeFile(filePath, plan.content, "utf8");
1645
1988
  generatedFiles.push(plan.name);
1646
1989
  console.log(`${chalk6.green("\u2713")} .plans/${plan.name}`);
1647
1990
  }
@@ -2100,8 +2443,8 @@ Once verified:
2100
2443
  }
2101
2444
 
2102
2445
  // src/commands/capture-snapshots.ts
2103
- import fs4 from "fs";
2104
- import path4 from "path";
2446
+ import fs7 from "fs";
2447
+ import path5 from "path";
2105
2448
  import yaml3 from "js-yaml";
2106
2449
  import chalk7 from "chalk";
2107
2450
  import ora3 from "ora";
@@ -2133,15 +2476,15 @@ async function waitForAboveFoldAssets(page) {
2133
2476
  if (img.complete && img.naturalWidth > 0) {
2134
2477
  return Promise.resolve();
2135
2478
  }
2136
- return new Promise((resolve) => {
2137
- const done = () => resolve();
2479
+ return new Promise((resolve2) => {
2480
+ const done = () => resolve2();
2138
2481
  img.addEventListener("load", done, { once: true });
2139
2482
  img.addEventListener("error", done, { once: true });
2140
2483
  });
2141
2484
  }));
2142
2485
  await Promise.race([
2143
2486
  Promise.all([waitForFonts, waitForImages]),
2144
- new Promise((resolve) => window.setTimeout(resolve, timeoutMs))
2487
+ new Promise((resolve2) => window.setTimeout(resolve2, timeoutMs))
2145
2488
  ]);
2146
2489
  }, ASSET_WAIT_TIMEOUT_MS).catch((error) => {
2147
2490
  console.warn(` Warning: could not verify above-fold assets: ${error instanceof Error ? error.message : String(error)}`);
@@ -2164,7 +2507,7 @@ async function resetScrollForFullPageCapture(page) {
2164
2507
  window.dispatchEvent(new Event("scroll"));
2165
2508
  });
2166
2509
  await page.waitForFunction(() => Math.abs(window.scrollY) < 1, void 0, { timeout: 1e3 }).catch(() => void 0);
2167
- await page.evaluate(() => new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve)))).catch(() => void 0);
2510
+ await page.evaluate(() => new Promise((resolve2) => requestAnimationFrame(() => requestAnimationFrame(resolve2)))).catch(() => void 0);
2168
2511
  await page.waitForTimeout(350);
2169
2512
  await waitForAboveFoldAssets(page);
2170
2513
  }
@@ -2207,7 +2550,7 @@ async function withFullPageCaptureLayout(page, capture) {
2207
2550
  window.scrollTo(0, 0);
2208
2551
  window.dispatchEvent(new Event("scroll"));
2209
2552
  });
2210
- await page.evaluate(() => new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve)))).catch(() => void 0);
2553
+ await page.evaluate(() => new Promise((resolve2) => requestAnimationFrame(() => requestAnimationFrame(resolve2)))).catch(() => void 0);
2211
2554
  try {
2212
2555
  return await capture();
2213
2556
  } finally {
@@ -2246,27 +2589,27 @@ var SECTION_SELECTORS = {
2246
2589
  cta: '[class*="cta"], [data-section-type="cta"], .call-to-action'
2247
2590
  };
2248
2591
  function loadPagesConfig(targetDir) {
2249
- const configPath = path4.join(targetDir, ".dcs", "pages.yaml");
2250
- if (!fs4.existsSync(configPath)) {
2592
+ const configPath = path5.join(targetDir, ".dcs", "pages.yaml");
2593
+ if (!fs7.existsSync(configPath)) {
2251
2594
  throw new Error(`Configuration file not found: ${configPath}`);
2252
2595
  }
2253
- const content = fs4.readFileSync(configPath, "utf-8");
2596
+ const content = fs7.readFileSync(configPath, "utf-8");
2254
2597
  return yaml3.load(content);
2255
2598
  }
2256
2599
  function loadContentConfig(targetDir) {
2257
- const contentPath = path4.join(targetDir, ".dcs", "content.yaml");
2258
- if (!fs4.existsSync(contentPath)) {
2600
+ const contentPath = path5.join(targetDir, ".dcs", "content.yaml");
2601
+ if (!fs7.existsSync(contentPath)) {
2259
2602
  return null;
2260
2603
  }
2261
- const content = fs4.readFileSync(contentPath, "utf-8");
2604
+ const content = fs7.readFileSync(contentPath, "utf-8");
2262
2605
  return yaml3.load(content);
2263
2606
  }
2264
2607
  function loadTargetedSnapshotsConfig(targetDir) {
2265
- const targetedPath = path4.join(targetDir, ".dcs", "targeted-snapshots.yaml");
2266
- if (!fs4.existsSync(targetedPath)) {
2608
+ const targetedPath = path5.join(targetDir, ".dcs", "targeted-snapshots.yaml");
2609
+ if (!fs7.existsSync(targetedPath)) {
2267
2610
  return null;
2268
2611
  }
2269
- const content = fs4.readFileSync(targetedPath, "utf-8");
2612
+ const content = fs7.readFileSync(targetedPath, "utf-8");
2270
2613
  return yaml3.load(content);
2271
2614
  }
2272
2615
  function getValidTextKeysForPage(contentConfig, pageSlug) {
@@ -2286,27 +2629,27 @@ function getValidTextKeysForPage(contentConfig, pageSlug) {
2286
2629
  }
2287
2630
  return keys;
2288
2631
  }
2289
- async function resolveAllPages(config2) {
2290
- const resolved = [];
2291
- for (const page of config2.pages) {
2632
+ async function resolveAllPages(config) {
2633
+ const resolved2 = [];
2634
+ for (const page of config.pages) {
2292
2635
  if (page.type === "dynamic" && page.instances) {
2293
2636
  for (const instance of page.instances) {
2294
2637
  const instancePath = page.pathTemplate ? page.pathTemplate.replace(/:[\w]+/, instance) : `${page.path}/${instance}`;
2295
- resolved.push({
2638
+ resolved2.push({
2296
2639
  slug: `${page.slug}-${instance}`,
2297
2640
  path: instancePath,
2298
2641
  config: { ...page, slug: `${page.slug}-${instance}` }
2299
2642
  });
2300
2643
  }
2301
2644
  } else {
2302
- resolved.push({
2645
+ resolved2.push({
2303
2646
  slug: page.slug,
2304
2647
  path: page.path,
2305
2648
  config: page
2306
2649
  });
2307
2650
  }
2308
2651
  }
2309
- return resolved;
2652
+ return resolved2;
2310
2653
  }
2311
2654
  async function getDomPath(element) {
2312
2655
  return element.evaluate((el) => {
@@ -2434,12 +2777,12 @@ async function capturePageSnapshot(browser, pageInfo, snapshotConfig, siteSlug,
2434
2777
  }
2435
2778
  await page.waitForTimeout(snapshotConfig.waitAfterLoad);
2436
2779
  await waitForAboveFoldAssets(page);
2437
- const pageOutputDir = path4.join(outputDir, siteSlug, pageInfo.slug);
2438
- fs4.mkdirSync(path4.join(pageOutputDir, "sections"), { recursive: true });
2780
+ const pageOutputDir = path5.join(outputDir, siteSlug, pageInfo.slug);
2781
+ fs7.mkdirSync(path5.join(pageOutputDir, "sections"), { recursive: true });
2439
2782
  const title = await page.title();
2440
- let pageHeight = await lazyLoadPage(page, snapshotConfig.viewport.height);
2783
+ const pageHeight = await lazyLoadPage(page, snapshotConfig.viewport.height);
2441
2784
  await resetScrollForFullPageCapture(page);
2442
- const fullPagePath = path4.join(pageOutputDir, "full-page.png");
2785
+ const fullPagePath = path5.join(pageOutputDir, "full-page.png");
2443
2786
  await withFullPageCaptureLayout(
2444
2787
  page,
2445
2788
  () => page.screenshot({
@@ -2447,7 +2790,7 @@ async function capturePageSnapshot(browser, pageInfo, snapshotConfig, siteSlug,
2447
2790
  fullPage: snapshotConfig.captureFullPage
2448
2791
  })
2449
2792
  );
2450
- const thumbnailPath = path4.join(pageOutputDir, "thumbnail.png");
2793
+ const thumbnailPath = path5.join(pageOutputDir, "thumbnail.png");
2451
2794
  await captureHeaderThumbnail(page, thumbnailPath, snapshotConfig.viewport);
2452
2795
  await page.evaluate(() => window.scrollTo(0, 0));
2453
2796
  await page.waitForTimeout(100);
@@ -2501,7 +2844,7 @@ async function capturePageSnapshot(browser, pageInfo, snapshotConfig, siteSlug,
2501
2844
  const bounds = await getAccurateBounds(element, resolvedType);
2502
2845
  if (!bounds || bounds.height < 10) continue;
2503
2846
  const sectionId = basicMetadata.sectionId || `section-${i}`;
2504
- const sectionPath = path4.join(pageOutputDir, "sections", `${sectionId}.png`);
2847
+ const sectionPath = path5.join(pageOutputDir, "sections", `${sectionId}.png`);
2505
2848
  try {
2506
2849
  await element.screenshot({ path: sectionPath });
2507
2850
  } catch {
@@ -2572,7 +2915,7 @@ async function capturePageSnapshot(browser, pageInfo, snapshotConfig, siteSlug,
2572
2915
  const bounds = await getAccurateBounds(element, sectionType);
2573
2916
  if (!bounds || bounds.height < 10) continue;
2574
2917
  const sectionId = `${sectionType}-${i}`;
2575
- const sectionPath = path4.join(pageOutputDir, "sections", `${sectionId}.png`);
2918
+ const sectionPath = path5.join(pageOutputDir, "sections", `${sectionId}.png`);
2576
2919
  try {
2577
2920
  await element.screenshot({ path: sectionPath });
2578
2921
  } catch {
@@ -2625,7 +2968,7 @@ async function capturePageSnapshot(browser, pageInfo, snapshotConfig, siteSlug,
2625
2968
  });
2626
2969
  if (!bounds || bounds.height < 10) continue;
2627
2970
  const sectionId = `content-fallback-${i}`;
2628
- const sectionPath = path4.join(pageOutputDir, "sections", `${sectionId}.png`);
2971
+ const sectionPath = path5.join(pageOutputDir, "sections", `${sectionId}.png`);
2629
2972
  try {
2630
2973
  await element.screenshot({ path: sectionPath });
2631
2974
  } catch {
@@ -2672,22 +3015,22 @@ async function capturePageSnapshot(browser, pageInfo, snapshotConfig, siteSlug,
2672
3015
  }
2673
3016
  async function captureSnapshotsCommand(options) {
2674
3017
  const { target, baseUrl, dryRun, verbose, pages: targetedPages } = options;
2675
- const targetDir = path4.resolve(target);
3018
+ const targetDir = path5.resolve(target);
2676
3019
  console.log(chalk7.blue("\u{1F4F8} Page Snapshot Capture"));
2677
3020
  console.log(chalk7.gray("========================"));
2678
3021
  console.log(chalk7.gray(`Target: ${targetDir}`));
2679
3022
  console.log(chalk7.gray(`Base URL: ${baseUrl}`));
2680
3023
  process.env.SITE_BASE_URL = baseUrl;
2681
- let config2;
3024
+ let config;
2682
3025
  try {
2683
- config2 = loadPagesConfig(targetDir);
3026
+ config = loadPagesConfig(targetDir);
2684
3027
  } catch (error) {
2685
3028
  console.error(chalk7.red("Error:"), error.message);
2686
3029
  console.log(chalk7.yellow("\nMake sure .dcs/pages.yaml exists in the target directory."));
2687
3030
  process.exit(1);
2688
3031
  }
2689
- console.log(chalk7.gray(`Site: ${config2.siteSlug}`));
2690
- const outputDir = path4.join(targetDir, config2.snapshot.outputDir || ".dcs/snapshots");
3032
+ console.log(chalk7.gray(`Site: ${config.siteSlug}`));
3033
+ const outputDir = path5.join(targetDir, config.snapshot.outputDir || ".dcs/snapshots");
2691
3034
  const contentConfig = loadContentConfig(targetDir);
2692
3035
  if (contentConfig) {
2693
3036
  const pageCount = Object.keys(contentConfig.pages || {}).length;
@@ -2704,19 +3047,19 @@ async function captureSnapshotsCommand(options) {
2704
3047
  console.log(chalk7.gray(` Reason: ${targetedConfig.reason}`));
2705
3048
  console.log(chalk7.gray(" No snapshots will be captured."));
2706
3049
  if (!dryRun) {
2707
- fs4.mkdirSync(outputDir, { recursive: true });
3050
+ fs7.mkdirSync(outputDir, { recursive: true });
2708
3051
  const skipMarker = {
2709
3052
  skipped: true,
2710
3053
  reason: targetedConfig.reason,
2711
3054
  triggeredBy: targetedConfig.triggeredBy,
2712
3055
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
2713
3056
  };
2714
- fs4.writeFileSync(path4.join(outputDir, "skipped.json"), JSON.stringify(skipMarker, null, 2));
3057
+ fs7.writeFileSync(path5.join(outputDir, "skipped.json"), JSON.stringify(skipMarker, null, 2));
2715
3058
  }
2716
3059
  console.log(chalk7.green("\n\u2705 Snapshot capture skipped (as requested)"));
2717
3060
  return;
2718
3061
  }
2719
- let pagesToCapture = await resolveAllPages(config2);
3062
+ let pagesToCapture = await resolveAllPages(config);
2720
3063
  console.log(chalk7.gray(`Found ${pagesToCapture.length} total pages in configuration`));
2721
3064
  if (targetedPages && targetedPages.length > 0) {
2722
3065
  const targetedSlugs = new Set(targetedPages);
@@ -2746,10 +3089,10 @@ async function captureSnapshotsCommand(options) {
2746
3089
  return;
2747
3090
  }
2748
3091
  const { chromium } = await import("playwright");
2749
- if (fs4.existsSync(outputDir)) {
2750
- fs4.rmSync(outputDir, { recursive: true });
3092
+ if (fs7.existsSync(outputDir)) {
3093
+ fs7.rmSync(outputDir, { recursive: true });
2751
3094
  }
2752
- fs4.mkdirSync(outputDir, { recursive: true });
3095
+ fs7.mkdirSync(outputDir, { recursive: true });
2753
3096
  const spinner = ora3("Launching browser...").start();
2754
3097
  const browser = await chromium.launch();
2755
3098
  spinner.succeed("Browser launched");
@@ -2762,15 +3105,15 @@ async function captureSnapshotsCommand(options) {
2762
3105
  const snapshot = await capturePageSnapshot(
2763
3106
  browser,
2764
3107
  pageInfo,
2765
- config2.snapshot,
2766
- config2.siteSlug,
3108
+ config.snapshot,
3109
+ config.siteSlug,
2767
3110
  validTextKeys,
2768
3111
  outputDir,
2769
3112
  verbose || false
2770
3113
  );
2771
3114
  snapshots.push(snapshot);
2772
- const snapshotPath = path4.join(outputDir, config2.siteSlug, pageInfo.slug, "snapshot.json");
2773
- fs4.writeFileSync(snapshotPath, JSON.stringify(snapshot, null, 2));
3115
+ const snapshotPath = path5.join(outputDir, config.siteSlug, pageInfo.slug, "snapshot.json");
3116
+ fs7.writeFileSync(snapshotPath, JSON.stringify(snapshot, null, 2));
2774
3117
  pageSpinner.succeed(`${pageInfo.slug}: ${snapshot.sections.length} sections captured`);
2775
3118
  } catch (error) {
2776
3119
  const message = `${pageInfo.slug}: ${error.message}`;
@@ -2780,11 +3123,11 @@ async function captureSnapshotsCommand(options) {
2780
3123
  }
2781
3124
  await browser.close();
2782
3125
  const manifest = {
2783
- siteSlug: config2.siteSlug,
3126
+ siteSlug: config.siteSlug,
2784
3127
  capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
2785
3128
  capturedVersion: process.env.GITHUB_SHA,
2786
3129
  deploymentRef: process.env.GITHUB_REF || "local",
2787
- pagesConfigVersion: config2.version,
3130
+ pagesConfigVersion: config.version,
2788
3131
  pages: snapshots.map((s) => ({
2789
3132
  pageSlug: s.pageSlug,
2790
3133
  pagePath: s.path,
@@ -2796,11 +3139,11 @@ async function captureSnapshotsCommand(options) {
2796
3139
  hasSnapshot: true
2797
3140
  }))
2798
3141
  };
2799
- const manifestPath = path4.join(outputDir, config2.siteSlug, "manifest.json");
3142
+ const manifestPath = path5.join(outputDir, config.siteSlug, "manifest.json");
2800
3143
  if (isTargetedCapture) {
2801
3144
  console.log(chalk7.gray(" Targeted capture: leaving the existing remote manifest intact"));
2802
3145
  } else {
2803
- fs4.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
3146
+ fs7.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
2804
3147
  }
2805
3148
  console.log("");
2806
3149
  console.log(chalk7.green("\u2705 Snapshot capture complete!"));