@duffcloudservices/cli 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/README.md +8 -0
- package/dist/index.js +126 -50
- package/dist/index.js.map +1 -1
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -42,6 +42,14 @@ dcs whoami
|
|
|
42
42
|
dcs logout
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
+
The CLI talks to the production portal by default (`config.portalApiUrl` in this
|
|
46
|
+
package's `package.json`). Set `DCS_API_URL` to point every command - login, token
|
|
47
|
+
refresh, logout and the API client - at another portal, for example a staging one:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
DCS_API_URL=https://portal-staging.example.test dcs login
|
|
51
|
+
```
|
|
52
|
+
|
|
45
53
|
### Site Management
|
|
46
54
|
|
|
47
55
|
```bash
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,10 @@ import { Command } from "commander";
|
|
|
5
5
|
import chalk8 from "chalk";
|
|
6
6
|
|
|
7
7
|
// package.json
|
|
8
|
-
var version = "0.4.
|
|
8
|
+
var version = "0.4.4";
|
|
9
|
+
var config = {
|
|
10
|
+
portalApiUrl: "https://portal.duffcloudservices.com"
|
|
11
|
+
};
|
|
9
12
|
|
|
10
13
|
// src/commands/auth.ts
|
|
11
14
|
import chalk2 from "chalk";
|
|
@@ -289,11 +292,11 @@ function getKeychain() {
|
|
|
289
292
|
// src/auth/credentials.ts
|
|
290
293
|
var ACCOUNT = "auth";
|
|
291
294
|
function defaultLegacyStore() {
|
|
292
|
-
const
|
|
295
|
+
const config2 = new Conf({ projectName: "dcs-cli" });
|
|
293
296
|
return {
|
|
294
|
-
read: () =>
|
|
297
|
+
read: () => config2.get("auth"),
|
|
295
298
|
clear: () => {
|
|
296
|
-
if (
|
|
299
|
+
if (config2.has("auth")) config2.delete("auth");
|
|
297
300
|
}
|
|
298
301
|
};
|
|
299
302
|
}
|
|
@@ -370,8 +373,20 @@ function getStorageBackendName() {
|
|
|
370
373
|
return getKeychain().name;
|
|
371
374
|
}
|
|
372
375
|
|
|
376
|
+
// src/config/portal-api-url.ts
|
|
377
|
+
var PORTAL_API_URL_ENV = "DCS_API_URL";
|
|
378
|
+
function trimTrailingSlashes(url) {
|
|
379
|
+
return url.replace(/\/+$/, "");
|
|
380
|
+
}
|
|
381
|
+
function resolvePortalApiUrl(explicit) {
|
|
382
|
+
const fromArg = explicit?.trim();
|
|
383
|
+
if (fromArg) return trimTrailingSlashes(fromArg);
|
|
384
|
+
const fromEnv = process.env[PORTAL_API_URL_ENV]?.trim();
|
|
385
|
+
if (fromEnv) return trimTrailingSlashes(fromEnv);
|
|
386
|
+
return trimTrailingSlashes(config.portalApiUrl);
|
|
387
|
+
}
|
|
388
|
+
|
|
373
389
|
// src/auth/device-flow.ts
|
|
374
|
-
var DEFAULT_API_URL = "https://portal.duffcloudservices.com";
|
|
375
390
|
var RefreshTokenRevokedError = class extends Error {
|
|
376
391
|
constructor(message = "Refresh token was rejected (expired or revoked).") {
|
|
377
392
|
super(message);
|
|
@@ -388,7 +403,7 @@ function sleep(ms) {
|
|
|
388
403
|
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
389
404
|
}
|
|
390
405
|
async function startDeviceAuth(options = {}) {
|
|
391
|
-
const apiUrl = options.apiUrl
|
|
406
|
+
const apiUrl = resolvePortalApiUrl(options.apiUrl);
|
|
392
407
|
const clientId = options.clientId || "dcs-cli";
|
|
393
408
|
const response = await fetch(`${apiUrl}/api/v1/cli/auth/device`, {
|
|
394
409
|
method: "POST",
|
|
@@ -437,7 +452,7 @@ async function pollForToken(deviceCode, apiUrl) {
|
|
|
437
452
|
};
|
|
438
453
|
}
|
|
439
454
|
async function login(options = {}) {
|
|
440
|
-
const apiUrl = options.apiUrl
|
|
455
|
+
const apiUrl = resolvePortalApiUrl(options.apiUrl);
|
|
441
456
|
const spinner = ora("Starting authentication...").start();
|
|
442
457
|
let deviceAuth;
|
|
443
458
|
try {
|
|
@@ -505,7 +520,7 @@ async function login(options = {}) {
|
|
|
505
520
|
}
|
|
506
521
|
var REVOKE_TIMEOUT_MS = 1e4;
|
|
507
522
|
async function revokeRefreshToken(refreshToken, options = {}) {
|
|
508
|
-
const apiUrl = options.apiUrl
|
|
523
|
+
const apiUrl = resolvePortalApiUrl(options.apiUrl);
|
|
509
524
|
const response = await fetch(`${apiUrl}/api/v1/cli/auth/logout`, {
|
|
510
525
|
method: "POST",
|
|
511
526
|
headers: {
|
|
@@ -521,7 +536,7 @@ async function revokeRefreshToken(refreshToken, options = {}) {
|
|
|
521
536
|
return data?.revoked === true;
|
|
522
537
|
}
|
|
523
538
|
async function refreshAccessToken(refreshToken, options = {}) {
|
|
524
|
-
const apiUrl = options.apiUrl
|
|
539
|
+
const apiUrl = resolvePortalApiUrl(options.apiUrl);
|
|
525
540
|
const response = await fetch(`${apiUrl}/api/v1/cli/auth/refresh`, {
|
|
526
541
|
method: "POST",
|
|
527
542
|
headers: {
|
|
@@ -600,11 +615,10 @@ import chalk3 from "chalk";
|
|
|
600
615
|
import Table from "cli-table3";
|
|
601
616
|
|
|
602
617
|
// src/api/portal-client.ts
|
|
603
|
-
var DEFAULT_API_URL2 = "https://portal.duffcloudservices.com";
|
|
604
618
|
var PortalClient = class {
|
|
605
619
|
apiUrl;
|
|
606
620
|
constructor(options = {}) {
|
|
607
|
-
this.apiUrl = options.apiUrl
|
|
621
|
+
this.apiUrl = resolvePortalApiUrl(options.apiUrl);
|
|
608
622
|
}
|
|
609
623
|
/**
|
|
610
624
|
* Refresh the session using the stored refresh token. Exactly one attempt —
|
|
@@ -1061,7 +1075,8 @@ async function initCommand(options) {
|
|
|
1061
1075
|
const generatedFiles = [];
|
|
1062
1076
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
1063
1077
|
try {
|
|
1064
|
-
const
|
|
1078
|
+
const portalApiUrl = resolvePortalApiUrl();
|
|
1079
|
+
const siteYaml = generateSiteYaml({ siteSlug, siteName, timestamp, framework, portalApiUrl });
|
|
1065
1080
|
generatedFiles.push(await writeFile(targetDir, ".dcs/site.yaml", siteYaml, { dryRun, force }));
|
|
1066
1081
|
const pagesYaml = generatePagesYaml({ siteSlug, timestamp });
|
|
1067
1082
|
generatedFiles.push(await writeFile(targetDir, ".dcs/pages.yaml", pagesYaml, { dryRun, force }));
|
|
@@ -1073,7 +1088,7 @@ async function initCommand(options) {
|
|
|
1073
1088
|
generatedFiles.push(
|
|
1074
1089
|
await writeFile(targetDir, ".dcs/SECTION-CONVENTIONS.md", sectionConventions, { dryRun, force })
|
|
1075
1090
|
);
|
|
1076
|
-
const copilotInstructions = generateCopilotInstructions({ siteName });
|
|
1091
|
+
const copilotInstructions = generateCopilotInstructions({ siteName, portalApiUrl });
|
|
1077
1092
|
generatedFiles.push(
|
|
1078
1093
|
await writeFile(targetDir, ".github/copilot-instructions.md", copilotInstructions, { dryRun, force })
|
|
1079
1094
|
);
|
|
@@ -1152,7 +1167,7 @@ azure:
|
|
|
1152
1167
|
subscription_id: ""
|
|
1153
1168
|
|
|
1154
1169
|
# Portal API Configuration
|
|
1155
|
-
portal_api_url: "
|
|
1170
|
+
portal_api_url: "${data.portalApiUrl}"
|
|
1156
1171
|
|
|
1157
1172
|
# Site metadata for portal
|
|
1158
1173
|
metadata:
|
|
@@ -1303,7 +1318,7 @@ function generateCopilotInstructions(data) {
|
|
|
1303
1318
|
## Project Overview
|
|
1304
1319
|
|
|
1305
1320
|
This is a **DCS-managed customer site**. Content and feature changes are managed
|
|
1306
|
-
through the DCS Portal at
|
|
1321
|
+
through the DCS Portal at ${data.portalApiUrl}.
|
|
1307
1322
|
|
|
1308
1323
|
## Technology Stack
|
|
1309
1324
|
|
|
@@ -1800,27 +1815,27 @@ async function validateSiteYaml(dcsDir, verbose) {
|
|
|
1800
1815
|
};
|
|
1801
1816
|
try {
|
|
1802
1817
|
const content = await fs5.readFile(filePath, "utf8");
|
|
1803
|
-
const
|
|
1804
|
-
if (!
|
|
1818
|
+
const config2 = yaml.load(content);
|
|
1819
|
+
if (!config2.site_name) {
|
|
1805
1820
|
result.errors.push("Missing required field: site_name");
|
|
1806
1821
|
result.valid = false;
|
|
1807
1822
|
}
|
|
1808
|
-
if (!
|
|
1823
|
+
if (!config2.site_slug) {
|
|
1809
1824
|
result.errors.push("Missing required field: site_slug");
|
|
1810
1825
|
result.valid = false;
|
|
1811
|
-
} else if (!/^[a-z0-9-]+$/.test(
|
|
1826
|
+
} else if (!/^[a-z0-9-]+$/.test(config2.site_slug)) {
|
|
1812
1827
|
result.errors.push("site_slug must be lowercase alphanumeric with hyphens only");
|
|
1813
1828
|
result.valid = false;
|
|
1814
1829
|
}
|
|
1815
|
-
if (!
|
|
1830
|
+
if (!config2.swa_resource_id) {
|
|
1816
1831
|
result.warnings.push("swa_resource_id is not set (required for deployment)");
|
|
1817
1832
|
}
|
|
1818
|
-
if (!
|
|
1833
|
+
if (!config2.production_url) {
|
|
1819
1834
|
result.warnings.push("production_url is not set");
|
|
1820
1835
|
}
|
|
1821
1836
|
if (verbose) {
|
|
1822
|
-
console.log(chalk5.dim(` site_name: ${
|
|
1823
|
-
console.log(chalk5.dim(` site_slug: ${
|
|
1837
|
+
console.log(chalk5.dim(` site_name: ${config2.site_name}`));
|
|
1838
|
+
console.log(chalk5.dim(` site_slug: ${config2.site_slug}`));
|
|
1824
1839
|
}
|
|
1825
1840
|
} catch (error) {
|
|
1826
1841
|
if (error.code === "ENOENT") {
|
|
@@ -1842,16 +1857,16 @@ async function validatePagesYaml(dcsDir, verbose) {
|
|
|
1842
1857
|
};
|
|
1843
1858
|
try {
|
|
1844
1859
|
const content = await fs5.readFile(filePath, "utf8");
|
|
1845
|
-
const
|
|
1846
|
-
if (
|
|
1860
|
+
const config2 = yaml.load(content);
|
|
1861
|
+
if (config2.version === void 0) {
|
|
1847
1862
|
result.errors.push("Missing required field: version");
|
|
1848
1863
|
result.valid = false;
|
|
1849
1864
|
}
|
|
1850
|
-
if (!
|
|
1865
|
+
if (!config2.siteSlug) {
|
|
1851
1866
|
result.errors.push("Missing required field: siteSlug");
|
|
1852
1867
|
result.valid = false;
|
|
1853
1868
|
}
|
|
1854
|
-
const pages =
|
|
1869
|
+
const pages = config2.pages;
|
|
1855
1870
|
if (!pages || !Array.isArray(pages)) {
|
|
1856
1871
|
result.errors.push("Missing or invalid pages array");
|
|
1857
1872
|
result.valid = false;
|
|
@@ -1898,22 +1913,22 @@ async function validateContentYaml(dcsDir, verbose) {
|
|
|
1898
1913
|
};
|
|
1899
1914
|
try {
|
|
1900
1915
|
const content = await fs5.readFile(filePath, "utf8");
|
|
1901
|
-
const
|
|
1902
|
-
if (
|
|
1916
|
+
const config2 = yaml.load(content);
|
|
1917
|
+
if (config2.version === void 0) {
|
|
1903
1918
|
result.errors.push("Missing required field: version");
|
|
1904
1919
|
result.valid = false;
|
|
1905
1920
|
}
|
|
1906
|
-
if (
|
|
1921
|
+
if (config2.global !== void 0 && typeof config2.global !== "object") {
|
|
1907
1922
|
result.errors.push("global must be an object");
|
|
1908
1923
|
result.valid = false;
|
|
1909
1924
|
}
|
|
1910
|
-
if (
|
|
1925
|
+
if (config2.pages !== void 0 && typeof config2.pages !== "object") {
|
|
1911
1926
|
result.errors.push("pages must be an object");
|
|
1912
1927
|
result.valid = false;
|
|
1913
1928
|
}
|
|
1914
1929
|
if (verbose) {
|
|
1915
|
-
const globalKeys =
|
|
1916
|
-
const pageCount =
|
|
1930
|
+
const globalKeys = config2.global ? Object.keys(config2.global).length : 0;
|
|
1931
|
+
const pageCount = config2.pages ? Object.keys(config2.pages).length : 0;
|
|
1917
1932
|
console.log(chalk5.dim(` ${globalKeys} global keys, ${pageCount} page sections`));
|
|
1918
1933
|
}
|
|
1919
1934
|
} catch (error) {
|
|
@@ -1936,12 +1951,12 @@ async function validateSeoYaml(dcsDir, verbose) {
|
|
|
1936
1951
|
};
|
|
1937
1952
|
try {
|
|
1938
1953
|
const content = await fs5.readFile(filePath, "utf8");
|
|
1939
|
-
const
|
|
1940
|
-
if (
|
|
1954
|
+
const config2 = yaml.load(content);
|
|
1955
|
+
if (config2.version === void 0) {
|
|
1941
1956
|
result.errors.push("Missing required field: version");
|
|
1942
1957
|
result.valid = false;
|
|
1943
1958
|
}
|
|
1944
|
-
const global =
|
|
1959
|
+
const global = config2.global;
|
|
1945
1960
|
if (!global) {
|
|
1946
1961
|
result.errors.push("Missing required section: global");
|
|
1947
1962
|
result.valid = false;
|
|
@@ -1956,7 +1971,7 @@ async function validateSeoYaml(dcsDir, verbose) {
|
|
|
1956
1971
|
result.warnings.push("global.defaultDescription is not set");
|
|
1957
1972
|
}
|
|
1958
1973
|
}
|
|
1959
|
-
const pages =
|
|
1974
|
+
const pages = config2.pages;
|
|
1960
1975
|
if (pages && typeof pages === "object") {
|
|
1961
1976
|
const pageKeys = Object.keys(pages);
|
|
1962
1977
|
if (!pageKeys.includes("home")) {
|
|
@@ -2492,6 +2507,49 @@ import path5 from "path";
|
|
|
2492
2507
|
import yaml3 from "js-yaml";
|
|
2493
2508
|
import chalk7 from "chalk";
|
|
2494
2509
|
import ora3 from "ora";
|
|
2510
|
+
|
|
2511
|
+
// src/commands/redirectedCapture.ts
|
|
2512
|
+
var RedirectedPageError = class extends Error {
|
|
2513
|
+
constructor(requested, landed) {
|
|
2514
|
+
super(`redirected to ${landed}`);
|
|
2515
|
+
this.requested = requested;
|
|
2516
|
+
this.landed = landed;
|
|
2517
|
+
this.name = "RedirectedPageError";
|
|
2518
|
+
}
|
|
2519
|
+
};
|
|
2520
|
+
var SYNTHETIC_CAPTURE_PARAMS = ["dcs-hide-ribbon", "dcs-no-telemetry"];
|
|
2521
|
+
function normalisePath(pathname) {
|
|
2522
|
+
const trimmed = pathname.replace(/\/+$/, "");
|
|
2523
|
+
return trimmed === "" ? "/" : trimmed;
|
|
2524
|
+
}
|
|
2525
|
+
function landedOffPath(requestedUrl, landedUrl) {
|
|
2526
|
+
let requested;
|
|
2527
|
+
let landed;
|
|
2528
|
+
try {
|
|
2529
|
+
requested = new URL(requestedUrl);
|
|
2530
|
+
} catch {
|
|
2531
|
+
return landedUrl;
|
|
2532
|
+
}
|
|
2533
|
+
try {
|
|
2534
|
+
landed = new URL(landedUrl);
|
|
2535
|
+
} catch {
|
|
2536
|
+
return landedUrl;
|
|
2537
|
+
}
|
|
2538
|
+
const sameOrigin = requested.origin === landed.origin;
|
|
2539
|
+
const samePath = normalisePath(requested.pathname) === normalisePath(landed.pathname);
|
|
2540
|
+
if (sameOrigin && samePath) return null;
|
|
2541
|
+
return `${landed.origin}${normalisePath(landed.pathname)}`;
|
|
2542
|
+
}
|
|
2543
|
+
function carriesSyntheticMarker(url) {
|
|
2544
|
+
try {
|
|
2545
|
+
const params = new URL(url).searchParams;
|
|
2546
|
+
return SYNTHETIC_CAPTURE_PARAMS.some((p) => params.has(p));
|
|
2547
|
+
} catch {
|
|
2548
|
+
return false;
|
|
2549
|
+
}
|
|
2550
|
+
}
|
|
2551
|
+
|
|
2552
|
+
// src/commands/capture-snapshots.ts
|
|
2495
2553
|
var CARD_THUMBNAIL_ASPECT_RATIO = 16 / 9;
|
|
2496
2554
|
var ASSET_WAIT_TIMEOUT_MS = 5e3;
|
|
2497
2555
|
async function disableCaptureMotion(page) {
|
|
@@ -2673,9 +2731,9 @@ function getValidTextKeysForPage(contentConfig, pageSlug) {
|
|
|
2673
2731
|
}
|
|
2674
2732
|
return keys;
|
|
2675
2733
|
}
|
|
2676
|
-
async function resolveAllPages(
|
|
2734
|
+
async function resolveAllPages(config2) {
|
|
2677
2735
|
const resolved2 = [];
|
|
2678
|
-
for (const page of
|
|
2736
|
+
for (const page of config2.pages) {
|
|
2679
2737
|
if (page.type === "dynamic" && page.instances) {
|
|
2680
2738
|
for (const instance of page.instances) {
|
|
2681
2739
|
const instancePath = page.pathTemplate ? page.pathTemplate.replace(/:[\w]+/, instance) : `${page.path}/${instance}`;
|
|
@@ -2811,11 +2869,18 @@ async function capturePageSnapshot(browser, pageInfo, snapshotConfig, siteSlug,
|
|
|
2811
2869
|
}
|
|
2812
2870
|
try {
|
|
2813
2871
|
await page.goto(url, { waitUntil: "domcontentloaded", timeout: 3e4 });
|
|
2872
|
+
const landedElsewhere = landedOffPath(url, page.url());
|
|
2873
|
+
if (landedElsewhere !== null) {
|
|
2874
|
+
const unmarked = !carriesSyntheticMarker(page.url());
|
|
2875
|
+
await context.close();
|
|
2876
|
+
throw new RedirectedPageError(url, `${landedElsewhere}${unmarked ? " (markers dropped)" : ""}`);
|
|
2877
|
+
}
|
|
2814
2878
|
await disableCaptureMotion(page);
|
|
2815
2879
|
await page.waitForLoadState("networkidle", { timeout: 1e4 }).catch(() => {
|
|
2816
2880
|
if (verbose) console.log(` Note: networkidle timeout, continuing anyway`);
|
|
2817
2881
|
});
|
|
2818
2882
|
} catch (error) {
|
|
2883
|
+
if (error instanceof RedirectedPageError) throw error;
|
|
2819
2884
|
console.error(` Failed to load: ${error}`);
|
|
2820
2885
|
await context.close();
|
|
2821
2886
|
throw error;
|
|
@@ -3066,16 +3131,16 @@ async function captureSnapshotsCommand(options) {
|
|
|
3066
3131
|
console.log(chalk7.gray(`Target: ${targetDir}`));
|
|
3067
3132
|
console.log(chalk7.gray(`Base URL: ${baseUrl}`));
|
|
3068
3133
|
process.env.SITE_BASE_URL = baseUrl;
|
|
3069
|
-
let
|
|
3134
|
+
let config2;
|
|
3070
3135
|
try {
|
|
3071
|
-
|
|
3136
|
+
config2 = loadPagesConfig(targetDir);
|
|
3072
3137
|
} catch (error) {
|
|
3073
3138
|
console.error(chalk7.red("Error:"), error.message);
|
|
3074
3139
|
console.log(chalk7.yellow("\nMake sure .dcs/pages.yaml exists in the target directory."));
|
|
3075
3140
|
process.exit(1);
|
|
3076
3141
|
}
|
|
3077
|
-
console.log(chalk7.gray(`Site: ${
|
|
3078
|
-
const outputDir = path5.join(targetDir,
|
|
3142
|
+
console.log(chalk7.gray(`Site: ${config2.siteSlug}`));
|
|
3143
|
+
const outputDir = path5.join(targetDir, config2.snapshot.outputDir || ".dcs/snapshots");
|
|
3079
3144
|
const contentConfig = loadContentConfig(targetDir);
|
|
3080
3145
|
if (contentConfig) {
|
|
3081
3146
|
const pageCount = Object.keys(contentConfig.pages || {}).length;
|
|
@@ -3104,7 +3169,7 @@ async function captureSnapshotsCommand(options) {
|
|
|
3104
3169
|
console.log(chalk7.green("\n\u2705 Snapshot capture skipped (as requested)"));
|
|
3105
3170
|
return;
|
|
3106
3171
|
}
|
|
3107
|
-
let pagesToCapture = await resolveAllPages(
|
|
3172
|
+
let pagesToCapture = await resolveAllPages(config2);
|
|
3108
3173
|
console.log(chalk7.gray(`Found ${pagesToCapture.length} total pages in configuration`));
|
|
3109
3174
|
if (targetedPages && targetedPages.length > 0) {
|
|
3110
3175
|
const targetedSlugs = new Set(targetedPages);
|
|
@@ -3143,6 +3208,7 @@ async function captureSnapshotsCommand(options) {
|
|
|
3143
3208
|
spinner.succeed("Browser launched");
|
|
3144
3209
|
const snapshots = [];
|
|
3145
3210
|
const failures = [];
|
|
3211
|
+
const redirectStubs = [];
|
|
3146
3212
|
for (const pageInfo of pagesToCapture) {
|
|
3147
3213
|
const pageSpinner = ora3(`Capturing ${pageInfo.slug}...`).start();
|
|
3148
3214
|
try {
|
|
@@ -3150,17 +3216,24 @@ async function captureSnapshotsCommand(options) {
|
|
|
3150
3216
|
const snapshot = await capturePageSnapshot(
|
|
3151
3217
|
browser,
|
|
3152
3218
|
pageInfo,
|
|
3153
|
-
|
|
3154
|
-
|
|
3219
|
+
config2.snapshot,
|
|
3220
|
+
config2.siteSlug,
|
|
3155
3221
|
validTextKeys,
|
|
3156
3222
|
outputDir,
|
|
3157
3223
|
verbose || false
|
|
3158
3224
|
);
|
|
3159
3225
|
snapshots.push(snapshot);
|
|
3160
|
-
const snapshotPath = path5.join(outputDir,
|
|
3226
|
+
const snapshotPath = path5.join(outputDir, config2.siteSlug, pageInfo.slug, "snapshot.json");
|
|
3161
3227
|
fs7.writeFileSync(snapshotPath, JSON.stringify(snapshot, null, 2));
|
|
3162
3228
|
pageSpinner.succeed(`${pageInfo.slug}: ${snapshot.sections.length} sections captured`);
|
|
3163
3229
|
} catch (error) {
|
|
3230
|
+
if (error instanceof RedirectedPageError) {
|
|
3231
|
+
redirectStubs.push(`${pageInfo.slug} -> ${error.landed}`);
|
|
3232
|
+
pageSpinner.warn(
|
|
3233
|
+
`${pageInfo.slug}: redirect stub -> ${error.landed} - skipped (C-1553: not a page of its own, and the redirect drops the synthetic markers)`
|
|
3234
|
+
);
|
|
3235
|
+
continue;
|
|
3236
|
+
}
|
|
3164
3237
|
const message = `${pageInfo.slug}: ${error.message}`;
|
|
3165
3238
|
failures.push(message);
|
|
3166
3239
|
pageSpinner.fail(`${pageInfo.slug}: Failed - ${error.message}`);
|
|
@@ -3168,11 +3241,11 @@ async function captureSnapshotsCommand(options) {
|
|
|
3168
3241
|
}
|
|
3169
3242
|
await browser.close();
|
|
3170
3243
|
const manifest = {
|
|
3171
|
-
siteSlug:
|
|
3244
|
+
siteSlug: config2.siteSlug,
|
|
3172
3245
|
capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3173
3246
|
capturedVersion: process.env.GITHUB_SHA,
|
|
3174
3247
|
deploymentRef: process.env.GITHUB_REF || "local",
|
|
3175
|
-
pagesConfigVersion:
|
|
3248
|
+
pagesConfigVersion: config2.version,
|
|
3176
3249
|
pages: snapshots.map((s) => ({
|
|
3177
3250
|
pageSlug: s.pageSlug,
|
|
3178
3251
|
pagePath: s.path,
|
|
@@ -3184,7 +3257,7 @@ async function captureSnapshotsCommand(options) {
|
|
|
3184
3257
|
hasSnapshot: true
|
|
3185
3258
|
}))
|
|
3186
3259
|
};
|
|
3187
|
-
const manifestPath = path5.join(outputDir,
|
|
3260
|
+
const manifestPath = path5.join(outputDir, config2.siteSlug, "manifest.json");
|
|
3188
3261
|
if (isTargetedCapture) {
|
|
3189
3262
|
console.log(chalk7.gray(" Targeted capture: leaving the existing remote manifest intact"));
|
|
3190
3263
|
} else {
|
|
@@ -3193,6 +3266,9 @@ async function captureSnapshotsCommand(options) {
|
|
|
3193
3266
|
console.log("");
|
|
3194
3267
|
console.log(chalk7.green("\u2705 Snapshot capture complete!"));
|
|
3195
3268
|
console.log(chalk7.gray(` Captured ${snapshots.length} pages`));
|
|
3269
|
+
if (redirectStubs.length > 0) {
|
|
3270
|
+
console.log(chalk7.yellow(` Skipped ${redirectStubs.length} redirect stub(s): ${redirectStubs.join("; ")}`));
|
|
3271
|
+
}
|
|
3196
3272
|
console.log(chalk7.gray(` - Static: ${snapshots.filter((s) => s.type === "static").length}`));
|
|
3197
3273
|
console.log(chalk7.gray(` - Index: ${snapshots.filter((s) => s.type === "index").length}`));
|
|
3198
3274
|
console.log(chalk7.gray(` - Dynamic: ${snapshots.filter((s) => s.type === "dynamic").length}`));
|