@duffcloudservices/cli 0.3.1 → 0.3.3

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.1";
8
+ var version = "0.3.3";
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;
32
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);
33
267
  }
34
- });
35
- function storeCredentials(credentials) {
36
- config.set("auth", credentials);
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 {
279
+ }
280
+ }
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
 
@@ -638,7 +975,7 @@ jobs:
638
975
  // src/commands/init.ts
639
976
  async function initCommand(options) {
640
977
  const { siteSlug, siteName, framework = "vue", target = ".", dryRun = false, force = false } = options;
641
- if (!isAuthenticated()) {
978
+ if (!hasStoredSession()) {
642
979
  console.log(chalk4.yellow("Not logged in."));
643
980
  console.log("Run", chalk4.cyan("dcs login"), "to authenticate.");
644
981
  process.exit(1);
@@ -669,7 +1006,7 @@ async function initCommand(options) {
669
1006
  }
670
1007
  process.exit(1);
671
1008
  }
672
- const targetDir = path.resolve(target);
1009
+ const targetDir = path2.resolve(target);
673
1010
  console.log(chalk4.dim(`Target directory: ${targetDir}`));
674
1011
  const generatedFiles = [];
675
1012
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
@@ -721,21 +1058,21 @@ async function initCommand(options) {
721
1058
  console.log();
722
1059
  }
723
1060
  async function writeFile(targetDir, relativePath, content, options) {
724
- const fullPath = path.join(targetDir, relativePath);
1061
+ const fullPath = path2.join(targetDir, relativePath);
725
1062
  if (options.dryRun) {
726
1063
  console.log(chalk4.dim(`Would create: ${relativePath}`));
727
1064
  return relativePath;
728
1065
  }
729
1066
  try {
730
- await fs.access(fullPath);
1067
+ await fs4.access(fullPath);
731
1068
  if (!options.force) {
732
1069
  console.log(chalk4.yellow(`Skipped (exists): ${relativePath}`));
733
1070
  return relativePath;
734
1071
  }
735
1072
  } catch {
736
1073
  }
737
- await fs.mkdir(path.dirname(fullPath), { recursive: true });
738
- await fs.writeFile(fullPath, content, "utf8");
1074
+ await fs4.mkdir(path2.dirname(fullPath), { recursive: true });
1075
+ await fs4.writeFile(fullPath, content, "utf8");
739
1076
  return relativePath;
740
1077
  }
741
1078
  function generateSiteYaml(data) {
@@ -1346,19 +1683,19 @@ After completing this plan:
1346
1683
  }
1347
1684
 
1348
1685
  // src/commands/validate.ts
1349
- import fs2 from "fs/promises";
1350
- import path2 from "path";
1686
+ import fs5 from "fs/promises";
1687
+ import path3 from "path";
1351
1688
  import chalk5 from "chalk";
1352
1689
  import yaml from "js-yaml";
1353
1690
  async function validateCommand(options) {
1354
1691
  const { target = ".", fix = false, verbose = false } = options;
1355
- const targetDir = path2.resolve(target);
1356
- const dcsDir = path2.join(targetDir, ".dcs");
1692
+ const targetDir = path3.resolve(target);
1693
+ const dcsDir = path3.join(targetDir, ".dcs");
1357
1694
  console.log(chalk5.bold("Validating DCS configuration..."));
1358
1695
  console.log(chalk5.dim(`Directory: ${targetDir}`));
1359
1696
  console.log();
1360
1697
  try {
1361
- await fs2.access(dcsDir);
1698
+ await fs5.access(dcsDir);
1362
1699
  } catch {
1363
1700
  console.log(chalk5.red("No .dcs directory found."));
1364
1701
  console.log("Run", chalk5.cyan("dcs init"), "to create DCS configuration.");
@@ -1404,7 +1741,7 @@ async function validateCommand(options) {
1404
1741
  }
1405
1742
  }
1406
1743
  async function validateSiteYaml(dcsDir, verbose) {
1407
- const filePath = path2.join(dcsDir, "site.yaml");
1744
+ const filePath = path3.join(dcsDir, "site.yaml");
1408
1745
  const result = {
1409
1746
  file: ".dcs/site.yaml",
1410
1747
  valid: true,
@@ -1412,28 +1749,28 @@ async function validateSiteYaml(dcsDir, verbose) {
1412
1749
  warnings: []
1413
1750
  };
1414
1751
  try {
1415
- const content = await fs2.readFile(filePath, "utf8");
1416
- const config2 = yaml.load(content);
1417
- if (!config2.site_name) {
1752
+ const content = await fs5.readFile(filePath, "utf8");
1753
+ const config = yaml.load(content);
1754
+ if (!config.site_name) {
1418
1755
  result.errors.push("Missing required field: site_name");
1419
1756
  result.valid = false;
1420
1757
  }
1421
- if (!config2.site_slug) {
1758
+ if (!config.site_slug) {
1422
1759
  result.errors.push("Missing required field: site_slug");
1423
1760
  result.valid = false;
1424
- } else if (!/^[a-z0-9-]+$/.test(config2.site_slug)) {
1761
+ } else if (!/^[a-z0-9-]+$/.test(config.site_slug)) {
1425
1762
  result.errors.push("site_slug must be lowercase alphanumeric with hyphens only");
1426
1763
  result.valid = false;
1427
1764
  }
1428
- if (!config2.swa_resource_id) {
1765
+ if (!config.swa_resource_id) {
1429
1766
  result.warnings.push("swa_resource_id is not set (required for deployment)");
1430
1767
  }
1431
- if (!config2.production_url) {
1768
+ if (!config.production_url) {
1432
1769
  result.warnings.push("production_url is not set");
1433
1770
  }
1434
1771
  if (verbose) {
1435
- console.log(chalk5.dim(` site_name: ${config2.site_name}`));
1436
- console.log(chalk5.dim(` site_slug: ${config2.site_slug}`));
1772
+ console.log(chalk5.dim(` site_name: ${config.site_name}`));
1773
+ console.log(chalk5.dim(` site_slug: ${config.site_slug}`));
1437
1774
  }
1438
1775
  } catch (error) {
1439
1776
  if (error.code === "ENOENT") {
@@ -1446,7 +1783,7 @@ async function validateSiteYaml(dcsDir, verbose) {
1446
1783
  return result;
1447
1784
  }
1448
1785
  async function validatePagesYaml(dcsDir, verbose) {
1449
- const filePath = path2.join(dcsDir, "pages.yaml");
1786
+ const filePath = path3.join(dcsDir, "pages.yaml");
1450
1787
  const result = {
1451
1788
  file: ".dcs/pages.yaml",
1452
1789
  valid: true,
@@ -1454,17 +1791,17 @@ async function validatePagesYaml(dcsDir, verbose) {
1454
1791
  warnings: []
1455
1792
  };
1456
1793
  try {
1457
- const content = await fs2.readFile(filePath, "utf8");
1458
- const config2 = yaml.load(content);
1459
- if (config2.version === void 0) {
1794
+ const content = await fs5.readFile(filePath, "utf8");
1795
+ const config = yaml.load(content);
1796
+ if (config.version === void 0) {
1460
1797
  result.errors.push("Missing required field: version");
1461
1798
  result.valid = false;
1462
1799
  }
1463
- if (!config2.siteSlug) {
1800
+ if (!config.siteSlug) {
1464
1801
  result.errors.push("Missing required field: siteSlug");
1465
1802
  result.valid = false;
1466
1803
  }
1467
- const pages = config2.pages;
1804
+ const pages = config.pages;
1468
1805
  if (!pages || !Array.isArray(pages)) {
1469
1806
  result.errors.push("Missing or invalid pages array");
1470
1807
  result.valid = false;
@@ -1502,7 +1839,7 @@ async function validatePagesYaml(dcsDir, verbose) {
1502
1839
  return result;
1503
1840
  }
1504
1841
  async function validateContentYaml(dcsDir, verbose) {
1505
- const filePath = path2.join(dcsDir, "content.yaml");
1842
+ const filePath = path3.join(dcsDir, "content.yaml");
1506
1843
  const result = {
1507
1844
  file: ".dcs/content.yaml",
1508
1845
  valid: true,
@@ -1510,23 +1847,23 @@ async function validateContentYaml(dcsDir, verbose) {
1510
1847
  warnings: []
1511
1848
  };
1512
1849
  try {
1513
- const content = await fs2.readFile(filePath, "utf8");
1514
- const config2 = yaml.load(content);
1515
- if (config2.version === void 0) {
1850
+ const content = await fs5.readFile(filePath, "utf8");
1851
+ const config = yaml.load(content);
1852
+ if (config.version === void 0) {
1516
1853
  result.errors.push("Missing required field: version");
1517
1854
  result.valid = false;
1518
1855
  }
1519
- if (config2.global !== void 0 && typeof config2.global !== "object") {
1856
+ if (config.global !== void 0 && typeof config.global !== "object") {
1520
1857
  result.errors.push("global must be an object");
1521
1858
  result.valid = false;
1522
1859
  }
1523
- if (config2.pages !== void 0 && typeof config2.pages !== "object") {
1860
+ if (config.pages !== void 0 && typeof config.pages !== "object") {
1524
1861
  result.errors.push("pages must be an object");
1525
1862
  result.valid = false;
1526
1863
  }
1527
1864
  if (verbose) {
1528
- const globalKeys = config2.global ? Object.keys(config2.global).length : 0;
1529
- const pageCount = config2.pages ? Object.keys(config2.pages).length : 0;
1865
+ const globalKeys = config.global ? Object.keys(config.global).length : 0;
1866
+ const pageCount = config.pages ? Object.keys(config.pages).length : 0;
1530
1867
  console.log(chalk5.dim(` ${globalKeys} global keys, ${pageCount} page sections`));
1531
1868
  }
1532
1869
  } catch (error) {
@@ -1540,7 +1877,7 @@ async function validateContentYaml(dcsDir, verbose) {
1540
1877
  return result;
1541
1878
  }
1542
1879
  async function validateSeoYaml(dcsDir, verbose) {
1543
- const filePath = path2.join(dcsDir, "seo.yaml");
1880
+ const filePath = path3.join(dcsDir, "seo.yaml");
1544
1881
  const result = {
1545
1882
  file: ".dcs/seo.yaml",
1546
1883
  valid: true,
@@ -1548,13 +1885,13 @@ async function validateSeoYaml(dcsDir, verbose) {
1548
1885
  warnings: []
1549
1886
  };
1550
1887
  try {
1551
- const content = await fs2.readFile(filePath, "utf8");
1552
- const config2 = yaml.load(content);
1553
- if (config2.version === void 0) {
1888
+ const content = await fs5.readFile(filePath, "utf8");
1889
+ const config = yaml.load(content);
1890
+ if (config.version === void 0) {
1554
1891
  result.errors.push("Missing required field: version");
1555
1892
  result.valid = false;
1556
1893
  }
1557
- const global = config2.global;
1894
+ const global = config.global;
1558
1895
  if (!global) {
1559
1896
  result.errors.push("Missing required section: global");
1560
1897
  result.valid = false;
@@ -1569,7 +1906,7 @@ async function validateSeoYaml(dcsDir, verbose) {
1569
1906
  result.warnings.push("global.defaultDescription is not set");
1570
1907
  }
1571
1908
  }
1572
- const pages = config2.pages;
1909
+ const pages = config.pages;
1573
1910
  if (pages && typeof pages === "object") {
1574
1911
  const pageKeys = Object.keys(pages);
1575
1912
  if (!pageKeys.includes("home")) {
@@ -1591,20 +1928,20 @@ async function validateSeoYaml(dcsDir, verbose) {
1591
1928
  }
1592
1929
 
1593
1930
  // src/commands/plans.ts
1594
- import fs3 from "fs/promises";
1595
- import path3 from "path";
1931
+ import fs6 from "fs/promises";
1932
+ import path4 from "path";
1596
1933
  import chalk6 from "chalk";
1597
1934
  import yaml2 from "js-yaml";
1598
1935
  async function plansCommand(options) {
1599
1936
  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");
1937
+ const targetDir = path4.resolve(target);
1938
+ const dcsDir = path4.join(targetDir, ".dcs");
1939
+ const plansDir = path4.join(targetDir, ".plans");
1603
1940
  console.log(chalk6.bold("Generating DCS integration plans..."));
1604
1941
  console.log(chalk6.dim(`Directory: ${targetDir}`));
1605
1942
  console.log();
1606
1943
  try {
1607
- await fs3.access(dcsDir);
1944
+ await fs6.access(dcsDir);
1608
1945
  } catch {
1609
1946
  console.log(chalk6.red("No .dcs directory found."));
1610
1947
  console.log("Run", chalk6.cyan("dcs init"), "first to create DCS configuration.");
@@ -1613,7 +1950,7 @@ async function plansCommand(options) {
1613
1950
  let siteName = "Customer Site";
1614
1951
  let framework = "vue";
1615
1952
  try {
1616
- const siteYaml = await fs3.readFile(path3.join(dcsDir, "site.yaml"), "utf8");
1953
+ const siteYaml = await fs6.readFile(path4.join(dcsDir, "site.yaml"), "utf8");
1617
1954
  const siteConfig = yaml2.load(siteYaml);
1618
1955
  siteName = siteConfig.site_name || siteName;
1619
1956
  const metadata = siteConfig.metadata;
@@ -1629,19 +1966,19 @@ async function plansCommand(options) {
1629
1966
  { name: "04-capture-snapshots.md", content: generateSnapshotsPlan(siteName) },
1630
1967
  { name: "05-verify-deployment.md", content: generateVerifyPlan(siteName) }
1631
1968
  ];
1632
- await fs3.mkdir(plansDir, { recursive: true });
1969
+ await fs6.mkdir(plansDir, { recursive: true });
1633
1970
  const generatedFiles = [];
1634
1971
  for (const plan of plans) {
1635
- const filePath = path3.join(plansDir, plan.name);
1972
+ const filePath = path4.join(plansDir, plan.name);
1636
1973
  try {
1637
- await fs3.access(filePath);
1974
+ await fs6.access(filePath);
1638
1975
  if (!force) {
1639
1976
  console.log(chalk6.yellow(`Skipped (exists): .plans/${plan.name}`));
1640
1977
  continue;
1641
1978
  }
1642
1979
  } catch {
1643
1980
  }
1644
- await fs3.writeFile(filePath, plan.content, "utf8");
1981
+ await fs6.writeFile(filePath, plan.content, "utf8");
1645
1982
  generatedFiles.push(plan.name);
1646
1983
  console.log(`${chalk6.green("\u2713")} .plans/${plan.name}`);
1647
1984
  }
@@ -2100,8 +2437,8 @@ Once verified:
2100
2437
  }
2101
2438
 
2102
2439
  // src/commands/capture-snapshots.ts
2103
- import fs4 from "fs";
2104
- import path4 from "path";
2440
+ import fs7 from "fs";
2441
+ import path5 from "path";
2105
2442
  import yaml3 from "js-yaml";
2106
2443
  import chalk7 from "chalk";
2107
2444
  import ora3 from "ora";
@@ -2133,15 +2470,15 @@ async function waitForAboveFoldAssets(page) {
2133
2470
  if (img.complete && img.naturalWidth > 0) {
2134
2471
  return Promise.resolve();
2135
2472
  }
2136
- return new Promise((resolve) => {
2137
- const done = () => resolve();
2473
+ return new Promise((resolve2) => {
2474
+ const done = () => resolve2();
2138
2475
  img.addEventListener("load", done, { once: true });
2139
2476
  img.addEventListener("error", done, { once: true });
2140
2477
  });
2141
2478
  }));
2142
2479
  await Promise.race([
2143
2480
  Promise.all([waitForFonts, waitForImages]),
2144
- new Promise((resolve) => window.setTimeout(resolve, timeoutMs))
2481
+ new Promise((resolve2) => window.setTimeout(resolve2, timeoutMs))
2145
2482
  ]);
2146
2483
  }, ASSET_WAIT_TIMEOUT_MS).catch((error) => {
2147
2484
  console.warn(` Warning: could not verify above-fold assets: ${error instanceof Error ? error.message : String(error)}`);
@@ -2164,7 +2501,7 @@ async function resetScrollForFullPageCapture(page) {
2164
2501
  window.dispatchEvent(new Event("scroll"));
2165
2502
  });
2166
2503
  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);
2504
+ await page.evaluate(() => new Promise((resolve2) => requestAnimationFrame(() => requestAnimationFrame(resolve2)))).catch(() => void 0);
2168
2505
  await page.waitForTimeout(350);
2169
2506
  await waitForAboveFoldAssets(page);
2170
2507
  }
@@ -2207,7 +2544,7 @@ async function withFullPageCaptureLayout(page, capture) {
2207
2544
  window.scrollTo(0, 0);
2208
2545
  window.dispatchEvent(new Event("scroll"));
2209
2546
  });
2210
- await page.evaluate(() => new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve)))).catch(() => void 0);
2547
+ await page.evaluate(() => new Promise((resolve2) => requestAnimationFrame(() => requestAnimationFrame(resolve2)))).catch(() => void 0);
2211
2548
  try {
2212
2549
  return await capture();
2213
2550
  } finally {
@@ -2246,27 +2583,27 @@ var SECTION_SELECTORS = {
2246
2583
  cta: '[class*="cta"], [data-section-type="cta"], .call-to-action'
2247
2584
  };
2248
2585
  function loadPagesConfig(targetDir) {
2249
- const configPath = path4.join(targetDir, ".dcs", "pages.yaml");
2250
- if (!fs4.existsSync(configPath)) {
2586
+ const configPath = path5.join(targetDir, ".dcs", "pages.yaml");
2587
+ if (!fs7.existsSync(configPath)) {
2251
2588
  throw new Error(`Configuration file not found: ${configPath}`);
2252
2589
  }
2253
- const content = fs4.readFileSync(configPath, "utf-8");
2590
+ const content = fs7.readFileSync(configPath, "utf-8");
2254
2591
  return yaml3.load(content);
2255
2592
  }
2256
2593
  function loadContentConfig(targetDir) {
2257
- const contentPath = path4.join(targetDir, ".dcs", "content.yaml");
2258
- if (!fs4.existsSync(contentPath)) {
2594
+ const contentPath = path5.join(targetDir, ".dcs", "content.yaml");
2595
+ if (!fs7.existsSync(contentPath)) {
2259
2596
  return null;
2260
2597
  }
2261
- const content = fs4.readFileSync(contentPath, "utf-8");
2598
+ const content = fs7.readFileSync(contentPath, "utf-8");
2262
2599
  return yaml3.load(content);
2263
2600
  }
2264
2601
  function loadTargetedSnapshotsConfig(targetDir) {
2265
- const targetedPath = path4.join(targetDir, ".dcs", "targeted-snapshots.yaml");
2266
- if (!fs4.existsSync(targetedPath)) {
2602
+ const targetedPath = path5.join(targetDir, ".dcs", "targeted-snapshots.yaml");
2603
+ if (!fs7.existsSync(targetedPath)) {
2267
2604
  return null;
2268
2605
  }
2269
- const content = fs4.readFileSync(targetedPath, "utf-8");
2606
+ const content = fs7.readFileSync(targetedPath, "utf-8");
2270
2607
  return yaml3.load(content);
2271
2608
  }
2272
2609
  function getValidTextKeysForPage(contentConfig, pageSlug) {
@@ -2286,27 +2623,27 @@ function getValidTextKeysForPage(contentConfig, pageSlug) {
2286
2623
  }
2287
2624
  return keys;
2288
2625
  }
2289
- async function resolveAllPages(config2) {
2290
- const resolved = [];
2291
- for (const page of config2.pages) {
2626
+ async function resolveAllPages(config) {
2627
+ const resolved2 = [];
2628
+ for (const page of config.pages) {
2292
2629
  if (page.type === "dynamic" && page.instances) {
2293
2630
  for (const instance of page.instances) {
2294
2631
  const instancePath = page.pathTemplate ? page.pathTemplate.replace(/:[\w]+/, instance) : `${page.path}/${instance}`;
2295
- resolved.push({
2632
+ resolved2.push({
2296
2633
  slug: `${page.slug}-${instance}`,
2297
2634
  path: instancePath,
2298
2635
  config: { ...page, slug: `${page.slug}-${instance}` }
2299
2636
  });
2300
2637
  }
2301
2638
  } else {
2302
- resolved.push({
2639
+ resolved2.push({
2303
2640
  slug: page.slug,
2304
2641
  path: page.path,
2305
2642
  config: page
2306
2643
  });
2307
2644
  }
2308
2645
  }
2309
- return resolved;
2646
+ return resolved2;
2310
2647
  }
2311
2648
  async function getDomPath(element) {
2312
2649
  return element.evaluate((el) => {
@@ -2434,12 +2771,12 @@ async function capturePageSnapshot(browser, pageInfo, snapshotConfig, siteSlug,
2434
2771
  }
2435
2772
  await page.waitForTimeout(snapshotConfig.waitAfterLoad);
2436
2773
  await waitForAboveFoldAssets(page);
2437
- const pageOutputDir = path4.join(outputDir, siteSlug, pageInfo.slug);
2438
- fs4.mkdirSync(path4.join(pageOutputDir, "sections"), { recursive: true });
2774
+ const pageOutputDir = path5.join(outputDir, siteSlug, pageInfo.slug);
2775
+ fs7.mkdirSync(path5.join(pageOutputDir, "sections"), { recursive: true });
2439
2776
  const title = await page.title();
2440
2777
  let pageHeight = await lazyLoadPage(page, snapshotConfig.viewport.height);
2441
2778
  await resetScrollForFullPageCapture(page);
2442
- const fullPagePath = path4.join(pageOutputDir, "full-page.png");
2779
+ const fullPagePath = path5.join(pageOutputDir, "full-page.png");
2443
2780
  await withFullPageCaptureLayout(
2444
2781
  page,
2445
2782
  () => page.screenshot({
@@ -2447,7 +2784,7 @@ async function capturePageSnapshot(browser, pageInfo, snapshotConfig, siteSlug,
2447
2784
  fullPage: snapshotConfig.captureFullPage
2448
2785
  })
2449
2786
  );
2450
- const thumbnailPath = path4.join(pageOutputDir, "thumbnail.png");
2787
+ const thumbnailPath = path5.join(pageOutputDir, "thumbnail.png");
2451
2788
  await captureHeaderThumbnail(page, thumbnailPath, snapshotConfig.viewport);
2452
2789
  await page.evaluate(() => window.scrollTo(0, 0));
2453
2790
  await page.waitForTimeout(100);
@@ -2501,7 +2838,7 @@ async function capturePageSnapshot(browser, pageInfo, snapshotConfig, siteSlug,
2501
2838
  const bounds = await getAccurateBounds(element, resolvedType);
2502
2839
  if (!bounds || bounds.height < 10) continue;
2503
2840
  const sectionId = basicMetadata.sectionId || `section-${i}`;
2504
- const sectionPath = path4.join(pageOutputDir, "sections", `${sectionId}.png`);
2841
+ const sectionPath = path5.join(pageOutputDir, "sections", `${sectionId}.png`);
2505
2842
  try {
2506
2843
  await element.screenshot({ path: sectionPath });
2507
2844
  } catch {
@@ -2572,7 +2909,7 @@ async function capturePageSnapshot(browser, pageInfo, snapshotConfig, siteSlug,
2572
2909
  const bounds = await getAccurateBounds(element, sectionType);
2573
2910
  if (!bounds || bounds.height < 10) continue;
2574
2911
  const sectionId = `${sectionType}-${i}`;
2575
- const sectionPath = path4.join(pageOutputDir, "sections", `${sectionId}.png`);
2912
+ const sectionPath = path5.join(pageOutputDir, "sections", `${sectionId}.png`);
2576
2913
  try {
2577
2914
  await element.screenshot({ path: sectionPath });
2578
2915
  } catch {
@@ -2625,7 +2962,7 @@ async function capturePageSnapshot(browser, pageInfo, snapshotConfig, siteSlug,
2625
2962
  });
2626
2963
  if (!bounds || bounds.height < 10) continue;
2627
2964
  const sectionId = `content-fallback-${i}`;
2628
- const sectionPath = path4.join(pageOutputDir, "sections", `${sectionId}.png`);
2965
+ const sectionPath = path5.join(pageOutputDir, "sections", `${sectionId}.png`);
2629
2966
  try {
2630
2967
  await element.screenshot({ path: sectionPath });
2631
2968
  } catch {
@@ -2672,22 +3009,22 @@ async function capturePageSnapshot(browser, pageInfo, snapshotConfig, siteSlug,
2672
3009
  }
2673
3010
  async function captureSnapshotsCommand(options) {
2674
3011
  const { target, baseUrl, dryRun, verbose, pages: targetedPages } = options;
2675
- const targetDir = path4.resolve(target);
3012
+ const targetDir = path5.resolve(target);
2676
3013
  console.log(chalk7.blue("\u{1F4F8} Page Snapshot Capture"));
2677
3014
  console.log(chalk7.gray("========================"));
2678
3015
  console.log(chalk7.gray(`Target: ${targetDir}`));
2679
3016
  console.log(chalk7.gray(`Base URL: ${baseUrl}`));
2680
3017
  process.env.SITE_BASE_URL = baseUrl;
2681
- let config2;
3018
+ let config;
2682
3019
  try {
2683
- config2 = loadPagesConfig(targetDir);
3020
+ config = loadPagesConfig(targetDir);
2684
3021
  } catch (error) {
2685
3022
  console.error(chalk7.red("Error:"), error.message);
2686
3023
  console.log(chalk7.yellow("\nMake sure .dcs/pages.yaml exists in the target directory."));
2687
3024
  process.exit(1);
2688
3025
  }
2689
- console.log(chalk7.gray(`Site: ${config2.siteSlug}`));
2690
- const outputDir = path4.join(targetDir, config2.snapshot.outputDir || ".dcs/snapshots");
3026
+ console.log(chalk7.gray(`Site: ${config.siteSlug}`));
3027
+ const outputDir = path5.join(targetDir, config.snapshot.outputDir || ".dcs/snapshots");
2691
3028
  const contentConfig = loadContentConfig(targetDir);
2692
3029
  if (contentConfig) {
2693
3030
  const pageCount = Object.keys(contentConfig.pages || {}).length;
@@ -2704,19 +3041,19 @@ async function captureSnapshotsCommand(options) {
2704
3041
  console.log(chalk7.gray(` Reason: ${targetedConfig.reason}`));
2705
3042
  console.log(chalk7.gray(" No snapshots will be captured."));
2706
3043
  if (!dryRun) {
2707
- fs4.mkdirSync(outputDir, { recursive: true });
3044
+ fs7.mkdirSync(outputDir, { recursive: true });
2708
3045
  const skipMarker = {
2709
3046
  skipped: true,
2710
3047
  reason: targetedConfig.reason,
2711
3048
  triggeredBy: targetedConfig.triggeredBy,
2712
3049
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
2713
3050
  };
2714
- fs4.writeFileSync(path4.join(outputDir, "skipped.json"), JSON.stringify(skipMarker, null, 2));
3051
+ fs7.writeFileSync(path5.join(outputDir, "skipped.json"), JSON.stringify(skipMarker, null, 2));
2715
3052
  }
2716
3053
  console.log(chalk7.green("\n\u2705 Snapshot capture skipped (as requested)"));
2717
3054
  return;
2718
3055
  }
2719
- let pagesToCapture = await resolveAllPages(config2);
3056
+ let pagesToCapture = await resolveAllPages(config);
2720
3057
  console.log(chalk7.gray(`Found ${pagesToCapture.length} total pages in configuration`));
2721
3058
  if (targetedPages && targetedPages.length > 0) {
2722
3059
  const targetedSlugs = new Set(targetedPages);
@@ -2746,10 +3083,10 @@ async function captureSnapshotsCommand(options) {
2746
3083
  return;
2747
3084
  }
2748
3085
  const { chromium } = await import("playwright");
2749
- if (fs4.existsSync(outputDir)) {
2750
- fs4.rmSync(outputDir, { recursive: true });
3086
+ if (fs7.existsSync(outputDir)) {
3087
+ fs7.rmSync(outputDir, { recursive: true });
2751
3088
  }
2752
- fs4.mkdirSync(outputDir, { recursive: true });
3089
+ fs7.mkdirSync(outputDir, { recursive: true });
2753
3090
  const spinner = ora3("Launching browser...").start();
2754
3091
  const browser = await chromium.launch();
2755
3092
  spinner.succeed("Browser launched");
@@ -2762,15 +3099,15 @@ async function captureSnapshotsCommand(options) {
2762
3099
  const snapshot = await capturePageSnapshot(
2763
3100
  browser,
2764
3101
  pageInfo,
2765
- config2.snapshot,
2766
- config2.siteSlug,
3102
+ config.snapshot,
3103
+ config.siteSlug,
2767
3104
  validTextKeys,
2768
3105
  outputDir,
2769
3106
  verbose || false
2770
3107
  );
2771
3108
  snapshots.push(snapshot);
2772
- const snapshotPath = path4.join(outputDir, config2.siteSlug, pageInfo.slug, "snapshot.json");
2773
- fs4.writeFileSync(snapshotPath, JSON.stringify(snapshot, null, 2));
3109
+ const snapshotPath = path5.join(outputDir, config.siteSlug, pageInfo.slug, "snapshot.json");
3110
+ fs7.writeFileSync(snapshotPath, JSON.stringify(snapshot, null, 2));
2774
3111
  pageSpinner.succeed(`${pageInfo.slug}: ${snapshot.sections.length} sections captured`);
2775
3112
  } catch (error) {
2776
3113
  const message = `${pageInfo.slug}: ${error.message}`;
@@ -2780,11 +3117,11 @@ async function captureSnapshotsCommand(options) {
2780
3117
  }
2781
3118
  await browser.close();
2782
3119
  const manifest = {
2783
- siteSlug: config2.siteSlug,
3120
+ siteSlug: config.siteSlug,
2784
3121
  capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
2785
3122
  capturedVersion: process.env.GITHUB_SHA,
2786
3123
  deploymentRef: process.env.GITHUB_REF || "local",
2787
- pagesConfigVersion: config2.version,
3124
+ pagesConfigVersion: config.version,
2788
3125
  pages: snapshots.map((s) => ({
2789
3126
  pageSlug: s.pageSlug,
2790
3127
  pagePath: s.path,
@@ -2796,11 +3133,11 @@ async function captureSnapshotsCommand(options) {
2796
3133
  hasSnapshot: true
2797
3134
  }))
2798
3135
  };
2799
- const manifestPath = path4.join(outputDir, config2.siteSlug, "manifest.json");
3136
+ const manifestPath = path5.join(outputDir, config.siteSlug, "manifest.json");
2800
3137
  if (isTargetedCapture) {
2801
3138
  console.log(chalk7.gray(" Targeted capture: leaving the existing remote manifest intact"));
2802
3139
  } else {
2803
- fs4.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
3140
+ fs7.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
2804
3141
  }
2805
3142
  console.log("");
2806
3143
  console.log(chalk7.green("\u2705 Snapshot capture complete!"));