@contentful/experience-design-system-cli 2.37.1-dev-build-eeb53912.0 → 2.37.1-dev-build-6df388e4.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/app.js +445 -827
- package/dist/index.js +446 -827
- package/dist/package.json +1 -1
- package/dist/src/tui/import/spawn-v1-import.d.ts +2 -1
- package/dist/src/tui/import/steps/03-path-validation/helpers/fold-svelte-and-md-into-other.d.ts +13 -0
- package/dist/src/tui/import/steps/03-path-validation/loader.d.ts +6 -1
- package/dist/src/tui/import/steps/03-path-validation/logic.d.ts +2 -1
- package/dist/src/tui/import/steps/03-path-validation/screen.d.ts +4 -1
- package/dist/src/tui/import/steps/04-credentials/logic.d.ts +3 -0
- package/dist/src/tui/import/steps/04-credentials/validate.d.ts +1 -1
- package/legacy/package.json +1 -1
- package/package.json +3 -3
package/dist/app.js
CHANGED
|
@@ -1164,8 +1164,8 @@ function WelcomeScreen({ onContinue, onQuit, initialPath = "" }) {
|
|
|
1164
1164
|
const [projectPath, setProjectPath] = useState3(initialPath);
|
|
1165
1165
|
useWelcomeControls(onQuit);
|
|
1166
1166
|
const submit = (value) => {
|
|
1167
|
-
const
|
|
1168
|
-
if (
|
|
1167
|
+
const path5 = toProjectPath(value);
|
|
1168
|
+
if (path5) onContinue(path5);
|
|
1169
1169
|
};
|
|
1170
1170
|
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", gap: 1, paddingX: 2, paddingY: 1, children: [
|
|
1171
1171
|
/* @__PURE__ */ jsx4(Text4, { bold: true, color: PALETTE.success, children: "\u{1F44B} Hey! Let's import your design system into Contentful." }),
|
|
@@ -1242,24 +1242,24 @@ function resolveUserPath(raw) {
|
|
|
1242
1242
|
|
|
1243
1243
|
// packages/experience-design-system-cli/src/tui/import/steps/02-token-input/validate-path.ts
|
|
1244
1244
|
function validateTokenPath(rawPath) {
|
|
1245
|
-
const
|
|
1245
|
+
const path5 = resolveUserPath(rawPath);
|
|
1246
1246
|
let stats;
|
|
1247
1247
|
try {
|
|
1248
|
-
stats = statSync(
|
|
1248
|
+
stats = statSync(path5);
|
|
1249
1249
|
} catch {
|
|
1250
|
-
return { ok: false, error: `Path not found: ${
|
|
1250
|
+
return { ok: false, error: `Path not found: ${path5}`, resolvedPath: path5 };
|
|
1251
1251
|
}
|
|
1252
1252
|
if (stats.isDirectory()) {
|
|
1253
1253
|
return {
|
|
1254
1254
|
ok: false,
|
|
1255
1255
|
error: "That's a directory \u2014 provide a path to a token file (e.g. tokens.json), not a folder.",
|
|
1256
|
-
resolvedPath:
|
|
1256
|
+
resolvedPath: path5
|
|
1257
1257
|
};
|
|
1258
1258
|
}
|
|
1259
1259
|
if (!stats.isFile()) {
|
|
1260
|
-
return { ok: false, error: `Not a regular file: ${
|
|
1260
|
+
return { ok: false, error: `Not a regular file: ${path5}`, resolvedPath: path5 };
|
|
1261
1261
|
}
|
|
1262
|
-
return { ok: true, path };
|
|
1262
|
+
return { ok: true, path: path5 };
|
|
1263
1263
|
}
|
|
1264
1264
|
|
|
1265
1265
|
// packages/experience-design-system-cli/src/tui/import/steps/02-token-input/screen.tsx
|
|
@@ -1319,26 +1319,6 @@ import { Box as Box5, Text as Text6 } from "ink";
|
|
|
1319
1319
|
import { useInput as useInput5 } from "ink";
|
|
1320
1320
|
|
|
1321
1321
|
// packages/experience-design-system-cli/src/tui/import/steps/03-path-validation/logic.ts
|
|
1322
|
-
var IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
1323
|
-
"node_modules",
|
|
1324
|
-
"dist",
|
|
1325
|
-
"build",
|
|
1326
|
-
".next",
|
|
1327
|
-
".nuxt",
|
|
1328
|
-
"coverage",
|
|
1329
|
-
"storybook-static",
|
|
1330
|
-
"out",
|
|
1331
|
-
".git"
|
|
1332
|
-
]);
|
|
1333
|
-
var CATEGORY_BY_EXTENSION = /* @__PURE__ */ new Map([
|
|
1334
|
-
["tsx", "tsx"],
|
|
1335
|
-
["ts", "ts"],
|
|
1336
|
-
["vue", "vue"],
|
|
1337
|
-
["astro", "astro"],
|
|
1338
|
-
["jsx", "jsx"],
|
|
1339
|
-
["js", "js"],
|
|
1340
|
-
["json", "json"]
|
|
1341
|
-
]);
|
|
1342
1322
|
var COMPONENT_CATEGORIES = ["tsx", "ts", "vue", "astro", "jsx", "js"];
|
|
1343
1323
|
var ROW_LABELS = {
|
|
1344
1324
|
tsx: ".tsx files",
|
|
@@ -1351,38 +1331,7 @@ var ROW_LABELS = {
|
|
|
1351
1331
|
other: "other (ignored)"
|
|
1352
1332
|
};
|
|
1353
1333
|
var ROW_ORDER = ["tsx", "ts", "vue", "astro", "jsx", "js", "json", "other"];
|
|
1354
|
-
function
|
|
1355
|
-
if (fileName.endsWith(".d.ts")) return "other";
|
|
1356
|
-
const dot = fileName.lastIndexOf(".");
|
|
1357
|
-
const extension = dot === -1 ? "" : fileName.slice(dot + 1);
|
|
1358
|
-
return CATEGORY_BY_EXTENSION.get(extension) ?? "other";
|
|
1359
|
-
}
|
|
1360
|
-
function emptyCounts() {
|
|
1361
|
-
return { tsx: 0, ts: 0, vue: 0, astro: 0, jsx: 0, js: 0, json: 0, other: 0, total: 0 };
|
|
1362
|
-
}
|
|
1363
|
-
function addFile(counts, fileName) {
|
|
1364
|
-
const category = categorize(fileName);
|
|
1365
|
-
return { ...counts, [category]: counts[category] + 1, total: counts.total + 1 };
|
|
1366
|
-
}
|
|
1367
|
-
function mergeCounts(a, b) {
|
|
1368
|
-
return {
|
|
1369
|
-
tsx: a.tsx + b.tsx,
|
|
1370
|
-
ts: a.ts + b.ts,
|
|
1371
|
-
vue: a.vue + b.vue,
|
|
1372
|
-
astro: a.astro + b.astro,
|
|
1373
|
-
jsx: a.jsx + b.jsx,
|
|
1374
|
-
js: a.js + b.js,
|
|
1375
|
-
json: a.json + b.json,
|
|
1376
|
-
other: a.other + b.other,
|
|
1377
|
-
total: a.total + b.total
|
|
1378
|
-
};
|
|
1379
|
-
}
|
|
1380
|
-
function failureFromErrorCode(code) {
|
|
1381
|
-
if (code === "ENOENT") return "not-found";
|
|
1382
|
-
if (code === "EACCES") return "permission-denied";
|
|
1383
|
-
return "unreadable";
|
|
1384
|
-
}
|
|
1385
|
-
function describeFailure(failure, path) {
|
|
1334
|
+
function describeFailure(failure, path5) {
|
|
1386
1335
|
switch (failure) {
|
|
1387
1336
|
case "is-file":
|
|
1388
1337
|
return {
|
|
@@ -1394,18 +1343,18 @@ function describeFailure(failure, path) {
|
|
|
1394
1343
|
};
|
|
1395
1344
|
case "not-found":
|
|
1396
1345
|
return {
|
|
1397
|
-
headline: `Directory not found: ${
|
|
1346
|
+
headline: `Directory not found: ${path5}`,
|
|
1398
1347
|
hints: ["Double-check the path and try again. Tip: you can use ~, relative, or absolute paths."]
|
|
1399
1348
|
};
|
|
1400
1349
|
case "permission-denied":
|
|
1401
1350
|
return {
|
|
1402
|
-
headline: `Permission denied: ${
|
|
1351
|
+
headline: `Permission denied: ${path5}`,
|
|
1403
1352
|
hints: ["You don't have read access to this directory."]
|
|
1404
1353
|
};
|
|
1405
1354
|
case "not-directory":
|
|
1406
|
-
return { headline: `Not a directory: ${
|
|
1355
|
+
return { headline: `Not a directory: ${path5}`, hints: [] };
|
|
1407
1356
|
case "unreadable":
|
|
1408
|
-
return { headline: `Cannot access: ${
|
|
1357
|
+
return { headline: `Cannot access: ${path5}`, hints: [] };
|
|
1409
1358
|
}
|
|
1410
1359
|
}
|
|
1411
1360
|
function summarize(counts) {
|
|
@@ -1463,132 +1412,6 @@ function usePathValidationControls(phase, handlers) {
|
|
|
1463
1412
|
});
|
|
1464
1413
|
}
|
|
1465
1414
|
|
|
1466
|
-
// packages/experience-design-system-cli/src/tui/import/steps/03-path-validation/loader.ts
|
|
1467
|
-
import { readdir, stat } from "node:fs/promises";
|
|
1468
|
-
import { join as join3 } from "node:path";
|
|
1469
|
-
async function isFile(path) {
|
|
1470
|
-
try {
|
|
1471
|
-
return (await stat(path)).isFile();
|
|
1472
|
-
} catch {
|
|
1473
|
-
return false;
|
|
1474
|
-
}
|
|
1475
|
-
}
|
|
1476
|
-
async function countEntry(directory, entry) {
|
|
1477
|
-
const path = join3(directory, entry.name);
|
|
1478
|
-
if (entry.isDirectory()) return countFiles(path);
|
|
1479
|
-
if (entry.isFile() || entry.isSymbolicLink() && await isFile(path)) return addFile(emptyCounts(), entry.name);
|
|
1480
|
-
return emptyCounts();
|
|
1481
|
-
}
|
|
1482
|
-
async function countFiles(directory) {
|
|
1483
|
-
let entries;
|
|
1484
|
-
try {
|
|
1485
|
-
entries = await readdir(directory, { withFileTypes: true });
|
|
1486
|
-
} catch {
|
|
1487
|
-
return emptyCounts();
|
|
1488
|
-
}
|
|
1489
|
-
const counts = await Promise.all(
|
|
1490
|
-
entries.filter((entry) => !IGNORED_DIRECTORIES.has(entry.name)).map((entry) => countEntry(directory, entry))
|
|
1491
|
-
);
|
|
1492
|
-
return counts.reduce(mergeCounts, emptyCounts());
|
|
1493
|
-
}
|
|
1494
|
-
async function scanProject(directory) {
|
|
1495
|
-
try {
|
|
1496
|
-
const stats = await stat(directory);
|
|
1497
|
-
if (stats.isFile()) return { ok: false, failure: "is-file" };
|
|
1498
|
-
if (!stats.isDirectory()) return { ok: false, failure: "not-directory" };
|
|
1499
|
-
} catch (error) {
|
|
1500
|
-
return { ok: false, failure: failureFromErrorCode(error.code) };
|
|
1501
|
-
}
|
|
1502
|
-
return { ok: true, counts: await countFiles(directory) };
|
|
1503
|
-
}
|
|
1504
|
-
|
|
1505
|
-
// packages/experience-design-system-cli/src/tui/import/steps/03-path-validation/screen.tsx
|
|
1506
|
-
import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1507
|
-
function Row({ row }) {
|
|
1508
|
-
return /* @__PURE__ */ jsxs5(Text6, { color: row.tone === "accent" ? PALETTE.accent : void 0, dimColor: row.tone === "muted", children: [
|
|
1509
|
-
" \u2022 ",
|
|
1510
|
-
String(row.count).padStart(3),
|
|
1511
|
-
" ",
|
|
1512
|
-
row.label
|
|
1513
|
-
] });
|
|
1514
|
-
}
|
|
1515
|
-
function Failure({ scan, path }) {
|
|
1516
|
-
const { headline, hints } = describeFailure(scan.failure, path);
|
|
1517
|
-
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
|
|
1518
|
-
/* @__PURE__ */ jsxs5(Text6, { color: PALETTE.error, children: [
|
|
1519
|
-
"\u2717 ",
|
|
1520
|
-
headline
|
|
1521
|
-
] }),
|
|
1522
|
-
hints.map((hint) => /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: hint }, hint))
|
|
1523
|
-
] });
|
|
1524
|
-
}
|
|
1525
|
-
function Summary({ scan, path }) {
|
|
1526
|
-
const { total, rows, warning } = summarize(scan.counts);
|
|
1527
|
-
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", gap: 1, children: [
|
|
1528
|
-
/* @__PURE__ */ jsxs5(Text6, { children: [
|
|
1529
|
-
"Scanned ",
|
|
1530
|
-
/* @__PURE__ */ jsx6(Text6, { bold: true, children: path })
|
|
1531
|
-
] }),
|
|
1532
|
-
/* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
|
|
1533
|
-
/* @__PURE__ */ jsxs5(Text6, { color: PALETTE.success, children: [
|
|
1534
|
-
"\u2713 Found ",
|
|
1535
|
-
total,
|
|
1536
|
-
" files:"
|
|
1537
|
-
] }),
|
|
1538
|
-
rows.map((row) => /* @__PURE__ */ jsx6(Row, { row }, row.label))
|
|
1539
|
-
] }),
|
|
1540
|
-
warning && /* @__PURE__ */ jsxs5(Text6, { color: PALETTE.warning, children: [
|
|
1541
|
-
"\u26A0 ",
|
|
1542
|
-
warning
|
|
1543
|
-
] }),
|
|
1544
|
-
/* @__PURE__ */ jsx6(Text6, { children: "Does this look right?" })
|
|
1545
|
-
] });
|
|
1546
|
-
}
|
|
1547
|
-
function PathValidationScreen({
|
|
1548
|
-
projectPath,
|
|
1549
|
-
onConfirm,
|
|
1550
|
-
onChangePath,
|
|
1551
|
-
onBack
|
|
1552
|
-
}) {
|
|
1553
|
-
const resolvedPath = useMemo(() => resolveUserPath(projectPath), [projectPath]);
|
|
1554
|
-
const [scan, setScan] = useState5();
|
|
1555
|
-
useEffect3(() => {
|
|
1556
|
-
let cancelled = false;
|
|
1557
|
-
setScan(void 0);
|
|
1558
|
-
void scanProject(resolvedPath).then((result) => {
|
|
1559
|
-
if (!cancelled) setScan(result);
|
|
1560
|
-
});
|
|
1561
|
-
return () => {
|
|
1562
|
-
cancelled = true;
|
|
1563
|
-
};
|
|
1564
|
-
}, [resolvedPath]);
|
|
1565
|
-
const phase = phaseOf(scan);
|
|
1566
|
-
usePathValidationControls(phase, { onConfirm: () => onConfirm(resolvedPath), onChangePath, onBack });
|
|
1567
|
-
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", gap: 1, paddingX: 2, paddingY: 1, children: [
|
|
1568
|
-
scan === void 0 && /* @__PURE__ */ jsxs5(Text6, { dimColor: true, children: [
|
|
1569
|
-
"Scanning ",
|
|
1570
|
-
/* @__PURE__ */ jsx6(Text6, { bold: true, children: resolvedPath }),
|
|
1571
|
-
"..."
|
|
1572
|
-
] }),
|
|
1573
|
-
scan?.ok === false && /* @__PURE__ */ jsx6(Failure, { scan, path: resolvedPath }),
|
|
1574
|
-
scan?.ok === true && /* @__PURE__ */ jsx6(Summary, { scan, path: resolvedPath }),
|
|
1575
|
-
/* @__PURE__ */ jsx6(Box5, { marginTop: 1, gap: 3, children: controlsFor(phase).map((control) => /* @__PURE__ */ jsxs5(Text6, { dimColor: true, children: [
|
|
1576
|
-
"[",
|
|
1577
|
-
control.keys,
|
|
1578
|
-
"] ",
|
|
1579
|
-
control.label
|
|
1580
|
-
] }, control.keys)) })
|
|
1581
|
-
] });
|
|
1582
|
-
}
|
|
1583
|
-
|
|
1584
|
-
// packages/experience-design-system-cli/src/tui/import/steps/04-credentials/screen.tsx
|
|
1585
|
-
import { useRef, useState as useState6 } from "react";
|
|
1586
|
-
import { Box as Box6, Text as Text7 } from "ink";
|
|
1587
|
-
import TextInput3 from "ink-text-input";
|
|
1588
|
-
|
|
1589
|
-
// packages/experience-design-system-cli/src/tui/import/steps/04-credentials/controls.ts
|
|
1590
|
-
import { useInput as useInput6 } from "ink";
|
|
1591
|
-
|
|
1592
1415
|
// packages/experience-design-system-backend-pipeline/dist/src/persistence/src/session/repositories/db.js
|
|
1593
1416
|
import { DatabaseSync } from "node:sqlite";
|
|
1594
1417
|
|
|
@@ -1794,15 +1617,224 @@ init_agents();
|
|
|
1794
1617
|
init_agents();
|
|
1795
1618
|
var DEFAULT_TIMEOUT_MS = Number(process.env.EDS_AGENT_TIMEOUT_MS ?? 5 * 60 * 1e3);
|
|
1796
1619
|
|
|
1620
|
+
// packages/experience-design-system-backend-pipeline/dist/src/persistence/src/helpers/collect-files/collect-files.js
|
|
1621
|
+
import { statSync as statSync2 } from "node:fs";
|
|
1622
|
+
import path4 from "node:path";
|
|
1623
|
+
|
|
1624
|
+
// packages/experience-design-system-backend-pipeline/dist/src/persistence/src/helpers/collect-files/build-scan-warnings.js
|
|
1625
|
+
function buildScanWarnings(counts) {
|
|
1626
|
+
const warnings = [];
|
|
1627
|
+
if (counts.total === 0) {
|
|
1628
|
+
warnings.push("No source files found. Double-check the path points at your component code.");
|
|
1629
|
+
return warnings;
|
|
1630
|
+
}
|
|
1631
|
+
const frameworkFiles = counts.tsx + counts.ts + counts.jsx + counts.js + counts.vue + counts.astro + counts.svelte;
|
|
1632
|
+
if (frameworkFiles === 0) {
|
|
1633
|
+
warnings.push("No framework source files found (.tsx, .ts, .vue, .svelte, .astro, .jsx, .js).");
|
|
1634
|
+
}
|
|
1635
|
+
return warnings;
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
// packages/experience-design-system-backend-pipeline/dist/src/persistence/src/helpers/collect-files/classify-stat-error.js
|
|
1639
|
+
function classifyStatError(code) {
|
|
1640
|
+
switch (code) {
|
|
1641
|
+
case "ENOENT":
|
|
1642
|
+
return "not-found";
|
|
1643
|
+
case "EACCES":
|
|
1644
|
+
case "EPERM":
|
|
1645
|
+
return "permission-denied";
|
|
1646
|
+
default:
|
|
1647
|
+
return "unreadable";
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1651
|
+
// packages/experience-design-system-backend-pipeline/dist/src/persistence/src/helpers/collect-files/empty-file-counts.js
|
|
1652
|
+
function emptyFileCounts() {
|
|
1653
|
+
return { tsx: 0, ts: 0, vue: 0, astro: 0, svelte: 0, jsx: 0, js: 0, json: 0, md: 0, other: 0, total: 0 };
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
// packages/experience-design-system-backend-pipeline/dist/src/persistence/src/helpers/collect-files/walk-project-tree.js
|
|
1657
|
+
import { readFileSync as readFileSync3 } from "node:fs";
|
|
1658
|
+
import path3 from "node:path";
|
|
1659
|
+
|
|
1660
|
+
// packages/experience-design-system-backend-pipeline/dist/src/persistence/src/constants/file-patterns.js
|
|
1661
|
+
var INCLUDED_EXTENSIONS = /* @__PURE__ */ new Set([".astro", ".js", ".jsx", ".svelte", ".ts", ".tsx", ".vue"]);
|
|
1662
|
+
var DENYLIST_GATED_EXTENSIONS = /* @__PURE__ */ new Set([".json", ".md"]);
|
|
1663
|
+
var EXCLUDED_SUFFIXES = [
|
|
1664
|
+
".d.ts",
|
|
1665
|
+
".stories.ts",
|
|
1666
|
+
".stories.tsx",
|
|
1667
|
+
".stories.js",
|
|
1668
|
+
".stories.jsx",
|
|
1669
|
+
".story.ts",
|
|
1670
|
+
".story.tsx",
|
|
1671
|
+
".story.js",
|
|
1672
|
+
".story.jsx",
|
|
1673
|
+
".spec.ts",
|
|
1674
|
+
".spec.tsx",
|
|
1675
|
+
".test.ts",
|
|
1676
|
+
".test.tsx"
|
|
1677
|
+
];
|
|
1678
|
+
var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
1679
|
+
".changeset",
|
|
1680
|
+
".git",
|
|
1681
|
+
".github",
|
|
1682
|
+
".idea",
|
|
1683
|
+
".next",
|
|
1684
|
+
".nuxt",
|
|
1685
|
+
".nx",
|
|
1686
|
+
".vscode",
|
|
1687
|
+
"build",
|
|
1688
|
+
"coverage",
|
|
1689
|
+
"demo",
|
|
1690
|
+
"demos",
|
|
1691
|
+
"dist",
|
|
1692
|
+
"example",
|
|
1693
|
+
"examples",
|
|
1694
|
+
"node_modules",
|
|
1695
|
+
"out",
|
|
1696
|
+
"storybook-static"
|
|
1697
|
+
]);
|
|
1698
|
+
var DENYLISTED_EXACT_FILE_NAMES = /* @__PURE__ */ new Set([
|
|
1699
|
+
"package.json",
|
|
1700
|
+
"package-lock.json",
|
|
1701
|
+
"npm-shrinkwrap.json",
|
|
1702
|
+
"nx.json",
|
|
1703
|
+
"project.json",
|
|
1704
|
+
"turbo.json",
|
|
1705
|
+
"lerna.json",
|
|
1706
|
+
"jsconfig.json"
|
|
1707
|
+
]);
|
|
1708
|
+
var DENYLISTED_FILE_NAME_PATTERNS = [
|
|
1709
|
+
/^tsconfig(\..+)?\.json$/,
|
|
1710
|
+
/^\.?eslintrc(\..+)?\.json$/,
|
|
1711
|
+
/^\.?prettierrc(\..+)?\.json$/,
|
|
1712
|
+
/^(readme|changelog|contributing|code_of_conduct|license|security)(\..+)?\.md$/i
|
|
1713
|
+
];
|
|
1714
|
+
|
|
1715
|
+
// packages/experience-design-system-backend-pipeline/dist/src/persistence/src/helpers/collect-files/categorize-file.js
|
|
1716
|
+
import path from "node:path";
|
|
1717
|
+
|
|
1718
|
+
// packages/experience-design-system-backend-pipeline/dist/src/persistence/src/constants/file-categories.js
|
|
1719
|
+
var CATEGORY_BY_EXTENSION = {
|
|
1720
|
+
".tsx": "tsx",
|
|
1721
|
+
".ts": "ts",
|
|
1722
|
+
".vue": "vue",
|
|
1723
|
+
".astro": "astro",
|
|
1724
|
+
".svelte": "svelte",
|
|
1725
|
+
".jsx": "jsx",
|
|
1726
|
+
".js": "js",
|
|
1727
|
+
".json": "json",
|
|
1728
|
+
".md": "md"
|
|
1729
|
+
};
|
|
1730
|
+
|
|
1731
|
+
// packages/experience-design-system-backend-pipeline/dist/src/persistence/src/helpers/collect-files/categorize-file.js
|
|
1732
|
+
function categorizeFile(name) {
|
|
1733
|
+
if (name.endsWith(".d.ts"))
|
|
1734
|
+
return "other";
|
|
1735
|
+
const ext = path.extname(name);
|
|
1736
|
+
return CATEGORY_BY_EXTENSION[ext] ?? "other";
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
// packages/experience-design-system-backend-pipeline/dist/src/persistence/src/helpers/collect-files/is-source-file.js
|
|
1740
|
+
import path2 from "node:path";
|
|
1741
|
+
|
|
1742
|
+
// packages/experience-design-system-backend-pipeline/dist/src/persistence/src/helpers/collect-files/is-noise-config-file.js
|
|
1743
|
+
function isNoiseConfigFile(name) {
|
|
1744
|
+
return DENYLISTED_EXACT_FILE_NAMES.has(name) || DENYLISTED_FILE_NAME_PATTERNS.some((pattern) => pattern.test(name));
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
// packages/experience-design-system-backend-pipeline/dist/src/persistence/src/helpers/collect-files/is-source-file.js
|
|
1748
|
+
function isSourceFile(name) {
|
|
1749
|
+
if (name.endsWith(".d.ts"))
|
|
1750
|
+
return false;
|
|
1751
|
+
if (EXCLUDED_SUFFIXES.some((suffix) => name.endsWith(suffix)))
|
|
1752
|
+
return false;
|
|
1753
|
+
const ext = path2.extname(name);
|
|
1754
|
+
if (INCLUDED_EXTENSIONS.has(ext))
|
|
1755
|
+
return true;
|
|
1756
|
+
if (DENYLIST_GATED_EXTENSIONS.has(ext) && !isNoiseConfigFile(name))
|
|
1757
|
+
return true;
|
|
1758
|
+
return false;
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
// packages/experience-design-system-backend-pipeline/dist/src/persistence/src/helpers/collect-files/read-dir-safely.js
|
|
1762
|
+
import { readdirSync } from "node:fs";
|
|
1763
|
+
function readDirSafely(dir) {
|
|
1764
|
+
try {
|
|
1765
|
+
return readdirSync(dir, { withFileTypes: true });
|
|
1766
|
+
} catch {
|
|
1767
|
+
return "unreadable";
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1771
|
+
// packages/experience-design-system-backend-pipeline/dist/src/persistence/src/helpers/collect-files/walk-project-tree.js
|
|
1772
|
+
function walkProjectTree(dir, result, includeContent) {
|
|
1773
|
+
const entries = readDirSafely(dir);
|
|
1774
|
+
if (entries === "unreadable") {
|
|
1775
|
+
result.warnings.push(`Could not read directory: ${dir}`);
|
|
1776
|
+
return;
|
|
1777
|
+
}
|
|
1778
|
+
for (const entry of entries) {
|
|
1779
|
+
const full = path3.join(dir, entry.name);
|
|
1780
|
+
if (entry.isDirectory()) {
|
|
1781
|
+
if (!IGNORED_DIRS.has(entry.name))
|
|
1782
|
+
walkProjectTree(full, result, includeContent);
|
|
1783
|
+
continue;
|
|
1784
|
+
}
|
|
1785
|
+
if (!entry.isFile())
|
|
1786
|
+
continue;
|
|
1787
|
+
if (!isSourceFile(entry.name))
|
|
1788
|
+
continue;
|
|
1789
|
+
const category = categorizeFile(entry.name);
|
|
1790
|
+
result.counts[category] += 1;
|
|
1791
|
+
result.counts.total += 1;
|
|
1792
|
+
result.filePaths.push(full);
|
|
1793
|
+
if (includeContent) {
|
|
1794
|
+
try {
|
|
1795
|
+
const content = readFileSync3(full, "utf8");
|
|
1796
|
+
(result.candidateFiles ??= []).push({ path: full, content });
|
|
1797
|
+
} catch {
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
// packages/experience-design-system-backend-pipeline/dist/src/persistence/src/helpers/collect-files/collect-files.js
|
|
1804
|
+
function collectFiles(root, options = {}) {
|
|
1805
|
+
const resolved = path4.resolve(root);
|
|
1806
|
+
let stat;
|
|
1807
|
+
try {
|
|
1808
|
+
stat = statSync2(resolved);
|
|
1809
|
+
} catch (err) {
|
|
1810
|
+
return { ok: false, failure: classifyStatError(err.code), rootPath: resolved };
|
|
1811
|
+
}
|
|
1812
|
+
if (stat.isFile())
|
|
1813
|
+
return { ok: false, failure: "is-file", rootPath: resolved };
|
|
1814
|
+
if (!stat.isDirectory())
|
|
1815
|
+
return { ok: false, failure: "not-directory", rootPath: resolved };
|
|
1816
|
+
const result = {
|
|
1817
|
+
filePaths: [],
|
|
1818
|
+
counts: emptyFileCounts(),
|
|
1819
|
+
warnings: [],
|
|
1820
|
+
rootPath: resolved
|
|
1821
|
+
};
|
|
1822
|
+
if (options.includeContent)
|
|
1823
|
+
result.candidateFiles = [];
|
|
1824
|
+
walkProjectTree(resolved, result, options.includeContent === true);
|
|
1825
|
+
result.filePaths.sort();
|
|
1826
|
+
if (result.candidateFiles)
|
|
1827
|
+
result.candidateFiles.sort((a, b) => a.path.localeCompare(b.path));
|
|
1828
|
+
result.warnings.push(...buildScanWarnings(result.counts));
|
|
1829
|
+
return { ok: true, result };
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1797
1832
|
// packages/experience-design-system-backend-pipeline/dist/src/persistence/src/session/index.js
|
|
1798
1833
|
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
1799
1834
|
|
|
1800
1835
|
// packages/experience-design-system-backend-pipeline/dist/src/persistence/src/session/repositories/migration.js
|
|
1801
1836
|
import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
|
|
1802
1837
|
|
|
1803
|
-
// packages/experience-design-system-backend-pipeline/dist/src/persistence/src/credentials/helpers/host-utils.js
|
|
1804
|
-
var DEFAULT_CONFIGURED_HOST = "api.contentful.com";
|
|
1805
|
-
|
|
1806
1838
|
// packages/experience-design-system-backend-pipeline/dist/src/persistence/src/index.js
|
|
1807
1839
|
init_cache_keys();
|
|
1808
1840
|
|
|
@@ -1953,7 +1985,7 @@ var serializeObjectParam = ({ allowReserved, explode, name, style, value, valueO
|
|
|
1953
1985
|
|
|
1954
1986
|
// packages/experience-design-system-client/dist/src/generated/client/utils.gen.js
|
|
1955
1987
|
var PATH_PARAM_RE = /\{[^{}]+\}/g;
|
|
1956
|
-
var defaultPathSerializer = ({ path, url: _url }) => {
|
|
1988
|
+
var defaultPathSerializer = ({ path: path5, url: _url }) => {
|
|
1957
1989
|
let url = _url;
|
|
1958
1990
|
const matches = _url.match(PATH_PARAM_RE);
|
|
1959
1991
|
if (matches) {
|
|
@@ -1972,7 +2004,7 @@ var defaultPathSerializer = ({ path, url: _url }) => {
|
|
|
1972
2004
|
name = name.substring(1);
|
|
1973
2005
|
style = "matrix";
|
|
1974
2006
|
}
|
|
1975
|
-
const value =
|
|
2007
|
+
const value = path5[name];
|
|
1976
2008
|
if (value === void 0 || value === null) {
|
|
1977
2009
|
continue;
|
|
1978
2010
|
}
|
|
@@ -2117,11 +2149,11 @@ var buildUrl = (options) => {
|
|
|
2117
2149
|
});
|
|
2118
2150
|
return url;
|
|
2119
2151
|
};
|
|
2120
|
-
var getUrl = ({ baseUrl, path, query, querySerializer, url: _url }) => {
|
|
2152
|
+
var getUrl = ({ baseUrl, path: path5, query, querySerializer, url: _url }) => {
|
|
2121
2153
|
const pathUrl = _url.startsWith("/") ? _url : `/${_url}`;
|
|
2122
2154
|
let url = (baseUrl ?? "") + pathUrl;
|
|
2123
|
-
if (
|
|
2124
|
-
url = defaultPathSerializer({ path, url });
|
|
2155
|
+
if (path5) {
|
|
2156
|
+
url = defaultPathSerializer({ path: path5, url });
|
|
2125
2157
|
}
|
|
2126
2158
|
let search = query ? querySerializer(query) : "";
|
|
2127
2159
|
if (search.startsWith("?")) {
|
|
@@ -2140,9 +2172,9 @@ var mergeConfigs = (a, b) => {
|
|
|
2140
2172
|
config.headers = mergeHeaders(a.headers, b.headers);
|
|
2141
2173
|
return config;
|
|
2142
2174
|
};
|
|
2143
|
-
var mergeHeaders = (...
|
|
2175
|
+
var mergeHeaders = (...headers2) => {
|
|
2144
2176
|
const mergedHeaders = new Headers();
|
|
2145
|
-
for (const header of
|
|
2177
|
+
for (const header of headers2) {
|
|
2146
2178
|
if (!header || typeof header !== "object") {
|
|
2147
2179
|
continue;
|
|
2148
2180
|
}
|
|
@@ -2361,587 +2393,9 @@ var createClient = (config = {}) => {
|
|
|
2361
2393
|
// packages/experience-design-system-client/dist/src/generated/client.gen.js
|
|
2362
2394
|
var client = createClient(createConfig());
|
|
2363
2395
|
|
|
2364
|
-
// packages/experience-design-system-client/dist/src/generated/sdk.gen.js
|
|
2365
|
-
var designSystemImportSourcelessPreview = (options) => {
|
|
2366
|
-
return (options.client ?? client).post({
|
|
2367
|
-
url: "/spaces/{spaceId}/environments/{environmentId}/design_systems/imports/preview",
|
|
2368
|
-
...options,
|
|
2369
|
-
headers: {
|
|
2370
|
-
"Content-Type": "application/json",
|
|
2371
|
-
...options.headers
|
|
2372
|
-
}
|
|
2373
|
-
});
|
|
2374
|
-
};
|
|
2375
|
-
var designSystemImportApply = (options) => {
|
|
2376
|
-
return (options.client ?? client).post({
|
|
2377
|
-
url: "/spaces/{spaceId}/environments/{environmentId}/design_systems/imports/apply",
|
|
2378
|
-
...options,
|
|
2379
|
-
headers: {
|
|
2380
|
-
"Content-Type": "application/json",
|
|
2381
|
-
...options.headers
|
|
2382
|
-
}
|
|
2383
|
-
});
|
|
2384
|
-
};
|
|
2385
|
-
var designSystemImportGetOperation = (options) => {
|
|
2386
|
-
return (options.client ?? client).get({
|
|
2387
|
-
url: "/spaces/{spaceId}/environments/{environmentId}/design_systems/imports/apply/{operationId}",
|
|
2388
|
-
...options
|
|
2389
|
-
});
|
|
2390
|
-
};
|
|
2391
|
-
|
|
2392
|
-
// packages/experience-design-system-backend-pipeline/dist/src/steps/apply/src/constants/api.js
|
|
2393
|
-
var PREVIEW_ERROR_PREFIX = "preview failed:";
|
|
2394
|
-
var APPLY_ERROR_PREFIX = "apply failed:";
|
|
2395
|
-
var USER_AGENT = "experience-design-system-backend-pipeline";
|
|
2396
|
-
|
|
2397
|
-
// packages/experience-design-system-backend-pipeline/dist/src/steps/apply/src/helpers/http-utils.js
|
|
2398
|
-
var MAX_RETRY_AFTER_MS = 6e4;
|
|
2399
|
-
function isTransientStatus(status) {
|
|
2400
|
-
return status >= 500 && status <= 599;
|
|
2401
|
-
}
|
|
2402
|
-
function retryAfterMs(response) {
|
|
2403
|
-
const value = response.headers?.get("retry-after")?.trim();
|
|
2404
|
-
if (!value)
|
|
2405
|
-
return void 0;
|
|
2406
|
-
const seconds = Number(value);
|
|
2407
|
-
const delayMs = Number.isFinite(seconds) ? seconds * 1e3 : Date.parse(value) - Date.now();
|
|
2408
|
-
if (!Number.isFinite(delayMs) || delayMs < 0 || delayMs > MAX_RETRY_AFTER_MS)
|
|
2409
|
-
return void 0;
|
|
2410
|
-
return Math.round(delayMs);
|
|
2411
|
-
}
|
|
2412
|
-
function isApsDenialBody(body) {
|
|
2413
|
-
if (!body)
|
|
2414
|
-
return false;
|
|
2415
|
-
try {
|
|
2416
|
-
const parsed = JSON.parse(body);
|
|
2417
|
-
const id = parsed?.sys?.id;
|
|
2418
|
-
return id === "NotFound" || id === "AccessDenied";
|
|
2419
|
-
} catch {
|
|
2420
|
-
return false;
|
|
2421
|
-
}
|
|
2422
|
-
}
|
|
2423
|
-
function stringifyError(error) {
|
|
2424
|
-
return typeof error === "string" ? error : JSON.stringify(error ?? {});
|
|
2425
|
-
}
|
|
2426
|
-
function errorMessage(error) {
|
|
2427
|
-
return error instanceof Error && error.message ? error.message : String(error);
|
|
2428
|
-
}
|
|
2429
|
-
function defaultSleep(delayMs) {
|
|
2430
|
-
return new Promise((resolve2) => setTimeout(resolve2, delayMs));
|
|
2431
|
-
}
|
|
2432
|
-
|
|
2433
|
-
// packages/experience-design-system-backend-pipeline/dist/src/steps/apply/src/helpers/sanitize-preview.js
|
|
2434
|
-
var PROPERTY_BREAKING_REASONS = /* @__PURE__ */ new Set([
|
|
2435
|
-
"removed",
|
|
2436
|
-
"added_required_no_default",
|
|
2437
|
-
"type_changed",
|
|
2438
|
-
"validation_narrowed"
|
|
2439
|
-
]);
|
|
2440
|
-
var SLOT_BREAKING_REASONS = /* @__PURE__ */ new Set(["slot_removed", "slot_allowed_components_narrowed"]);
|
|
2441
|
-
function sanitizeBreakingChanges(raw) {
|
|
2442
|
-
if (!Array.isArray(raw))
|
|
2443
|
-
return [];
|
|
2444
|
-
const out = [];
|
|
2445
|
-
for (const bc of raw) {
|
|
2446
|
-
if (typeof bc !== "object" || bc === null)
|
|
2447
|
-
continue;
|
|
2448
|
-
const reason = bc.reason;
|
|
2449
|
-
if (typeof reason !== "string")
|
|
2450
|
-
continue;
|
|
2451
|
-
if ("propertyId" in bc && typeof bc.propertyId === "string") {
|
|
2452
|
-
if (PROPERTY_BREAKING_REASONS.has(reason))
|
|
2453
|
-
out.push(bc);
|
|
2454
|
-
continue;
|
|
2455
|
-
}
|
|
2456
|
-
if ("slotId" in bc && typeof bc.slotId === "string") {
|
|
2457
|
-
if (SLOT_BREAKING_REASONS.has(reason))
|
|
2458
|
-
out.push(bc);
|
|
2459
|
-
continue;
|
|
2460
|
-
}
|
|
2461
|
-
}
|
|
2462
|
-
return out;
|
|
2463
|
-
}
|
|
2464
|
-
function sanitizePreviewResponse(res) {
|
|
2465
|
-
for (const item of res.components?.changed ?? []) {
|
|
2466
|
-
const cc = item.changeClassification;
|
|
2467
|
-
if (cc && Array.isArray(cc.breakingChanges)) {
|
|
2468
|
-
cc.breakingChanges = sanitizeBreakingChanges(cc.breakingChanges);
|
|
2469
|
-
}
|
|
2470
|
-
}
|
|
2471
|
-
return res;
|
|
2472
|
-
}
|
|
2473
|
-
|
|
2474
|
-
// packages/experience-design-system-backend-pipeline/dist/src/steps/apply/src/helpers/host-utils.js
|
|
2475
|
-
var DEFAULT_API_HOST = "https://api.contentful.com";
|
|
2476
|
-
function trimTrailingSlashes(value) {
|
|
2477
|
-
return value.replace(/\/+$/, "");
|
|
2478
|
-
}
|
|
2479
|
-
function normalizeHostInput(host) {
|
|
2480
|
-
const value = host?.trim();
|
|
2481
|
-
if (!value)
|
|
2482
|
-
return void 0;
|
|
2483
|
-
return trimTrailingSlashes(value);
|
|
2484
|
-
}
|
|
2485
|
-
function toConfiguredHost2(host) {
|
|
2486
|
-
const normalized = normalizeHostInput(host);
|
|
2487
|
-
if (!normalized)
|
|
2488
|
-
return void 0;
|
|
2489
|
-
return normalized.replace(/^https:\/\//i, "");
|
|
2490
|
-
}
|
|
2491
|
-
function toApiHost2(host) {
|
|
2492
|
-
const normalized = normalizeHostInput(host);
|
|
2493
|
-
if (!normalized)
|
|
2494
|
-
return DEFAULT_API_HOST;
|
|
2495
|
-
if (/^[a-z][a-z\d+\-.]*:\/\//i.test(normalized))
|
|
2496
|
-
return normalized;
|
|
2497
|
-
return `https://${normalized}`;
|
|
2498
|
-
}
|
|
2499
|
-
|
|
2500
|
-
// packages/experience-design-system-backend-pipeline/dist/src/steps/apply/src/types/api-error.js
|
|
2501
|
-
var ERROR_BODY_LOG_CAP = 16384;
|
|
2502
|
-
var ApiError = class extends Error {
|
|
2503
|
-
status;
|
|
2504
|
-
body;
|
|
2505
|
-
guidance;
|
|
2506
|
-
constructor(message, status, body, guidance) {
|
|
2507
|
-
super(message);
|
|
2508
|
-
this.status = status;
|
|
2509
|
-
this.body = body;
|
|
2510
|
-
this.guidance = guidance;
|
|
2511
|
-
if (body) {
|
|
2512
|
-
const trimmed = body.length > ERROR_BODY_LOG_CAP ? body.slice(0, ERROR_BODY_LOG_CAP) + "\u2026" : body;
|
|
2513
|
-
this.message = `${message}
|
|
2514
|
-
${trimmed}`;
|
|
2515
|
-
}
|
|
2516
|
-
}
|
|
2517
|
-
};
|
|
2518
|
-
|
|
2519
|
-
// packages/experience-design-system-backend-pipeline/dist/src/steps/apply/src/helpers/api-client/api-client.js
|
|
2520
|
-
var ApiClient = class {
|
|
2521
|
-
host;
|
|
2522
|
-
token;
|
|
2523
|
-
spaceId;
|
|
2524
|
-
environmentId;
|
|
2525
|
-
retry;
|
|
2526
|
-
lastRequestId;
|
|
2527
|
-
constructor(opts) {
|
|
2528
|
-
this.host = toApiHost2(opts.host);
|
|
2529
|
-
this.token = opts.cmaToken;
|
|
2530
|
-
this.spaceId = opts.spaceId;
|
|
2531
|
-
this.environmentId = opts.environmentId;
|
|
2532
|
-
const initialDelayMs = Math.max(0, opts.retry?.initialDelayMs ?? 250);
|
|
2533
|
-
this.retry = {
|
|
2534
|
-
maxAttempts: Math.max(1, Math.floor(opts.retry?.maxAttempts ?? 3)),
|
|
2535
|
-
initialDelayMs,
|
|
2536
|
-
maxDelayMs: Math.max(initialDelayMs, opts.retry?.maxDelayMs ?? 2e3),
|
|
2537
|
-
sleep: opts.retry?.sleep ?? defaultSleep
|
|
2538
|
-
};
|
|
2539
|
-
}
|
|
2540
|
-
getLastRequestId() {
|
|
2541
|
-
return this.lastRequestId;
|
|
2542
|
-
}
|
|
2543
|
-
noteRequestId(response) {
|
|
2544
|
-
const id = response.headers.get("x-contentful-request-id");
|
|
2545
|
-
if (id)
|
|
2546
|
-
this.lastRequestId = id;
|
|
2547
|
-
}
|
|
2548
|
-
headers() {
|
|
2549
|
-
return {
|
|
2550
|
-
Authorization: `Bearer ${this.token}`,
|
|
2551
|
-
"Content-Type": "application/json",
|
|
2552
|
-
"X-Contentful-User-Agent": USER_AGENT
|
|
2553
|
-
};
|
|
2554
|
-
}
|
|
2555
|
-
async requestWithRetry(phase, errorPrefix, makeCall) {
|
|
2556
|
-
for (let attempt = 1; attempt <= this.retry.maxAttempts; attempt++) {
|
|
2557
|
-
let result;
|
|
2558
|
-
try {
|
|
2559
|
-
result = await makeCall();
|
|
2560
|
-
} catch (error) {
|
|
2561
|
-
if (attempt === this.retry.maxAttempts) {
|
|
2562
|
-
const body = `The request failed after ${attempt} attempts because of a network error: ${errorMessage(error)}. Check your connection and try again.`;
|
|
2563
|
-
throw new ApiError(`${errorPrefix} 0`, 0, body);
|
|
2564
|
-
}
|
|
2565
|
-
await this.retry.sleep(Math.min(this.retry.initialDelayMs * 2 ** (attempt - 1), this.retry.maxDelayMs));
|
|
2566
|
-
continue;
|
|
2567
|
-
}
|
|
2568
|
-
if (!isTransientStatus(result.response.status)) {
|
|
2569
|
-
this.noteRequestId(result.response);
|
|
2570
|
-
return result;
|
|
2571
|
-
}
|
|
2572
|
-
if (attempt === this.retry.maxAttempts) {
|
|
2573
|
-
const body = stringifyError(result.error);
|
|
2574
|
-
const guidance = `The ${phase} request failed after ${attempt} attempts because the service remained unavailable. Wait a moment and try again.`;
|
|
2575
|
-
throw new ApiError(`${errorPrefix} ${result.response.status}`, result.response.status, body, guidance);
|
|
2576
|
-
}
|
|
2577
|
-
const backoffMs = Math.min(this.retry.initialDelayMs * 2 ** (attempt - 1), this.retry.maxDelayMs);
|
|
2578
|
-
await this.retry.sleep(retryAfterMs(result.response) ?? backoffMs);
|
|
2579
|
-
}
|
|
2580
|
-
throw new Error("Retry attempts exhausted");
|
|
2581
|
-
}
|
|
2582
|
-
async validateToken() {
|
|
2583
|
-
const res = await fetch(`${this.host}/users/me`, { headers: this.headers() });
|
|
2584
|
-
this.noteRequestId(res);
|
|
2585
|
-
if (res.status === 401) {
|
|
2586
|
-
throw new ApiError("CMA token is invalid or revoked", res.status, await res.text());
|
|
2587
|
-
}
|
|
2588
|
-
if (!res.ok) {
|
|
2589
|
-
throw new ApiError(`unexpected error validating token: ${res.status}`, res.status, await res.text());
|
|
2590
|
-
}
|
|
2591
|
-
await this.checkPreflight();
|
|
2592
|
-
}
|
|
2593
|
-
async checkPreflight() {
|
|
2594
|
-
const url = `${this.host}/spaces/${this.spaceId}/environments/${this.environmentId}/design_systems/preflight`;
|
|
2595
|
-
let res;
|
|
2596
|
-
try {
|
|
2597
|
-
res = await fetch(url, { headers: this.headers() });
|
|
2598
|
-
} catch {
|
|
2599
|
-
return;
|
|
2600
|
-
}
|
|
2601
|
-
this.noteRequestId(res);
|
|
2602
|
-
if (res.ok)
|
|
2603
|
-
return;
|
|
2604
|
-
if (res.status >= 500)
|
|
2605
|
-
return;
|
|
2606
|
-
const body = await res.text();
|
|
2607
|
-
if (res.status === 404 && !isApsDenialBody(body))
|
|
2608
|
-
return;
|
|
2609
|
-
throw new ApiError(`preflight failed: ${res.status}`, res.status, body);
|
|
2610
|
-
}
|
|
2611
|
-
async previewImport(cdf) {
|
|
2612
|
-
const result = await this.requestWithRetry("preview", PREVIEW_ERROR_PREFIX, () => designSystemImportSourcelessPreview({
|
|
2613
|
-
baseUrl: this.host,
|
|
2614
|
-
headers: this.headers(),
|
|
2615
|
-
path: { spaceId: this.spaceId, environmentId: this.environmentId },
|
|
2616
|
-
body: cdf,
|
|
2617
|
-
parseAs: "json"
|
|
2618
|
-
}));
|
|
2619
|
-
if (!result.response.ok) {
|
|
2620
|
-
throw new ApiError(`${PREVIEW_ERROR_PREFIX} ${result.response.status}`, result.response.status, stringifyError(result.error));
|
|
2621
|
-
}
|
|
2622
|
-
return sanitizePreviewResponse(result.data);
|
|
2623
|
-
}
|
|
2624
|
-
async applyImport(cdf, opts) {
|
|
2625
|
-
let result;
|
|
2626
|
-
try {
|
|
2627
|
-
result = await designSystemImportApply({
|
|
2628
|
-
baseUrl: this.host,
|
|
2629
|
-
headers: this.headers(),
|
|
2630
|
-
path: { spaceId: this.spaceId, environmentId: this.environmentId },
|
|
2631
|
-
body: { ...cdf, acknowledgeBreakingChanges: opts.acknowledgeBreakingChanges },
|
|
2632
|
-
parseAs: "json"
|
|
2633
|
-
});
|
|
2634
|
-
} catch (error) {
|
|
2635
|
-
const body = `The apply request was not retried because its outcome is unknown and retrying could start a duplicate operation. Cause: ${errorMessage(error)}`;
|
|
2636
|
-
throw new ApiError(`${APPLY_ERROR_PREFIX} 0`, 0, body);
|
|
2637
|
-
}
|
|
2638
|
-
if (!result.response.ok) {
|
|
2639
|
-
const body = stringifyError(result.error);
|
|
2640
|
-
const guidance = isTransientStatus(result.response.status) ? "The apply request was not retried because the server may already have started an operation and retrying could create a duplicate." : void 0;
|
|
2641
|
-
throw new ApiError(`${APPLY_ERROR_PREFIX} ${result.response.status}`, result.response.status, body, guidance);
|
|
2642
|
-
}
|
|
2643
|
-
this.noteRequestId(result.response);
|
|
2644
|
-
return result.data;
|
|
2645
|
-
}
|
|
2646
|
-
async pollOperation(operationId, opts = {}) {
|
|
2647
|
-
const intervalMs = opts.intervalMs ?? 2e3;
|
|
2648
|
-
const maxIntervalMs = opts.maxIntervalMs ?? Math.round(intervalMs * 2.5);
|
|
2649
|
-
const maxAttempts = opts.maxAttempts ?? 150;
|
|
2650
|
-
const terminalStatuses = /* @__PURE__ */ new Set(["succeeded", "partial", "failed"]);
|
|
2651
|
-
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
2652
|
-
const result = await this.requestWithRetry("poll", "poll failed:", () => designSystemImportGetOperation({
|
|
2653
|
-
baseUrl: this.host,
|
|
2654
|
-
headers: this.headers(),
|
|
2655
|
-
path: { spaceId: this.spaceId, environmentId: this.environmentId, operationId },
|
|
2656
|
-
parseAs: "json"
|
|
2657
|
-
}));
|
|
2658
|
-
if (!result.response.ok) {
|
|
2659
|
-
throw new ApiError(`poll failed: ${result.response.status}`, result.response.status, stringifyError(result.error));
|
|
2660
|
-
}
|
|
2661
|
-
const op = result.data;
|
|
2662
|
-
opts.onProgress?.(op);
|
|
2663
|
-
if (terminalStatuses.has(op.sys.status))
|
|
2664
|
-
return op;
|
|
2665
|
-
if (attempt < maxAttempts - 1) {
|
|
2666
|
-
const progress = maxAttempts > 1 ? attempt / (maxAttempts - 1) : 0;
|
|
2667
|
-
const baseDelay = intervalMs + (maxIntervalMs - intervalMs) * progress;
|
|
2668
|
-
const jitter = Math.random() * baseDelay * 0.15;
|
|
2669
|
-
await new Promise((resolve2) => setTimeout(resolve2, baseDelay + jitter));
|
|
2670
|
-
}
|
|
2671
|
-
}
|
|
2672
|
-
throw new Error(`Operation ${operationId} timed out after ${maxAttempts} attempts`);
|
|
2673
|
-
}
|
|
2674
|
-
};
|
|
2675
|
-
|
|
2676
2396
|
// packages/experience-design-system-backend-pipeline/dist/src/steps/apply/src/controller/validation/assert-no-unresolved-slot-references.js
|
|
2677
2397
|
init_shared();
|
|
2678
2398
|
|
|
2679
|
-
// packages/experience-design-system-backend-pipeline/dist/src/steps/apply/src/helpers/edsi-errors/parse-binding-diagnostics.js
|
|
2680
|
-
function parseBindingDiagnostics(body) {
|
|
2681
|
-
if (!/binding configurations are invalid|Pointer path does not exist/i.test(body))
|
|
2682
|
-
return null;
|
|
2683
|
-
const locationMatches = [...body.matchAll(/(?:^|\.\s)([A-Za-z0-9_$-]+(?:\s*›\s*[A-Za-z0-9_$-]+){2,})/g)];
|
|
2684
|
-
const location = locationMatches.at(-1)?.[1];
|
|
2685
|
-
const pointerMessage = body.match(/Pointer path does not exist for '\[object Object\]':\s*(.+?)(?=\.\s*Default\s*›|$)/i)?.[1];
|
|
2686
|
-
const graphQlMessage = body.match(/return GraphQL validation error:\s*(.+?)(?=\.\s*Default\s*›|$)/i)?.[1];
|
|
2687
|
-
const heading = body.match(/^(.*?)(?=\.?\s*Pointer path does not exist|\.?\s*Default\s*›)/i)?.[1]?.trim();
|
|
2688
|
-
const diagnostics = [];
|
|
2689
|
-
if (pointerMessage)
|
|
2690
|
-
diagnostics.push({ message: pointerMessage.trim(), ...location ? { path: location } : {} });
|
|
2691
|
-
if (graphQlMessage)
|
|
2692
|
-
diagnostics.push({ message: graphQlMessage.trim(), ...location ? { path: location } : {} });
|
|
2693
|
-
return {
|
|
2694
|
-
code: "BindingValidationFailed",
|
|
2695
|
-
message: heading || "One or more binding configurations are invalid.",
|
|
2696
|
-
...diagnostics.length > 0 ? { diagnostics } : {}
|
|
2697
|
-
};
|
|
2698
|
-
}
|
|
2699
|
-
|
|
2700
|
-
// packages/experience-design-system-backend-pipeline/dist/src/steps/apply/src/helpers/edsi-errors/helpers.js
|
|
2701
|
-
function asRecord(value) {
|
|
2702
|
-
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
2703
|
-
}
|
|
2704
|
-
function displayValue(value) {
|
|
2705
|
-
if (typeof value === "string")
|
|
2706
|
-
return value;
|
|
2707
|
-
if (typeof value === "number" || typeof value === "boolean")
|
|
2708
|
-
return String(value);
|
|
2709
|
-
if (value === null || value === void 0)
|
|
2710
|
-
return null;
|
|
2711
|
-
try {
|
|
2712
|
-
return JSON.stringify(value);
|
|
2713
|
-
} catch {
|
|
2714
|
-
return null;
|
|
2715
|
-
}
|
|
2716
|
-
}
|
|
2717
|
-
function isPathSegment(value) {
|
|
2718
|
-
return typeof value === "string" || typeof value === "number" && Number.isFinite(value);
|
|
2719
|
-
}
|
|
2720
|
-
function formatPath(value) {
|
|
2721
|
-
if (isPathSegment(value))
|
|
2722
|
-
return String(value) ? { path: String(value) } : { path: null };
|
|
2723
|
-
if (Array.isArray(value)) {
|
|
2724
|
-
const parts = value.filter(isPathSegment);
|
|
2725
|
-
return parts.length > 0 ? { path: parts.join(" \u203A ") } : { path: null };
|
|
2726
|
-
}
|
|
2727
|
-
return {};
|
|
2728
|
-
}
|
|
2729
|
-
function componentFromPath(path) {
|
|
2730
|
-
if (!path)
|
|
2731
|
-
return void 0;
|
|
2732
|
-
const manifestMatch = path.match(/manifest:components\/([^/›]+)/);
|
|
2733
|
-
if (manifestMatch?.[1])
|
|
2734
|
-
return manifestMatch[1];
|
|
2735
|
-
const parts = path.split(/[›/]/).map((part) => part.trim()).filter(Boolean);
|
|
2736
|
-
const componentsIndex = parts.findIndex((part) => part === "components");
|
|
2737
|
-
return componentsIndex === -1 ? void 0 : parts[componentsIndex + 1];
|
|
2738
|
-
}
|
|
2739
|
-
function diagnosticMessage(error) {
|
|
2740
|
-
const candidates = [
|
|
2741
|
-
["message", error.message],
|
|
2742
|
-
["details", error.details],
|
|
2743
|
-
["error", error.error],
|
|
2744
|
-
["name", error.name]
|
|
2745
|
-
];
|
|
2746
|
-
for (const [messageSource, value] of candidates) {
|
|
2747
|
-
const message = displayValue(value);
|
|
2748
|
-
if (message !== null)
|
|
2749
|
-
return { message, messageSource };
|
|
2750
|
-
}
|
|
2751
|
-
return { message: "Validation failed" };
|
|
2752
|
-
}
|
|
2753
|
-
function parseObjectLiteralTail(body) {
|
|
2754
|
-
const braceStart = body.lastIndexOf("{");
|
|
2755
|
-
if (braceStart === -1)
|
|
2756
|
-
return null;
|
|
2757
|
-
const tail = body.slice(braceStart);
|
|
2758
|
-
const codeMatch = tail.match(/code:\s*['"]([^'"]+)['"]/);
|
|
2759
|
-
const cycleMatch = tail.match(/cycle:\s*\[\s*([^\]]*)\s*\]/);
|
|
2760
|
-
if (!codeMatch && !cycleMatch)
|
|
2761
|
-
return null;
|
|
2762
|
-
const code = codeMatch ? codeMatch[1] : null;
|
|
2763
|
-
let cycle = null;
|
|
2764
|
-
if (cycleMatch) {
|
|
2765
|
-
cycle = cycleMatch[1].split(",").map((s) => s.trim().replace(/^['"]|['"]$/g, "")).filter((s) => s.length > 0);
|
|
2766
|
-
if (cycle.length === 0)
|
|
2767
|
-
cycle = null;
|
|
2768
|
-
}
|
|
2769
|
-
return { code, cycle };
|
|
2770
|
-
}
|
|
2771
|
-
|
|
2772
|
-
// packages/experience-design-system-backend-pipeline/dist/src/steps/apply/src/helpers/edsi-errors/parse-json-body.js
|
|
2773
|
-
function parseValidationDiagnostics(details) {
|
|
2774
|
-
if (!Array.isArray(details.errors))
|
|
2775
|
-
return [];
|
|
2776
|
-
const diagnostics = [];
|
|
2777
|
-
for (const rawError of details.errors) {
|
|
2778
|
-
const error = asRecord(rawError);
|
|
2779
|
-
if (!error)
|
|
2780
|
-
continue;
|
|
2781
|
-
const location = formatPath(error.path);
|
|
2782
|
-
const component = displayValue(error.component ?? error.componentName ?? error.componentId) ?? componentFromPath(location.path);
|
|
2783
|
-
diagnostics.push({ ...diagnosticMessage(error), ...location, ...component ? { component } : {} });
|
|
2784
|
-
}
|
|
2785
|
-
return diagnostics;
|
|
2786
|
-
}
|
|
2787
|
-
function parseJsonBody(body) {
|
|
2788
|
-
let parsed;
|
|
2789
|
-
try {
|
|
2790
|
-
parsed = JSON.parse(body);
|
|
2791
|
-
} catch {
|
|
2792
|
-
return null;
|
|
2793
|
-
}
|
|
2794
|
-
const p = asRecord(parsed);
|
|
2795
|
-
if (!p)
|
|
2796
|
-
return null;
|
|
2797
|
-
const details = asRecord(p.details) ?? {};
|
|
2798
|
-
const pick = (k) => p[k] !== void 0 ? p[k] : details[k];
|
|
2799
|
-
const codeRaw = pick("code") ?? asRecord(p.sys)?.id;
|
|
2800
|
-
const messageRaw = pick("message");
|
|
2801
|
-
const cycleRaw = pick("cycle");
|
|
2802
|
-
const out = {};
|
|
2803
|
-
if (typeof codeRaw === "string")
|
|
2804
|
-
out.code = codeRaw;
|
|
2805
|
-
if (typeof messageRaw === "string")
|
|
2806
|
-
out.message = messageRaw;
|
|
2807
|
-
if (Array.isArray(cycleRaw)) {
|
|
2808
|
-
const strs = cycleRaw.filter((x) => typeof x === "string");
|
|
2809
|
-
if (strs.length > 0)
|
|
2810
|
-
out.cycle = strs;
|
|
2811
|
-
}
|
|
2812
|
-
const diagnostics = parseValidationDiagnostics(details);
|
|
2813
|
-
if (diagnostics.length > 0)
|
|
2814
|
-
out.diagnostics = diagnostics;
|
|
2815
|
-
return out;
|
|
2816
|
-
}
|
|
2817
|
-
|
|
2818
|
-
// packages/experience-design-system-backend-pipeline/dist/src/steps/apply/src/helpers/edsi-errors/strip-lambda-log-prefix.js
|
|
2819
|
-
var LAMBDA_LOG_PREFIX_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z\s+[0-9a-f-]+\s+ERROR\s+(?:\[dd\.[^\]]*\]\s*)?/;
|
|
2820
|
-
var DD_TAG_RE = /\[dd\.(?:trace_id|span_id)=[^\]]*\]\s*/g;
|
|
2821
|
-
function stripLambdaLogPrefix(body) {
|
|
2822
|
-
return body.replace(LAMBDA_LOG_PREFIX_RE, "").replace(DD_TAG_RE, "").trim();
|
|
2823
|
-
}
|
|
2824
|
-
|
|
2825
|
-
// packages/experience-design-system-backend-pipeline/dist/src/steps/apply/src/helpers/edsi-errors/parse-edsi-error.js
|
|
2826
|
-
var API_ERROR_PREFIX_RE = /^(?:apply|preview|poll) failed: \d+$/;
|
|
2827
|
-
function parseEdsiError(rawInput) {
|
|
2828
|
-
if (!rawInput)
|
|
2829
|
-
return { code: null, message: "", cycle: null, raw: true };
|
|
2830
|
-
const { body, prefix } = splitPhasePrefix(rawInput);
|
|
2831
|
-
const cleaned = stripLambdaLogPrefix(body);
|
|
2832
|
-
const json = parseJsonBody(cleaned) ?? parseJsonBody(body);
|
|
2833
|
-
const bindingFromJson = typeof json?.message === "string" ? parseBindingDiagnostics(json.message) : null;
|
|
2834
|
-
if (bindingFromJson) {
|
|
2835
|
-
return {
|
|
2836
|
-
code: bindingFromJson.code ?? json?.code ?? null,
|
|
2837
|
-
message: bindingFromJson.message ?? cleaned,
|
|
2838
|
-
cycle: null,
|
|
2839
|
-
raw: false,
|
|
2840
|
-
...bindingFromJson.diagnostics ? { diagnostics: bindingFromJson.diagnostics } : {}
|
|
2841
|
-
};
|
|
2842
|
-
}
|
|
2843
|
-
if (json && (json.code || json.message || json.cycle)) {
|
|
2844
|
-
return {
|
|
2845
|
-
code: json.code ?? null,
|
|
2846
|
-
message: json.message ?? (cleaned || prefix),
|
|
2847
|
-
cycle: json.cycle ?? null,
|
|
2848
|
-
raw: false,
|
|
2849
|
-
...json.diagnostics ? { diagnostics: json.diagnostics } : {}
|
|
2850
|
-
};
|
|
2851
|
-
}
|
|
2852
|
-
const binding = parseBindingDiagnostics(cleaned);
|
|
2853
|
-
if (binding) {
|
|
2854
|
-
return {
|
|
2855
|
-
code: binding.code ?? null,
|
|
2856
|
-
message: binding.message ?? cleaned,
|
|
2857
|
-
cycle: null,
|
|
2858
|
-
raw: false,
|
|
2859
|
-
...binding.diagnostics ? { diagnostics: binding.diagnostics } : {}
|
|
2860
|
-
};
|
|
2861
|
-
}
|
|
2862
|
-
const literal2 = parseObjectLiteralTail(cleaned);
|
|
2863
|
-
if (literal2 && (literal2.code || literal2.cycle)) {
|
|
2864
|
-
const braceStart = cleaned.lastIndexOf("{");
|
|
2865
|
-
const head = braceStart === -1 ? cleaned : cleaned.slice(0, braceStart).trim();
|
|
2866
|
-
return { code: literal2.code ?? null, message: head || cleaned, cycle: literal2.cycle ?? null, raw: false };
|
|
2867
|
-
}
|
|
2868
|
-
return { code: null, message: cleaned || prefix || rawInput, cycle: null, raw: true };
|
|
2869
|
-
}
|
|
2870
|
-
function splitPhasePrefix(input) {
|
|
2871
|
-
const nlIndex = input.indexOf("\n");
|
|
2872
|
-
if (nlIndex === -1)
|
|
2873
|
-
return { body: input, prefix: "" };
|
|
2874
|
-
const firstLine = input.slice(0, nlIndex);
|
|
2875
|
-
if (!API_ERROR_PREFIX_RE.test(firstLine))
|
|
2876
|
-
return { body: input, prefix: "" };
|
|
2877
|
-
return { body: input.slice(nlIndex + 1), prefix: firstLine };
|
|
2878
|
-
}
|
|
2879
|
-
|
|
2880
|
-
// packages/experience-design-system-backend-pipeline/dist/src/steps/apply/src/helpers/edsi-errors/format-parsed-edsi-error.js
|
|
2881
|
-
function formatParsedEdsiError(parsed, opts = {}) {
|
|
2882
|
-
const lines = [];
|
|
2883
|
-
if (parsed.code)
|
|
2884
|
-
lines.push(`[${parsed.code}]`);
|
|
2885
|
-
if (parsed.message)
|
|
2886
|
-
lines.push(parsed.message);
|
|
2887
|
-
for (const diagnostic of parsed.diagnostics ?? []) {
|
|
2888
|
-
const context = [
|
|
2889
|
-
diagnostic.component ? `Component: ${diagnostic.component}` : null,
|
|
2890
|
-
diagnostic.path ? `Path: ${diagnostic.path}` : null
|
|
2891
|
-
].filter(Boolean);
|
|
2892
|
-
lines.push(`- ${diagnostic.message}${context.length > 0 ? ` (${context.join("; ")})` : ""}`);
|
|
2893
|
-
if (diagnostic.path === null) {
|
|
2894
|
-
lines.push(" Location: not provided by the server. Review the submitted CDF file; no component or field was identified.");
|
|
2895
|
-
}
|
|
2896
|
-
if (opts.verbose && diagnostic.messageSource) {
|
|
2897
|
-
lines.push(` Message field: ${diagnostic.messageSource}`);
|
|
2898
|
-
}
|
|
2899
|
-
}
|
|
2900
|
-
if (parsed.code === "BindingValidationFailed") {
|
|
2901
|
-
const location = parsed.diagnostics?.find((d) => d.path)?.path;
|
|
2902
|
-
lines.push(location ? `Next action: review the binding at ${location} and correct its pointer or GraphQL selection.` : "Next action: review the binding pointer and GraphQL selection.");
|
|
2903
|
-
}
|
|
2904
|
-
if (parsed.code === "AccessDenied") {
|
|
2905
|
-
lines.push("Next action: this operation requires DesignToken and Component permissions in this environment. Ask a Contentful admin to grant both on your role.");
|
|
2906
|
-
}
|
|
2907
|
-
if (parsed.code === "NotFound") {
|
|
2908
|
-
lines.push("Next action: verify the resource id is correct. If it should exist, your role may lack read permission on DesignToken or Component.");
|
|
2909
|
-
}
|
|
2910
|
-
if (parsed.cycle && parsed.cycle.length > 0) {
|
|
2911
|
-
lines.push(`Cycle: ${parsed.cycle.join(" \u2192 ")} \u2192 ${parsed.cycle[0]}`);
|
|
2912
|
-
lines.push("Break the cycle by removing at least one $allowedComponents entry.");
|
|
2913
|
-
}
|
|
2914
|
-
if (opts.verbose && opts.raw) {
|
|
2915
|
-
lines.push("");
|
|
2916
|
-
lines.push("--- raw ---");
|
|
2917
|
-
lines.push(opts.raw);
|
|
2918
|
-
}
|
|
2919
|
-
return lines.filter(Boolean).join("\n");
|
|
2920
|
-
}
|
|
2921
|
-
|
|
2922
|
-
// packages/experience-design-system-backend-pipeline/dist/src/steps/apply/src/helpers/edsi-errors/format-edsi-error.js
|
|
2923
|
-
function formatEdsiError(raw, opts = {}) {
|
|
2924
|
-
const input = typeof raw === "string" ? raw : displayValue(raw);
|
|
2925
|
-
if (!input)
|
|
2926
|
-
return "";
|
|
2927
|
-
return formatParsedEdsiError(parseEdsiError(input), { ...opts, raw: opts.raw ?? input });
|
|
2928
|
-
}
|
|
2929
|
-
|
|
2930
|
-
// packages/experience-design-system-backend-pipeline/dist/src/steps/apply/src/helpers/edsi-errors/format-api-error.js
|
|
2931
|
-
function formatApiError(error, verbose = false) {
|
|
2932
|
-
const formatted = formatEdsiError(error.body || error.message, { verbose, raw: error.body }) || error.message;
|
|
2933
|
-
const phase = error.message.split("\n", 1)[0];
|
|
2934
|
-
const withPhase = /^(?:apply|preview|poll) failed: \d+$/.test(phase) && formatted !== phase ? `${phase}
|
|
2935
|
-
${formatted}` : formatted;
|
|
2936
|
-
return error.guidance ? `${withPhase}
|
|
2937
|
-
${error.guidance}` : withPhase;
|
|
2938
|
-
}
|
|
2939
|
-
|
|
2940
|
-
// packages/experience-design-system-backend-pipeline/dist/src/steps/apply/src/controller/errors/format-api-error.js
|
|
2941
|
-
function formatApiError2(request) {
|
|
2942
|
-
return formatApiError(request.error, request.verbose ?? false);
|
|
2943
|
-
}
|
|
2944
|
-
|
|
2945
2399
|
// packages/experience-design-system-backend-pipeline/dist/src/steps/apply/src/helpers/tokens/read-tokens-from-path.js
|
|
2946
2400
|
init_shared();
|
|
2947
2401
|
|
|
@@ -2974,8 +2428,134 @@ var execFileAsync = promisify(execFile);
|
|
|
2974
2428
|
// packages/experience-design-system-backend-pipeline/dist/src/steps/generation/src/controller/map-tokens/run-map-tokens.js
|
|
2975
2429
|
var DEFAULT_TIMEOUT_MS2 = 5 * 60 * 1e3;
|
|
2976
2430
|
|
|
2431
|
+
// packages/experience-design-system-cli/src/tui/import/steps/03-path-validation/helpers/fold-svelte-and-md-into-other.ts
|
|
2432
|
+
function foldSvelteAndMdIntoOther(counts) {
|
|
2433
|
+
return {
|
|
2434
|
+
tsx: counts.tsx,
|
|
2435
|
+
ts: counts.ts,
|
|
2436
|
+
vue: counts.vue,
|
|
2437
|
+
astro: counts.astro,
|
|
2438
|
+
jsx: counts.jsx,
|
|
2439
|
+
js: counts.js,
|
|
2440
|
+
json: counts.json,
|
|
2441
|
+
other: counts.other + counts.svelte + counts.md,
|
|
2442
|
+
total: counts.total
|
|
2443
|
+
};
|
|
2444
|
+
}
|
|
2445
|
+
|
|
2446
|
+
// packages/experience-design-system-cli/src/tui/import/steps/03-path-validation/loader.ts
|
|
2447
|
+
async function scanProject(directory) {
|
|
2448
|
+
const outcome = collectFiles(directory);
|
|
2449
|
+
return toScanResult(outcome);
|
|
2450
|
+
}
|
|
2451
|
+
function toScanResult(outcome) {
|
|
2452
|
+
if (!outcome.ok) {
|
|
2453
|
+
return { ok: false, failure: outcome.failure };
|
|
2454
|
+
}
|
|
2455
|
+
return {
|
|
2456
|
+
ok: true,
|
|
2457
|
+
counts: foldSvelteAndMdIntoOther(outcome.result.counts),
|
|
2458
|
+
filePaths: outcome.result.filePaths,
|
|
2459
|
+
warnings: outcome.result.warnings
|
|
2460
|
+
};
|
|
2461
|
+
}
|
|
2462
|
+
|
|
2463
|
+
// packages/experience-design-system-cli/src/tui/import/steps/03-path-validation/screen.tsx
|
|
2464
|
+
import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
2465
|
+
function Row({ row }) {
|
|
2466
|
+
return /* @__PURE__ */ jsxs5(Text6, { color: row.tone === "accent" ? PALETTE.accent : void 0, dimColor: row.tone === "muted", children: [
|
|
2467
|
+
" \u2022 ",
|
|
2468
|
+
String(row.count).padStart(3),
|
|
2469
|
+
" ",
|
|
2470
|
+
row.label
|
|
2471
|
+
] });
|
|
2472
|
+
}
|
|
2473
|
+
function Failure({ scan, path: path5 }) {
|
|
2474
|
+
const { headline, hints } = describeFailure(scan.failure, path5);
|
|
2475
|
+
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
|
|
2476
|
+
/* @__PURE__ */ jsxs5(Text6, { color: PALETTE.error, children: [
|
|
2477
|
+
"\u2717 ",
|
|
2478
|
+
headline
|
|
2479
|
+
] }),
|
|
2480
|
+
hints.map((hint) => /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: hint }, hint))
|
|
2481
|
+
] });
|
|
2482
|
+
}
|
|
2483
|
+
function Summary({ scan, path: path5 }) {
|
|
2484
|
+
const { total, rows, warning } = summarize(scan.counts);
|
|
2485
|
+
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", gap: 1, children: [
|
|
2486
|
+
/* @__PURE__ */ jsxs5(Text6, { children: [
|
|
2487
|
+
"Scanned ",
|
|
2488
|
+
/* @__PURE__ */ jsx6(Text6, { bold: true, children: path5 })
|
|
2489
|
+
] }),
|
|
2490
|
+
/* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
|
|
2491
|
+
/* @__PURE__ */ jsxs5(Text6, { color: PALETTE.success, children: [
|
|
2492
|
+
"\u2713 Found ",
|
|
2493
|
+
total,
|
|
2494
|
+
" files:"
|
|
2495
|
+
] }),
|
|
2496
|
+
rows.map((row) => /* @__PURE__ */ jsx6(Row, { row }, row.label))
|
|
2497
|
+
] }),
|
|
2498
|
+
warning && /* @__PURE__ */ jsxs5(Text6, { color: PALETTE.warning, children: [
|
|
2499
|
+
"\u26A0 ",
|
|
2500
|
+
warning
|
|
2501
|
+
] }),
|
|
2502
|
+
/* @__PURE__ */ jsx6(Text6, { children: "Does this look right?" })
|
|
2503
|
+
] });
|
|
2504
|
+
}
|
|
2505
|
+
function PathValidationScreen({
|
|
2506
|
+
projectPath,
|
|
2507
|
+
onConfirm,
|
|
2508
|
+
onChangePath,
|
|
2509
|
+
onBack
|
|
2510
|
+
}) {
|
|
2511
|
+
const resolvedPath = useMemo(() => resolveUserPath(projectPath), [projectPath]);
|
|
2512
|
+
const [scan, setScan] = useState5();
|
|
2513
|
+
useEffect3(() => {
|
|
2514
|
+
let cancelled = false;
|
|
2515
|
+
setScan(void 0);
|
|
2516
|
+
void scanProject(resolvedPath).then((result) => {
|
|
2517
|
+
if (!cancelled) setScan(result);
|
|
2518
|
+
});
|
|
2519
|
+
return () => {
|
|
2520
|
+
cancelled = true;
|
|
2521
|
+
};
|
|
2522
|
+
}, [resolvedPath]);
|
|
2523
|
+
const phase = phaseOf(scan);
|
|
2524
|
+
usePathValidationControls(phase, {
|
|
2525
|
+
onConfirm: () => {
|
|
2526
|
+
if (scan?.ok) onConfirm({ projectPath: resolvedPath, filePaths: scan.filePaths });
|
|
2527
|
+
},
|
|
2528
|
+
onChangePath,
|
|
2529
|
+
onBack
|
|
2530
|
+
});
|
|
2531
|
+
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", gap: 1, paddingX: 2, paddingY: 1, children: [
|
|
2532
|
+
scan === void 0 && /* @__PURE__ */ jsxs5(Text6, { dimColor: true, children: [
|
|
2533
|
+
"Scanning ",
|
|
2534
|
+
/* @__PURE__ */ jsx6(Text6, { bold: true, children: resolvedPath }),
|
|
2535
|
+
"..."
|
|
2536
|
+
] }),
|
|
2537
|
+
scan?.ok === false && /* @__PURE__ */ jsx6(Failure, { scan, path: resolvedPath }),
|
|
2538
|
+
scan?.ok === true && /* @__PURE__ */ jsx6(Summary, { scan, path: resolvedPath }),
|
|
2539
|
+
/* @__PURE__ */ jsx6(Box5, { marginTop: 1, gap: 3, children: controlsFor(phase).map((control) => /* @__PURE__ */ jsxs5(Text6, { dimColor: true, children: [
|
|
2540
|
+
"[",
|
|
2541
|
+
control.keys,
|
|
2542
|
+
"] ",
|
|
2543
|
+
control.label
|
|
2544
|
+
] }, control.keys)) })
|
|
2545
|
+
] });
|
|
2546
|
+
}
|
|
2547
|
+
|
|
2548
|
+
// packages/experience-design-system-cli/src/tui/import/steps/04-credentials/screen.tsx
|
|
2549
|
+
import { useRef, useState as useState6 } from "react";
|
|
2550
|
+
import { Box as Box6, Text as Text7 } from "ink";
|
|
2551
|
+
import TextInput3 from "ink-text-input";
|
|
2552
|
+
|
|
2553
|
+
// packages/experience-design-system-cli/src/tui/import/steps/04-credentials/controls.ts
|
|
2554
|
+
import { useInput as useInput6 } from "ink";
|
|
2555
|
+
|
|
2977
2556
|
// packages/experience-design-system-cli/src/tui/import/steps/04-credentials/logic.ts
|
|
2978
2557
|
var FIELDS = ["spaceId", "environmentId", "cmaToken", "host"];
|
|
2558
|
+
var DEFAULT_HOST = "api.contentful.com";
|
|
2979
2559
|
function keyAction2(input, key) {
|
|
2980
2560
|
if (key.escape) return "back";
|
|
2981
2561
|
if (key.tab) return "next-field";
|
|
@@ -2985,12 +2565,21 @@ function keyAction2(input, key) {
|
|
|
2985
2565
|
function nextField(field) {
|
|
2986
2566
|
return FIELDS[(FIELDS.indexOf(field) + 1) % FIELDS.length];
|
|
2987
2567
|
}
|
|
2568
|
+
function toConfiguredHost3(host) {
|
|
2569
|
+
const value = (host ?? "").trim().replace(/\/+$/, "");
|
|
2570
|
+
return value === "" ? DEFAULT_HOST : value.replace(/^https:\/\//i, "");
|
|
2571
|
+
}
|
|
2572
|
+
function toApiHost3(host) {
|
|
2573
|
+
const value = host.trim().replace(/\/+$/, "");
|
|
2574
|
+
if (value === "") return `https://${DEFAULT_HOST}`;
|
|
2575
|
+
return /^[a-z][a-z\d+\-.]*:\/\//i.test(value) ? value : `https://${value}`;
|
|
2576
|
+
}
|
|
2988
2577
|
function normalize(values) {
|
|
2989
2578
|
return {
|
|
2990
2579
|
spaceId: values.spaceId.trim(),
|
|
2991
2580
|
environmentId: values.environmentId.trim(),
|
|
2992
2581
|
cmaToken: values.cmaToken.trim(),
|
|
2993
|
-
host:
|
|
2582
|
+
host: toConfiguredHost3(values.host)
|
|
2994
2583
|
};
|
|
2995
2584
|
}
|
|
2996
2585
|
function missingField(values) {
|
|
@@ -3018,30 +2607,59 @@ function useCredentialsControls(handlers) {
|
|
|
3018
2607
|
}
|
|
3019
2608
|
|
|
3020
2609
|
// packages/experience-design-system-cli/src/tui/import/steps/04-credentials/validate.ts
|
|
2610
|
+
var USER_AGENT2 = "app contentful.experience-design-system-cli";
|
|
2611
|
+
function headers(token) {
|
|
2612
|
+
return { Authorization: `Bearer ${token}`, "X-Contentful-User-Agent": USER_AGENT2 };
|
|
2613
|
+
}
|
|
2614
|
+
function isApsDenial(body) {
|
|
2615
|
+
try {
|
|
2616
|
+
const id = JSON.parse(body).sys?.id;
|
|
2617
|
+
return id === "NotFound" || id === "AccessDenied";
|
|
2618
|
+
} catch {
|
|
2619
|
+
return false;
|
|
2620
|
+
}
|
|
2621
|
+
}
|
|
2622
|
+
function messageFrom(body) {
|
|
2623
|
+
try {
|
|
2624
|
+
const message = JSON.parse(body).message;
|
|
2625
|
+
return typeof message === "string" && message !== "" ? message : void 0;
|
|
2626
|
+
} catch {
|
|
2627
|
+
return void 0;
|
|
2628
|
+
}
|
|
2629
|
+
}
|
|
3021
2630
|
async function validateCredentials(values) {
|
|
3022
|
-
const
|
|
3023
|
-
|
|
3024
|
-
host,
|
|
3025
|
-
spaceId: values.spaceId,
|
|
3026
|
-
environmentId: values.environmentId,
|
|
3027
|
-
cmaToken: values.cmaToken
|
|
3028
|
-
});
|
|
2631
|
+
const base = toApiHost3(values.host);
|
|
2632
|
+
let me;
|
|
3029
2633
|
try {
|
|
3030
|
-
await
|
|
3031
|
-
return { ok: true };
|
|
2634
|
+
me = await fetch(`${base}/users/me`, { headers: headers(values.cmaToken) });
|
|
3032
2635
|
} catch (error) {
|
|
3033
|
-
return { ok: false, error:
|
|
2636
|
+
return { ok: false, error: `Could not reach ${base}: ${error instanceof Error ? error.message : String(error)}` };
|
|
3034
2637
|
}
|
|
3035
|
-
}
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
2638
|
+
if (me.status === 401) return { ok: false, error: "CMA token is invalid or revoked." };
|
|
2639
|
+
if (!me.ok) return { ok: false, error: `Unexpected error validating the token (${me.status}).` };
|
|
2640
|
+
let preflight;
|
|
2641
|
+
try {
|
|
2642
|
+
preflight = await fetch(
|
|
2643
|
+
`${base}/spaces/${values.spaceId}/environments/${values.environmentId}/design_systems/preflight`,
|
|
2644
|
+
{ headers: headers(values.cmaToken) }
|
|
2645
|
+
);
|
|
2646
|
+
} catch {
|
|
2647
|
+
return { ok: true };
|
|
3042
2648
|
}
|
|
3043
|
-
if (
|
|
3044
|
-
|
|
2649
|
+
if (preflight.ok || preflight.status >= 500) return { ok: true };
|
|
2650
|
+
const body = await preflight.text();
|
|
2651
|
+
if (preflight.status === 404 && !isApsDenial(body)) return { ok: true };
|
|
2652
|
+
const detail = messageFrom(body);
|
|
2653
|
+
if (preflight.status === 403) {
|
|
2654
|
+
return { ok: false, error: `Access denied. ${detail ?? "Your token cannot write design system components here."}` };
|
|
2655
|
+
}
|
|
2656
|
+
if (preflight.status === 404) {
|
|
2657
|
+
return {
|
|
2658
|
+
ok: false,
|
|
2659
|
+
error: `Not found. Check the space ID, environment and API host.${detail ? ` (${detail})` : ""}`
|
|
2660
|
+
};
|
|
2661
|
+
}
|
|
2662
|
+
return { ok: false, error: `Credential check failed (${preflight.status}).${detail ? ` ${detail}` : ""}` };
|
|
3045
2663
|
}
|
|
3046
2664
|
|
|
3047
2665
|
// packages/experience-design-system-cli/src/tui/import/steps/04-credentials/screen.tsx
|
|
@@ -3118,7 +2736,7 @@ function CredentialsScreen({
|
|
|
3118
2736
|
value: values[field],
|
|
3119
2737
|
focus: active === field && !validating,
|
|
3120
2738
|
mask: field === "cmaToken" ? "\u2022" : void 0,
|
|
3121
|
-
placeholder: field === "host" ?
|
|
2739
|
+
placeholder: field === "host" ? DEFAULT_HOST : void 0,
|
|
3122
2740
|
onChange: change(field),
|
|
3123
2741
|
onSubmit: () => void submit(field)
|
|
3124
2742
|
}
|
|
@@ -3145,13 +2763,13 @@ import { spawn } from "node:child_process";
|
|
|
3145
2763
|
|
|
3146
2764
|
// packages/experience-design-system-cli/src/legacy/legacy-cli-path.ts
|
|
3147
2765
|
import { existsSync as existsSync2 } from "node:fs";
|
|
3148
|
-
import { join as
|
|
2766
|
+
import { join as join3 } from "node:path";
|
|
3149
2767
|
var PACKAGE_NAME2 = "@contentful/experience-design-system-cli";
|
|
3150
2768
|
function findLegacyCliPath() {
|
|
3151
2769
|
const root = findPackageRoot(import.meta.url, PACKAGE_NAME2);
|
|
3152
2770
|
const candidates = [
|
|
3153
|
-
|
|
3154
|
-
|
|
2771
|
+
join3(root, "legacy", "bin", "cli.js"),
|
|
2772
|
+
join3(root, "..", "experience-design-system-cli-legacy", "bin", "cli.js")
|
|
3155
2773
|
];
|
|
3156
2774
|
const found = candidates.find((candidate) => existsSync2(candidate));
|
|
3157
2775
|
if (!found) {
|
|
@@ -3164,8 +2782,8 @@ ${candidates.join("\n")}`);
|
|
|
3164
2782
|
// packages/experience-design-system-cli/src/tui/import/spawn-v1-import.ts
|
|
3165
2783
|
function buildArgs2(options) {
|
|
3166
2784
|
const args = ["import"];
|
|
3167
|
-
if (options.
|
|
3168
|
-
args.push("--project", options.
|
|
2785
|
+
if (options.projectPath) {
|
|
2786
|
+
args.push("--project", options.projectPath);
|
|
3169
2787
|
}
|
|
3170
2788
|
if (options.tokens) {
|
|
3171
2789
|
args.push("--tokens", options.tokens);
|
|
@@ -3213,19 +2831,19 @@ import { Box as Box7, Text as Text8, useInput as useInput7 } from "ink";
|
|
|
3213
2831
|
|
|
3214
2832
|
// packages/experience-design-system-cli/src/tui/debug-store.ts
|
|
3215
2833
|
import { mkdir as mkdir2, writeFile as writeFile2 } from "node:fs/promises";
|
|
3216
|
-
import { existsSync as existsSync3, readdirSync } from "node:fs";
|
|
3217
|
-
import { join as
|
|
2834
|
+
import { existsSync as existsSync3, readdirSync as readdirSync2 } from "node:fs";
|
|
2835
|
+
import { join as join5 } from "node:path";
|
|
3218
2836
|
|
|
3219
2837
|
// packages/experience-design-system-types/dist/src/config/index.js
|
|
3220
2838
|
import { randomBytes } from "node:crypto";
|
|
3221
2839
|
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
3222
2840
|
import { homedir as homedir2 } from "node:os";
|
|
3223
|
-
import { dirname as dirname2, join as
|
|
2841
|
+
import { dirname as dirname2, join as join4 } from "node:path";
|
|
3224
2842
|
function configRoot2() {
|
|
3225
|
-
return process.env["EDS_HOME"] ||
|
|
2843
|
+
return process.env["EDS_HOME"] || join4(homedir2(), ".contentful", "experience-design-system-cli");
|
|
3226
2844
|
}
|
|
3227
|
-
var configFilePath2 = () =>
|
|
3228
|
-
var debugSessionsDir2 = () =>
|
|
2845
|
+
var configFilePath2 = () => join4(configRoot2(), "config.json");
|
|
2846
|
+
var debugSessionsDir2 = () => join4(configRoot2(), "debug", "sessions");
|
|
3229
2847
|
async function readSettings2() {
|
|
3230
2848
|
try {
|
|
3231
2849
|
return JSON.parse(await readFile(configFilePath2(), "utf8"));
|
|
@@ -3234,12 +2852,12 @@ async function readSettings2() {
|
|
|
3234
2852
|
}
|
|
3235
2853
|
}
|
|
3236
2854
|
async function writeSettings2(settings) {
|
|
3237
|
-
const
|
|
3238
|
-
await mkdir(dirname2(
|
|
3239
|
-
const tmp = `${
|
|
2855
|
+
const path5 = configFilePath2();
|
|
2856
|
+
await mkdir(dirname2(path5), { recursive: true });
|
|
2857
|
+
const tmp = `${path5}.${process.pid}.${randomBytes(4).toString("hex")}.tmp`;
|
|
3240
2858
|
await writeFile(tmp, `${JSON.stringify(settings, null, 2)}
|
|
3241
2859
|
`, { mode: 384 });
|
|
3242
|
-
await rename(tmp,
|
|
2860
|
+
await rename(tmp, path5);
|
|
3243
2861
|
}
|
|
3244
2862
|
async function updateSettings2(patch) {
|
|
3245
2863
|
await writeSettings2({ ...await readSettings2(), ...patch });
|
|
@@ -3274,7 +2892,7 @@ function slugify(value) {
|
|
|
3274
2892
|
function generateSessionId3() {
|
|
3275
2893
|
const today = datePrefix();
|
|
3276
2894
|
const root = debugSessionsDir2();
|
|
3277
|
-
const existing = existsSync3(root) ?
|
|
2895
|
+
const existing = existsSync3(root) ? readdirSync2(root) : [];
|
|
3278
2896
|
const seqPattern = new RegExp(`^${today}-session-(\\d+)$`);
|
|
3279
2897
|
const maxSeq = existing.reduce((max, entry) => {
|
|
3280
2898
|
const match = seqPattern.exec(entry);
|
|
@@ -3284,7 +2902,7 @@ function generateSessionId3() {
|
|
|
3284
2902
|
}
|
|
3285
2903
|
var SESSION_ID = generateSessionId3();
|
|
3286
2904
|
function sessionDebugDir() {
|
|
3287
|
-
return
|
|
2905
|
+
return join5(debugSessionsDir2(), SESSION_ID);
|
|
3288
2906
|
}
|
|
3289
2907
|
function currentDebugSessionDir() {
|
|
3290
2908
|
return sessionDebugDir();
|
|
@@ -3333,15 +2951,15 @@ async function finishDebugRun({
|
|
|
3333
2951
|
const durationMs = Date.now() - run.startedAt;
|
|
3334
2952
|
const content = renderMarkdown(run, { outputs: redact(outputs), status, exitMethod, durationMs });
|
|
3335
2953
|
const flowSegments = run.flow.split("/");
|
|
3336
|
-
const dir =
|
|
2954
|
+
const dir = join5(sessionDebugDir(), ...flowSegments);
|
|
3337
2955
|
await mkdir2(dir, { recursive: true });
|
|
3338
2956
|
const filename = nextAttemptFilename(dir, run.menuOption);
|
|
3339
|
-
await writeFile2(
|
|
2957
|
+
await writeFile2(join5(dir, filename), content, { mode: 384 });
|
|
3340
2958
|
}
|
|
3341
2959
|
function nextAttemptFilename(dir, menuOption) {
|
|
3342
2960
|
const today = datePrefix();
|
|
3343
2961
|
const optionSlug = slugify(menuOption);
|
|
3344
|
-
const existing = existsSync3(dir) ?
|
|
2962
|
+
const existing = existsSync3(dir) ? readdirSync2(dir) : [];
|
|
3345
2963
|
const attemptPattern = new RegExp(`^${today}-${optionSlug}-attempt-(\\d+)\\.md$`);
|
|
3346
2964
|
const maxAttempt = existing.reduce((max, entry) => {
|
|
3347
2965
|
const match = attemptPattern.exec(entry);
|
|
@@ -3939,7 +3557,7 @@ function App({ onLaunchImport, importExitCode }) {
|
|
|
3939
3557
|
const [showImportResult, setShowImportResult] = useState13(returnedFromImport);
|
|
3940
3558
|
const [projectPath, setProjectPath] = useState13();
|
|
3941
3559
|
const [tokensPath, setTokensPath] = useState13();
|
|
3942
|
-
const [
|
|
3560
|
+
const [scannedFiles, setScannedFiles] = useState13();
|
|
3943
3561
|
const [importDefaults, setImportDefaults] = useState13();
|
|
3944
3562
|
useEffect9(() => {
|
|
3945
3563
|
if (screen !== "import") {
|
|
@@ -3954,16 +3572,16 @@ function App({ onLaunchImport, importExitCode }) {
|
|
|
3954
3572
|
})
|
|
3955
3573
|
);
|
|
3956
3574
|
}, [screen]);
|
|
3957
|
-
const finishCredentials = async (result,
|
|
3575
|
+
const finishCredentials = async (result, projectPath2, tokens, filePaths) => {
|
|
3958
3576
|
if (!result.skipped) await writeCredentials({ ...result.credentials });
|
|
3959
|
-
onLaunchImport?.({
|
|
3577
|
+
onLaunchImport?.({ projectPath: projectPath2, tokens, filePaths, skipCredentials: result.skipped });
|
|
3960
3578
|
};
|
|
3961
3579
|
const goToStart = () => setScreen("start");
|
|
3962
3580
|
const goToSettings = () => setScreen("settings");
|
|
3963
3581
|
const resetImport = () => {
|
|
3964
3582
|
setProjectPath(void 0);
|
|
3965
3583
|
setTokensPath(void 0);
|
|
3966
|
-
|
|
3584
|
+
setScannedFiles(void 0);
|
|
3967
3585
|
};
|
|
3968
3586
|
const finishImportResult = () => {
|
|
3969
3587
|
setShowImportResult(false);
|
|
@@ -3991,12 +3609,12 @@ function App({ onLaunchImport, importExitCode }) {
|
|
|
3991
3609
|
}
|
|
3992
3610
|
);
|
|
3993
3611
|
}
|
|
3994
|
-
if (
|
|
3612
|
+
if (scannedFiles === void 0) {
|
|
3995
3613
|
return /* @__PURE__ */ jsx14(
|
|
3996
3614
|
PathValidationScreen,
|
|
3997
3615
|
{
|
|
3998
3616
|
projectPath,
|
|
3999
|
-
onConfirm: () =>
|
|
3617
|
+
onConfirm: ({ filePaths }) => setScannedFiles(filePaths),
|
|
4000
3618
|
onChangePath: resetImport,
|
|
4001
3619
|
onBack: () => setTokensPath(void 0)
|
|
4002
3620
|
}
|
|
@@ -4006,8 +3624,8 @@ function App({ onLaunchImport, importExitCode }) {
|
|
|
4006
3624
|
CredentialsScreen,
|
|
4007
3625
|
{
|
|
4008
3626
|
initial: importDefaults.credentials,
|
|
4009
|
-
onBack: () =>
|
|
4010
|
-
onDone: (result) => void finishCredentials(result, projectPath, tokensPath)
|
|
3627
|
+
onBack: () => setScannedFiles(void 0),
|
|
3628
|
+
onDone: (result) => void finishCredentials(result, projectPath, tokensPath, scannedFiles)
|
|
4011
3629
|
}
|
|
4012
3630
|
);
|
|
4013
3631
|
case "help":
|