appfunnel 0.11.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +561 -100
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/default/README.md +69 -0
package/dist/index.js
CHANGED
|
@@ -231,49 +231,21 @@ async function fetchStorePrices(projectId, storeId, options) {
|
|
|
231
231
|
const data = await response.json();
|
|
232
232
|
return data.data || [];
|
|
233
233
|
}
|
|
234
|
-
async function
|
|
235
|
-
const
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
}
|
|
250
|
-
try {
|
|
251
|
-
const response = await apiFetch(`/project/${projectId}/headless/publish`, {
|
|
252
|
-
...options,
|
|
253
|
-
method: "POST",
|
|
254
|
-
body: formData
|
|
255
|
-
});
|
|
256
|
-
return await response.json();
|
|
257
|
-
} catch (err) {
|
|
258
|
-
if (err instanceof CLIError && err.code === "API_ERROR") {
|
|
259
|
-
if (err.statusCode === 413) {
|
|
260
|
-
throw new CLIError(
|
|
261
|
-
"BUNDLE_TOO_LARGE",
|
|
262
|
-
err.message,
|
|
263
|
-
"Reduce page bundle sizes. Check for large dependencies."
|
|
264
|
-
);
|
|
265
|
-
}
|
|
266
|
-
if (err.statusCode === 409) {
|
|
267
|
-
throw new CLIError(
|
|
268
|
-
"FUNNEL_NOT_HEADLESS",
|
|
269
|
-
err.message,
|
|
270
|
-
"Remove funnelId from config to create a new headless funnel."
|
|
271
|
-
);
|
|
272
|
-
}
|
|
273
|
-
throw new CLIError("PUBLISH_FAILED", err.message);
|
|
274
|
-
}
|
|
275
|
-
throw err;
|
|
276
|
-
}
|
|
234
|
+
async function publishInit(projectId, body, options) {
|
|
235
|
+
const response = await apiFetch(`/project/${projectId}/headless/publish-init`, {
|
|
236
|
+
...options,
|
|
237
|
+
method: "POST",
|
|
238
|
+
body: JSON.stringify(body)
|
|
239
|
+
});
|
|
240
|
+
return await response.json();
|
|
241
|
+
}
|
|
242
|
+
async function publishFinalize(projectId, body, options) {
|
|
243
|
+
const response = await apiFetch(`/project/${projectId}/headless/publish-finalize`, {
|
|
244
|
+
...options,
|
|
245
|
+
method: "POST",
|
|
246
|
+
body: JSON.stringify(body)
|
|
247
|
+
});
|
|
248
|
+
return await response.json();
|
|
277
249
|
}
|
|
278
250
|
var DEFAULT_API_BASE2;
|
|
279
251
|
var init_api = __esm({
|
|
@@ -455,6 +427,12 @@ async function initCommand(nameArg) {
|
|
|
455
427
|
const { renameSync } = await import("fs");
|
|
456
428
|
renameSync(gitignoreSrc, join2(dir, ".gitignore"));
|
|
457
429
|
}
|
|
430
|
+
const readmePath = join2(dir, "README.md");
|
|
431
|
+
if (existsSync(readmePath)) {
|
|
432
|
+
let readme = readFileSync2(readmePath, "utf-8");
|
|
433
|
+
readme = readme.replace("__NAME__", name);
|
|
434
|
+
writeFileSync2(readmePath, readme);
|
|
435
|
+
}
|
|
458
436
|
const configPath = join2(dir, "appfunnel.config.ts");
|
|
459
437
|
if (existsSync(configPath)) {
|
|
460
438
|
let config = readFileSync2(configPath, "utf-8");
|
|
@@ -485,7 +463,7 @@ ${itemsStr},
|
|
|
485
463
|
}
|
|
486
464
|
writeFileSync2(configPath, config);
|
|
487
465
|
}
|
|
488
|
-
const sdkVersion = `^${"0.
|
|
466
|
+
const sdkVersion = `^${"0.13.0"}`;
|
|
489
467
|
writeFileSync2(
|
|
490
468
|
join2(dir, "package.json"),
|
|
491
469
|
JSON.stringify(
|
|
@@ -731,7 +709,7 @@ var init_config = __esm({
|
|
|
731
709
|
import { readFileSync as readFileSync4 } from "fs";
|
|
732
710
|
import { join as join4 } from "path";
|
|
733
711
|
function checkVersionCompatibility(cwd) {
|
|
734
|
-
const cliVersion = "0.
|
|
712
|
+
const cliVersion = "0.13.0";
|
|
735
713
|
const sdkVersion = getSdkVersion(cwd);
|
|
736
714
|
const [cliMajor, cliMinor] = cliVersion.split(".").map(Number);
|
|
737
715
|
const [sdkMajor, sdkMinor] = sdkVersion.split(".").map(Number);
|
|
@@ -1448,6 +1426,386 @@ var init_dev = __esm({
|
|
|
1448
1426
|
}
|
|
1449
1427
|
});
|
|
1450
1428
|
|
|
1429
|
+
// src/lib/computeFlow.ts
|
|
1430
|
+
function computeFunnelFlow(pages, initialPageKey) {
|
|
1431
|
+
const allPageKeys = new Set(Object.keys(pages));
|
|
1432
|
+
if (allPageKeys.size === 0) {
|
|
1433
|
+
return { initialPageKey, segments: [], terminalPageKeys: [], unreachablePageKeys: [] };
|
|
1434
|
+
}
|
|
1435
|
+
function getTargets(pageKey) {
|
|
1436
|
+
const routes = pages[pageKey]?.routes || [];
|
|
1437
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1438
|
+
const targets = [];
|
|
1439
|
+
for (const route of routes) {
|
|
1440
|
+
if (allPageKeys.has(route.to) && !seen.has(route.to)) {
|
|
1441
|
+
seen.add(route.to);
|
|
1442
|
+
targets.push(route.to);
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
return targets;
|
|
1446
|
+
}
|
|
1447
|
+
const reachable = /* @__PURE__ */ new Set();
|
|
1448
|
+
{
|
|
1449
|
+
const queue = [];
|
|
1450
|
+
if (allPageKeys.has(initialPageKey)) {
|
|
1451
|
+
reachable.add(initialPageKey);
|
|
1452
|
+
queue.push(initialPageKey);
|
|
1453
|
+
}
|
|
1454
|
+
while (queue.length > 0) {
|
|
1455
|
+
const pageKey = queue.shift();
|
|
1456
|
+
for (const target of getTargets(pageKey)) {
|
|
1457
|
+
if (!reachable.has(target)) {
|
|
1458
|
+
reachable.add(target);
|
|
1459
|
+
queue.push(target);
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
const unreachablePageKeys = [];
|
|
1465
|
+
const terminalPageKeys = [];
|
|
1466
|
+
for (const pageKey of allPageKeys) {
|
|
1467
|
+
if (!reachable.has(pageKey)) {
|
|
1468
|
+
unreachablePageKeys.push(pageKey);
|
|
1469
|
+
continue;
|
|
1470
|
+
}
|
|
1471
|
+
if (getTargets(pageKey).length === 0) {
|
|
1472
|
+
terminalPageKeys.push(pageKey);
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
function bfsForward(start) {
|
|
1476
|
+
const dist = /* @__PURE__ */ new Map();
|
|
1477
|
+
dist.set(start, 0);
|
|
1478
|
+
const queue = [start];
|
|
1479
|
+
while (queue.length > 0) {
|
|
1480
|
+
const current2 = queue.shift();
|
|
1481
|
+
const d = dist.get(current2);
|
|
1482
|
+
for (const target of getTargets(current2)) {
|
|
1483
|
+
if (!dist.has(target)) {
|
|
1484
|
+
dist.set(target, d + 1);
|
|
1485
|
+
queue.push(target);
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
return dist;
|
|
1490
|
+
}
|
|
1491
|
+
function findConvergence(armStarts) {
|
|
1492
|
+
if (armStarts.length === 0) return null;
|
|
1493
|
+
const armReachable = armStarts.map((s) => bfsForward(s));
|
|
1494
|
+
const armStartSet = new Set(armStarts);
|
|
1495
|
+
let candidates = /* @__PURE__ */ new Set();
|
|
1496
|
+
for (const key of armReachable[0].keys()) {
|
|
1497
|
+
if (!armStartSet.has(key)) candidates.add(key);
|
|
1498
|
+
}
|
|
1499
|
+
for (let i = 1; i < armReachable.length; i++) {
|
|
1500
|
+
const reachSet = new Set(armReachable[i].keys());
|
|
1501
|
+
candidates = new Set([...candidates].filter((k) => reachSet.has(k)));
|
|
1502
|
+
}
|
|
1503
|
+
if (candidates.size === 0) return null;
|
|
1504
|
+
let bestPage = null;
|
|
1505
|
+
let bestMinDist = Infinity;
|
|
1506
|
+
for (const pageKey of candidates) {
|
|
1507
|
+
const minDist = Math.min(...armReachable.map((r) => r.get(pageKey) ?? Infinity));
|
|
1508
|
+
if (minDist < bestMinDist) {
|
|
1509
|
+
bestMinDist = minDist;
|
|
1510
|
+
bestPage = pageKey;
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
return bestPage;
|
|
1514
|
+
}
|
|
1515
|
+
function walkArm(start, convergence, globalVisited2) {
|
|
1516
|
+
if (convergence === null) {
|
|
1517
|
+
const pages2 = [];
|
|
1518
|
+
let current2 = start;
|
|
1519
|
+
while (true) {
|
|
1520
|
+
if (globalVisited2.has(current2)) break;
|
|
1521
|
+
globalVisited2.add(current2);
|
|
1522
|
+
pages2.push(current2);
|
|
1523
|
+
const targets = getTargets(current2);
|
|
1524
|
+
if (targets.length === 0) break;
|
|
1525
|
+
const next = targets.find((t) => !globalVisited2.has(t));
|
|
1526
|
+
if (!next) break;
|
|
1527
|
+
current2 = next;
|
|
1528
|
+
}
|
|
1529
|
+
return pages2;
|
|
1530
|
+
}
|
|
1531
|
+
const parent = /* @__PURE__ */ new Map();
|
|
1532
|
+
parent.set(start, null);
|
|
1533
|
+
const queue = [start];
|
|
1534
|
+
let found = false;
|
|
1535
|
+
while (queue.length > 0 && !found) {
|
|
1536
|
+
const current2 = queue.shift();
|
|
1537
|
+
for (const target of getTargets(current2)) {
|
|
1538
|
+
if (!parent.has(target)) {
|
|
1539
|
+
parent.set(target, current2);
|
|
1540
|
+
if (target === convergence) {
|
|
1541
|
+
found = true;
|
|
1542
|
+
break;
|
|
1543
|
+
}
|
|
1544
|
+
queue.push(target);
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
if (!found) {
|
|
1549
|
+
const pages2 = [];
|
|
1550
|
+
let current2 = start;
|
|
1551
|
+
while (true) {
|
|
1552
|
+
if (globalVisited2.has(current2)) break;
|
|
1553
|
+
globalVisited2.add(current2);
|
|
1554
|
+
pages2.push(current2);
|
|
1555
|
+
const targets = getTargets(current2);
|
|
1556
|
+
if (targets.length === 0) break;
|
|
1557
|
+
const next = targets.find((t) => !globalVisited2.has(t));
|
|
1558
|
+
if (!next) break;
|
|
1559
|
+
current2 = next;
|
|
1560
|
+
}
|
|
1561
|
+
return pages2;
|
|
1562
|
+
}
|
|
1563
|
+
const path = [];
|
|
1564
|
+
let node = convergence;
|
|
1565
|
+
while (node !== null && node !== start) {
|
|
1566
|
+
path.unshift(node);
|
|
1567
|
+
node = parent.get(node) ?? null;
|
|
1568
|
+
}
|
|
1569
|
+
path.unshift(start);
|
|
1570
|
+
if (path[path.length - 1] === convergence) {
|
|
1571
|
+
path.pop();
|
|
1572
|
+
}
|
|
1573
|
+
for (const p of path) {
|
|
1574
|
+
globalVisited2.add(p);
|
|
1575
|
+
}
|
|
1576
|
+
return path;
|
|
1577
|
+
}
|
|
1578
|
+
if (!allPageKeys.has(initialPageKey)) {
|
|
1579
|
+
return { initialPageKey, segments: [], terminalPageKeys, unreachablePageKeys };
|
|
1580
|
+
}
|
|
1581
|
+
const segments = [];
|
|
1582
|
+
const globalVisited = /* @__PURE__ */ new Set();
|
|
1583
|
+
let current = initialPageKey;
|
|
1584
|
+
let sharedPages = [];
|
|
1585
|
+
while (current !== null && !globalVisited.has(current)) {
|
|
1586
|
+
const targets = getTargets(current);
|
|
1587
|
+
if (targets.length === 0) {
|
|
1588
|
+
sharedPages.push(current);
|
|
1589
|
+
globalVisited.add(current);
|
|
1590
|
+
break;
|
|
1591
|
+
}
|
|
1592
|
+
const uniqueTargets = [...new Set(targets)];
|
|
1593
|
+
if (uniqueTargets.length === 1) {
|
|
1594
|
+
sharedPages.push(current);
|
|
1595
|
+
globalVisited.add(current);
|
|
1596
|
+
current = uniqueTargets[0];
|
|
1597
|
+
continue;
|
|
1598
|
+
}
|
|
1599
|
+
sharedPages.push(current);
|
|
1600
|
+
globalVisited.add(current);
|
|
1601
|
+
segments.push({ type: "shared", pageKeys: [...sharedPages] });
|
|
1602
|
+
sharedPages = [];
|
|
1603
|
+
const convergence = findConvergence(uniqueTargets);
|
|
1604
|
+
const arms = [];
|
|
1605
|
+
for (const armStart of uniqueTargets) {
|
|
1606
|
+
const armPages = walkArm(armStart, convergence, globalVisited);
|
|
1607
|
+
arms.push({ pageKeys: armPages });
|
|
1608
|
+
}
|
|
1609
|
+
segments.push({ type: "branch", sourcePageKey: current, arms });
|
|
1610
|
+
if (convergence !== null && !globalVisited.has(convergence)) {
|
|
1611
|
+
current = convergence;
|
|
1612
|
+
} else {
|
|
1613
|
+
current = null;
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
if (sharedPages.length > 0) {
|
|
1617
|
+
segments.push({ type: "shared", pageKeys: sharedPages });
|
|
1618
|
+
}
|
|
1619
|
+
return { initialPageKey, segments, terminalPageKeys, unreachablePageKeys };
|
|
1620
|
+
}
|
|
1621
|
+
var init_computeFlow = __esm({
|
|
1622
|
+
"src/lib/computeFlow.ts"() {
|
|
1623
|
+
"use strict";
|
|
1624
|
+
}
|
|
1625
|
+
});
|
|
1626
|
+
|
|
1627
|
+
// src/commands/routes.ts
|
|
1628
|
+
var routes_exports = {};
|
|
1629
|
+
__export(routes_exports, {
|
|
1630
|
+
routesCommand: () => routesCommand
|
|
1631
|
+
});
|
|
1632
|
+
import pc7 from "picocolors";
|
|
1633
|
+
async function routesCommand() {
|
|
1634
|
+
const cwd = process.cwd();
|
|
1635
|
+
const s = spinner("Analyzing pages...");
|
|
1636
|
+
const config = await loadConfig(cwd);
|
|
1637
|
+
const pageKeys = scanPages(cwd);
|
|
1638
|
+
const pages = await extractPageDefinitions(cwd, pageKeys);
|
|
1639
|
+
s.stop();
|
|
1640
|
+
const flow = computeFunnelFlow(pages, config.initialPageKey);
|
|
1641
|
+
console.log();
|
|
1642
|
+
console.log(` ${pc7.bold(config.name)}`);
|
|
1643
|
+
console.log(` ${pc7.dim(`${pageKeys.length} pages`)}`);
|
|
1644
|
+
console.log();
|
|
1645
|
+
renderFlow(flow, pages);
|
|
1646
|
+
}
|
|
1647
|
+
function renderFlow(flow, pages) {
|
|
1648
|
+
const indent = " ";
|
|
1649
|
+
for (let i = 0; i < flow.segments.length; i++) {
|
|
1650
|
+
const segment = flow.segments[i];
|
|
1651
|
+
if (segment.type === "shared") {
|
|
1652
|
+
for (let j = 0; j < segment.pageKeys.length; j++) {
|
|
1653
|
+
const key = segment.pageKeys[j];
|
|
1654
|
+
const page = pages[key];
|
|
1655
|
+
const isStart = key === flow.initialPageKey;
|
|
1656
|
+
const isTerminal = flow.terminalPageKeys.includes(key);
|
|
1657
|
+
printPageBox(indent, key, page, isStart, isTerminal);
|
|
1658
|
+
const isLastInSegment = j === segment.pageKeys.length - 1;
|
|
1659
|
+
const hasNextSegment = i < flow.segments.length - 1;
|
|
1660
|
+
if ((!isLastInSegment || hasNextSegment) && !isTerminal) {
|
|
1661
|
+
printArrow(indent);
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
} else {
|
|
1665
|
+
renderBranch(indent, segment, pages, flow);
|
|
1666
|
+
if (i < flow.segments.length - 1) {
|
|
1667
|
+
printArrow(indent);
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
if (flow.unreachablePageKeys.length > 0) {
|
|
1672
|
+
console.log();
|
|
1673
|
+
console.log(` ${pc7.yellow("\u26A0")} ${pc7.yellow("Unreachable pages:")}`);
|
|
1674
|
+
for (const key of flow.unreachablePageKeys) {
|
|
1675
|
+
const page = pages[key];
|
|
1676
|
+
console.log(` ${pc7.dim("\u2022")} ${key}${page?.name ? pc7.dim(` (${page.name})`) : ""}`);
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
console.log();
|
|
1680
|
+
}
|
|
1681
|
+
function formatPageLabel(key, page, isStart, isTerminal) {
|
|
1682
|
+
const name = page?.name || key;
|
|
1683
|
+
const type = page?.type;
|
|
1684
|
+
const badges = [];
|
|
1685
|
+
if (isStart) badges.push(pc7.green("start"));
|
|
1686
|
+
if (isTerminal) badges.push(pc7.red("end"));
|
|
1687
|
+
if (type && type !== "default") badges.push(pc7.magenta(type));
|
|
1688
|
+
const label = key === name.toLowerCase().replace(/\s+/g, "-") ? name : `${name} ${pc7.dim(`(${key})`)}`;
|
|
1689
|
+
const badgeStr = badges.length > 0 ? " " + badges.map((b) => pc7.dim("[") + b + pc7.dim("]")).join(" ") : "";
|
|
1690
|
+
return label + badgeStr;
|
|
1691
|
+
}
|
|
1692
|
+
function printPageBox(indent, key, page, isStart, isTerminal) {
|
|
1693
|
+
const label = formatPageLabel(key, page, isStart, isTerminal);
|
|
1694
|
+
const rawLen = stripAnsi(label).length;
|
|
1695
|
+
const boxWidth = Math.max(rawLen + 2, 20);
|
|
1696
|
+
const padding = boxWidth - rawLen - 2;
|
|
1697
|
+
console.log(`${indent}\u250C${H.repeat(boxWidth)}\u2510`);
|
|
1698
|
+
console.log(`${indent}\u2502 ${label}${" ".repeat(padding)} \u2502`);
|
|
1699
|
+
console.log(`${indent}\u2514${H.repeat(boxWidth)}\u2518`);
|
|
1700
|
+
}
|
|
1701
|
+
function printArrow(indent) {
|
|
1702
|
+
console.log(`${indent} ${V}`);
|
|
1703
|
+
console.log(`${indent} \u25BC`);
|
|
1704
|
+
}
|
|
1705
|
+
function renderBranch(indent, segment, pages, flow) {
|
|
1706
|
+
const arms = segment.arms;
|
|
1707
|
+
const colWidths = arms.map((arm) => {
|
|
1708
|
+
const maxNameLen = Math.max(
|
|
1709
|
+
...arm.pageKeys.map((key) => {
|
|
1710
|
+
const page = pages[key];
|
|
1711
|
+
const name = page?.name || key;
|
|
1712
|
+
const type = page?.type;
|
|
1713
|
+
const typeLen = type && type !== "default" ? ` [${type}]`.length : 0;
|
|
1714
|
+
return name.length + typeLen;
|
|
1715
|
+
}),
|
|
1716
|
+
8
|
|
1717
|
+
);
|
|
1718
|
+
return maxNameLen + 4;
|
|
1719
|
+
});
|
|
1720
|
+
let splitLine = "";
|
|
1721
|
+
for (let a = 0; a < arms.length; a++) {
|
|
1722
|
+
const mid = Math.floor(colWidths[a] / 2);
|
|
1723
|
+
if (a === 0) {
|
|
1724
|
+
splitLine += " ".repeat(mid) + "\u251C" + H.repeat(colWidths[a] - mid - 1);
|
|
1725
|
+
} else if (a === arms.length - 1) {
|
|
1726
|
+
splitLine += H.repeat(mid) + "\u2524" + " ".repeat(colWidths[a] - mid - 1);
|
|
1727
|
+
} else {
|
|
1728
|
+
splitLine += H.repeat(mid) + "\u252C" + H.repeat(colWidths[a] - mid - 1);
|
|
1729
|
+
}
|
|
1730
|
+
if (a < arms.length - 1) splitLine += H.repeat(3);
|
|
1731
|
+
}
|
|
1732
|
+
console.log(`${indent}${splitLine}`);
|
|
1733
|
+
let pipeLine = "";
|
|
1734
|
+
for (let a = 0; a < arms.length; a++) {
|
|
1735
|
+
const mid = Math.floor(colWidths[a] / 2);
|
|
1736
|
+
pipeLine += " ".repeat(mid) + V + " ".repeat(colWidths[a] - mid - 1);
|
|
1737
|
+
if (a < arms.length - 1) pipeLine += " ";
|
|
1738
|
+
}
|
|
1739
|
+
console.log(`${indent}${pipeLine}`);
|
|
1740
|
+
const maxDepth = Math.max(...arms.map((a) => a.pageKeys.length));
|
|
1741
|
+
for (let row = 0; row < maxDepth; row++) {
|
|
1742
|
+
let line = "";
|
|
1743
|
+
for (let a = 0; a < arms.length; a++) {
|
|
1744
|
+
const key = arms[a].pageKeys[row];
|
|
1745
|
+
const mid = Math.floor(colWidths[a] / 2);
|
|
1746
|
+
if (key) {
|
|
1747
|
+
const page = pages[key];
|
|
1748
|
+
const name = page?.name || key;
|
|
1749
|
+
const type = page?.type;
|
|
1750
|
+
const typeBadge = type && type !== "default" ? " " + pc7.magenta(`[${type}]`) : "";
|
|
1751
|
+
const display = name + typeBadge;
|
|
1752
|
+
const displayLen = stripAnsi(display).length;
|
|
1753
|
+
const truncated = displayLen > colWidths[a] - 2 ? name.slice(0, colWidths[a] - 5) + "..." : display;
|
|
1754
|
+
const truncLen = stripAnsi(truncated).length;
|
|
1755
|
+
const leftPad = mid - Math.floor(truncLen / 2);
|
|
1756
|
+
const rightPad = colWidths[a] - leftPad - truncLen;
|
|
1757
|
+
line += " ".repeat(Math.max(0, leftPad)) + pc7.bold(truncated) + " ".repeat(Math.max(0, rightPad));
|
|
1758
|
+
} else {
|
|
1759
|
+
line += " ".repeat(colWidths[a]);
|
|
1760
|
+
}
|
|
1761
|
+
if (a < arms.length - 1) line += " ";
|
|
1762
|
+
}
|
|
1763
|
+
console.log(`${indent}${line}`);
|
|
1764
|
+
if (row < maxDepth - 1) {
|
|
1765
|
+
let connector = "";
|
|
1766
|
+
for (let a = 0; a < arms.length; a++) {
|
|
1767
|
+
const mid = Math.floor(colWidths[a] / 2);
|
|
1768
|
+
const hasMore = row + 1 < arms[a].pageKeys.length;
|
|
1769
|
+
if (hasMore) {
|
|
1770
|
+
connector += " ".repeat(mid) + "\u25BC" + " ".repeat(colWidths[a] - mid - 1);
|
|
1771
|
+
} else {
|
|
1772
|
+
connector += " ".repeat(colWidths[a]);
|
|
1773
|
+
}
|
|
1774
|
+
if (a < arms.length - 1) connector += " ";
|
|
1775
|
+
}
|
|
1776
|
+
console.log(`${indent}${connector}`);
|
|
1777
|
+
}
|
|
1778
|
+
}
|
|
1779
|
+
let mergeLine = "";
|
|
1780
|
+
for (let a = 0; a < arms.length; a++) {
|
|
1781
|
+
const mid = Math.floor(colWidths[a] / 2);
|
|
1782
|
+
if (a === 0) {
|
|
1783
|
+
mergeLine += " ".repeat(mid) + "\u251C" + H.repeat(colWidths[a] - mid - 1);
|
|
1784
|
+
} else if (a === arms.length - 1) {
|
|
1785
|
+
mergeLine += H.repeat(mid) + "\u2524" + " ".repeat(colWidths[a] - mid - 1);
|
|
1786
|
+
} else {
|
|
1787
|
+
mergeLine += H.repeat(mid) + "\u2534" + H.repeat(colWidths[a] - mid - 1);
|
|
1788
|
+
}
|
|
1789
|
+
if (a < arms.length - 1) mergeLine += H.repeat(3);
|
|
1790
|
+
}
|
|
1791
|
+
console.log(`${indent}${mergeLine}`);
|
|
1792
|
+
}
|
|
1793
|
+
function stripAnsi(str) {
|
|
1794
|
+
return str.replace(/\x1b\[[0-9;]*m/g, "");
|
|
1795
|
+
}
|
|
1796
|
+
var H, V;
|
|
1797
|
+
var init_routes = __esm({
|
|
1798
|
+
"src/commands/routes.ts"() {
|
|
1799
|
+
"use strict";
|
|
1800
|
+
init_logger();
|
|
1801
|
+
init_config();
|
|
1802
|
+
init_pages();
|
|
1803
|
+
init_computeFlow();
|
|
1804
|
+
H = "\u2500";
|
|
1805
|
+
V = "\u2502";
|
|
1806
|
+
}
|
|
1807
|
+
});
|
|
1808
|
+
|
|
1451
1809
|
// src/commands/build.ts
|
|
1452
1810
|
var build_exports = {};
|
|
1453
1811
|
__export(build_exports, {
|
|
@@ -1456,7 +1814,7 @@ __export(build_exports, {
|
|
|
1456
1814
|
import { resolve as resolve3, join as join9 } from "path";
|
|
1457
1815
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
1458
1816
|
import { readFileSync as readFileSync8, writeFileSync as writeFileSync5, statSync, readdirSync as readdirSync4 } from "fs";
|
|
1459
|
-
import
|
|
1817
|
+
import pc8 from "picocolors";
|
|
1460
1818
|
async function buildCommand() {
|
|
1461
1819
|
const cwd = process.cwd();
|
|
1462
1820
|
requireAuth();
|
|
@@ -1570,6 +1928,7 @@ async function buildCommand() {
|
|
|
1570
1928
|
responses: config.responses || {},
|
|
1571
1929
|
queryParams: { ...BUILTIN_QUERY_PARAMS, ...config.queryParams },
|
|
1572
1930
|
data: config.data || {},
|
|
1931
|
+
user: BUILTIN_USER_VARS,
|
|
1573
1932
|
products: config.products || {},
|
|
1574
1933
|
defaultLocale: config.defaultLocale,
|
|
1575
1934
|
assets,
|
|
@@ -1579,20 +1938,20 @@ async function buildCommand() {
|
|
|
1579
1938
|
console.log();
|
|
1580
1939
|
success("Build complete");
|
|
1581
1940
|
console.log();
|
|
1582
|
-
console.log(` ${
|
|
1583
|
-
console.log(` ${
|
|
1584
|
-
console.log(` ${
|
|
1941
|
+
console.log(` ${pc8.dim("Output:")} dist/`);
|
|
1942
|
+
console.log(` ${pc8.dim("Pages:")} ${pageKeys.length}`);
|
|
1943
|
+
console.log(` ${pc8.dim("Size:")} ${formatSize(totalSize)}`);
|
|
1585
1944
|
console.log();
|
|
1586
1945
|
for (const asset of assets) {
|
|
1587
1946
|
const sizeStr = formatSize(asset.size);
|
|
1588
1947
|
const isOver = asset.size > MAX_PAGE_SIZE;
|
|
1589
|
-
console.log(` ${isOver ?
|
|
1948
|
+
console.log(` ${isOver ? pc8.yellow("!") : pc8.dim("\xB7")} ${pc8.dim(asset.path)} ${isOver ? pc8.yellow(sizeStr) : pc8.dim(sizeStr)}`);
|
|
1590
1949
|
}
|
|
1591
1950
|
console.log();
|
|
1592
1951
|
if (totalSize > MAX_TOTAL_SIZE) {
|
|
1593
1952
|
console.log(formatWarning(
|
|
1594
1953
|
"BUNDLE_TOO_LARGE",
|
|
1595
|
-
`Total bundle size (${formatSize(totalSize)}) exceeds the
|
|
1954
|
+
`Total bundle size (${formatSize(totalSize)}) exceeds the 10MB limit.`,
|
|
1596
1955
|
"This will be rejected on publish. Reduce dependencies or code-split."
|
|
1597
1956
|
));
|
|
1598
1957
|
console.log();
|
|
@@ -1701,7 +2060,7 @@ function getSdkVersion2(cwd) {
|
|
|
1701
2060
|
return "0.0.0";
|
|
1702
2061
|
}
|
|
1703
2062
|
}
|
|
1704
|
-
var MAX_TOTAL_SIZE, MAX_PAGE_SIZE, BUILTIN_QUERY_PARAMS;
|
|
2063
|
+
var MAX_TOTAL_SIZE, MAX_PAGE_SIZE, BUILTIN_QUERY_PARAMS, BUILTIN_USER_VARS;
|
|
1705
2064
|
var init_build = __esm({
|
|
1706
2065
|
"src/commands/build.ts"() {
|
|
1707
2066
|
"use strict";
|
|
@@ -1714,7 +2073,7 @@ var init_build = __esm({
|
|
|
1714
2073
|
init_html();
|
|
1715
2074
|
init_errors();
|
|
1716
2075
|
init_errors();
|
|
1717
|
-
MAX_TOTAL_SIZE =
|
|
2076
|
+
MAX_TOTAL_SIZE = 10 * 1024 * 1024;
|
|
1718
2077
|
MAX_PAGE_SIZE = 500 * 1024;
|
|
1719
2078
|
BUILTIN_QUERY_PARAMS = {
|
|
1720
2079
|
utm_source: { type: "string" },
|
|
@@ -1723,6 +2082,14 @@ var init_build = __esm({
|
|
|
1723
2082
|
utm_content: { type: "string" },
|
|
1724
2083
|
utm_term: { type: "string" }
|
|
1725
2084
|
};
|
|
2085
|
+
BUILTIN_USER_VARS = {
|
|
2086
|
+
email: { type: "string" },
|
|
2087
|
+
name: { type: "string" },
|
|
2088
|
+
dateOfBirth: { type: "string" },
|
|
2089
|
+
gender: { type: "string" },
|
|
2090
|
+
stripeCustomerId: { type: "string" },
|
|
2091
|
+
paddleCustomerId: { type: "string" }
|
|
2092
|
+
};
|
|
1726
2093
|
}
|
|
1727
2094
|
});
|
|
1728
2095
|
|
|
@@ -1755,10 +2122,22 @@ __export(publish_exports, {
|
|
|
1755
2122
|
});
|
|
1756
2123
|
import { resolve as resolve4, join as join11 } from "path";
|
|
1757
2124
|
import { readFileSync as readFileSync10, existsSync as existsSync6 } from "fs";
|
|
1758
|
-
import
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
2125
|
+
import { execSync } from "child_process";
|
|
2126
|
+
import pc9 from "picocolors";
|
|
2127
|
+
import select3 from "@inquirer/select";
|
|
2128
|
+
function tryGit(command, cwd) {
|
|
2129
|
+
try {
|
|
2130
|
+
return execSync(command, { cwd, stdio: ["pipe", "pipe", "ignore"] }).toString().trim() || void 0;
|
|
2131
|
+
} catch {
|
|
2132
|
+
return void 0;
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
function getGitMetadata(cwd) {
|
|
2136
|
+
const commitSha = tryGit("git rev-parse HEAD", cwd);
|
|
2137
|
+
if (!commitSha) return {};
|
|
2138
|
+
const branch = tryGit("git rev-parse --abbrev-ref HEAD", cwd);
|
|
2139
|
+
const message = tryGit("git log -1 --pretty=%s", cwd);
|
|
2140
|
+
return { commitSha, branch, message };
|
|
1762
2141
|
}
|
|
1763
2142
|
function formatSize2(bytes) {
|
|
1764
2143
|
if (bytes < 1024) return `${bytes}B`;
|
|
@@ -1790,7 +2169,7 @@ async function publishCommand(options) {
|
|
|
1790
2169
|
const manifest = JSON.parse(readFileSync10(manifestPath, "utf-8"));
|
|
1791
2170
|
const assets = manifest.assets || [];
|
|
1792
2171
|
const s = spinner("Preparing assets...");
|
|
1793
|
-
const
|
|
2172
|
+
const assetBuffers = [];
|
|
1794
2173
|
let totalBytes = 0;
|
|
1795
2174
|
for (let i = 0; i < assets.length; i++) {
|
|
1796
2175
|
const asset = assets[i];
|
|
@@ -1803,45 +2182,133 @@ async function publishCommand(options) {
|
|
|
1803
2182
|
"Run 'appfunnel build' to regenerate."
|
|
1804
2183
|
);
|
|
1805
2184
|
}
|
|
1806
|
-
const
|
|
1807
|
-
totalBytes +=
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
2185
|
+
const body = readFileSync10(fullPath);
|
|
2186
|
+
totalBytes += body.length;
|
|
2187
|
+
assetBuffers.push({ path: asset.path, body, size: body.length });
|
|
2188
|
+
s.text = `Preparing assets... ${i + 1}/${assets.length} ${pc9.dim(`(${formatSize2(totalBytes)})`)}`;
|
|
2189
|
+
}
|
|
2190
|
+
s.stop();
|
|
2191
|
+
const MAX_TOTAL_SIZE2 = 10 * 1024 * 1024;
|
|
2192
|
+
if (totalBytes > MAX_TOTAL_SIZE2) {
|
|
2193
|
+
throw new CLIError(
|
|
2194
|
+
"BUNDLE_TOO_LARGE",
|
|
2195
|
+
`Total bundle size (${formatSize2(totalBytes)}) exceeds the ${formatSize2(MAX_TOTAL_SIZE2)} limit.`,
|
|
2196
|
+
"Reduce page bundle sizes. Check for large dependencies."
|
|
2197
|
+
);
|
|
2198
|
+
}
|
|
2199
|
+
info(`${assets.length} assets prepared ${pc9.dim(`(${formatSize2(totalBytes)})`)}`);
|
|
2200
|
+
let promote = options?.promote ?? false;
|
|
2201
|
+
if (!promote) {
|
|
2202
|
+
promote = await select3({
|
|
2203
|
+
message: "Publish as the new active version?",
|
|
2204
|
+
choices: [
|
|
2205
|
+
{ name: "Yes", value: true },
|
|
2206
|
+
{ name: "No", value: false }
|
|
2207
|
+
]
|
|
2208
|
+
});
|
|
2209
|
+
}
|
|
2210
|
+
const git = getGitMetadata(cwd);
|
|
2211
|
+
const initSpinner = spinner("Initializing publish...");
|
|
2212
|
+
let initResult;
|
|
2213
|
+
try {
|
|
2214
|
+
initResult = await publishInit(
|
|
2215
|
+
projectId,
|
|
2216
|
+
{
|
|
2217
|
+
manifest,
|
|
2218
|
+
funnelId: config.funnelId || void 0,
|
|
2219
|
+
promote,
|
|
2220
|
+
commitSha: git.commitSha,
|
|
2221
|
+
branch: git.branch,
|
|
2222
|
+
message: git.message,
|
|
2223
|
+
triggeredBy: creds.email || void 0
|
|
2224
|
+
},
|
|
2225
|
+
{ token: creds.token }
|
|
2226
|
+
);
|
|
2227
|
+
} catch (err) {
|
|
2228
|
+
initSpinner.stop();
|
|
2229
|
+
if (err instanceof CLIError && err.code === "API_ERROR") {
|
|
2230
|
+
if (err.statusCode === 413) {
|
|
2231
|
+
throw new CLIError(
|
|
2232
|
+
"BUNDLE_TOO_LARGE",
|
|
2233
|
+
err.message,
|
|
2234
|
+
"Reduce page bundle sizes. Check for large dependencies."
|
|
2235
|
+
);
|
|
2236
|
+
}
|
|
2237
|
+
if (err.statusCode === 409) {
|
|
2238
|
+
throw new CLIError(
|
|
2239
|
+
"FUNNEL_NOT_HEADLESS",
|
|
2240
|
+
err.message,
|
|
2241
|
+
"Remove funnelId from config to create a new headless funnel."
|
|
2242
|
+
);
|
|
2243
|
+
}
|
|
2244
|
+
throw new CLIError("PUBLISH_FAILED", err.message);
|
|
2245
|
+
}
|
|
2246
|
+
throw err;
|
|
2247
|
+
}
|
|
2248
|
+
initSpinner.stop();
|
|
2249
|
+
info(`Build ${pc9.dim(initResult.buildId)} initialized`);
|
|
2250
|
+
const uploadSpinner = spinner(`Uploading 0/${assets.length} assets...`);
|
|
2251
|
+
const uploadInfoMap = new Map(initResult.assets.map((a) => [a.path, a]));
|
|
2252
|
+
let uploaded = 0;
|
|
2253
|
+
let uploadedBytes = 0;
|
|
2254
|
+
const uploadAsset = async (asset) => {
|
|
2255
|
+
const info2 = uploadInfoMap.get(asset.path);
|
|
2256
|
+
if (!info2) {
|
|
2257
|
+
throw new CLIError("PUBLISH_FAILED", `No upload URL for asset: ${asset.path}`);
|
|
2258
|
+
}
|
|
2259
|
+
const response = await fetch(info2.uploadUrl, {
|
|
2260
|
+
method: "PUT",
|
|
2261
|
+
body: new Uint8Array(asset.body),
|
|
2262
|
+
headers: {
|
|
2263
|
+
"Content-Type": info2.contentType
|
|
2264
|
+
}
|
|
1812
2265
|
});
|
|
1813
|
-
|
|
2266
|
+
if (!response.ok) {
|
|
2267
|
+
throw new CLIError(
|
|
2268
|
+
"PUBLISH_FAILED",
|
|
2269
|
+
`Failed to upload ${asset.path}: ${response.status} ${response.statusText}`
|
|
2270
|
+
);
|
|
2271
|
+
}
|
|
2272
|
+
uploaded++;
|
|
2273
|
+
uploadedBytes += asset.size;
|
|
2274
|
+
uploadSpinner.text = `Uploading ${uploaded}/${assets.length} assets... ${pc9.dim(`(${formatSize2(uploadedBytes)}/${formatSize2(totalBytes)})`)}`;
|
|
2275
|
+
};
|
|
2276
|
+
const queue = [...assetBuffers];
|
|
2277
|
+
const workers = [];
|
|
2278
|
+
for (let i = 0; i < Math.min(UPLOAD_CONCURRENCY, queue.length); i++) {
|
|
2279
|
+
workers.push((async () => {
|
|
2280
|
+
while (queue.length > 0) {
|
|
2281
|
+
const asset = queue.shift();
|
|
2282
|
+
await uploadAsset(asset);
|
|
2283
|
+
}
|
|
2284
|
+
})());
|
|
1814
2285
|
}
|
|
1815
|
-
|
|
1816
|
-
|
|
2286
|
+
await Promise.all(workers);
|
|
2287
|
+
uploadSpinner.stop();
|
|
2288
|
+
info(`${assets.length} assets uploaded ${pc9.dim(`(${formatSize2(totalBytes)})`)}`);
|
|
2289
|
+
const finalizeSpinner = spinner("Finalizing publish...");
|
|
2290
|
+
const result = await publishFinalize(
|
|
1817
2291
|
projectId,
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
assetPayloads,
|
|
1821
|
-
{ token: creds.token },
|
|
1822
|
-
options?.promote
|
|
2292
|
+
{ buildId: initResult.buildId, funnelId: initResult.funnelId },
|
|
2293
|
+
{ token: creds.token }
|
|
1823
2294
|
);
|
|
1824
|
-
|
|
1825
|
-
if (
|
|
1826
|
-
patchConfigFunnelId(cwd,
|
|
2295
|
+
finalizeSpinner.stop();
|
|
2296
|
+
if (initResult.created && initResult.funnelId) {
|
|
2297
|
+
patchConfigFunnelId(cwd, initResult.funnelId);
|
|
1827
2298
|
info(`Funnel created \u2014 funnelId added to appfunnel.config.ts`);
|
|
1828
2299
|
}
|
|
1829
2300
|
console.log();
|
|
1830
2301
|
success(result.activated ? "Published and activated" : "Published successfully");
|
|
1831
2302
|
console.log();
|
|
1832
|
-
console.log(` ${
|
|
1833
|
-
if (
|
|
1834
|
-
console.log(` ${
|
|
1835
|
-
}
|
|
1836
|
-
console.log(` ${pc8.dim("Dashboard:")} ${pc8.cyan(result.dashboardUrl)}`);
|
|
1837
|
-
console.log(` ${pc8.dim("Assets:")} ${assets.length} files ${pc8.dim(`(${formatSize2(totalBytes)})`)}`);
|
|
1838
|
-
if (!result.activated) {
|
|
1839
|
-
console.log();
|
|
1840
|
-
console.log(` ${pc8.dim("Tip:")} Use ${pc8.cyan("--promote")} to activate immediately, or promote from the dashboard.`);
|
|
2303
|
+
console.log(` ${pc9.dim("Build ID:")} ${result.buildId}`);
|
|
2304
|
+
if (initResult.funnelId && !config.funnelId) {
|
|
2305
|
+
console.log(` ${pc9.dim("Funnel:")} ${initResult.funnelId}`);
|
|
1841
2306
|
}
|
|
2307
|
+
console.log(` ${pc9.dim("Dashboard:")} ${pc9.cyan(result.dashboardUrl)}`);
|
|
2308
|
+
console.log(` ${pc9.dim("Assets:")} ${assets.length} files ${pc9.dim(`(${formatSize2(totalBytes)})`)}`);
|
|
1842
2309
|
console.log();
|
|
1843
2310
|
}
|
|
1844
|
-
var
|
|
2311
|
+
var UPLOAD_CONCURRENCY;
|
|
1845
2312
|
var init_publish = __esm({
|
|
1846
2313
|
"src/commands/publish.ts"() {
|
|
1847
2314
|
"use strict";
|
|
@@ -1852,26 +2319,16 @@ var init_publish = __esm({
|
|
|
1852
2319
|
init_api();
|
|
1853
2320
|
init_errors();
|
|
1854
2321
|
init_config_patch();
|
|
1855
|
-
|
|
1856
|
-
".js": "application/javascript",
|
|
1857
|
-
".css": "text/css",
|
|
1858
|
-
".html": "text/html",
|
|
1859
|
-
".json": "application/json",
|
|
1860
|
-
".svg": "image/svg+xml",
|
|
1861
|
-
".png": "image/png",
|
|
1862
|
-
".jpg": "image/jpeg",
|
|
1863
|
-
".woff2": "font/woff2",
|
|
1864
|
-
".woff": "font/woff"
|
|
1865
|
-
};
|
|
2322
|
+
UPLOAD_CONCURRENCY = 10;
|
|
1866
2323
|
}
|
|
1867
2324
|
});
|
|
1868
2325
|
|
|
1869
2326
|
// src/index.ts
|
|
1870
2327
|
init_errors();
|
|
1871
2328
|
import { Command } from "commander";
|
|
1872
|
-
import
|
|
2329
|
+
import pc10 from "picocolors";
|
|
1873
2330
|
var program = new Command();
|
|
1874
|
-
program.name("appfunnel").description("Build and publish headless AppFunnel projects").version("0.
|
|
2331
|
+
program.name("appfunnel").description("Build and publish headless AppFunnel projects").version("0.13.0");
|
|
1875
2332
|
program.command("init").argument("[name]", "Project directory name").description("Create a new AppFunnel project").action(async (name) => {
|
|
1876
2333
|
const { initCommand: initCommand2 } = await Promise.resolve().then(() => (init_init(), init_exports));
|
|
1877
2334
|
await initCommand2(name);
|
|
@@ -1888,6 +2345,10 @@ program.command("dev").description("Start the development server").option("-p, -
|
|
|
1888
2345
|
const { devCommand: devCommand2 } = await Promise.resolve().then(() => (init_dev(), dev_exports));
|
|
1889
2346
|
await devCommand2({ port: parseInt(options.port, 10) });
|
|
1890
2347
|
});
|
|
2348
|
+
program.command("routes").description("Visualize the funnel page flow").action(async () => {
|
|
2349
|
+
const { routesCommand: routesCommand2 } = await Promise.resolve().then(() => (init_routes(), routes_exports));
|
|
2350
|
+
await routesCommand2();
|
|
2351
|
+
});
|
|
1891
2352
|
program.command("build").description("Build the funnel for production").action(async () => {
|
|
1892
2353
|
const { buildCommand: buildCommand2 } = await Promise.resolve().then(() => (init_build(), build_exports));
|
|
1893
2354
|
await buildCommand2();
|
|
@@ -1906,9 +2367,9 @@ async function main() {
|
|
|
1906
2367
|
console.error(formatError(err));
|
|
1907
2368
|
process.exit(1);
|
|
1908
2369
|
}
|
|
1909
|
-
console.error(`${
|
|
2370
|
+
console.error(`${pc10.red("ERROR")}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1910
2371
|
if (err instanceof Error && err.stack) {
|
|
1911
|
-
console.error(
|
|
2372
|
+
console.error(pc10.dim(err.stack));
|
|
1912
2373
|
}
|
|
1913
2374
|
process.exit(1);
|
|
1914
2375
|
}
|