@woodsportal/hubspot-kit 1.0.29 → 1.0.30

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/cli.js CHANGED
@@ -9,8 +9,8 @@ import {
9
9
  } from "./chunk-GTTIJVAR.js";
10
10
 
11
11
  // src/cli/program.ts
12
- import { readFileSync as readFileSync13 } from "fs";
13
- import { resolve as resolve21 } from "path";
12
+ import { readFileSync as readFileSync15 } from "fs";
13
+ import { resolve as resolve23 } from "path";
14
14
  import { Command } from "commander";
15
15
 
16
16
  // src/lib/config.ts
@@ -520,7 +520,7 @@ var VITE_DEV_PEER_DEPS = {
520
520
  tailwindcss: "^4.0.6"
521
521
  };
522
522
  var KIT_DEV_DEP = "@woodsportal/hubspot-kit";
523
- var KIT_DEV_DEP_VERSION = "^1.0.29";
523
+ var KIT_DEV_DEP_VERSION = "^1.0.30";
524
524
  var MODULE_CONFIG_UI_DEPS = [
525
525
  "@dnd-kit/core",
526
526
  "@dnd-kit/sortable",
@@ -1109,7 +1109,7 @@ function formatProjectLabel(config) {
1109
1109
  return `${config.module.name} \xB7 ${config.projectType}${routerNote}`;
1110
1110
  }
1111
1111
  function section(color, title, body) {
1112
- const bold = (text2) => color ? pc3.bold(text2) : text2;
1112
+ const bold = (text3) => color ? pc3.bold(text3) : text3;
1113
1113
  return ` ${bold(title)}
1114
1114
  ${body}`;
1115
1115
  }
@@ -1118,10 +1118,10 @@ function printWoodsCliDevBanner(options) {
1118
1118
  return;
1119
1119
  }
1120
1120
  const color = useCliColor();
1121
- const cyan = (text2) => color ? pc3.cyan(text2) : text2;
1122
- const dim = (text2) => color ? pc3.dim(text2) : text2;
1123
- const bold = (text2) => color ? pc3.bold(text2) : text2;
1124
- const yellow = (text2) => color ? pc3.yellow(text2) : text2;
1121
+ const cyan = (text3) => color ? pc3.cyan(text3) : text3;
1122
+ const dim = (text3) => color ? pc3.dim(text3) : text3;
1123
+ const bold = (text3) => color ? pc3.bold(text3) : text3;
1124
+ const yellow = (text3) => color ? pc3.yellow(text3) : text3;
1125
1125
  const version = options.version ?? readKitVersion();
1126
1126
  const project = formatProjectLabel(options.config);
1127
1127
  const isTheme = isThemeProject(options.config);
@@ -1247,7 +1247,76 @@ async function runDev(ctx, options) {
1247
1247
  }
1248
1248
 
1249
1249
  // src/cli/commands/deploy.ts
1250
- import { resolve as resolve12 } from "path";
1250
+ import { resolve as resolve13 } from "path";
1251
+
1252
+ // src/lib/project-env-files.ts
1253
+ import { existsSync as existsSync8, readFileSync as readFileSync6, writeFileSync } from "fs";
1254
+ import { resolve as resolve11 } from "path";
1255
+ var ENV_LOCAL_FILE = ".env.local";
1256
+ var ENV_GITIGNORE_LINES = [".env", ".env.*", "!.env*.example", ENV_LOCAL_FILE];
1257
+ function parseEnvFile(content) {
1258
+ const result = {};
1259
+ for (const line of content.split("\n")) {
1260
+ const trimmed = line.trim();
1261
+ if (!trimmed || trimmed.startsWith("#")) continue;
1262
+ const eq = trimmed.indexOf("=");
1263
+ if (eq <= 0) continue;
1264
+ const key = trimmed.slice(0, eq).trim();
1265
+ let value = trimmed.slice(eq + 1).trim();
1266
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
1267
+ value = value.slice(1, -1);
1268
+ }
1269
+ result[key] = value;
1270
+ }
1271
+ return result;
1272
+ }
1273
+ function readEnvLocal(projectRoot) {
1274
+ const path = resolve11(projectRoot, ENV_LOCAL_FILE);
1275
+ if (!existsSync8(path)) return {};
1276
+ return parseEnvFile(readFileSync6(path, "utf8"));
1277
+ }
1278
+ function formatEnvLocal(entries) {
1279
+ const lines = [
1280
+ "# Local secrets \u2014 gitignored. Created by wp init / wp setup (hybrid CDN).",
1281
+ "# PORTAL_CDN_PUBLISH_TOKEN needs repo push on your portal.cdn.json mirror.",
1282
+ ""
1283
+ ];
1284
+ for (const [key, value] of Object.entries(entries)) {
1285
+ if (value.includes(" ") || value.includes("#")) {
1286
+ lines.push(`${key}="${value.replace(/"/g, '\\"')}"`);
1287
+ } else {
1288
+ lines.push(`${key}=${value}`);
1289
+ }
1290
+ }
1291
+ return `${lines.join("\n").trimEnd()}
1292
+ `;
1293
+ }
1294
+ function writeEnvLocal(projectRoot, entries) {
1295
+ const path = resolve11(projectRoot, ENV_LOCAL_FILE);
1296
+ const existing = existsSync8(path) ? parseEnvFile(readFileSync6(path, "utf8")) : {};
1297
+ writeFileSync(path, formatEnvLocal({ ...existing, ...entries }));
1298
+ }
1299
+ function mergeProjectEnv(projectRoot, base = process.env) {
1300
+ const local = readEnvLocal(projectRoot);
1301
+ const merged = { ...base };
1302
+ for (const [key, value] of Object.entries(local)) {
1303
+ if (merged[key] == null || merged[key] === "") {
1304
+ merged[key] = value;
1305
+ }
1306
+ }
1307
+ return merged;
1308
+ }
1309
+ function ensureEnvFilesGitignore(projectRoot) {
1310
+ const gitignorePath = resolve11(projectRoot, ".gitignore");
1311
+ const existing = existsSync8(gitignorePath) ? readFileSync6(gitignorePath, "utf8") : "";
1312
+ const toAppend = ENV_GITIGNORE_LINES.filter((line) => !existing.includes(line)).join("\n");
1313
+ if (!toAppend) return;
1314
+ writeFileSync(gitignorePath, `${existing.trimEnd()}
1315
+
1316
+ # Env files (secrets)
1317
+ ${toAppend}
1318
+ `);
1319
+ }
1251
1320
 
1252
1321
  // src/cli/commands/publish.ts
1253
1322
  async function runPublish(ctx, options) {
@@ -1258,7 +1327,10 @@ async function runPublish(ctx, options) {
1258
1327
  });
1259
1328
  }
1260
1329
  const tier = options.tier ?? DEFAULT_BUILD_TIER;
1261
- const env = { ...process.env, WP_HS_PROJECT_ROOT: ctx.projectRoot };
1330
+ const env = mergeProjectEnv(ctx.projectRoot, {
1331
+ ...process.env,
1332
+ WP_HS_PROJECT_ROOT: ctx.projectRoot
1333
+ });
1262
1334
  const execOpts = {
1263
1335
  cwd: ctx.projectRoot,
1264
1336
  env,
@@ -1305,14 +1377,14 @@ async function runPublish(ctx, options) {
1305
1377
  }
1306
1378
 
1307
1379
  // src/cli/commands/upload.ts
1308
- import { resolve as resolve11 } from "path";
1380
+ import { resolve as resolve12 } from "path";
1309
1381
  async function uploadModuleOrTheme(ctx) {
1310
1382
  if (isThemeProject(ctx.config)) {
1311
1383
  const theme = ctx.config.theme;
1312
1384
  if (!theme) {
1313
1385
  throw new WpHsError("theme block missing in woodscli.json", { exitCode: ExitCode.USER_ERROR });
1314
1386
  }
1315
- const srcDir = resolve11(ctx.projectRoot, theme.srcDir);
1387
+ const srcDir = resolve12(ctx.projectRoot, theme.srcDir);
1316
1388
  runCommand("hs", ["cms", "upload", srcDir, theme.uploadPath], {
1317
1389
  cwd: ctx.projectRoot,
1318
1390
  logger: ctx.logger,
@@ -1320,7 +1392,7 @@ async function uploadModuleOrTheme(ctx) {
1320
1392
  });
1321
1393
  return `Uploaded theme ${srcDir} \u2192 ${theme.uploadPath}`;
1322
1394
  }
1323
- const outDir = resolve11(ctx.projectRoot, ctx.config.module.outDir);
1395
+ const outDir = resolve12(ctx.projectRoot, ctx.config.module.outDir);
1324
1396
  const uploadPath = ctx.config.module.uploadPath;
1325
1397
  runCommand("hs", ["cms", "upload", outDir, uploadPath], {
1326
1398
  cwd: ctx.projectRoot,
@@ -1432,7 +1504,7 @@ async function runDeploy(ctx, options) {
1432
1504
  if (!ctx.global.json) {
1433
1505
  printCommandDone(
1434
1506
  ctx,
1435
- `Deploy complete (${resolve12(ctx.projectRoot, ctx.config.module.outDir)} \u2192 ${ctx.config.module.uploadPath})`
1507
+ `Deploy complete (${resolve13(ctx.projectRoot, ctx.config.module.outDir)} \u2192 ${ctx.config.module.uploadPath})`
1436
1508
  );
1437
1509
  }
1438
1510
  return ExitCode.SUCCESS;
@@ -1440,8 +1512,8 @@ async function runDeploy(ctx, options) {
1440
1512
 
1441
1513
  // src/cli/commands/audit.ts
1442
1514
  import { createHash } from "crypto";
1443
- import { cpSync, existsSync as existsSync8, mkdirSync, readFileSync as readFileSync6, readdirSync, rmSync, statSync } from "fs";
1444
- import { resolve as resolve13, join as join2 } from "path";
1515
+ import { cpSync, existsSync as existsSync9, mkdirSync, readFileSync as readFileSync7, readdirSync, rmSync, statSync } from "fs";
1516
+ import { resolve as resolve14, join as join2 } from "path";
1445
1517
  var FORBIDDEN_SHIP_TOKENS = [
1446
1518
  "ModuleConfigOverlay",
1447
1519
  "module-config-store",
@@ -1450,10 +1522,10 @@ var FORBIDDEN_SHIP_TOKENS = [
1450
1522
  "dev.module-config"
1451
1523
  ];
1452
1524
  function hashFile(path) {
1453
- return createHash("sha256").update(readFileSync6(path)).digest("hex");
1525
+ return createHash("sha256").update(readFileSync7(path)).digest("hex");
1454
1526
  }
1455
1527
  function walkFiles(dir) {
1456
- if (!existsSync8(dir)) return [];
1528
+ if (!existsSync9(dir)) return [];
1457
1529
  const entries = readdirSync(dir);
1458
1530
  const files = [];
1459
1531
  for (const entry of entries) {
@@ -1469,14 +1541,14 @@ function walkFiles(dir) {
1469
1541
  async function runAuditShipBundle(ctx) {
1470
1542
  const root = ctx.projectRoot;
1471
1543
  const targets = [
1472
- resolve13(root, "dist/cdn"),
1473
- resolve13(root, ctx.config.module.outDir)
1544
+ resolve14(root, "dist/cdn"),
1545
+ resolve14(root, ctx.config.module.outDir)
1474
1546
  ];
1475
1547
  const violations = [];
1476
1548
  for (const dir of targets) {
1477
1549
  for (const file of walkFiles(dir)) {
1478
1550
  if (!/\.(js|css|html)$/i.test(file)) continue;
1479
- const content = readFileSync6(file, "utf8");
1551
+ const content = readFileSync7(file, "utf8");
1480
1552
  for (const token of FORBIDDEN_SHIP_TOKENS) {
1481
1553
  if (content.includes(token)) {
1482
1554
  violations.push(`${file}: contains forbidden token "${token}"`);
@@ -1503,21 +1575,21 @@ async function runAuditBaselineCapture(ctx, options) {
1503
1575
  const root = ctx.projectRoot;
1504
1576
  const baseline = options.baselineDir ?? defaultBaselineDir(root, ctx.config);
1505
1577
  const sources = [
1506
- { label: "cdn", path: resolve13(root, "dist/cdn") },
1507
- { label: "hubspot", path: resolve13(root, ctx.config.module.outDir) }
1578
+ { label: "cdn", path: resolve14(root, "dist/cdn") },
1579
+ { label: "hubspot", path: resolve14(root, ctx.config.module.outDir) }
1508
1580
  ];
1509
1581
  if (ctx.global.dryRun) {
1510
1582
  ctx.logger.info(`[dry-run] Would capture baseline to ${baseline}`);
1511
1583
  return ExitCode.SUCCESS;
1512
1584
  }
1513
1585
  for (const { label, path } of sources) {
1514
- if (!existsSync8(path)) {
1586
+ if (!existsSync9(path)) {
1515
1587
  throw new WpHsError(`Cannot capture baseline \u2014 missing ${path}`, {
1516
1588
  exitCode: ExitCode.USER_ERROR,
1517
1589
  remediation: "Run wp build before wp audit baseline --capture."
1518
1590
  });
1519
1591
  }
1520
- const target = resolve13(baseline, label);
1592
+ const target = resolve14(baseline, label);
1521
1593
  rmSync(target, { recursive: true, force: true });
1522
1594
  mkdirSync(baseline, { recursive: true });
1523
1595
  cpSync(path, target, { recursive: true });
@@ -1533,13 +1605,13 @@ async function runAuditParity(ctx, options) {
1533
1605
  const root = ctx.projectRoot;
1534
1606
  const baseline = options.baselineDir ?? defaultBaselineDir(root, ctx.config);
1535
1607
  const currentDirs = [
1536
- { label: "cdn", path: resolve13(root, "dist/cdn") },
1537
- { label: "hubspot", path: resolve13(root, ctx.config.module.outDir) }
1608
+ { label: "cdn", path: resolve14(root, "dist/cdn") },
1609
+ { label: "hubspot", path: resolve14(root, ctx.config.module.outDir) }
1538
1610
  ];
1539
1611
  const mismatches = [];
1540
1612
  for (const { label, path } of currentDirs) {
1541
- const baselinePath = resolve13(baseline, label);
1542
- if (!existsSync8(baselinePath)) {
1613
+ const baselinePath = resolve14(baseline, label);
1614
+ if (!existsSync9(baselinePath)) {
1543
1615
  ctx.logger.warn(`No baseline at ${baselinePath} \u2014 skipping ${label} parity`);
1544
1616
  continue;
1545
1617
  }
@@ -1554,8 +1626,8 @@ async function runAuditParity(ctx, options) {
1554
1626
  }
1555
1627
  for (const file of baselineFiles) {
1556
1628
  const rel = file.replace(baselinePath, "");
1557
- const current = resolve13(path, rel.slice(1));
1558
- if (!existsSync8(current)) continue;
1629
+ const current = resolve14(path, rel.slice(1));
1630
+ if (!existsSync9(current)) continue;
1559
1631
  const bh = hashFile(file);
1560
1632
  const ch = hashFile(current);
1561
1633
  if (bh !== ch) {
@@ -1582,13 +1654,13 @@ var SECRET_PATTERNS = [
1582
1654
  ];
1583
1655
  async function runAuditSecrets(ctx) {
1584
1656
  const root = ctx.projectRoot;
1585
- const scanRoots = ["src", "public", ".wphs/config"].map((d) => resolve13(root, d));
1657
+ const scanRoots = ["src", "public", ".wphs/config"].map((d) => resolve14(root, d));
1586
1658
  const findings = [];
1587
1659
  for (const dir of scanRoots) {
1588
1660
  for (const file of walkFiles(dir)) {
1589
1661
  if (!/\.(ts|tsx|js|jsx|json|env|yml|yaml|html)$/i.test(file)) continue;
1590
1662
  if (file.includes("node_modules")) continue;
1591
- const content = readFileSync6(file, "utf8");
1663
+ const content = readFileSync7(file, "utf8");
1592
1664
  for (const pattern of SECRET_PATTERNS) {
1593
1665
  pattern.lastIndex = 0;
1594
1666
  if (pattern.test(content)) {
@@ -1614,13 +1686,13 @@ async function runAuditSecrets(ctx) {
1614
1686
  }
1615
1687
 
1616
1688
  // src/cli/commands/init.ts
1617
- import { cpSync as cpSync2, existsSync as existsSync11, mkdirSync as mkdirSync3, readdirSync as readdirSync2, readFileSync as readFileSync8, writeFileSync as writeFileSync3 } from "fs";
1618
- import { basename as basename2, resolve as resolve16 } from "path";
1619
- import * as p2 from "@clack/prompts";
1689
+ import { cpSync as cpSync2, existsSync as existsSync13, mkdirSync as mkdirSync3, readdirSync as readdirSync2, readFileSync as readFileSync10, writeFileSync as writeFileSync5 } from "fs";
1690
+ import { basename as basename2, resolve as resolve18 } from "path";
1691
+ import * as p3 from "@clack/prompts";
1620
1692
 
1621
1693
  // src/lib/init-router-scaffold.ts
1622
- import { existsSync as existsSync9, mkdirSync as mkdirSync2, readFileSync as readFileSync7, rmSync as rmSync2, writeFileSync } from "fs";
1623
- import { resolve as resolve14 } from "path";
1694
+ import { existsSync as existsSync10, mkdirSync as mkdirSync2, readFileSync as readFileSync8, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
1695
+ import { resolve as resolve15 } from "path";
1624
1696
  var ROUTER_FILES = [
1625
1697
  "src/routes",
1626
1698
  "src/routeTree.gen.ts",
@@ -1634,10 +1706,10 @@ function isModuleTemplate(template) {
1634
1706
  return template === "module-hybrid-cdn" || template === "module-monolith";
1635
1707
  }
1636
1708
  function writeRouterConfig(targetDir, mode) {
1637
- const configDir = resolve14(targetDir, "src/config");
1709
+ const configDir = resolve15(targetDir, "src/config");
1638
1710
  mkdirSync2(configDir, { recursive: true });
1639
- writeFileSync(
1640
- resolve14(configDir, "router.ts"),
1711
+ writeFileSync2(
1712
+ resolve15(configDir, "router.ts"),
1641
1713
  `/** Router history \u2014 set by \`wp init\`. Hash matches woodsportal-client-frontend (HubSpot CMS embeds). */
1642
1714
  export type RouterHistoryMode = 'hash' | 'browser'
1643
1715
 
@@ -1646,8 +1718,8 @@ export const ROUTER_HISTORY: RouterHistoryMode = '${mode}'
1646
1718
  );
1647
1719
  }
1648
1720
  function writeMountDevPreviewRouter(targetDir) {
1649
- writeFileSync(
1650
- resolve14(targetDir, "src/portal/mount-dev-preview.tsx"),
1721
+ writeFileSync2(
1722
+ resolve15(targetDir, "src/portal/mount-dev-preview.tsx"),
1651
1723
  `import { mountGreenfieldDev } from '../../.wphs/.kit-bootstrap-greenfield.tsx'
1652
1724
 
1653
1725
  import { App } from '@/app'
@@ -1660,8 +1732,8 @@ export async function mountDevPreview() {
1660
1732
  );
1661
1733
  }
1662
1734
  function writeMountDevPreviewSinglePage(targetDir) {
1663
- writeFileSync(
1664
- resolve14(targetDir, "src/portal/mount-dev-preview.tsx"),
1735
+ writeFileSync2(
1736
+ resolve15(targetDir, "src/portal/mount-dev-preview.tsx"),
1665
1737
  `import { mountGreenfieldDev } from '../../.wphs/.kit-bootstrap-greenfield.tsx'
1666
1738
 
1667
1739
  import { StarterSinglePage } from '@/features/starter'
@@ -1674,8 +1746,8 @@ export async function mountDevPreview() {
1674
1746
  );
1675
1747
  }
1676
1748
  function writeMountPortalRouter(targetDir) {
1677
- writeFileSync(
1678
- resolve14(targetDir, "src/portal/mount-portal.tsx"),
1749
+ writeFileSync2(
1750
+ resolve15(targetDir, "src/portal/mount-portal.tsx"),
1679
1751
  `import ReactDOM from 'react-dom/client'
1680
1752
 
1681
1753
  import { App } from '@/app'
@@ -1692,8 +1764,8 @@ export async function mountPortal() {
1692
1764
  );
1693
1765
  }
1694
1766
  function writeMountPortalSinglePage(targetDir) {
1695
- writeFileSync(
1696
- resolve14(targetDir, "src/portal/mount-portal.tsx"),
1767
+ writeFileSync2(
1768
+ resolve15(targetDir, "src/portal/mount-portal.tsx"),
1697
1769
  `import ReactDOM from 'react-dom/client'
1698
1770
 
1699
1771
  import { StarterSinglePage } from '@/features/starter'
@@ -1710,11 +1782,11 @@ export async function mountPortal() {
1710
1782
  );
1711
1783
  }
1712
1784
  function writeStarterSinglePage(targetDir) {
1713
- const file = resolve14(targetDir, "src/features/starter/components/starter-single-page.tsx");
1714
- if (existsSync9(file)) {
1785
+ const file = resolve15(targetDir, "src/features/starter/components/starter-single-page.tsx");
1786
+ if (existsSync10(file)) {
1715
1787
  return;
1716
1788
  }
1717
- writeFileSync(
1789
+ writeFileSync2(
1718
1790
  file,
1719
1791
  `import { StarterShell } from '@/components/layout/starter-shell'
1720
1792
 
@@ -1737,25 +1809,25 @@ export function StarterSinglePage() {
1737
1809
  );
1738
1810
  }
1739
1811
  function removePath(targetDir, relativePath) {
1740
- const full = resolve14(targetDir, relativePath);
1741
- if (!existsSync9(full)) {
1812
+ const full = resolve15(targetDir, relativePath);
1813
+ if (!existsSync10(full)) {
1742
1814
  return;
1743
1815
  }
1744
1816
  rmSync2(full, { recursive: true, force: true });
1745
1817
  }
1746
1818
  function stripTanStackFromPackage(targetDir) {
1747
- const pkgPath = resolve14(targetDir, "package.json");
1748
- if (!existsSync9(pkgPath)) {
1819
+ const pkgPath = resolve15(targetDir, "package.json");
1820
+ if (!existsSync10(pkgPath)) {
1749
1821
  return;
1750
1822
  }
1751
- const pkg = JSON.parse(readFileSync7(pkgPath, "utf8"));
1823
+ const pkg = JSON.parse(readFileSync8(pkgPath, "utf8"));
1752
1824
  if (!pkg.devDependencies) {
1753
1825
  return;
1754
1826
  }
1755
1827
  for (const dep of TANSTACK_DEPS) {
1756
1828
  delete pkg.devDependencies[dep];
1757
1829
  }
1758
- writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}
1830
+ writeFileSync2(pkgPath, `${JSON.stringify(pkg, null, 2)}
1759
1831
  `);
1760
1832
  }
1761
1833
  function applyRouterScaffold(targetDir, mode, template) {
@@ -1775,22 +1847,141 @@ function applyRouterScaffold(targetDir, mode, template) {
1775
1847
  writeRouterConfig(targetDir, mode);
1776
1848
  writeMountDevPreviewRouter(targetDir);
1777
1849
  writeMountPortalRouter(targetDir);
1778
- const featuresIndex = resolve14(targetDir, "src/features/starter/index.ts");
1779
- if (existsSync9(featuresIndex)) {
1780
- let content = readFileSync7(featuresIndex, "utf8");
1850
+ const featuresIndex = resolve15(targetDir, "src/features/starter/index.ts");
1851
+ if (existsSync10(featuresIndex)) {
1852
+ let content = readFileSync8(featuresIndex, "utf8");
1781
1853
  if (!content.includes("StarterSinglePage")) {
1782
1854
  content = content.replace(
1783
1855
  "export type { StarterPreviewValues } from './hooks/use-starter-preview'\n",
1784
1856
  "export type { StarterPreviewValues } from './hooks/use-starter-preview'\nexport { StarterSinglePage } from './components/starter-single-page'\n"
1785
1857
  );
1786
- writeFileSync(featuresIndex, content);
1858
+ writeFileSync2(featuresIndex, content);
1787
1859
  }
1788
1860
  }
1789
1861
  }
1790
1862
 
1863
+ // src/lib/cdn-github-setup.ts
1864
+ import { existsSync as existsSync11, readFileSync as readFileSync9, writeFileSync as writeFileSync3 } from "fs";
1865
+ import { resolve as resolve16 } from "path";
1866
+ import * as p2 from "@clack/prompts";
1867
+ var PORTAL_CDN_PUBLISH_TOKEN_ENV = "PORTAL_CDN_PUBLISH_TOKEN";
1868
+ var PORTAL_CDN_JSON = "portal.cdn.json";
1869
+ var PLACEHOLDER_GITHUB = /your-org|your-portal-cdn-mirror/i;
1870
+ function defaultPortalCdnJson() {
1871
+ const examplePath = kitPath("templates", "shared", "portal.cdn.json.example");
1872
+ if (existsSync11(examplePath)) {
1873
+ return JSON.parse(readFileSync9(examplePath, "utf8"));
1874
+ }
1875
+ return {
1876
+ publishMode: "jsdelivr",
1877
+ github: "your-org/your-portal-cdn-mirror",
1878
+ gitRemote: "https://github.com/your-org/your-portal-cdn-mirror.git",
1879
+ cdnPathInRepo: "dist/cdn",
1880
+ defaultBranch: "main"
1881
+ };
1882
+ }
1883
+ function readPortalCdnJson(projectRoot) {
1884
+ const path = resolve16(projectRoot, PORTAL_CDN_JSON);
1885
+ if (!existsSync11(path)) return null;
1886
+ try {
1887
+ return JSON.parse(readFileSync9(path, "utf8"));
1888
+ } catch {
1889
+ return null;
1890
+ }
1891
+ }
1892
+ function normalizeGithubSlug(input) {
1893
+ const trimmed = input.trim().replace(/^https:\/\/github\.com\//, "").replace(/\.git$/, "");
1894
+ const parts = trimmed.split("/").filter(Boolean);
1895
+ if (parts.length !== 2) return null;
1896
+ return `${parts[0]}/${parts[1]}`;
1897
+ }
1898
+ function portalCdnJsonFromGithub(github, base) {
1899
+ const slug = normalizeGithubSlug(github);
1900
+ if (!slug) {
1901
+ throw new Error(`Invalid GitHub repo \u2014 use org/repo (got "${github}")`);
1902
+ }
1903
+ return {
1904
+ publishMode: base?.publishMode ?? "jsdelivr",
1905
+ github: slug,
1906
+ gitRemote: `https://github.com/${slug}.git`,
1907
+ cdnPathInRepo: base?.cdnPathInRepo ?? "dist/cdn",
1908
+ defaultBranch: base?.defaultBranch ?? "main"
1909
+ };
1910
+ }
1911
+ function hasCdnPublishToken(projectRoot) {
1912
+ if (process.env[PORTAL_CDN_PUBLISH_TOKEN_ENV]?.trim()) return true;
1913
+ if (process.env.GITHUB_TOKEN?.trim()) return true;
1914
+ return Boolean(readEnvLocal(projectRoot)[PORTAL_CDN_PUBLISH_TOKEN_ENV]?.trim());
1915
+ }
1916
+ function hasConfiguredCdnGithubMirror(projectRoot) {
1917
+ const config = readPortalCdnJson(projectRoot);
1918
+ const github = config?.github ?? "";
1919
+ return Boolean(github) && !PLACEHOLDER_GITHUB.test(github);
1920
+ }
1921
+ function githubDefaultForPrompt(projectRoot) {
1922
+ const existing = readPortalCdnJson(projectRoot);
1923
+ if (existing?.github && !PLACEHOLDER_GITHUB.test(existing.github)) {
1924
+ return existing.github;
1925
+ }
1926
+ return defaultPortalCdnJson().github;
1927
+ }
1928
+ function needsCdnGitHubSetup(projectRoot) {
1929
+ return !hasConfiguredCdnGithubMirror(projectRoot) || !hasCdnPublishToken(projectRoot);
1930
+ }
1931
+ function applyCdnGitHubSetup(projectRoot, input) {
1932
+ const existing = readPortalCdnJson(projectRoot) ?? defaultPortalCdnJson();
1933
+ const updated = portalCdnJsonFromGithub(input.github, existing);
1934
+ writeFileSync3(resolve16(projectRoot, PORTAL_CDN_JSON), `${JSON.stringify(updated, null, 2)}
1935
+ `);
1936
+ if (input.token?.trim()) {
1937
+ writeEnvLocal(projectRoot, { [PORTAL_CDN_PUBLISH_TOKEN_ENV]: input.token.trim() });
1938
+ }
1939
+ ensureEnvFilesGitignore(projectRoot);
1940
+ }
1941
+ async function promptCdnGitHubSetup(options = {}) {
1942
+ if (options.skip) return null;
1943
+ const projectRoot = options.projectRoot ?? process.cwd();
1944
+ if (!needsCdnGitHubSetup(projectRoot)) {
1945
+ return null;
1946
+ }
1947
+ let github = options.github?.trim();
1948
+ if (!github && hasConfiguredCdnGithubMirror(projectRoot)) {
1949
+ github = readPortalCdnJson(projectRoot)?.github;
1950
+ }
1951
+ if (!github) {
1952
+ const answer = await p2.text({
1953
+ message: "Public GitHub CDN mirror (org/repo)",
1954
+ placeholder: options.githubDefault ?? "your-org/your-portal-cdn-mirror",
1955
+ initialValue: options.githubDefault,
1956
+ validate: (value) => {
1957
+ const slug = normalizeGithubSlug(value ?? "");
1958
+ return slug ? void 0 : "Enter a public repo as org/repo (jsDelivr serves from it)";
1959
+ }
1960
+ });
1961
+ if (p2.isCancel(answer)) return null;
1962
+ github = normalizeGithubSlug(answer) ?? void 0;
1963
+ }
1964
+ if (!github) return null;
1965
+ if (hasCdnPublishToken(projectRoot)) {
1966
+ return { github };
1967
+ }
1968
+ const tokenAnswer = await p2.password({
1969
+ message: "GitHub token (repo push on CDN mirror)",
1970
+ validate: (value) => {
1971
+ if (!value?.trim()) return "Required for wp publish \u2014 create a fine-grained PAT with Contents: Read and write";
1972
+ return void 0;
1973
+ }
1974
+ });
1975
+ if (p2.isCancel(tokenAnswer)) return null;
1976
+ return { github, token: tokenAnswer.trim() };
1977
+ }
1978
+ function formatCdnSetupNote(_projectRoot) {
1979
+ return `CDN config written \u2014 edit ${PORTAL_CDN_JSON} if needed; token saved to ${ENV_LOCAL_FILE} (gitignored)`;
1980
+ }
1981
+
1791
1982
  // src/lib/project-setup.ts
1792
- import { copyFileSync, existsSync as existsSync10, writeFileSync as writeFileSync2 } from "fs";
1793
- import { basename, resolve as resolve15 } from "path";
1983
+ import { copyFileSync, existsSync as existsSync12, writeFileSync as writeFileSync4 } from "fs";
1984
+ import { basename, resolve as resolve17 } from "path";
1794
1985
  function envFileForTier(tier) {
1795
1986
  return tier === "dev" ? ".env.dev" : tier === "stg" ? ".env.stg" : ".env.prod";
1796
1987
  }
@@ -1800,13 +1991,13 @@ function tiersToSetup(config, tier) {
1800
1991
  }
1801
1992
  function firstExisting(paths) {
1802
1993
  for (const path of paths) {
1803
- if (existsSync10(path)) return path;
1994
+ if (existsSync12(path)) return path;
1804
1995
  }
1805
1996
  return null;
1806
1997
  }
1807
1998
  function copyMissingFile(projectRoot, targetName, sourceCandidates, dryRun) {
1808
- const target = resolve15(projectRoot, targetName);
1809
- if (existsSync10(target)) {
1999
+ const target = resolve17(projectRoot, targetName);
2000
+ if (existsSync12(target)) {
1810
2001
  return { action: "skipped", path: targetName, detail: "already present" };
1811
2002
  }
1812
2003
  const source = firstExisting(sourceCandidates);
@@ -1828,12 +2019,12 @@ function copyMissingFile(projectRoot, targetName, sourceCandidates, dryRun) {
1828
2019
  };
1829
2020
  }
1830
2021
  function writeMissingFile(projectRoot, targetName, content, dryRun) {
1831
- const target = resolve15(projectRoot, targetName);
1832
- if (existsSync10(target)) {
2022
+ const target = resolve17(projectRoot, targetName);
2023
+ if (existsSync12(target)) {
1833
2024
  return { action: "skipped", path: targetName, detail: "already present" };
1834
2025
  }
1835
2026
  if (!dryRun) {
1836
- writeFileSync2(target, content);
2027
+ writeFileSync4(target, content);
1837
2028
  }
1838
2029
  return {
1839
2030
  action: "copied",
@@ -1859,8 +2050,8 @@ function applyProjectSetup(ctx, options = {}) {
1859
2050
  const templateDir = kitTemplateDir(config);
1860
2051
  results.push(
1861
2052
  copyMissingFile(projectRoot, HUBSPOT_CONFIG_FILE, [
1862
- resolve15(projectRoot, HUBSPOT_CONFIG_EXAMPLE),
1863
- resolve15(templateDir, HUBSPOT_CONFIG_EXAMPLE),
2053
+ resolve17(projectRoot, HUBSPOT_CONFIG_EXAMPLE),
2054
+ resolve17(templateDir, HUBSPOT_CONFIG_EXAMPLE),
1864
2055
  kitPath("templates", HUBSPOT_CONFIG_EXAMPLE)
1865
2056
  ], dryRun)
1866
2057
  );
@@ -1869,8 +2060,8 @@ function applyProjectSetup(ctx, options = {}) {
1869
2060
  const envExample = `${envFile}.example`;
1870
2061
  results.push(
1871
2062
  copyMissingFile(projectRoot, envFile, [
1872
- resolve15(projectRoot, envExample),
1873
- resolve15(sharedDir, envExample)
2063
+ resolve17(projectRoot, envExample),
2064
+ resolve17(sharedDir, envExample)
1874
2065
  ], dryRun)
1875
2066
  );
1876
2067
  }
@@ -1878,8 +2069,8 @@ function applyProjectSetup(ctx, options = {}) {
1878
2069
  const mirror = config.cdn.mirror;
1879
2070
  results.push(
1880
2071
  copyMissingFile(projectRoot, mirror, [
1881
- resolve15(projectRoot, `${mirror}.example`),
1882
- resolve15(sharedDir, "portal.cdn.json.example")
2072
+ resolve17(projectRoot, `${mirror}.example`),
2073
+ resolve17(sharedDir, "portal.cdn.json.example")
1883
2074
  ], dryRun)
1884
2075
  );
1885
2076
  const buildIdFile = config.cdn.buildIdFile;
@@ -1889,15 +2080,15 @@ function applyProjectSetup(ctx, options = {}) {
1889
2080
  }
1890
2081
  for (const file of templateViteFiles(config)) {
1891
2082
  results.push(
1892
- copyMissingFile(projectRoot, file, [resolve15(templateDir, file)], dryRun)
2083
+ copyMissingFile(projectRoot, file, [resolve17(templateDir, file)], dryRun)
1893
2084
  );
1894
2085
  }
1895
2086
  if (isModuleProject(config) && !isThemeProject(config)) {
1896
2087
  const adapter = config.dev.adapter;
1897
- const adapterPath = resolve15(projectRoot, adapter);
1898
- if (!existsSync10(adapterPath)) {
1899
- const templateAdapter = resolve15(templateDir, "wphs.adapter.ts");
1900
- if (existsSync10(templateAdapter)) {
2088
+ const adapterPath = resolve17(projectRoot, adapter);
2089
+ if (!existsSync12(adapterPath)) {
2090
+ const templateAdapter = resolve17(templateDir, "wphs.adapter.ts");
2091
+ if (existsSync12(templateAdapter)) {
1901
2092
  results.push(copyMissingFile(projectRoot, adapter, [templateAdapter], dryRun));
1902
2093
  } else {
1903
2094
  results.push({
@@ -1914,26 +2105,26 @@ function applyProjectSetup(ctx, options = {}) {
1914
2105
  }
1915
2106
  function copySharedSetupExamples(targetDir) {
1916
2107
  const sharedDir = kitPath("templates", "shared");
1917
- if (!existsSync10(sharedDir)) return;
2108
+ if (!existsSync12(sharedDir)) return;
1918
2109
  for (const name of [
1919
2110
  ".env.dev.example",
1920
2111
  ".env.stg.example",
1921
2112
  ".env.prod.example",
1922
2113
  "portal.cdn.json.example"
1923
2114
  ]) {
1924
- const src = resolve15(sharedDir, name);
1925
- const dest = resolve15(targetDir, name);
1926
- if (existsSync10(src) && !existsSync10(dest)) {
2115
+ const src = resolve17(sharedDir, name);
2116
+ const dest = resolve17(targetDir, name);
2117
+ if (existsSync12(src) && !existsSync12(dest)) {
1927
2118
  copyFileSync(src, dest);
1928
2119
  }
1929
2120
  }
1930
2121
  const hubspotExample = firstExisting([
1931
- resolve15(sharedDir, HUBSPOT_CONFIG_EXAMPLE),
2122
+ resolve17(sharedDir, HUBSPOT_CONFIG_EXAMPLE),
1932
2123
  kitPath("templates", HUBSPOT_CONFIG_EXAMPLE)
1933
2124
  ]);
1934
2125
  if (!hubspotExample) return;
1935
- const destHubspot = resolve15(targetDir, HUBSPOT_CONFIG_EXAMPLE);
1936
- if (!existsSync10(destHubspot) && !existsSync10(resolve15(targetDir, HUBSPOT_CONFIG_FILE))) {
2126
+ const destHubspot = resolve17(targetDir, HUBSPOT_CONFIG_EXAMPLE);
2127
+ if (!existsSync12(destHubspot) && !existsSync12(resolve17(targetDir, HUBSPOT_CONFIG_FILE))) {
1937
2128
  copyFileSync(hubspotExample, destHubspot);
1938
2129
  }
1939
2130
  }
@@ -1946,6 +2137,10 @@ function formatSetupHint(results) {
1946
2137
  if (envCopied) {
1947
2138
  return "Edit .env.* files \u2014 set VITE_PUBLIC_REST_API_ENDPOINT and portal IDs for your environment";
1948
2139
  }
2140
+ const cdnMirror = results.find((r) => r.path === "portal.cdn.json" && r.action === "copied");
2141
+ if (cdnMirror) {
2142
+ return "Hybrid CDN: set public GitHub mirror in portal.cdn.json; token in .env.local (wp init prompts)";
2143
+ }
1949
2144
  return void 0;
1950
2145
  }
1951
2146
 
@@ -2027,7 +2222,7 @@ function buildConfigForProject(template, projectName, router) {
2027
2222
  return config;
2028
2223
  }
2029
2224
  function assertTargetDirIsScaffoldable(targetDir) {
2030
- if (!existsSync11(targetDir)) return;
2225
+ if (!existsSync13(targetDir)) return;
2031
2226
  const entries = readdirSync2(targetDir).filter((entry) => entry !== ".DS_Store");
2032
2227
  if (entries.length > 0) {
2033
2228
  throw new WpHsError(`Target directory is not empty: ${targetDir}`, {
@@ -2038,7 +2233,7 @@ function assertTargetDirIsScaffoldable(targetDir) {
2038
2233
  }
2039
2234
  function resolveInitTargetDir(options, interactiveName) {
2040
2235
  if (options.directory) {
2041
- return resolve16(options.directory);
2236
+ return resolve18(options.directory);
2042
2237
  }
2043
2238
  const parentDir = process.cwd();
2044
2239
  const slug = options.name ?? interactiveName;
@@ -2052,7 +2247,7 @@ function resolveInitTargetDir(options, interactiveName) {
2052
2247
  if (error) {
2053
2248
  throw new WpHsError(error, { exitCode: ExitCode.USER_ERROR });
2054
2249
  }
2055
- return resolve16(parentDir, slug);
2250
+ return resolve18(parentDir, slug);
2056
2251
  }
2057
2252
  async function runInit(ctx, options) {
2058
2253
  const nonInteractive = ctx.global.ci || ctx.global.yes || ctx.global.json;
@@ -2061,7 +2256,7 @@ async function runInit(ctx, options) {
2061
2256
  let router = options.router;
2062
2257
  if (!nonInteractive) {
2063
2258
  if (!template) {
2064
- const type = await p2.select({
2259
+ const type = await p3.select({
2065
2260
  message: "Project type",
2066
2261
  options: [
2067
2262
  {
@@ -2073,22 +2268,22 @@ async function runInit(ctx, options) {
2073
2268
  { value: "theme-starter", label: "Theme starter", hint: "CMS theme templates" }
2074
2269
  ]
2075
2270
  });
2076
- if (p2.isCancel(type)) throw new WpHsError("Init cancelled", { exitCode: ExitCode.USER_ERROR });
2271
+ if (p3.isCancel(type)) throw new WpHsError("Init cancelled", { exitCode: ExitCode.USER_ERROR });
2077
2272
  template = type;
2078
2273
  }
2079
2274
  if (!options.directory && !projectName) {
2080
- const nameAnswer = await p2.text({
2275
+ const nameAnswer = await p3.text({
2081
2276
  message: "Project name (folder name)",
2082
2277
  placeholder: "my-portal-module",
2083
2278
  validate: (value) => validateProjectName(normalizeProjectName(value ?? ""))
2084
2279
  });
2085
- if (p2.isCancel(nameAnswer)) {
2280
+ if (p3.isCancel(nameAnswer)) {
2086
2281
  throw new WpHsError("Init cancelled", { exitCode: ExitCode.USER_ERROR });
2087
2282
  }
2088
2283
  projectName = normalizeProjectName(nameAnswer);
2089
2284
  }
2090
2285
  if (!router && template && isModuleTemplate(template)) {
2091
- const routerAnswer = await p2.select({
2286
+ const routerAnswer = await p3.select({
2092
2287
  message: "Client-side routing",
2093
2288
  initialValue: "hash",
2094
2289
  options: [
@@ -2109,7 +2304,7 @@ async function runInit(ctx, options) {
2109
2304
  }
2110
2305
  ]
2111
2306
  });
2112
- if (p2.isCancel(routerAnswer)) {
2307
+ if (p3.isCancel(routerAnswer)) {
2113
2308
  throw new WpHsError("Init cancelled", { exitCode: ExitCode.USER_ERROR });
2114
2309
  }
2115
2310
  router = routerAnswer;
@@ -2125,7 +2320,7 @@ async function runInit(ctx, options) {
2125
2320
  const slug = projectName ?? basename2(targetDir);
2126
2321
  assertTargetDirIsScaffoldable(targetDir);
2127
2322
  const templateDir = kitPath("templates", template);
2128
- if (!existsSync11(templateDir)) {
2323
+ if (!existsSync13(templateDir)) {
2129
2324
  throw new WpHsError(`Template not found: ${template}`, { exitCode: ExitCode.USER_ERROR });
2130
2325
  }
2131
2326
  if (ctx.global.dryRun) {
@@ -2136,18 +2331,18 @@ async function runInit(ctx, options) {
2136
2331
  cpSync2(templateDir, targetDir, { recursive: true });
2137
2332
  copySharedSetupExamples(targetDir);
2138
2333
  const config = buildConfigForProject(template, slug, router);
2139
- writeFileSync3(resolve16(targetDir, WOODSCLI_CONFIG_FILE), `${JSON.stringify(config, null, 2)}
2334
+ writeFileSync5(resolve18(targetDir, WOODSCLI_CONFIG_FILE), `${JSON.stringify(config, null, 2)}
2140
2335
  `);
2141
- const pkgPath = resolve16(targetDir, "package.json");
2142
- if (existsSync11(pkgPath)) {
2143
- const pkg = JSON.parse(readFileSync8(pkgPath, "utf8"));
2336
+ const pkgPath = resolve18(targetDir, "package.json");
2337
+ if (existsSync13(pkgPath)) {
2338
+ const pkg = JSON.parse(readFileSync10(pkgPath, "utf8"));
2144
2339
  pkg.name = slug;
2145
2340
  pkg.scripts = { ...pkg.scripts, ...TEMPLATE_SCRIPTS[template] };
2146
2341
  pkg.devDependencies = {
2147
2342
  ...pkg.devDependencies,
2148
2343
  ...templateDevDependencies()
2149
2344
  };
2150
- writeFileSync3(pkgPath, `${JSON.stringify(pkg, null, 2)}
2345
+ writeFileSync5(pkgPath, `${JSON.stringify(pkg, null, 2)}
2151
2346
  `);
2152
2347
  }
2153
2348
  if (router && isModuleTemplate(template)) {
@@ -2158,7 +2353,7 @@ async function runInit(ctx, options) {
2158
2353
  logger: ctx.logger,
2159
2354
  dryRun: false
2160
2355
  });
2161
- const gitignorePath = resolve16(targetDir, ".gitignore");
2356
+ const gitignorePath = resolve18(targetDir, ".gitignore");
2162
2357
  const gitignoreExtras = [
2163
2358
  "hubspot.config.yml",
2164
2359
  ".wphs/config/module-config.local.json",
@@ -2171,16 +2366,40 @@ async function runInit(ctx, options) {
2171
2366
  ".wphs/.kit-bootstrap.tsx",
2172
2367
  ".wphs/.kit-bootstrap-greenfield.tsx"
2173
2368
  ];
2174
- const existing = existsSync11(gitignorePath) ? readFileSync8(gitignorePath, "utf8") : "";
2369
+ const existing = existsSync13(gitignorePath) ? readFileSync10(gitignorePath, "utf8") : "";
2175
2370
  const toAppend = gitignoreExtras.filter((line) => !existing.includes(line)).join("\n");
2176
2371
  if (toAppend) {
2177
- writeFileSync3(gitignorePath, `${existing.trimEnd()}
2372
+ writeFileSync5(gitignorePath, `${existing.trimEnd()}
2178
2373
  ${toAppend}
2179
2374
  `);
2180
2375
  }
2376
+ if (template === "module-hybrid-cdn" && !ctx.global.dryRun && needsCdnGitHubSetup(targetDir)) {
2377
+ const githubDefault = githubDefaultForPrompt(targetDir);
2378
+ let cdnSetup = null;
2379
+ if (nonInteractive) {
2380
+ if (options.cdnGithub) {
2381
+ cdnSetup = { github: options.cdnGithub };
2382
+ }
2383
+ } else {
2384
+ p3.log.step("Hybrid CDN \u2014 GitHub mirror (public repo for jsDelivr)");
2385
+ cdnSetup = await promptCdnGitHubSetup({
2386
+ projectRoot: targetDir,
2387
+ githubDefault,
2388
+ github: options.cdnGithub
2389
+ });
2390
+ }
2391
+ if (cdnSetup) {
2392
+ applyCdnGitHubSetup(targetDir, cdnSetup);
2393
+ if (!nonInteractive) {
2394
+ p3.log.success(formatCdnSetupNote(targetDir));
2395
+ } else {
2396
+ ctx.logger.success(formatCdnSetupNote(targetDir));
2397
+ }
2398
+ }
2399
+ }
2181
2400
  if (!nonInteractive) {
2182
- p2.log.success(`Scaffolded ${template} in ${targetDir}`);
2183
- p2.note(`cd ${slug} && yarn install && wp setup && wp doctor && wp dev`, "Getting started");
2401
+ p3.log.success(`Scaffolded ${template} in ${targetDir}`);
2402
+ p3.note(`cd ${slug} && yarn install && wp setup && wp doctor && wp dev`, "Getting started");
2184
2403
  } else {
2185
2404
  ctx.logger.success(`Scaffolded ${template} in ${targetDir}`);
2186
2405
  }
@@ -2188,40 +2407,40 @@ ${toAppend}
2188
2407
  }
2189
2408
 
2190
2409
  // src/cli/commands/migrate.ts
2191
- import { cpSync as cpSync3, existsSync as existsSync12, mkdirSync as mkdirSync4, readFileSync as readFileSync9, readdirSync as readdirSync3, renameSync, rmSync as rmSync3, writeFileSync as writeFileSync4 } from "fs";
2192
- import { join as join3, resolve as resolve17 } from "path";
2410
+ import { cpSync as cpSync3, existsSync as existsSync14, mkdirSync as mkdirSync4, readFileSync as readFileSync11, readdirSync as readdirSync3, renameSync, rmSync as rmSync3, writeFileSync as writeFileSync6 } from "fs";
2411
+ import { join as join3, resolve as resolve19 } from "path";
2193
2412
  var LEGACY_DIRS = [".dev", ".woodsportal"];
2194
2413
  var LEGACY_BASELINE = ".wp-hs-baseline";
2195
2414
  async function runMigrate(ctx, _options = {}) {
2196
2415
  const root = ctx.projectRoot;
2197
- const wphsRoot = resolve17(root, WPHS_WORKSPACE_DIR);
2416
+ const wphsRoot = resolve19(root, WPHS_WORKSPACE_DIR);
2198
2417
  const actions = [];
2199
2418
  for (const legacy of LEGACY_DIRS) {
2200
- const legacyPath = resolve17(root, legacy);
2201
- if (!existsSync12(legacyPath)) continue;
2419
+ const legacyPath = resolve19(root, legacy);
2420
+ if (!existsSync14(legacyPath)) continue;
2202
2421
  actions.push(`Move ${legacy}/ \u2192 .wphs/`);
2203
2422
  if (ctx.global.dryRun) continue;
2204
2423
  mkdirSync4(join3(wphsRoot, "config"), { recursive: true });
2205
2424
  mkdirSync4(join3(wphsRoot, "assets", "branding"), { recursive: true });
2206
2425
  const localConfig = join3(legacyPath, "module-config.local.json");
2207
2426
  const exampleConfig = join3(legacyPath, "module-config.example.json");
2208
- if (existsSync12(localConfig)) {
2427
+ if (existsSync14(localConfig)) {
2209
2428
  cpSync3(localConfig, join3(wphsRoot, "config", "module-config.local.json"));
2210
2429
  }
2211
- if (existsSync12(exampleConfig)) {
2430
+ if (existsSync14(exampleConfig)) {
2212
2431
  cpSync3(exampleConfig, join3(wphsRoot, "config", "module-config.example.json"));
2213
2432
  }
2214
2433
  const branding = join3(legacyPath, "branding-assets");
2215
- if (existsSync12(branding)) {
2434
+ if (existsSync14(branding)) {
2216
2435
  for (const file of readdirSync3(branding)) {
2217
2436
  cpSync3(join3(branding, file), join3(wphsRoot, "assets", "branding", file));
2218
2437
  }
2219
2438
  }
2220
2439
  rmSync3(legacyPath, { recursive: true, force: true });
2221
2440
  }
2222
- const legacyBaseline = resolve17(root, LEGACY_BASELINE);
2441
+ const legacyBaseline = resolve19(root, LEGACY_BASELINE);
2223
2442
  const targetBaseline = join3(wphsRoot, "audit", "baseline");
2224
- if (existsSync12(legacyBaseline)) {
2443
+ if (existsSync14(legacyBaseline)) {
2225
2444
  actions.push(`Move ${LEGACY_BASELINE}/ \u2192 .wphs/audit/baseline/`);
2226
2445
  if (!ctx.global.dryRun) {
2227
2446
  mkdirSync4(targetBaseline, { recursive: true });
@@ -2229,26 +2448,26 @@ async function runMigrate(ctx, _options = {}) {
2229
2448
  rmSync3(legacyBaseline, { recursive: true, force: true });
2230
2449
  }
2231
2450
  }
2232
- const legacyConfigPath2 = resolve17(root, LEGACY_WOODSPORTAL_HS_FILE);
2233
- const woodscliPath = resolve17(root, WOODSCLI_CONFIG_FILE);
2234
- if (existsSync12(legacyConfigPath2) && !existsSync12(woodscliPath)) {
2451
+ const legacyConfigPath2 = resolve19(root, LEGACY_WOODSPORTAL_HS_FILE);
2452
+ const woodscliPath = resolve19(root, WOODSCLI_CONFIG_FILE);
2453
+ if (existsSync14(legacyConfigPath2) && !existsSync14(woodscliPath)) {
2235
2454
  actions.push(`Rename ${LEGACY_WOODSPORTAL_HS_FILE} \u2192 ${WOODSCLI_CONFIG_FILE}`);
2236
2455
  if (!ctx.global.dryRun) {
2237
2456
  renameSync(legacyConfigPath2, woodscliPath);
2238
2457
  }
2239
- } else if (existsSync12(legacyConfigPath2) && existsSync12(woodscliPath)) {
2458
+ } else if (existsSync14(legacyConfigPath2) && existsSync14(woodscliPath)) {
2240
2459
  actions.push(`Remove duplicate ${LEGACY_WOODSPORTAL_HS_FILE}`);
2241
2460
  if (!ctx.global.dryRun) {
2242
2461
  rmSync3(legacyConfigPath2, { force: true });
2243
2462
  }
2244
2463
  }
2245
- if (existsSync12(woodscliPath)) {
2246
- const raw = JSON.parse(readFileSync9(woodscliPath, "utf8"));
2464
+ if (existsSync14(woodscliPath)) {
2465
+ const raw = JSON.parse(readFileSync11(woodscliPath, "utf8"));
2247
2466
  if (raw?.dev?.moduleConfigDir && raw.dev.moduleConfigDir !== WPHS_WORKSPACE_DIR) {
2248
2467
  actions.push(`Update dev.moduleConfigDir \u2192 .wphs in project config`);
2249
2468
  if (!ctx.global.dryRun) {
2250
2469
  raw.dev.moduleConfigDir = WPHS_WORKSPACE_DIR;
2251
- writeFileSync4(woodscliPath, `${JSON.stringify(raw, null, 2)}
2470
+ writeFileSync6(woodscliPath, `${JSON.stringify(raw, null, 2)}
2252
2471
  `);
2253
2472
  }
2254
2473
  }
@@ -2277,10 +2496,10 @@ async function runMigrate(ctx, _options = {}) {
2277
2496
  }
2278
2497
 
2279
2498
  // src/cli/commands/examples.ts
2280
- import { readFileSync as readFileSync10 } from "fs";
2499
+ import { readFileSync as readFileSync12 } from "fs";
2281
2500
  function loadManifest() {
2282
2501
  const manifestPath = kitPath("examples", "manifest.json");
2283
- const raw = JSON.parse(readFileSync10(manifestPath, "utf8"));
2502
+ const raw = JSON.parse(readFileSync12(manifestPath, "utf8"));
2284
2503
  return raw.scenarios;
2285
2504
  }
2286
2505
  async function runExamplesList(ctx, options = {}) {
@@ -2313,15 +2532,15 @@ async function runExamplesSmoke(ctx, options = {}) {
2313
2532
  }
2314
2533
 
2315
2534
  // src/cli/commands/fields.ts
2316
- import { cpSync as cpSync4, existsSync as existsSync13, readFileSync as readFileSync11, writeFileSync as writeFileSync5 } from "fs";
2317
- import { resolve as resolve18 } from "path";
2535
+ import { cpSync as cpSync4, existsSync as existsSync15, readFileSync as readFileSync13, writeFileSync as writeFileSync7 } from "fs";
2536
+ import { resolve as resolve20 } from "path";
2318
2537
  async function runFieldsSync(ctx, options = {}) {
2319
2538
  const direction = options.direction ?? "pull";
2320
- const fieldsPath = resolve18(ctx.projectRoot, ctx.config.dev.fieldsJson);
2321
- const cacheDir = resolve18(ctx.projectRoot, ctx.config.dev.moduleConfigDir, "cache", "fields-sync");
2322
- const cachePath = resolve18(cacheDir, "fields.json");
2539
+ const fieldsPath = resolve20(ctx.projectRoot, ctx.config.dev.fieldsJson);
2540
+ const cacheDir = resolve20(ctx.projectRoot, ctx.config.dev.moduleConfigDir, "cache", "fields-sync");
2541
+ const cachePath = resolve20(cacheDir, "fields.json");
2323
2542
  if (direction === "pull") {
2324
- if (!existsSync13(fieldsPath)) {
2543
+ if (!existsSync15(fieldsPath)) {
2325
2544
  throw new WpHsError(`Missing ${ctx.config.dev.fieldsJson}`, {
2326
2545
  exitCode: ExitCode.USER_ERROR,
2327
2546
  remediation: "Add fields.json or run wp init."
@@ -2337,7 +2556,7 @@ async function runFieldsSync(ctx, options = {}) {
2337
2556
  ctx.logger.success(`Cached fields \u2192 ${cachePath}`);
2338
2557
  return ExitCode.SUCCESS;
2339
2558
  }
2340
- if (!existsSync13(cachePath)) {
2559
+ if (!existsSync15(cachePath)) {
2341
2560
  throw new WpHsError("No cached fields \u2014 run wp fields sync pull first.", {
2342
2561
  exitCode: ExitCode.USER_ERROR
2343
2562
  });
@@ -2346,14 +2565,14 @@ async function runFieldsSync(ctx, options = {}) {
2346
2565
  ctx.logger.info(`[dry-run] Would write ${cachePath} \u2192 ${fieldsPath}`);
2347
2566
  return ExitCode.SUCCESS;
2348
2567
  }
2349
- writeFileSync5(fieldsPath, readFileSync11(cachePath, "utf8"));
2568
+ writeFileSync7(fieldsPath, readFileSync13(cachePath, "utf8"));
2350
2569
  ctx.logger.success(`Restored fields from cache \u2192 ${fieldsPath}`);
2351
2570
  return ExitCode.SUCCESS;
2352
2571
  }
2353
2572
 
2354
2573
  // src/cli/commands/regression.ts
2355
- import { existsSync as existsSync14 } from "fs";
2356
- import { resolve as resolve19 } from "path";
2574
+ import { existsSync as existsSync16 } from "fs";
2575
+ import { resolve as resolve21 } from "path";
2357
2576
  var LEGACY_SHIM_SCRIPTS = [
2358
2577
  "build-cdn.mjs",
2359
2578
  "run-vite-build.mjs",
@@ -2376,21 +2595,21 @@ async function runRegressionSmoke(ctx, options = {}) {
2376
2595
  if (!validateOk) failures.push("wp config validate failed");
2377
2596
  if (options.checkLegacyShims !== false) {
2378
2597
  for (const script of LEGACY_SHIM_SCRIPTS) {
2379
- const shim = resolve19(ctx.projectRoot, "scripts", script);
2598
+ const shim = resolve21(ctx.projectRoot, "scripts", script);
2380
2599
  const kitScript = kitPath("scripts", script);
2381
- const shimOk = existsSync14(shim) && existsSync14(kitScript);
2600
+ const shimOk = existsSync16(shim) && existsSync16(kitScript);
2382
2601
  checks.push({ name: `shim:${script}`, ok: shimOk });
2383
2602
  if (!shimOk) failures.push(`Missing legacy shim or kit script: ${script}`);
2384
2603
  }
2385
2604
  }
2386
2605
  for (const rel of FORBIDDEN_CONSUMER_PATHS) {
2387
- const forbidden = resolve19(ctx.projectRoot, rel);
2388
- const absent = !existsSync14(forbidden);
2606
+ const forbidden = resolve21(ctx.projectRoot, rel);
2607
+ const absent = !existsSync16(forbidden);
2389
2608
  checks.push({ name: `no-${rel}`, ok: absent });
2390
2609
  if (!absent) failures.push(`Forbidden dev path still present: ${rel}`);
2391
2610
  }
2392
- const woodscliConfig = resolve19(ctx.projectRoot, WOODSCLI_CONFIG_FILE);
2393
- const configOk = existsSync14(woodscliConfig);
2611
+ const woodscliConfig = resolve21(ctx.projectRoot, WOODSCLI_CONFIG_FILE);
2612
+ const configOk = existsSync16(woodscliConfig);
2394
2613
  checks.push({ name: WOODSCLI_CONFIG_FILE, ok: configOk });
2395
2614
  if (!configOk) failures.push(`${WOODSCLI_CONFIG_FILE} missing`);
2396
2615
  if (ctx.global.json) {
@@ -2448,6 +2667,17 @@ async function runSetup(ctx, options = {}) {
2448
2667
  );
2449
2668
  const copied = results.filter((r) => r.action === "copied");
2450
2669
  const manual = results.filter((r) => r.action === "manual");
2670
+ const nonInteractive = ctx.global.ci || ctx.global.yes || ctx.global.json || ctx.global.dryRun;
2671
+ if (usesCdnPipeline(ctx.config) && needsCdnGitHubSetup(ctx.projectRoot) && !nonInteractive) {
2672
+ const cdnSetup = await promptCdnGitHubSetup({
2673
+ projectRoot: ctx.projectRoot,
2674
+ githubDefault: githubDefaultForPrompt(ctx.projectRoot)
2675
+ });
2676
+ if (cdnSetup) {
2677
+ applyCdnGitHubSetup(ctx.projectRoot, cdnSetup);
2678
+ ctx.logger.success(formatCdnSetupNote(ctx.projectRoot));
2679
+ }
2680
+ }
2451
2681
  if (ctx.global.json) {
2452
2682
  ctx.logger.json({
2453
2683
  command: "setup",
@@ -2483,11 +2713,11 @@ async function runSetup(ctx, options = {}) {
2483
2713
  }
2484
2714
 
2485
2715
  // src/cli/commands/portal.ts
2486
- import * as p3 from "@clack/prompts";
2716
+ import * as p4 from "@clack/prompts";
2487
2717
 
2488
2718
  // src/lib/hubspot-portals.ts
2489
- import { existsSync as existsSync15, readFileSync as readFileSync12 } from "fs";
2490
- import { resolve as resolve20 } from "path";
2719
+ import { existsSync as existsSync17, readFileSync as readFileSync14 } from "fs";
2720
+ import { resolve as resolve22 } from "path";
2491
2721
  function stripYamlScalar(value) {
2492
2722
  const trimmed = value.trim();
2493
2723
  if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
@@ -2525,20 +2755,20 @@ function parseHubSpotConfigPortals(raw) {
2525
2755
  return { defaultPortal, portals };
2526
2756
  }
2527
2757
  function readHubSpotPortals(projectRoot) {
2528
- const configPath = resolve20(projectRoot, HUBSPOT_CONFIG_FILE);
2529
- if (!existsSync15(configPath)) {
2758
+ const configPath = resolve22(projectRoot, HUBSPOT_CONFIG_FILE);
2759
+ if (!existsSync17(configPath)) {
2530
2760
  throw new WpHsError(`${HUBSPOT_CONFIG_FILE} not found in project root.`, {
2531
2761
  exitCode: ExitCode.USER_ERROR,
2532
2762
  remediation: "Run wp setup, then add your portal + PAT."
2533
2763
  });
2534
2764
  }
2535
- const raw = readFileSync12(configPath, "utf8");
2765
+ const raw = readFileSync14(configPath, "utf8");
2536
2766
  const { defaultPortal, portals } = parseHubSpotConfigPortals(raw);
2537
2767
  return { configPath, defaultPortal, portals };
2538
2768
  }
2539
2769
  function resolvePortalName(portals, nameOrId) {
2540
2770
  const needle = nameOrId.trim();
2541
- return portals.find((p4) => p4.name === needle) ?? portals.find((p4) => p4.portalId === needle) ?? null;
2771
+ return portals.find((p5) => p5.name === needle) ?? portals.find((p5) => p5.portalId === needle) ?? null;
2542
2772
  }
2543
2773
 
2544
2774
  // src/cli/commands/portal.ts
@@ -2593,7 +2823,7 @@ async function runPortalUse(ctx, options = {}) {
2593
2823
  remediation: "wp portal use <name> \xB7 wp portal list"
2594
2824
  });
2595
2825
  }
2596
- const answer = await p3.select({
2826
+ const answer = await p4.select({
2597
2827
  message: "Set default HubSpot portal",
2598
2828
  options: data.portals.map((portal2) => ({
2599
2829
  value: portal2.name,
@@ -2601,7 +2831,7 @@ async function runPortalUse(ctx, options = {}) {
2601
2831
  hint: portal2.name === data.defaultPortal ? `current default \xB7 ${portal2.portalId ?? "no id"}` : portal2.portalId ?? void 0
2602
2832
  }))
2603
2833
  });
2604
- if (p3.isCancel(answer)) {
2834
+ if (p4.isCancel(answer)) {
2605
2835
  throw new WpHsError("Portal selection cancelled", { exitCode: ExitCode.USER_ERROR });
2606
2836
  }
2607
2837
  targetName = answer;
@@ -2662,7 +2892,7 @@ async function runWatch(ctx, options) {
2662
2892
  // src/cli/program.ts
2663
2893
  function readVersion() {
2664
2894
  try {
2665
- const pkg = JSON.parse(readFileSync13(resolve21(getKitRoot(), "package.json"), "utf8"));
2895
+ const pkg = JSON.parse(readFileSync15(resolve23(getKitRoot(), "package.json"), "utf8"));
2666
2896
  return pkg.version ?? "0.0.0";
2667
2897
  } catch {
2668
2898
  return "0.0.0";
@@ -2721,7 +2951,10 @@ function createProgram() {
2721
2951
  const global = program2.opts();
2722
2952
  await handle(() => runConfigValidate(buildContext(global)));
2723
2953
  });
2724
- program2.command("init").description("Scaffold a new module or theme project").option("-t, --template <type>", "module-hybrid-cdn | module-monolith | theme-starter").option("-n, --name <slug>", "Project name (creates ./<name>/ under cwd)").option("-d, --directory <path>", "Full target directory (skips name \u2192 folder prompt)").option("--router <mode>", "hash (client-frontend default) | browser | none \u2014 modules only").action(
2954
+ program2.command("init").description("Scaffold a new module or theme project").option("-t, --template <type>", "module-hybrid-cdn | module-monolith | theme-starter").option("-n, --name <slug>", "Project name (creates ./<name>/ under cwd)").option("-d, --directory <path>", "Full target directory (skips name \u2192 folder prompt)").option("--router <mode>", "hash (client-frontend default) | browser | none \u2014 modules only").option(
2955
+ "--cdn-github <org/repo>",
2956
+ "Public GitHub CDN mirror repo (hybrid CDN; use with --ci / -y)"
2957
+ ).action(
2725
2958
  async (opts) => {
2726
2959
  const global = program2.opts();
2727
2960
  const logger = createLogger({
@@ -2742,7 +2975,8 @@ function createProgram() {
2742
2975
  template: opts.template,
2743
2976
  name: opts.name,
2744
2977
  directory: opts.directory,
2745
- router: routerMode
2978
+ router: routerMode,
2979
+ cdnGithub: opts.cdnGithub
2746
2980
  })
2747
2981
  );
2748
2982
  }