@woodsportal/hubspot-kit 1.0.41-dev.6 → 1.0.42-dev.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/cli.js CHANGED
@@ -26,7 +26,6 @@ import {
26
26
  isWpHsError,
27
27
  runCommand,
28
28
  runCommandAsync,
29
- runNodeScript,
30
29
  runNodeScriptAsync,
31
30
  runNodeScriptLongRunning
32
31
  } from "./chunk-4GOJPOW3.js";
@@ -276,9 +275,20 @@ function defaultBaselineDir(projectRoot, config) {
276
275
  return resolve2(projectRoot, workspace, "audit", "baseline");
277
276
  }
278
277
 
278
+ // src/lib/exit-cli.ts
279
+ async function exitCli(code) {
280
+ await new Promise((resolve23) => {
281
+ setImmediate(resolve23);
282
+ });
283
+ await new Promise((resolve23) => {
284
+ setTimeout(resolve23, 0);
285
+ });
286
+ process.exit(code);
287
+ }
288
+
279
289
  // src/cli/commands/doctor.ts
280
- import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
281
- import { join as join2, resolve as resolve7 } from "path";
290
+ import { existsSync as existsSync8, readFileSync as readFileSync8 } from "fs";
291
+ import { join as join2, resolve as resolve9 } from "path";
282
292
 
283
293
  // src/lib/cli-spinner.ts
284
294
  import * as p from "@clack/prompts";
@@ -526,7 +536,7 @@ function doctorHubSpotChecks(projectRoot) {
526
536
  ok: assessment.hsCliInstalled,
527
537
  message: assessment.hsCliInstalled ? `HubSpot CLI ${assessment.hsCliVersion ?? ""}`.trim() : "HubSpot CLI not installed",
528
538
  remediation: assessment.hsCliInstalled ? void 0 : "npm i -g @hubspot/cli",
529
- severity: "error"
539
+ severity: "warn"
530
540
  },
531
541
  {
532
542
  name: "hubspot-config",
@@ -545,6 +555,215 @@ function doctorHubSpotChecks(projectRoot) {
545
555
  ];
546
556
  }
547
557
 
558
+ // src/lib/cdn-github-setup.ts
559
+ import { existsSync as existsSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
560
+ import { resolve as resolve5 } from "path";
561
+ import * as p2 from "@clack/prompts";
562
+
563
+ // src/lib/project-env-files.ts
564
+ import { existsSync as existsSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
565
+ import { resolve as resolve4 } from "path";
566
+ var ENV_LOCAL_FILE = ".env.local";
567
+ var ENV_GITIGNORE_LINES = [".env", ".env.*", "!.env*.example", ENV_LOCAL_FILE];
568
+ function parseEnvFile(content) {
569
+ const result = {};
570
+ for (const line of content.split("\n")) {
571
+ const trimmed = line.trim();
572
+ if (!trimmed || trimmed.startsWith("#")) continue;
573
+ const eq = trimmed.indexOf("=");
574
+ if (eq <= 0) continue;
575
+ const key = trimmed.slice(0, eq).trim();
576
+ let value = trimmed.slice(eq + 1).trim();
577
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
578
+ value = value.slice(1, -1);
579
+ }
580
+ result[key] = value;
581
+ }
582
+ return result;
583
+ }
584
+ function readEnvLocal(projectRoot) {
585
+ const path = resolve4(projectRoot, ENV_LOCAL_FILE);
586
+ if (!existsSync3(path)) return {};
587
+ return parseEnvFile(readFileSync5(path, "utf8"));
588
+ }
589
+ function formatEnvLocal(entries) {
590
+ const lines = [
591
+ "# Local secrets \u2014 gitignored. Created by wp init / wp setup (hybrid CDN).",
592
+ "# PORTAL_CDN_PUBLISH_TOKEN needs repo push on your portal.cdn.json mirror.",
593
+ ""
594
+ ];
595
+ for (const [key, value] of Object.entries(entries)) {
596
+ if (value.includes(" ") || value.includes("#")) {
597
+ lines.push(`${key}="${value.replace(/"/g, '\\"')}"`);
598
+ } else {
599
+ lines.push(`${key}=${value}`);
600
+ }
601
+ }
602
+ return `${lines.join("\n").trimEnd()}
603
+ `;
604
+ }
605
+ function writeEnvLocal(projectRoot, entries) {
606
+ const path = resolve4(projectRoot, ENV_LOCAL_FILE);
607
+ const existing = existsSync3(path) ? parseEnvFile(readFileSync5(path, "utf8")) : {};
608
+ writeFileSync2(path, formatEnvLocal({ ...existing, ...entries }));
609
+ }
610
+ function mergeProjectEnv(projectRoot, base = process.env) {
611
+ const local = readEnvLocal(projectRoot);
612
+ const merged = { ...base };
613
+ for (const [key, value] of Object.entries(local)) {
614
+ if (merged[key] == null || merged[key] === "") {
615
+ merged[key] = value;
616
+ }
617
+ }
618
+ return merged;
619
+ }
620
+ function ensureEnvFilesGitignore(projectRoot) {
621
+ const gitignorePath = resolve4(projectRoot, ".gitignore");
622
+ const existing = existsSync3(gitignorePath) ? readFileSync5(gitignorePath, "utf8") : "";
623
+ const toAppend = ENV_GITIGNORE_LINES.filter((line) => !existing.includes(line)).join("\n");
624
+ if (!toAppend) return;
625
+ writeFileSync2(gitignorePath, `${existing.trimEnd()}
626
+
627
+ # Env files (secrets)
628
+ ${toAppend}
629
+ `);
630
+ }
631
+
632
+ // src/lib/cdn-github-setup.ts
633
+ var PORTAL_CDN_PUBLISH_TOKEN_ENV = "PORTAL_CDN_PUBLISH_TOKEN";
634
+ var PORTAL_CDN_JSON = "portal.cdn.json";
635
+ var PLACEHOLDER_GITHUB = /your-org|your-portal-cdn-mirror/i;
636
+ function isPlaceholderGithubSlug(github) {
637
+ return PLACEHOLDER_GITHUB.test(github);
638
+ }
639
+ function defaultPortalCdnJson() {
640
+ const examplePath = kitPath("templates", "shared", "portal.cdn.json.example");
641
+ if (existsSync4(examplePath)) {
642
+ return JSON.parse(readFileSync6(examplePath, "utf8"));
643
+ }
644
+ return {
645
+ publishMode: "jsdelivr",
646
+ github: "your-org/your-portal-cdn-mirror",
647
+ gitRemote: "https://github.com/your-org/your-portal-cdn-mirror.git",
648
+ cdnPathInRepo: "dist/cdn",
649
+ defaultBranch: "main"
650
+ };
651
+ }
652
+ function readPortalCdnJson(projectRoot) {
653
+ const path = resolve5(projectRoot, PORTAL_CDN_JSON);
654
+ if (!existsSync4(path)) return null;
655
+ try {
656
+ return JSON.parse(readFileSync6(path, "utf8"));
657
+ } catch {
658
+ return null;
659
+ }
660
+ }
661
+ function normalizeGithubSlug(input) {
662
+ const trimmed = input.trim().replace(/^https:\/\/github\.com\//, "").replace(/\.git$/, "");
663
+ const parts = trimmed.split("/").filter(Boolean);
664
+ if (parts.length !== 2) return null;
665
+ return `${parts[0]}/${parts[1]}`;
666
+ }
667
+ function portalCdnJsonFromGithub(github, base) {
668
+ const slug = normalizeGithubSlug(github);
669
+ if (!slug) {
670
+ throw new Error(`Invalid GitHub repo \u2014 use org/repo (got "${github}")`);
671
+ }
672
+ return {
673
+ publishMode: base?.publishMode ?? "jsdelivr",
674
+ github: slug,
675
+ gitRemote: `https://github.com/${slug}.git`,
676
+ cdnPathInRepo: base?.cdnPathInRepo ?? "dist/cdn",
677
+ defaultBranch: base?.defaultBranch ?? "main"
678
+ };
679
+ }
680
+ function hasCdnPublishToken(projectRoot) {
681
+ if (process.env[PORTAL_CDN_PUBLISH_TOKEN_ENV]?.trim()) return true;
682
+ if (process.env.GITHUB_TOKEN?.trim()) return true;
683
+ return Boolean(readEnvLocal(projectRoot)[PORTAL_CDN_PUBLISH_TOKEN_ENV]?.trim());
684
+ }
685
+ function hasConfiguredCdnGithubMirror(projectRoot) {
686
+ const config = readPortalCdnJson(projectRoot);
687
+ const github = config?.github ?? "";
688
+ return Boolean(github) && !PLACEHOLDER_GITHUB.test(github);
689
+ }
690
+ function githubDefaultForPrompt(projectRoot) {
691
+ const existing = readPortalCdnJson(projectRoot);
692
+ if (existing?.github && !PLACEHOLDER_GITHUB.test(existing.github)) {
693
+ return existing.github;
694
+ }
695
+ return defaultPortalCdnJson().github;
696
+ }
697
+ function needsCdnGitHubSetup(projectRoot) {
698
+ return !hasConfiguredCdnGithubMirror(projectRoot) || !hasCdnPublishToken(projectRoot);
699
+ }
700
+ function applyCdnGitHubSetup(projectRoot, input) {
701
+ const existing = readPortalCdnJson(projectRoot) ?? defaultPortalCdnJson();
702
+ const updated = portalCdnJsonFromGithub(input.github, existing);
703
+ writeFileSync3(resolve5(projectRoot, PORTAL_CDN_JSON), `${JSON.stringify(updated, null, 2)}
704
+ `);
705
+ if (input.token?.trim()) {
706
+ writeEnvLocal(projectRoot, { [PORTAL_CDN_PUBLISH_TOKEN_ENV]: input.token.trim() });
707
+ }
708
+ ensureEnvFilesGitignore(projectRoot);
709
+ }
710
+ async function promptCdnGitHubSetup(options = {}) {
711
+ if (options.skip) return null;
712
+ const projectRoot = options.projectRoot ?? process.cwd();
713
+ if (!needsCdnGitHubSetup(projectRoot)) {
714
+ return null;
715
+ }
716
+ let github = options.github?.trim();
717
+ if (!github && hasConfiguredCdnGithubMirror(projectRoot)) {
718
+ github = readPortalCdnJson(projectRoot)?.github;
719
+ }
720
+ if (!github) {
721
+ const answer = await p2.text({
722
+ message: "Public GitHub CDN mirror (org/repo)",
723
+ placeholder: options.githubDefault ?? "your-org/your-portal-cdn-mirror",
724
+ initialValue: options.githubDefault,
725
+ validate: (value) => {
726
+ const slug = normalizeGithubSlug(value ?? "");
727
+ return slug ? void 0 : "Enter a public repo as org/repo (jsDelivr serves from it)";
728
+ }
729
+ });
730
+ if (p2.isCancel(answer)) return null;
731
+ github = normalizeGithubSlug(answer) ?? void 0;
732
+ }
733
+ if (!github) return null;
734
+ if (hasCdnPublishToken(projectRoot)) {
735
+ return { github };
736
+ }
737
+ const tokenAnswer = await p2.password({
738
+ message: "GitHub token (repo push on CDN mirror)",
739
+ validate: (value) => {
740
+ if (!value?.trim()) return "Required for wp publish \u2014 create a fine-grained PAT with Contents: Read and write";
741
+ return void 0;
742
+ }
743
+ });
744
+ if (p2.isCancel(tokenAnswer)) return null;
745
+ return { github, token: tokenAnswer.trim() };
746
+ }
747
+ function formatCdnSetupNote(_projectRoot) {
748
+ return `CDN config written \u2014 edit ${PORTAL_CDN_JSON} if needed; token saved to ${ENV_LOCAL_FILE} (gitignored)`;
749
+ }
750
+
751
+ // src/lib/wphs-workspace.ts
752
+ var ensureModulePromise;
753
+ function loadEnsureModule() {
754
+ if (!ensureModulePromise) {
755
+ ensureModulePromise = Promise.resolve().then(async () => {
756
+ const mod = await import("./ensure-wphs-workspace-5S3GTO6K.js");
757
+ return mod;
758
+ });
759
+ }
760
+ return ensureModulePromise;
761
+ }
762
+ async function ensureProjectWphsWorkspace(projectRoot) {
763
+ const { ensureWphsWorkspace } = await loadEnsureModule();
764
+ return ensureWphsWorkspace(projectRoot, getKitRoot());
765
+ }
766
+
548
767
  // src/lib/template-dev-deps.ts
549
768
  var VITE_DEV_PEER_DEPS = {
550
769
  vite: "^6.1.0",
@@ -667,14 +886,14 @@ function validateHubSpotFieldsJson(fields) {
667
886
  }
668
887
 
669
888
  // src/lib/developer-slug.ts
670
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
671
- import { resolve as resolve4 } from "path";
672
- import * as p2 from "@clack/prompts";
889
+ import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
890
+ import { resolve as resolve6 } from "path";
891
+ import * as p3 from "@clack/prompts";
673
892
  var SLUG_MAX_LEN = 12;
674
893
  var SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9-]{0,10}[a-z0-9])?$/;
675
894
  var DEVELOPER_CONFIG_FILE = "developer.json";
676
895
  function developerConfigPath(projectRoot) {
677
- return resolve4(projectRoot, ".wphs", "config", DEVELOPER_CONFIG_FILE);
896
+ return resolve6(projectRoot, ".wphs", "config", DEVELOPER_CONFIG_FILE);
678
897
  }
679
898
  function sanitizeDevSlug(input) {
680
899
  if (!input?.trim()) return void 0;
@@ -686,9 +905,9 @@ function readDeveloperSlug(projectRoot) {
686
905
  const fromEnv = sanitizeDevSlug(process.env.PORTAL_CDN_DEV_SLUG) ?? sanitizeDevSlug(process.env.MODULE_CDN_DEV_SLUG);
687
906
  if (fromEnv) return fromEnv;
688
907
  const path = developerConfigPath(projectRoot);
689
- if (!existsSync3(path)) return void 0;
908
+ if (!existsSync5(path)) return void 0;
690
909
  try {
691
- const raw = JSON.parse(readFileSync5(path, "utf8"));
910
+ const raw = JSON.parse(readFileSync7(path, "utf8"));
692
911
  return sanitizeDevSlug(raw.slug);
693
912
  } catch {
694
913
  return void 0;
@@ -700,8 +919,8 @@ function writeDeveloperSlug(projectRoot, slug) {
700
919
  throw new Error("Invalid CDN dev slug \u2014 use lowercase letters, numbers, and hyphens (max 12 chars)");
701
920
  }
702
921
  const path = developerConfigPath(projectRoot);
703
- mkdirSync2(resolve4(path, ".."), { recursive: true });
704
- writeFileSync2(path, `${JSON.stringify({ slug: normalized }, null, 2)}
922
+ mkdirSync2(resolve6(path, ".."), { recursive: true });
923
+ writeFileSync4(path, `${JSON.stringify({ slug: normalized }, null, 2)}
705
924
  `);
706
925
  }
707
926
  function needsDeveloperSlugSetup(projectRoot) {
@@ -711,7 +930,7 @@ async function promptDeveloperSlug(projectRoot) {
711
930
  if (!needsDeveloperSlugSetup(projectRoot)) {
712
931
  return readDeveloperSlug(projectRoot);
713
932
  }
714
- const slug = await p2.text({
933
+ const slug = await p3.text({
715
934
  message: "CDN dev slug (unique per developer for shared CDN mirror \u2014 e.g. your first name)",
716
935
  placeholder: "manab",
717
936
  validate: (value) => {
@@ -721,7 +940,7 @@ async function promptDeveloperSlug(projectRoot) {
721
940
  return void 0;
722
941
  }
723
942
  });
724
- if (p2.isCancel(slug) || typeof slug !== "string") {
943
+ if (p3.isCancel(slug) || typeof slug !== "string") {
725
944
  return void 0;
726
945
  }
727
946
  writeDeveloperSlug(projectRoot, slug);
@@ -763,37 +982,37 @@ function resolveModuleBuildIdMetaForCli(projectRoot, env = process.env) {
763
982
  }
764
983
 
765
984
  // src/lib/upload-only-module.ts
766
- import { existsSync as existsSync4 } from "fs";
767
- import { resolve as resolve5 } from "path";
985
+ import { existsSync as existsSync6 } from "fs";
986
+ import { resolve as resolve7 } from "path";
768
987
  function isUploadOnlyPreexistingProject(projectRoot, moduleArtifactDirs) {
769
988
  if (moduleArtifactDirs.length === 0) return false;
770
- if (existsSync4(resolve5(projectRoot, "package.json"))) return false;
771
- if (existsSync4(resolve5(projectRoot, "src"))) return false;
772
- return moduleArtifactDirs.some((dir) => existsSync4(resolve5(dir, "fields.json")));
989
+ if (existsSync6(resolve7(projectRoot, "package.json"))) return false;
990
+ if (existsSync6(resolve7(projectRoot, "src"))) return false;
991
+ return moduleArtifactDirs.some((dir) => existsSync6(resolve7(dir, "fields.json")));
773
992
  }
774
993
 
775
994
  // src/vite/resolve-dev-config.ts
776
- import { existsSync as existsSync5 } from "fs";
995
+ import { existsSync as existsSync7 } from "fs";
777
996
  import { platform } from "os";
778
- import { resolve as resolve6 } from "path";
997
+ import { resolve as resolve8 } from "path";
779
998
  var VITE_JS_SEGMENTS = ["node_modules", "vite", "bin", "vite.js"];
780
999
  function isProjectViteInstalled(projectRoot) {
781
- return existsSync5(resolve6(projectRoot, ...VITE_JS_SEGMENTS));
1000
+ return existsSync7(resolve8(projectRoot, ...VITE_JS_SEGMENTS));
782
1001
  }
783
1002
  function resolveProjectViteSpawn(projectRoot) {
784
- const viteJs = resolve6(projectRoot, ...VITE_JS_SEGMENTS);
785
- if (existsSync5(viteJs)) {
1003
+ const viteJs = resolve8(projectRoot, ...VITE_JS_SEGMENTS);
1004
+ if (existsSync7(viteJs)) {
786
1005
  return { command: process.execPath, prefixArgs: [viteJs] };
787
1006
  }
788
- const binDir = resolve6(projectRoot, "node_modules", ".bin");
1007
+ const binDir = resolve8(projectRoot, "node_modules", ".bin");
789
1008
  if (platform() === "win32") {
790
- const winCmd = resolve6(binDir, "vite.cmd");
791
- if (existsSync5(winCmd)) {
1009
+ const winCmd = resolve8(binDir, "vite.cmd");
1010
+ if (existsSync7(winCmd)) {
792
1011
  return { command: winCmd, prefixArgs: [] };
793
1012
  }
794
1013
  }
795
- const posixShim = resolve6(binDir, "vite");
796
- if (existsSync5(posixShim)) {
1014
+ const posixShim = resolve8(binDir, "vite");
1015
+ if (existsSync7(posixShim)) {
797
1016
  return { command: posixShim, prefixArgs: [] };
798
1017
  }
799
1018
  return null;
@@ -880,10 +1099,10 @@ function envFileForTier(tier) {
880
1099
  return ".env.prod";
881
1100
  }
882
1101
  function readEnvFileValue(projectRoot, file, key) {
883
- const path = resolve7(projectRoot, file);
884
- if (!existsSync6(path)) return void 0;
1102
+ const path = resolve9(projectRoot, file);
1103
+ if (!existsSync8(path)) return void 0;
885
1104
  try {
886
- const content = readFileSync6(path, "utf8");
1105
+ const content = readFileSync8(path, "utf8");
887
1106
  const match = content.match(new RegExp(`^${key}=(.*)$`, "m"));
888
1107
  return match?.[1]?.trim();
889
1108
  } catch {
@@ -892,8 +1111,8 @@ function readEnvFileValue(projectRoot, file, key) {
892
1111
  }
893
1112
  function checkEnvFiles(ctx) {
894
1113
  const checks = [];
895
- const localPath = resolve7(ctx.projectRoot, ".env.local");
896
- const localOk = existsSync6(localPath);
1114
+ const localPath = resolve9(ctx.projectRoot, ".env.local");
1115
+ const localOk = existsSync8(localPath);
897
1116
  checks.push({
898
1117
  name: "env-local",
899
1118
  ok: localOk,
@@ -901,8 +1120,8 @@ function checkEnvFiles(ctx) {
901
1120
  remediation: localOk ? void 0 : "wp setup or copy .env.local.example \u2192 .env.local",
902
1121
  severity: "warn"
903
1122
  });
904
- const legacyLocalDev = resolve7(ctx.projectRoot, ".env.dev");
905
- if (!localOk && existsSync6(legacyLocalDev)) {
1123
+ const legacyLocalDev = resolve9(ctx.projectRoot, ".env.dev");
1124
+ if (!localOk && existsSync8(legacyLocalDev)) {
906
1125
  const nodeEnv = readEnvFileValue(ctx.projectRoot, ".env.dev", "VITE_NODE_ENV");
907
1126
  const hasDevMode = readEnvFileValue(ctx.projectRoot, ".env.dev", "VITE_DEV_MODE") != null;
908
1127
  const looksLikeLocal = hasDevMode || nodeEnv === "local" || nodeEnv == null || nodeEnv === "development";
@@ -918,7 +1137,7 @@ function checkEnvFiles(ctx) {
918
1137
  }
919
1138
  for (const tier of ctx.config.env.tiers) {
920
1139
  const file = envFileForTier(tier);
921
- const ok = existsSync6(resolve7(ctx.projectRoot, file));
1140
+ const ok = existsSync8(resolve9(ctx.projectRoot, file));
922
1141
  if (tier === "stg") {
923
1142
  checks.push({
924
1143
  name: "env-stg",
@@ -941,8 +1160,8 @@ function checkEnvFiles(ctx) {
941
1160
  }
942
1161
  function checkLegacyBuildIdFile(ctx) {
943
1162
  if (isThemeProject(ctx.config) || !usesCdnPipeline(ctx.config)) return null;
944
- const legacy = resolve7(ctx.projectRoot, ".portal-build-id");
945
- if (!existsSync6(legacy)) return null;
1163
+ const legacy = resolve9(ctx.projectRoot, ".portal-build-id");
1164
+ if (!existsSync8(legacy)) return null;
946
1165
  return {
947
1166
  name: "legacy-build-id",
948
1167
  ok: false,
@@ -953,8 +1172,8 @@ function checkLegacyBuildIdFile(ctx) {
953
1172
  }
954
1173
  function checkBuildId(ctx) {
955
1174
  if (isThemeProject(ctx.config) || !usesCdnPipeline(ctx.config)) return null;
956
- const file = resolve7(ctx.projectRoot, ctx.config.cdn.buildIdFile);
957
- const ok = existsSync6(file);
1175
+ const file = resolve9(ctx.projectRoot, ctx.config.cdn.buildIdFile);
1176
+ const ok = existsSync8(file);
958
1177
  return {
959
1178
  name: "build-id",
960
1179
  ok,
@@ -1008,7 +1227,7 @@ function checkDevSlug(ctx) {
1008
1227
  };
1009
1228
  }
1010
1229
  function isUploadOnlyProject(ctx) {
1011
- const moduleDirs = [resolve7(ctx.projectRoot, ctx.config.module.uploadPath)];
1230
+ const moduleDirs = [resolve9(ctx.projectRoot, ctx.config.module.uploadPath)];
1012
1231
  return isUploadOnlyPreexistingProject(ctx.projectRoot, moduleDirs);
1013
1232
  }
1014
1233
  function checkUploadOnlyNote(ctx) {
@@ -1022,8 +1241,8 @@ function checkUploadOnlyNote(ctx) {
1022
1241
  };
1023
1242
  }
1024
1243
  function checkDevViteConfig(ctx) {
1025
- const path = resolve7(ctx.projectRoot, "vite.config.dev.mjs");
1026
- const ok = existsSync6(path);
1244
+ const path = resolve9(ctx.projectRoot, "vite.config.dev.mjs");
1245
+ const ok = existsSync8(path);
1027
1246
  return {
1028
1247
  name: "vite-dev-config",
1029
1248
  ok,
@@ -1034,8 +1253,8 @@ function checkDevViteConfig(ctx) {
1034
1253
  }
1035
1254
  function checkMetaJson(ctx) {
1036
1255
  if (isThemeProject(ctx.config)) return null;
1037
- const path = resolve7(ctx.projectRoot, "public/meta.json");
1038
- if (!existsSync6(path)) {
1256
+ const path = resolve9(ctx.projectRoot, "public/meta.json");
1257
+ if (!existsSync8(path)) {
1039
1258
  return {
1040
1259
  name: "meta-json",
1041
1260
  ok: false,
@@ -1045,7 +1264,7 @@ function checkMetaJson(ctx) {
1045
1264
  };
1046
1265
  }
1047
1266
  try {
1048
- const meta = JSON.parse(readFileSync6(path, "utf8"));
1267
+ const meta = JSON.parse(readFileSync8(path, "utf8"));
1049
1268
  const hasContentTypes = Array.isArray(meta.content_types) && meta.content_types.length > 0;
1050
1269
  return {
1051
1270
  name: "meta-json",
@@ -1067,8 +1286,8 @@ function checkMetaJson(ctx) {
1067
1286
  }
1068
1287
  async function checkCdnMirrorPublic(ctx) {
1069
1288
  if (isThemeProject(ctx.config) || !usesCdnPipeline(ctx.config)) return null;
1070
- const mirrorPath = resolve7(ctx.projectRoot, ctx.config.cdn?.mirror ?? "portal.cdn.json");
1071
- if (!existsSync6(mirrorPath)) {
1289
+ const mirrorPath = resolve9(ctx.projectRoot, ctx.config.cdn?.mirror ?? "portal.cdn.json");
1290
+ if (!existsSync8(mirrorPath)) {
1072
1291
  return {
1073
1292
  name: "cdn-mirror-public",
1074
1293
  ok: false,
@@ -1079,7 +1298,7 @@ async function checkCdnMirrorPublic(ctx) {
1079
1298
  }
1080
1299
  let github;
1081
1300
  try {
1082
- const mirror = JSON.parse(readFileSync6(mirrorPath, "utf8"));
1301
+ const mirror = JSON.parse(readFileSync8(mirrorPath, "utf8"));
1083
1302
  github = mirror.github?.trim();
1084
1303
  } catch {
1085
1304
  return {
@@ -1099,6 +1318,15 @@ async function checkCdnMirrorPublic(ctx) {
1099
1318
  severity: "error"
1100
1319
  };
1101
1320
  }
1321
+ if (isPlaceholderGithubSlug(github)) {
1322
+ return {
1323
+ name: "cdn-mirror-public",
1324
+ ok: false,
1325
+ message: `portal.cdn.json still has placeholder github slug (${github})`,
1326
+ remediation: "Run wp setup and set a real public org/repo for jsDelivr",
1327
+ severity: "warn"
1328
+ };
1329
+ }
1102
1330
  try {
1103
1331
  const res = await fetch(`https://api.github.com/repos/${github}`, {
1104
1332
  headers: { Accept: "application/vnd.github+json" }
@@ -1131,8 +1359,8 @@ async function checkCdnMirrorPublic(ctx) {
1131
1359
  severity: "warn"
1132
1360
  };
1133
1361
  }
1134
- const manifestPath = resolve7(ctx.projectRoot, "dist/cdn/manifest.json");
1135
- if (!existsSync6(manifestPath)) {
1362
+ const manifestPath = resolve9(ctx.projectRoot, "dist/cdn/manifest.json");
1363
+ if (!existsSync8(manifestPath)) {
1136
1364
  return {
1137
1365
  name: "cdn-mirror-public",
1138
1366
  ok: true,
@@ -1141,7 +1369,7 @@ async function checkCdnMirrorPublic(ctx) {
1141
1369
  };
1142
1370
  }
1143
1371
  try {
1144
- const manifest = JSON.parse(readFileSync6(manifestPath, "utf8"));
1372
+ const manifest = JSON.parse(readFileSync8(manifestPath, "utf8"));
1145
1373
  const appUrl = manifest.scripts?.find((s) => s.name === "app")?.url;
1146
1374
  if (!appUrl) {
1147
1375
  return {
@@ -1194,8 +1422,8 @@ async function checkCdnMirrorPublic(ctx) {
1194
1422
  }
1195
1423
  function checkFieldsJson(ctx) {
1196
1424
  if (isThemeProject(ctx.config)) return null;
1197
- const path = resolve7(ctx.projectRoot, "public/fields.json");
1198
- if (!existsSync6(path)) {
1425
+ const path = resolve9(ctx.projectRoot, "public/fields.json");
1426
+ if (!existsSync8(path)) {
1199
1427
  return {
1200
1428
  name: "fields-json",
1201
1429
  ok: false,
@@ -1205,7 +1433,7 @@ function checkFieldsJson(ctx) {
1205
1433
  };
1206
1434
  }
1207
1435
  try {
1208
- const fields = JSON.parse(readFileSync6(path, "utf8"));
1436
+ const fields = JSON.parse(readFileSync8(path, "utf8"));
1209
1437
  const issues = validateHubSpotFieldsJson(fields);
1210
1438
  if (issues.length === 0) {
1211
1439
  return {
@@ -1235,11 +1463,11 @@ function checkFieldsJson(ctx) {
1235
1463
  }
1236
1464
  }
1237
1465
  function readProjectFile(projectRoot, relativePath) {
1238
- const path = resolve7(projectRoot, relativePath);
1239
- if (!existsSync6(path)) {
1466
+ const path = resolve9(projectRoot, relativePath);
1467
+ if (!existsSync8(path)) {
1240
1468
  return null;
1241
1469
  }
1242
- return readFileSync6(path, "utf8");
1470
+ return readFileSync8(path, "utf8");
1243
1471
  }
1244
1472
  function checkCssPipeline(ctx) {
1245
1473
  if (isThemeProject(ctx.config) || isUploadOnlyProject(ctx)) {
@@ -1306,9 +1534,9 @@ function checkCssPipeline(ctx) {
1306
1534
  }
1307
1535
  const moduleOutDir = ctx.config.module?.outDir;
1308
1536
  if (moduleOutDir && usesCdnPipeline(ctx.config)) {
1309
- const moduleCssPath = resolve7(ctx.projectRoot, moduleOutDir, "module.css");
1310
- if (existsSync6(moduleCssPath)) {
1311
- const css = readFileSync6(moduleCssPath, "utf8");
1537
+ const moduleCssPath = resolve9(ctx.projectRoot, moduleOutDir, "module.css");
1538
+ if (existsSync8(moduleCssPath)) {
1539
+ const css = readFileSync8(moduleCssPath, "utf8");
1312
1540
  const hasDwUtilities = /dw\\:/.test(css) || /\.dw:/.test(css);
1313
1541
  checks.push({
1314
1542
  name: "css-module-dist-prefixed",
@@ -1323,8 +1551,8 @@ function checkCssPipeline(ctx) {
1323
1551
  }
1324
1552
  function checkHybridModuleHtml(ctx) {
1325
1553
  if (isThemeProject(ctx.config) || !usesCdnPipeline(ctx.config)) return null;
1326
- const path = resolve7(ctx.projectRoot, "public/module.html");
1327
- if (!existsSync6(path)) {
1554
+ const path = resolve9(ctx.projectRoot, "public/module.html");
1555
+ if (!existsSync8(path)) {
1328
1556
  return {
1329
1557
  name: "module-html",
1330
1558
  ok: false,
@@ -1333,7 +1561,7 @@ function checkHybridModuleHtml(ctx) {
1333
1561
  severity: "error"
1334
1562
  };
1335
1563
  }
1336
- const html = readFileSync6(path, "utf8");
1564
+ const html = readFileSync8(path, "utf8");
1337
1565
  const needs = ["__PORTAL_CDN_BASE__", "require_js", "hubSpotData", 'id="app"'];
1338
1566
  const missing = needs.filter((token) => !html.includes(token));
1339
1567
  const usesUndeclaredCdnFields = /module\.portal_(cdn_base_url|vendor_script|app_script|app_css)/.test(
@@ -1351,8 +1579,8 @@ function checkHybridModuleHtml(ctx) {
1351
1579
  function checkCdnViteConfigs(ctx) {
1352
1580
  if (isThemeProject(ctx.config) || !usesCdnPipeline(ctx.config)) return [];
1353
1581
  return ["vite.config.cdn-vendor.mjs", "vite.config.cdn-app.mjs"].map((file) => {
1354
- const path = resolve7(ctx.projectRoot, file);
1355
- const ok = existsSync6(path);
1582
+ const path = resolve9(ctx.projectRoot, file);
1583
+ const ok = existsSync8(path);
1356
1584
  return {
1357
1585
  name: file.replace(/\./g, "-"),
1358
1586
  ok,
@@ -1364,8 +1592,8 @@ function checkCdnViteConfigs(ctx) {
1364
1592
  }
1365
1593
  function checkDevEntry(ctx) {
1366
1594
  const checks = [];
1367
- const indexHtml = resolve7(ctx.projectRoot, "index.html");
1368
- const hasIndex = existsSync6(indexHtml);
1595
+ const indexHtml = resolve9(ctx.projectRoot, "index.html");
1596
+ const hasIndex = existsSync8(indexHtml);
1369
1597
  checks.push({
1370
1598
  name: "index-html",
1371
1599
  ok: hasIndex,
@@ -1373,8 +1601,8 @@ function checkDevEntry(ctx) {
1373
1601
  remediation: hasIndex ? void 0 : "Re-run wp init or copy index.html from kit templates",
1374
1602
  severity: "error"
1375
1603
  });
1376
- const localDev = resolve7(ctx.projectRoot, ctx.config.entries.localDev);
1377
- const hasEntry = existsSync6(localDev);
1604
+ const localDev = resolve9(ctx.projectRoot, ctx.config.entries.localDev);
1605
+ const hasEntry = existsSync8(localDev);
1378
1606
  checks.push({
1379
1607
  name: "local-dev-entry",
1380
1608
  ok: hasEntry,
@@ -1384,7 +1612,7 @@ function checkDevEntry(ctx) {
1384
1612
  });
1385
1613
  if (hasEntry) {
1386
1614
  try {
1387
- const source = readFileSync6(localDev, "utf8");
1615
+ const source = readFileSync8(localDev, "utf8");
1388
1616
  const usesKitNodeBootstrap = source.includes("@woodsportal/hubspot-kit/dev/bootstrap-greenfield") || source.includes("@woodsportal/hubspot-kit/dev/bootstrap");
1389
1617
  const usesWphsShim = source.includes(".wphs/.kit-bootstrap-greenfield") || source.includes(".wphs/.kit-bootstrap") || source.includes("@wphs/bootstrap-greenfield") || source.includes("@wphs/bootstrap");
1390
1618
  checks.push({
@@ -1401,7 +1629,7 @@ function checkDevEntry(ctx) {
1401
1629
  }
1402
1630
  function checkKitScripts() {
1403
1631
  const script = kitPath("scripts", "build-cdn.mjs");
1404
- const ok = existsSync6(script);
1632
+ const ok = existsSync8(script);
1405
1633
  return {
1406
1634
  name: "kit-scripts",
1407
1635
  ok,
@@ -1410,12 +1638,38 @@ function checkKitScripts() {
1410
1638
  severity: "error"
1411
1639
  };
1412
1640
  }
1641
+ function checkWphsScaffold(scaffoldError) {
1642
+ if (!scaffoldError) return null;
1643
+ return {
1644
+ name: "wphs-scaffold",
1645
+ ok: false,
1646
+ message: `.wphs workspace scaffold failed \u2014 ${scaffoldError}`,
1647
+ remediation: "Run wp doctor --verbose or wp setup; check write permissions on the project root (Windows: enable Developer Mode if junction creation fails)",
1648
+ severity: "error"
1649
+ };
1650
+ }
1651
+ async function runWphsScaffold(ctx) {
1652
+ if (ctx.global.dryRun) return void 0;
1653
+ try {
1654
+ const paths = await ensureProjectWphsWorkspace(ctx.projectRoot);
1655
+ if (ctx.global.verbose) {
1656
+ ctx.logger.info(`.wphs workspace ready at ${paths.workspaceRoot}`);
1657
+ }
1658
+ return void 0;
1659
+ } catch (error) {
1660
+ const message = error instanceof Error ? error.message : String(error);
1661
+ if (ctx.global.verbose) {
1662
+ ctx.logger.error(`Could not scaffold .wphs workspace: ${message}`);
1663
+ }
1664
+ return message;
1665
+ }
1666
+ }
1413
1667
  function checkWphsWorkspace(ctx) {
1414
1668
  const workspace = ctx.config.dev.moduleConfigDir;
1415
1669
  const checks = [];
1416
1670
  for (const legacy of [".dev", ".woodsportal"]) {
1417
- const legacyPath = resolve7(ctx.projectRoot, legacy);
1418
- if (existsSync6(legacyPath)) {
1671
+ const legacyPath = resolve9(ctx.projectRoot, legacy);
1672
+ if (existsSync8(legacyPath)) {
1419
1673
  checks.push({
1420
1674
  name: `legacy-${legacy}`,
1421
1675
  ok: false,
@@ -1425,7 +1679,7 @@ function checkWphsWorkspace(ctx) {
1425
1679
  });
1426
1680
  }
1427
1681
  }
1428
- if (existsSync6(resolve7(ctx.projectRoot, ".wp-hs-baseline"))) {
1682
+ if (existsSync8(resolve9(ctx.projectRoot, ".wp-hs-baseline"))) {
1429
1683
  checks.push({
1430
1684
  name: "legacy-baseline",
1431
1685
  ok: false,
@@ -1444,15 +1698,15 @@ function checkWphsWorkspace(ctx) {
1444
1698
  });
1445
1699
  return checks;
1446
1700
  }
1447
- const wphsRoot = resolve7(ctx.projectRoot, workspace);
1701
+ const wphsRoot = resolve9(ctx.projectRoot, workspace);
1448
1702
  const required = ["config", "assets/branding", "fixtures", "cache", "audit/baseline", "state"];
1449
1703
  for (const sub of required) {
1450
- const ok = existsSync6(join2(wphsRoot, sub));
1704
+ const ok = existsSync8(join2(wphsRoot, sub));
1451
1705
  checks.push({
1452
1706
  name: `wphs-${sub.replace(/\//g, "-")}`,
1453
1707
  ok,
1454
1708
  message: ok ? `.wphs/${sub}/ present` : `.wphs/${sub}/ missing`,
1455
- remediation: ok ? void 0 : "Run wp doctor (auto-scaffolds) or wp init",
1709
+ remediation: ok ? void 0 : "Run wp setup or wp doctor --verbose; check write permissions on the project root",
1456
1710
  severity: "error"
1457
1711
  });
1458
1712
  }
@@ -1460,8 +1714,8 @@ function checkWphsWorkspace(ctx) {
1460
1714
  }
1461
1715
  function checkAdapter(ctx) {
1462
1716
  if (isThemeProject(ctx.config)) return null;
1463
- const adapterPath = resolve7(ctx.projectRoot, ctx.config.dev.adapter);
1464
- const ok = existsSync6(adapterPath);
1717
+ const adapterPath = resolve9(ctx.projectRoot, ctx.config.dev.adapter);
1718
+ const ok = existsSync8(adapterPath);
1465
1719
  return {
1466
1720
  name: "wphs-adapter",
1467
1721
  ok,
@@ -1472,11 +1726,11 @@ function checkAdapter(ctx) {
1472
1726
  }
1473
1727
  function checkModuleConfigUiDeps(ctx) {
1474
1728
  if (isThemeProject(ctx.config)) return [];
1475
- const pkgPath = resolve7(ctx.projectRoot, "package.json");
1476
- if (!existsSync6(pkgPath)) return [];
1729
+ const pkgPath = resolve9(ctx.projectRoot, "package.json");
1730
+ if (!existsSync8(pkgPath)) return [];
1477
1731
  let deps = {};
1478
1732
  try {
1479
- const pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
1733
+ const pkg = JSON.parse(readFileSync8(pkgPath, "utf8"));
1480
1734
  deps = { ...pkg.dependencies, ...pkg.devDependencies };
1481
1735
  } catch {
1482
1736
  return [];
@@ -1503,7 +1757,7 @@ function checkNodeLoaderInterop() {
1503
1757
  };
1504
1758
  }
1505
1759
  function checkViteInstalled(ctx) {
1506
- if (isThemeProject(ctx.config) && !existsSync6(resolve7(ctx.projectRoot, "vite.config.dev.mjs"))) {
1760
+ if (isThemeProject(ctx.config) && !existsSync8(resolve9(ctx.projectRoot, "vite.config.dev.mjs"))) {
1507
1761
  return null;
1508
1762
  }
1509
1763
  const ok = isProjectViteInstalled(ctx.projectRoot);
@@ -1517,19 +1771,12 @@ function checkViteInstalled(ctx) {
1517
1771
  }
1518
1772
  async function gatherDoctorChecks(ctx) {
1519
1773
  const checks = [];
1774
+ let wphsScaffoldError;
1520
1775
  await runCliTasks(ctx, [
1521
1776
  {
1522
1777
  title: "Preparing .wphs workspace",
1523
1778
  task: async () => {
1524
- if (ctx.global.dryRun) return;
1525
- try {
1526
- runNodeScript(kitPath("scripts", "ensure-wphs-workspace.mjs"), [], {
1527
- cwd: ctx.projectRoot,
1528
- logger: ctx.logger,
1529
- dryRun: false
1530
- });
1531
- } catch {
1532
- }
1779
+ wphsScaffoldError = await runWphsScaffold(ctx);
1533
1780
  }
1534
1781
  },
1535
1782
  {
@@ -1553,7 +1800,11 @@ async function gatherDoctorChecks(ctx) {
1553
1800
  {
1554
1801
  title: "Checking woodscli.json and project layout",
1555
1802
  task: async () => {
1556
- checks.push(...checkConfig(ctx), ...checkWphsWorkspace(ctx));
1803
+ wphsScaffoldError = await runWphsScaffold(ctx) ?? wphsScaffoldError;
1804
+ checks.push(...checkConfig(ctx));
1805
+ const scaffoldCheck = checkWphsScaffold(wphsScaffoldError);
1806
+ if (scaffoldCheck) checks.push(scaffoldCheck);
1807
+ checks.push(...checkWphsWorkspace(ctx));
1557
1808
  const fieldsCheck = checkFieldsJson(ctx);
1558
1809
  if (fieldsCheck) checks.push(fieldsCheck);
1559
1810
  const metaCheck = checkMetaJson(ctx);
@@ -1660,7 +1911,7 @@ async function runConfigValidate(ctx) {
1660
1911
  }
1661
1912
 
1662
1913
  // src/cli/commands/build.ts
1663
- import { resolve as resolve8 } from "path";
1914
+ import { resolve as resolve10 } from "path";
1664
1915
 
1665
1916
  // src/lib/cli-banner.ts
1666
1917
  import pc4 from "picocolors";
@@ -1789,7 +2040,7 @@ function buildTasks(ctx, options, run) {
1789
2040
  title: `Building monolith module (${tier})`,
1790
2041
  task: async () => {
1791
2042
  await run("run-vite-build.mjs", [
1792
- resolve8(ctx.projectRoot, "vite.config.monolith.mjs"),
2043
+ resolve10(ctx.projectRoot, "vite.config.monolith.mjs"),
1793
2044
  ...tierToViteModeFlag(tier)
1794
2045
  ]);
1795
2046
  await run("run-hubspot-module-postbuild.mjs");
@@ -1803,7 +2054,7 @@ function buildTasks(ctx, options, run) {
1803
2054
  task: async () => {
1804
2055
  if (options.cdnEsm) {
1805
2056
  await run("run-vite-build.mjs", [
1806
- resolve8(ctx.projectRoot, "vite.config.cdn-esm.mjs"),
2057
+ resolve10(ctx.projectRoot, "vite.config.cdn-esm.mjs"),
1807
2058
  ...tierToViteModeFlag(tier)
1808
2059
  ]);
1809
2060
  } else {
@@ -1818,7 +2069,7 @@ function buildTasks(ctx, options, run) {
1818
2069
  title: `Building HubSpot module (${tier})`,
1819
2070
  task: async () => {
1820
2071
  await run("run-vite-build.mjs", [
1821
- resolve8(ctx.projectRoot, "vite.config.hubspot.mjs"),
2072
+ resolve10(ctx.projectRoot, "vite.config.hubspot.mjs"),
1822
2073
  ...tierToViteModeFlag(tier)
1823
2074
  ]);
1824
2075
  await run("run-hubspot-module-postbuild.mjs");
@@ -1915,7 +2166,7 @@ async function runBuild(ctx, options) {
1915
2166
  command: "build",
1916
2167
  tier,
1917
2168
  ok: true,
1918
- outDir: resolve8(ctx.projectRoot, ctx.config.module.outDir)
2169
+ outDir: resolve10(ctx.projectRoot, ctx.config.module.outDir)
1919
2170
  });
1920
2171
  } else if (!options.quiet) {
1921
2172
  printCommandDone(ctx, "Build complete");
@@ -1966,86 +2217,17 @@ async function runDev(ctx, options) {
1966
2217
  }
1967
2218
 
1968
2219
  // src/cli/commands/deploy.ts
1969
- import { resolve as resolve11 } from "path";
1970
-
1971
- // src/lib/project-env-files.ts
1972
- import { existsSync as existsSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync3 } from "fs";
1973
- import { resolve as resolve9 } from "path";
1974
- var ENV_LOCAL_FILE = ".env.local";
1975
- var ENV_GITIGNORE_LINES = [".env", ".env.*", "!.env*.example", ENV_LOCAL_FILE];
1976
- function parseEnvFile(content) {
1977
- const result = {};
1978
- for (const line of content.split("\n")) {
1979
- const trimmed = line.trim();
1980
- if (!trimmed || trimmed.startsWith("#")) continue;
1981
- const eq = trimmed.indexOf("=");
1982
- if (eq <= 0) continue;
1983
- const key = trimmed.slice(0, eq).trim();
1984
- let value = trimmed.slice(eq + 1).trim();
1985
- if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
1986
- value = value.slice(1, -1);
1987
- }
1988
- result[key] = value;
1989
- }
1990
- return result;
1991
- }
1992
- function readEnvLocal(projectRoot) {
1993
- const path = resolve9(projectRoot, ENV_LOCAL_FILE);
1994
- if (!existsSync7(path)) return {};
1995
- return parseEnvFile(readFileSync7(path, "utf8"));
1996
- }
1997
- function formatEnvLocal(entries) {
1998
- const lines = [
1999
- "# Local secrets \u2014 gitignored. Created by wp init / wp setup (hybrid CDN).",
2000
- "# PORTAL_CDN_PUBLISH_TOKEN needs repo push on your portal.cdn.json mirror.",
2001
- ""
2002
- ];
2003
- for (const [key, value] of Object.entries(entries)) {
2004
- if (value.includes(" ") || value.includes("#")) {
2005
- lines.push(`${key}="${value.replace(/"/g, '\\"')}"`);
2006
- } else {
2007
- lines.push(`${key}=${value}`);
2008
- }
2009
- }
2010
- return `${lines.join("\n").trimEnd()}
2011
- `;
2012
- }
2013
- function writeEnvLocal(projectRoot, entries) {
2014
- const path = resolve9(projectRoot, ENV_LOCAL_FILE);
2015
- const existing = existsSync7(path) ? parseEnvFile(readFileSync7(path, "utf8")) : {};
2016
- writeFileSync3(path, formatEnvLocal({ ...existing, ...entries }));
2017
- }
2018
- function mergeProjectEnv(projectRoot, base = process.env) {
2019
- const local = readEnvLocal(projectRoot);
2020
- const merged = { ...base };
2021
- for (const [key, value] of Object.entries(local)) {
2022
- if (merged[key] == null || merged[key] === "") {
2023
- merged[key] = value;
2024
- }
2025
- }
2026
- return merged;
2027
- }
2028
- function ensureEnvFilesGitignore(projectRoot) {
2029
- const gitignorePath = resolve9(projectRoot, ".gitignore");
2030
- const existing = existsSync7(gitignorePath) ? readFileSync7(gitignorePath, "utf8") : "";
2031
- const toAppend = ENV_GITIGNORE_LINES.filter((line) => !existing.includes(line)).join("\n");
2032
- if (!toAppend) return;
2033
- writeFileSync3(gitignorePath, `${existing.trimEnd()}
2034
-
2035
- # Env files (secrets)
2036
- ${toAppend}
2037
- `);
2038
- }
2220
+ import { resolve as resolve12 } from "path";
2039
2221
 
2040
2222
  // src/cli/commands/upload.ts
2041
- import { resolve as resolve10 } from "path";
2223
+ import { resolve as resolve11 } from "path";
2042
2224
  async function uploadModuleOrTheme(ctx) {
2043
2225
  if (isThemeProject(ctx.config)) {
2044
2226
  const theme = ctx.config.theme;
2045
2227
  if (!theme) {
2046
2228
  throw new WpHsError("theme block missing in woodscli.json", { exitCode: ExitCode.USER_ERROR });
2047
2229
  }
2048
- const srcDir = resolve10(ctx.projectRoot, theme.srcDir);
2230
+ const srcDir = resolve11(ctx.projectRoot, theme.srcDir);
2049
2231
  await runCommandAsync("hs", ["cms", "upload", srcDir, theme.uploadPath], {
2050
2232
  cwd: ctx.projectRoot,
2051
2233
  projectRoot: ctx.projectRoot,
@@ -2055,7 +2237,7 @@ async function uploadModuleOrTheme(ctx) {
2055
2237
  });
2056
2238
  return `Uploaded theme ${srcDir} \u2192 ${theme.uploadPath}`;
2057
2239
  }
2058
- const outDir = resolve10(ctx.projectRoot, ctx.config.module.outDir);
2240
+ const outDir = resolve11(ctx.projectRoot, ctx.config.module.outDir);
2059
2241
  const uploadPath = ctx.config.module.uploadPath;
2060
2242
  await runCommandAsync("hs", ["cms", "upload", outDir, uploadPath], {
2061
2243
  cwd: ctx.projectRoot,
@@ -2232,7 +2414,7 @@ async function runDeploy(ctx, options) {
2232
2414
  if (!ctx.global.json) {
2233
2415
  printCommandDone(
2234
2416
  ctx,
2235
- `Deploy complete (${resolve11(ctx.projectRoot, ctx.config.module.outDir)} \u2192 ${ctx.config.module.uploadPath})`
2417
+ `Deploy complete (${resolve12(ctx.projectRoot, ctx.config.module.outDir)} \u2192 ${ctx.config.module.uploadPath})`
2236
2418
  );
2237
2419
  }
2238
2420
  return ExitCode.SUCCESS;
@@ -2305,8 +2487,8 @@ async function runPublish(ctx, options) {
2305
2487
 
2306
2488
  // src/cli/commands/audit.ts
2307
2489
  import { createHash } from "crypto";
2308
- import { cpSync, existsSync as existsSync8, mkdirSync as mkdirSync3, readFileSync as readFileSync8, readdirSync, rmSync, statSync } from "fs";
2309
- import { resolve as resolve12, join as join3 } from "path";
2490
+ import { cpSync, existsSync as existsSync9, mkdirSync as mkdirSync3, readFileSync as readFileSync9, readdirSync, rmSync, statSync } from "fs";
2491
+ import { resolve as resolve13, join as join3 } from "path";
2310
2492
  var FORBIDDEN_SHIP_TOKENS = [
2311
2493
  "ModuleConfigOverlay",
2312
2494
  "module-config-store",
@@ -2317,10 +2499,10 @@ var FORBIDDEN_SHIP_TOKENS = [
2317
2499
  var FORBIDDEN_FIXTURE_MENU_TOKENS = ["User Home", "Primary Company Deals"];
2318
2500
  var FORBIDDEN_PREVIEW_FIXTURE_TOKENS = ["preview.editor@example.com", "CMS_EDITOR_PREVIEW"];
2319
2501
  function hashFile(path) {
2320
- return createHash("sha256").update(readFileSync8(path)).digest("hex");
2502
+ return createHash("sha256").update(readFileSync9(path)).digest("hex");
2321
2503
  }
2322
2504
  function walkFiles(dir) {
2323
- if (!existsSync8(dir)) return [];
2505
+ if (!existsSync9(dir)) return [];
2324
2506
  const entries = readdirSync(dir);
2325
2507
  const files = [];
2326
2508
  for (const entry of entries) {
@@ -2336,14 +2518,14 @@ function walkFiles(dir) {
2336
2518
  async function runAuditShipBundle(ctx) {
2337
2519
  const root = ctx.projectRoot;
2338
2520
  const targets = [
2339
- resolve12(root, "dist/cdn"),
2340
- resolve12(root, ctx.config.module.outDir)
2521
+ resolve13(root, "dist/cdn"),
2522
+ resolve13(root, ctx.config.module.outDir)
2341
2523
  ];
2342
2524
  const violations = [];
2343
2525
  for (const dir of targets) {
2344
2526
  for (const file of walkFiles(dir)) {
2345
2527
  if (!/\.(js|css|html)$/i.test(file)) continue;
2346
- const content = readFileSync8(file, "utf8");
2528
+ const content = readFileSync9(file, "utf8");
2347
2529
  for (const token of FORBIDDEN_SHIP_TOKENS) {
2348
2530
  if (content.includes(token)) {
2349
2531
  violations.push(`${file}: contains forbidden token "${token}"`);
@@ -2383,21 +2565,21 @@ async function runAuditBaselineCapture(ctx, options) {
2383
2565
  const root = ctx.projectRoot;
2384
2566
  const baseline = options.baselineDir ?? defaultBaselineDir(root, ctx.config);
2385
2567
  const sources = [
2386
- { label: "cdn", path: resolve12(root, "dist/cdn") },
2387
- { label: "hubspot", path: resolve12(root, ctx.config.module.outDir) }
2568
+ { label: "cdn", path: resolve13(root, "dist/cdn") },
2569
+ { label: "hubspot", path: resolve13(root, ctx.config.module.outDir) }
2388
2570
  ];
2389
2571
  if (ctx.global.dryRun) {
2390
2572
  ctx.logger.info(`[dry-run] Would capture baseline to ${baseline}`);
2391
2573
  return ExitCode.SUCCESS;
2392
2574
  }
2393
2575
  for (const { label, path } of sources) {
2394
- if (!existsSync8(path)) {
2576
+ if (!existsSync9(path)) {
2395
2577
  throw new WpHsError(`Cannot capture baseline \u2014 missing ${path}`, {
2396
2578
  exitCode: ExitCode.USER_ERROR,
2397
2579
  remediation: "Run wp build before wp audit baseline --capture."
2398
2580
  });
2399
2581
  }
2400
- const target = resolve12(baseline, label);
2582
+ const target = resolve13(baseline, label);
2401
2583
  rmSync(target, { recursive: true, force: true });
2402
2584
  mkdirSync3(baseline, { recursive: true });
2403
2585
  cpSync(path, target, { recursive: true });
@@ -2413,13 +2595,13 @@ async function runAuditParity(ctx, options) {
2413
2595
  const root = ctx.projectRoot;
2414
2596
  const baseline = options.baselineDir ?? defaultBaselineDir(root, ctx.config);
2415
2597
  const currentDirs = [
2416
- { label: "cdn", path: resolve12(root, "dist/cdn") },
2417
- { label: "hubspot", path: resolve12(root, ctx.config.module.outDir) }
2598
+ { label: "cdn", path: resolve13(root, "dist/cdn") },
2599
+ { label: "hubspot", path: resolve13(root, ctx.config.module.outDir) }
2418
2600
  ];
2419
2601
  const mismatches = [];
2420
2602
  for (const { label, path } of currentDirs) {
2421
- const baselinePath = resolve12(baseline, label);
2422
- if (!existsSync8(baselinePath)) {
2603
+ const baselinePath = resolve13(baseline, label);
2604
+ if (!existsSync9(baselinePath)) {
2423
2605
  ctx.logger.warn(`No baseline at ${baselinePath} \u2014 skipping ${label} parity`);
2424
2606
  continue;
2425
2607
  }
@@ -2434,8 +2616,8 @@ async function runAuditParity(ctx, options) {
2434
2616
  }
2435
2617
  for (const file of baselineFiles) {
2436
2618
  const rel = file.replace(baselinePath, "");
2437
- const current = resolve12(path, rel.slice(1));
2438
- if (!existsSync8(current)) continue;
2619
+ const current = resolve13(path, rel.slice(1));
2620
+ if (!existsSync9(current)) continue;
2439
2621
  const bh = hashFile(file);
2440
2622
  const ch = hashFile(current);
2441
2623
  if (bh !== ch) {
@@ -2462,13 +2644,13 @@ var SECRET_PATTERNS = [
2462
2644
  ];
2463
2645
  async function runAuditSecrets(ctx) {
2464
2646
  const root = ctx.projectRoot;
2465
- const scanRoots = ["src", "public", ".wphs/config"].map((d) => resolve12(root, d));
2647
+ const scanRoots = ["src", "public", ".wphs/config"].map((d) => resolve13(root, d));
2466
2648
  const findings = [];
2467
2649
  for (const dir of scanRoots) {
2468
2650
  for (const file of walkFiles(dir)) {
2469
2651
  if (!/\.(ts|tsx|js|jsx|json|env|yml|yaml|html)$/i.test(file)) continue;
2470
2652
  if (file.includes("node_modules")) continue;
2471
- const content = readFileSync8(file, "utf8");
2653
+ const content = readFileSync9(file, "utf8");
2472
2654
  for (const pattern of SECRET_PATTERNS) {
2473
2655
  pattern.lastIndex = 0;
2474
2656
  if (pattern.test(content)) {
@@ -2499,8 +2681,8 @@ import { basename as basename2, resolve as resolve17 } from "path";
2499
2681
  import * as p5 from "@clack/prompts";
2500
2682
 
2501
2683
  // src/lib/init-router-scaffold.ts
2502
- import { existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync9, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
2503
- import { resolve as resolve13 } from "path";
2684
+ import { existsSync as existsSync10, mkdirSync as mkdirSync4, readFileSync as readFileSync10, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "fs";
2685
+ import { resolve as resolve14 } from "path";
2504
2686
  var ROUTER_FILES = [
2505
2687
  "src/routes",
2506
2688
  "src/routeTree.gen.ts",
@@ -2514,10 +2696,10 @@ function isModuleTemplate(template) {
2514
2696
  return template === "module-hybrid-cdn" || template === "module-monolith";
2515
2697
  }
2516
2698
  function writeRouterConfig(targetDir, mode) {
2517
- const configDir = resolve13(targetDir, "src/config");
2699
+ const configDir = resolve14(targetDir, "src/config");
2518
2700
  mkdirSync4(configDir, { recursive: true });
2519
- writeFileSync4(
2520
- resolve13(configDir, "router.ts"),
2701
+ writeFileSync5(
2702
+ resolve14(configDir, "router.ts"),
2521
2703
  `/** Router history \u2014 set by \`wp init\`. Hash matches woodsportal-client-frontend (HubSpot CMS embeds). */
2522
2704
  export type RouterHistoryMode = 'hash' | 'browser'
2523
2705
 
@@ -2526,8 +2708,8 @@ export const ROUTER_HISTORY: RouterHistoryMode = '${mode}'
2526
2708
  );
2527
2709
  }
2528
2710
  function writeMountDevPreviewRouter(targetDir) {
2529
- writeFileSync4(
2530
- resolve13(targetDir, "src/portal/mount-dev-preview.tsx"),
2711
+ writeFileSync5(
2712
+ resolve14(targetDir, "src/portal/mount-dev-preview.tsx"),
2531
2713
  `import { mountGreenfieldDev } from '../../.wphs/.kit-bootstrap-greenfield.tsx'
2532
2714
 
2533
2715
  import { App } from '@/app'
@@ -2540,8 +2722,8 @@ export async function mountDevPreview() {
2540
2722
  );
2541
2723
  }
2542
2724
  function writeMountDevPreviewSinglePage(targetDir) {
2543
- writeFileSync4(
2544
- resolve13(targetDir, "src/portal/mount-dev-preview.tsx"),
2725
+ writeFileSync5(
2726
+ resolve14(targetDir, "src/portal/mount-dev-preview.tsx"),
2545
2727
  `import { mountGreenfieldDev } from '../../.wphs/.kit-bootstrap-greenfield.tsx'
2546
2728
 
2547
2729
  import { StarterSinglePage } from '@/features/starter'
@@ -2554,8 +2736,8 @@ export async function mountDevPreview() {
2554
2736
  );
2555
2737
  }
2556
2738
  function writeMountPortalRouter(targetDir) {
2557
- writeFileSync4(
2558
- resolve13(targetDir, "src/portal/mount-portal.tsx"),
2739
+ writeFileSync5(
2740
+ resolve14(targetDir, "src/portal/mount-portal.tsx"),
2559
2741
  `import ReactDOM from 'react-dom/client'
2560
2742
 
2561
2743
  import { App } from '@/app'
@@ -2572,8 +2754,8 @@ export async function mountPortal() {
2572
2754
  );
2573
2755
  }
2574
2756
  function writeMountPortalSinglePage(targetDir) {
2575
- writeFileSync4(
2576
- resolve13(targetDir, "src/portal/mount-portal.tsx"),
2757
+ writeFileSync5(
2758
+ resolve14(targetDir, "src/portal/mount-portal.tsx"),
2577
2759
  `import ReactDOM from 'react-dom/client'
2578
2760
 
2579
2761
  import { StarterSinglePage } from '@/features/starter'
@@ -2590,11 +2772,11 @@ export async function mountPortal() {
2590
2772
  );
2591
2773
  }
2592
2774
  function writeStarterSinglePage(targetDir) {
2593
- const file = resolve13(targetDir, "src/features/starter/components/starter-single-page.tsx");
2594
- if (existsSync9(file)) {
2775
+ const file = resolve14(targetDir, "src/features/starter/components/starter-single-page.tsx");
2776
+ if (existsSync10(file)) {
2595
2777
  return;
2596
2778
  }
2597
- writeFileSync4(
2779
+ writeFileSync5(
2598
2780
  file,
2599
2781
  `import { StarterShell } from '@/components/layout/starter-shell'
2600
2782
 
@@ -2617,25 +2799,25 @@ export function StarterSinglePage() {
2617
2799
  );
2618
2800
  }
2619
2801
  function removePath(targetDir, relativePath) {
2620
- const full = resolve13(targetDir, relativePath);
2621
- if (!existsSync9(full)) {
2802
+ const full = resolve14(targetDir, relativePath);
2803
+ if (!existsSync10(full)) {
2622
2804
  return;
2623
2805
  }
2624
2806
  rmSync2(full, { recursive: true, force: true });
2625
2807
  }
2626
2808
  function stripTanStackFromPackage(targetDir) {
2627
- const pkgPath = resolve13(targetDir, "package.json");
2628
- if (!existsSync9(pkgPath)) {
2809
+ const pkgPath = resolve14(targetDir, "package.json");
2810
+ if (!existsSync10(pkgPath)) {
2629
2811
  return;
2630
2812
  }
2631
- const pkg = JSON.parse(readFileSync9(pkgPath, "utf8"));
2813
+ const pkg = JSON.parse(readFileSync10(pkgPath, "utf8"));
2632
2814
  if (!pkg.devDependencies) {
2633
2815
  return;
2634
2816
  }
2635
2817
  for (const dep of TANSTACK_DEPS) {
2636
2818
  delete pkg.devDependencies[dep];
2637
2819
  }
2638
- writeFileSync4(pkgPath, `${JSON.stringify(pkg, null, 2)}
2820
+ writeFileSync5(pkgPath, `${JSON.stringify(pkg, null, 2)}
2639
2821
  `);
2640
2822
  }
2641
2823
  function applyRouterScaffold(targetDir, mode, template) {
@@ -2655,22 +2837,22 @@ function applyRouterScaffold(targetDir, mode, template) {
2655
2837
  writeRouterConfig(targetDir, mode);
2656
2838
  writeMountDevPreviewRouter(targetDir);
2657
2839
  writeMountPortalRouter(targetDir);
2658
- const featuresIndex = resolve13(targetDir, "src/features/starter/index.ts");
2659
- if (existsSync9(featuresIndex)) {
2660
- let content = readFileSync9(featuresIndex, "utf8");
2840
+ const featuresIndex = resolve14(targetDir, "src/features/starter/index.ts");
2841
+ if (existsSync10(featuresIndex)) {
2842
+ let content = readFileSync10(featuresIndex, "utf8");
2661
2843
  if (!content.includes("StarterSinglePage")) {
2662
2844
  content = content.replace(
2663
2845
  "export type { StarterPreviewValues } from './hooks/use-starter-preview'\n",
2664
2846
  "export type { StarterPreviewValues } from './hooks/use-starter-preview'\nexport { StarterSinglePage } from './components/starter-single-page'\n"
2665
2847
  );
2666
- writeFileSync4(featuresIndex, content);
2848
+ writeFileSync5(featuresIndex, content);
2667
2849
  }
2668
2850
  }
2669
2851
  }
2670
2852
 
2671
2853
  // src/lib/init-auth-scaffold.ts
2672
- import { cpSync as cpSync2, existsSync as existsSync10, readFileSync as readFileSync10, rmSync as rmSync3, writeFileSync as writeFileSync5 } from "fs";
2673
- import { resolve as resolve14 } from "path";
2854
+ import { cpSync as cpSync2, existsSync as existsSync11, readFileSync as readFileSync11, rmSync as rmSync3, writeFileSync as writeFileSync6 } from "fs";
2855
+ import { resolve as resolve15 } from "path";
2674
2856
 
2675
2857
  // src/lib/template-auth-deps.ts
2676
2858
  var AUTH_SCAFFOLD_DEPS = {
@@ -2690,12 +2872,12 @@ function mergeAuthDevDependencies(existing) {
2690
2872
  // src/lib/init-auth-scaffold.ts
2691
2873
  var AUTH_SCAFFOLD_ROOT = kitPath("templates", "scaffold", "auth");
2692
2874
  function readTierManifest() {
2693
- const path = resolve14(AUTH_SCAFFOLD_ROOT, "tier-manifest.json");
2694
- return JSON.parse(readFileSync10(path, "utf8"));
2875
+ const path = resolve15(AUTH_SCAFFOLD_ROOT, "tier-manifest.json");
2876
+ return JSON.parse(readFileSync11(path, "utf8"));
2695
2877
  }
2696
2878
  function readNavManifest() {
2697
- const path = resolve14(AUTH_SCAFFOLD_ROOT, "nav-manifest.json");
2698
- return JSON.parse(readFileSync10(path, "utf8"));
2879
+ const path = resolve15(AUTH_SCAFFOLD_ROOT, "nav-manifest.json");
2880
+ return JSON.parse(readFileSync11(path, "utf8"));
2699
2881
  }
2700
2882
  function layersForMode(mode) {
2701
2883
  const manifest = readTierManifest();
@@ -2706,8 +2888,8 @@ function layersForMode(mode) {
2706
2888
  return entry.layers;
2707
2889
  }
2708
2890
  function copyLayer(targetDir, layerName) {
2709
- const layerRoot = resolve14(AUTH_SCAFFOLD_ROOT, layerName);
2710
- if (!existsSync10(layerRoot)) {
2891
+ const layerRoot = resolve15(AUTH_SCAFFOLD_ROOT, layerName);
2892
+ if (!existsSync11(layerRoot)) {
2711
2893
  throw new Error(`Auth scaffold layer not found: ${layerName}`);
2712
2894
  }
2713
2895
  cpSync2(layerRoot, targetDir, { recursive: true, force: true });
@@ -2724,8 +2906,8 @@ function copyNavLayer(targetDir, nav) {
2724
2906
  copyLayer(targetDir, layerForNavMode(nav));
2725
2907
  }
2726
2908
  function patchNavScaffoldFile(targetDir, nav) {
2727
- const navScaffoldPath = resolve14(targetDir, "src/config/nav-scaffold.ts");
2728
- if (!existsSync10(navScaffoldPath)) {
2909
+ const navScaffoldPath = resolve15(targetDir, "src/config/nav-scaffold.ts");
2910
+ if (!existsSync11(navScaffoldPath)) {
2729
2911
  return;
2730
2912
  }
2731
2913
  const content = `/** Set by wp init --nav ${nav}. */
@@ -2733,11 +2915,11 @@ export const NAV_SCAFFOLD_MODE = '${nav}' as const
2733
2915
 
2734
2916
  export type NavScaffoldMode = typeof NAV_SCAFFOLD_MODE
2735
2917
  `;
2736
- writeFileSync5(navScaffoldPath, content);
2918
+ writeFileSync6(navScaffoldPath, content);
2737
2919
  }
2738
2920
  function patchAuthTierFile(targetDir, mode) {
2739
- const authTierPath = resolve14(targetDir, "src/config/auth-tier.ts");
2740
- if (!existsSync10(authTierPath)) {
2921
+ const authTierPath = resolve15(targetDir, "src/config/auth-tier.ts");
2922
+ if (!existsSync11(authTierPath)) {
2741
2923
  return;
2742
2924
  }
2743
2925
  const content = `/** Set by wp init --auth ${mode}. */
@@ -2745,7 +2927,7 @@ export const AUTH_SCAFFOLD_TIER = '${mode}' as const
2745
2927
 
2746
2928
  export type AuthScaffoldTier = typeof AUTH_SCAFFOLD_TIER
2747
2929
  `;
2748
- writeFileSync5(authTierPath, content);
2930
+ writeFileSync6(authTierPath, content);
2749
2931
  }
2750
2932
  function patchStarterOnlyBlocks(targetDir, mode) {
2751
2933
  const hasSso = mode === "sso" || mode === "full";
@@ -2759,11 +2941,11 @@ function patchStarterOnlyBlocks(targetDir, mode) {
2759
2941
  "src/features/auth/auth-route-paths.ts"
2760
2942
  ];
2761
2943
  for (const relative of filesToPatch) {
2762
- const filePath = resolve14(targetDir, relative);
2763
- if (!existsSync10(filePath)) {
2944
+ const filePath = resolve15(targetDir, relative);
2945
+ if (!existsSync11(filePath)) {
2764
2946
  continue;
2765
2947
  }
2766
- let content = readFileSync10(filePath, "utf8");
2948
+ let content = readFileSync11(filePath, "utf8");
2767
2949
  content = stripMarkedBlock(content, "WPHS_AUTH_SSO", hasSso);
2768
2950
  content = stripMarkedBlock(content, "WPHS_AUTH_FULL", hasFull);
2769
2951
  if (!hasFull && relative === "src/integrations/configure-woodsportal-sdk.ts") {
@@ -2772,7 +2954,7 @@ function patchStarterOnlyBlocks(targetDir, mode) {
2772
2954
  ""
2773
2955
  );
2774
2956
  }
2775
- writeFileSync5(filePath, content);
2957
+ writeFileSync6(filePath, content);
2776
2958
  }
2777
2959
  }
2778
2960
  function stripMarkedBlock(content, marker, keep) {
@@ -2800,11 +2982,11 @@ var LOWER_TIER_AUTH_GUARD_FILES = [
2800
2982
  function assertLowerTierAuthScaffold(targetDir, mode) {
2801
2983
  const violations = [];
2802
2984
  for (const relative of LOWER_TIER_AUTH_GUARD_FILES) {
2803
- const filePath = resolve14(targetDir, relative);
2804
- if (!existsSync10(filePath)) {
2985
+ const filePath = resolve15(targetDir, relative);
2986
+ if (!existsSync11(filePath)) {
2805
2987
  continue;
2806
2988
  }
2807
- const content = readFileSync10(filePath, "utf8");
2989
+ const content = readFileSync11(filePath, "utf8");
2808
2990
  if (content.includes("WPHS_AUTH_")) {
2809
2991
  violations.push(`${relative}: leftover WPHS_AUTH_* strip markers`);
2810
2992
  }
@@ -2817,7 +2999,7 @@ function assertLowerTierAuthScaffold(targetDir, mode) {
2817
2999
  }
2818
3000
  }
2819
3001
  }
2820
- if (existsSync10(resolve14(targetDir, "src/routes/_auth/two-fa.tsx"))) {
3002
+ if (existsSync11(resolve15(targetDir, "src/routes/_auth/two-fa.tsx"))) {
2821
3003
  violations.push("src/routes/_auth/two-fa.tsx: MFA route must not exist on --auth " + mode);
2822
3004
  }
2823
3005
  if (violations.length > 0) {
@@ -2833,14 +3015,14 @@ function assertFullTierAuthScaffold(targetDir) {
2833
3015
  "src/features/auth/mfa-pending-storage.ts",
2834
3016
  "src/routes/_auth/two-fa.tsx"
2835
3017
  ];
2836
- const missing = required.filter((relative) => !existsSync10(resolve14(targetDir, relative)));
3018
+ const missing = required.filter((relative) => !existsSync11(resolve15(targetDir, relative)));
2837
3019
  if (missing.length > 0) {
2838
3020
  throw new WpHsError(`Auth scaffold tier "full" is missing required files:
2839
3021
  - ${missing.join("\n- ")}`, {
2840
3022
  exitCode: ExitCode.INTERNAL
2841
3023
  });
2842
3024
  }
2843
- const bridge = readFileSync10(resolve14(targetDir, "src/features/auth/login-session-bridge.ts"), "utf8");
3025
+ const bridge = readFileSync11(resolve15(targetDir, "src/features/auth/login-session-bridge.ts"), "utf8");
2844
3026
  if (bridge.includes("WPHS_AUTH_")) {
2845
3027
  throw new WpHsError('Auth scaffold tier "full" left WPHS_AUTH_* markers in login-session-bridge.ts', {
2846
3028
  exitCode: ExitCode.INTERNAL
@@ -2853,18 +3035,18 @@ function assertFullTierAuthScaffold(targetDir) {
2853
3035
  }
2854
3036
  }
2855
3037
  function patchPackageJson(targetDir) {
2856
- const pkgPath = resolve14(targetDir, "package.json");
2857
- if (!existsSync10(pkgPath)) {
3038
+ const pkgPath = resolve15(targetDir, "package.json");
3039
+ if (!existsSync11(pkgPath)) {
2858
3040
  return;
2859
3041
  }
2860
- const pkg = JSON.parse(readFileSync10(pkgPath, "utf8"));
3042
+ const pkg = JSON.parse(readFileSync11(pkgPath, "utf8"));
2861
3043
  pkg.devDependencies = mergeAuthDevDependencies(pkg.devDependencies);
2862
- writeFileSync5(pkgPath, `${JSON.stringify(pkg, null, 2)}
3044
+ writeFileSync6(pkgPath, `${JSON.stringify(pkg, null, 2)}
2863
3045
  `);
2864
3046
  }
2865
3047
  function writeAuthMountDevPreview(targetDir) {
2866
- writeFileSync5(
2867
- resolve14(targetDir, "src/portal/mount-dev-preview.tsx"),
3048
+ writeFileSync6(
3049
+ resolve15(targetDir, "src/portal/mount-dev-preview.tsx"),
2868
3050
  `import { mountGreenfieldDev } from '../../.wphs/.kit-bootstrap-greenfield.tsx'
2869
3051
 
2870
3052
  import { App } from '@/app'
@@ -2901,25 +3083,25 @@ function validateNavAuthCombo(auth, nav) {
2901
3083
  }
2902
3084
  }
2903
3085
  function assertNavScaffold(targetDir, nav) {
2904
- const mainLayoutPath = resolve14(targetDir, "src/components/Layouts/MainLayout.tsx");
2905
- if (!existsSync10(mainLayoutPath)) {
3086
+ const mainLayoutPath = resolve15(targetDir, "src/components/Layouts/MainLayout.tsx");
3087
+ if (!existsSync11(mainLayoutPath)) {
2906
3088
  throw new WpHsError("Auth scaffold is missing MainLayout.tsx after nav layer copy", {
2907
3089
  exitCode: ExitCode.INTERNAL
2908
3090
  });
2909
3091
  }
2910
- const sidebarPath = resolve14(targetDir, "src/components/layout/portal-sidebar.tsx");
3092
+ const sidebarPath = resolve15(targetDir, "src/components/layout/portal-sidebar.tsx");
2911
3093
  if (nav === "sidebar") {
2912
- if (!existsSync10(sidebarPath)) {
3094
+ if (!existsSync11(sidebarPath)) {
2913
3095
  throw new WpHsError('Nav scaffold mode "sidebar" is missing portal-sidebar.tsx', {
2914
3096
  exitCode: ExitCode.INTERNAL
2915
3097
  });
2916
3098
  }
2917
- } else if (existsSync10(sidebarPath)) {
3099
+ } else if (existsSync11(sidebarPath)) {
2918
3100
  throw new WpHsError('Nav scaffold mode "top" must not include portal-sidebar.tsx', {
2919
3101
  exitCode: ExitCode.INTERNAL
2920
3102
  });
2921
3103
  }
2922
- const navScaffold = readFileSync10(resolve14(targetDir, "src/config/nav-scaffold.ts"), "utf8");
3104
+ const navScaffold = readFileSync11(resolve15(targetDir, "src/config/nav-scaffold.ts"), "utf8");
2923
3105
  if (!navScaffold.includes(`'${nav}'`)) {
2924
3106
  throw new WpHsError(`nav-scaffold.ts was not patched for mode "${nav}"`, {
2925
3107
  exitCode: ExitCode.INTERNAL
@@ -2955,11 +3137,11 @@ function authTierRank(tier) {
2955
3137
  return AUTH_TIER_ORDER.indexOf(tier);
2956
3138
  }
2957
3139
  function readCurrentAuthTier(targetDir) {
2958
- const authTierPath = resolve14(targetDir, "src/config/auth-tier.ts");
2959
- if (!existsSync10(authTierPath)) {
3140
+ const authTierPath = resolve15(targetDir, "src/config/auth-tier.ts");
3141
+ if (!existsSync11(authTierPath)) {
2960
3142
  return "none";
2961
3143
  }
2962
- const content = readFileSync10(authTierPath, "utf8");
3144
+ const content = readFileSync11(authTierPath, "utf8");
2963
3145
  const match = content.match(/AUTH_SCAFFOLD_TIER = '(starter|sso|full)'/);
2964
3146
  return match?.[1] ?? "none";
2965
3147
  }
@@ -3008,8 +3190,8 @@ var SIDEBAR_NAV_FILES = [
3008
3190
  "src/components/layout/portal-sidebar-nav.tsx"
3009
3191
  ];
3010
3192
  function applyNavScaffoldUpgrade(targetDir, nav) {
3011
- const authTierPath = resolve14(targetDir, "src/config/auth-tier.ts");
3012
- if (!existsSync10(authTierPath)) {
3193
+ const authTierPath = resolve15(targetDir, "src/config/auth-tier.ts");
3194
+ if (!existsSync11(authTierPath)) {
3013
3195
  throw new WpHsError("Nav scaffold requires portal auth (missing src/config/auth-tier.ts).", {
3014
3196
  exitCode: ExitCode.USER_ERROR,
3015
3197
  remediation: "Run wp scaffold auth upgrade --tier starter first."
@@ -3017,8 +3199,8 @@ function applyNavScaffoldUpgrade(targetDir, nav) {
3017
3199
  }
3018
3200
  if (nav === "top") {
3019
3201
  for (const relative of SIDEBAR_NAV_FILES) {
3020
- const filePath = resolve14(targetDir, relative);
3021
- if (existsSync10(filePath)) {
3202
+ const filePath = resolve15(targetDir, relative);
3203
+ if (existsSync11(filePath)) {
3022
3204
  rmSync3(filePath);
3023
3205
  }
3024
3206
  }
@@ -3052,125 +3234,6 @@ function moduleOutDirFromSlug(slug) {
3052
3234
  return `dist/${pascal || "HubSpot"}.Module`;
3053
3235
  }
3054
3236
 
3055
- // src/lib/cdn-github-setup.ts
3056
- import { existsSync as existsSync11, readFileSync as readFileSync11, writeFileSync as writeFileSync6 } from "fs";
3057
- import { resolve as resolve15 } from "path";
3058
- import * as p3 from "@clack/prompts";
3059
- var PORTAL_CDN_PUBLISH_TOKEN_ENV = "PORTAL_CDN_PUBLISH_TOKEN";
3060
- var PORTAL_CDN_JSON = "portal.cdn.json";
3061
- var PLACEHOLDER_GITHUB = /your-org|your-portal-cdn-mirror/i;
3062
- function defaultPortalCdnJson() {
3063
- const examplePath = kitPath("templates", "shared", "portal.cdn.json.example");
3064
- if (existsSync11(examplePath)) {
3065
- return JSON.parse(readFileSync11(examplePath, "utf8"));
3066
- }
3067
- return {
3068
- publishMode: "jsdelivr",
3069
- github: "your-org/your-portal-cdn-mirror",
3070
- gitRemote: "https://github.com/your-org/your-portal-cdn-mirror.git",
3071
- cdnPathInRepo: "dist/cdn",
3072
- defaultBranch: "main"
3073
- };
3074
- }
3075
- function readPortalCdnJson(projectRoot) {
3076
- const path = resolve15(projectRoot, PORTAL_CDN_JSON);
3077
- if (!existsSync11(path)) return null;
3078
- try {
3079
- return JSON.parse(readFileSync11(path, "utf8"));
3080
- } catch {
3081
- return null;
3082
- }
3083
- }
3084
- function normalizeGithubSlug(input) {
3085
- const trimmed = input.trim().replace(/^https:\/\/github\.com\//, "").replace(/\.git$/, "");
3086
- const parts = trimmed.split("/").filter(Boolean);
3087
- if (parts.length !== 2) return null;
3088
- return `${parts[0]}/${parts[1]}`;
3089
- }
3090
- function portalCdnJsonFromGithub(github, base) {
3091
- const slug = normalizeGithubSlug(github);
3092
- if (!slug) {
3093
- throw new Error(`Invalid GitHub repo \u2014 use org/repo (got "${github}")`);
3094
- }
3095
- return {
3096
- publishMode: base?.publishMode ?? "jsdelivr",
3097
- github: slug,
3098
- gitRemote: `https://github.com/${slug}.git`,
3099
- cdnPathInRepo: base?.cdnPathInRepo ?? "dist/cdn",
3100
- defaultBranch: base?.defaultBranch ?? "main"
3101
- };
3102
- }
3103
- function hasCdnPublishToken(projectRoot) {
3104
- if (process.env[PORTAL_CDN_PUBLISH_TOKEN_ENV]?.trim()) return true;
3105
- if (process.env.GITHUB_TOKEN?.trim()) return true;
3106
- return Boolean(readEnvLocal(projectRoot)[PORTAL_CDN_PUBLISH_TOKEN_ENV]?.trim());
3107
- }
3108
- function hasConfiguredCdnGithubMirror(projectRoot) {
3109
- const config = readPortalCdnJson(projectRoot);
3110
- const github = config?.github ?? "";
3111
- return Boolean(github) && !PLACEHOLDER_GITHUB.test(github);
3112
- }
3113
- function githubDefaultForPrompt(projectRoot) {
3114
- const existing = readPortalCdnJson(projectRoot);
3115
- if (existing?.github && !PLACEHOLDER_GITHUB.test(existing.github)) {
3116
- return existing.github;
3117
- }
3118
- return defaultPortalCdnJson().github;
3119
- }
3120
- function needsCdnGitHubSetup(projectRoot) {
3121
- return !hasConfiguredCdnGithubMirror(projectRoot) || !hasCdnPublishToken(projectRoot);
3122
- }
3123
- function applyCdnGitHubSetup(projectRoot, input) {
3124
- const existing = readPortalCdnJson(projectRoot) ?? defaultPortalCdnJson();
3125
- const updated = portalCdnJsonFromGithub(input.github, existing);
3126
- writeFileSync6(resolve15(projectRoot, PORTAL_CDN_JSON), `${JSON.stringify(updated, null, 2)}
3127
- `);
3128
- if (input.token?.trim()) {
3129
- writeEnvLocal(projectRoot, { [PORTAL_CDN_PUBLISH_TOKEN_ENV]: input.token.trim() });
3130
- }
3131
- ensureEnvFilesGitignore(projectRoot);
3132
- }
3133
- async function promptCdnGitHubSetup(options = {}) {
3134
- if (options.skip) return null;
3135
- const projectRoot = options.projectRoot ?? process.cwd();
3136
- if (!needsCdnGitHubSetup(projectRoot)) {
3137
- return null;
3138
- }
3139
- let github = options.github?.trim();
3140
- if (!github && hasConfiguredCdnGithubMirror(projectRoot)) {
3141
- github = readPortalCdnJson(projectRoot)?.github;
3142
- }
3143
- if (!github) {
3144
- const answer = await p3.text({
3145
- message: "Public GitHub CDN mirror (org/repo)",
3146
- placeholder: options.githubDefault ?? "your-org/your-portal-cdn-mirror",
3147
- initialValue: options.githubDefault,
3148
- validate: (value) => {
3149
- const slug = normalizeGithubSlug(value ?? "");
3150
- return slug ? void 0 : "Enter a public repo as org/repo (jsDelivr serves from it)";
3151
- }
3152
- });
3153
- if (p3.isCancel(answer)) return null;
3154
- github = normalizeGithubSlug(answer) ?? void 0;
3155
- }
3156
- if (!github) return null;
3157
- if (hasCdnPublishToken(projectRoot)) {
3158
- return { github };
3159
- }
3160
- const tokenAnswer = await p3.password({
3161
- message: "GitHub token (repo push on CDN mirror)",
3162
- validate: (value) => {
3163
- if (!value?.trim()) return "Required for wp publish \u2014 create a fine-grained PAT with Contents: Read and write";
3164
- return void 0;
3165
- }
3166
- });
3167
- if (p3.isCancel(tokenAnswer)) return null;
3168
- return { github, token: tokenAnswer.trim() };
3169
- }
3170
- function formatCdnSetupNote(_projectRoot) {
3171
- return `CDN config written \u2014 edit ${PORTAL_CDN_JSON} if needed; token saved to ${ENV_LOCAL_FILE} (gitignored)`;
3172
- }
3173
-
3174
3237
  // src/lib/init-wizard.ts
3175
3238
  function cancelIfNeeded(value) {
3176
3239
  if (p4.isCancel(value)) {
@@ -3696,11 +3759,7 @@ async function runInit(ctx, options) {
3696
3759
  if (isModuleTemplate(template)) {
3697
3760
  applyAuthScaffold(targetDir, auth, router, template, nav);
3698
3761
  }
3699
- runNodeScript(kitPath("scripts/ensure-wphs-workspace.mjs"), [], {
3700
- cwd: targetDir,
3701
- logger: ctx.logger,
3702
- dryRun: false
3703
- });
3762
+ await ensureProjectWphsWorkspace(targetDir);
3704
3763
  const gitignorePath = resolve17(targetDir, ".gitignore");
3705
3764
  const gitignoreExtras = [
3706
3765
  "hubspot.config.yml",
@@ -3863,11 +3922,7 @@ ${baseline}
3863
3922
  remediation: "Run: wp migrate --yes"
3864
3923
  });
3865
3924
  }
3866
- runNodeScript(kitPath("scripts/ensure-wphs-workspace.mjs"), [], {
3867
- cwd: root,
3868
- logger: ctx.logger,
3869
- dryRun: false
3870
- });
3925
+ await ensureProjectWphsWorkspace(root);
3871
3926
  for (const action of actions) ctx.logger.success(action);
3872
3927
  return ExitCode.SUCCESS;
3873
3928
  }
@@ -3896,8 +3951,8 @@ async function runExamplesSmoke(ctx, options = {}) {
3896
3951
  const tier = options.tier ?? "static";
3897
3952
  const args = ["--tier", tier];
3898
3953
  if (options.scenario) args.push("--scenario", options.scenario);
3899
- const { runNodeScript: runNodeScript2 } = await import("./exec-VY6BGWF3.js");
3900
- runNodeScript2(kitPath("examples", "smoke", "run-matrix.mjs"), args, {
3954
+ const { runNodeScript } = await import("./exec-VY6BGWF3.js");
3955
+ runNodeScript(kitPath("examples", "smoke", "run-matrix.mjs"), args, {
3901
3956
  cwd: ctx.projectRoot,
3902
3957
  logger: ctx.logger,
3903
3958
  dryRun: ctx.global.dryRun
@@ -4020,14 +4075,7 @@ async function runSetup(ctx, options = {}) {
4020
4075
  title: "Preparing .wphs workspace",
4021
4076
  task: async () => {
4022
4077
  if (ctx.global.dryRun) return;
4023
- try {
4024
- runNodeScript(kitPath("scripts", "ensure-wphs-workspace.mjs"), [], {
4025
- cwd: ctx.projectRoot,
4026
- logger: ctx.logger,
4027
- dryRun: false
4028
- });
4029
- } catch {
4030
- }
4078
+ await ensureProjectWphsWorkspace(ctx.projectRoot);
4031
4079
  }
4032
4080
  },
4033
4081
  {
@@ -4608,7 +4656,7 @@ function attachGlobals(command) {
4608
4656
  async function handle(fn) {
4609
4657
  try {
4610
4658
  const code = await fn();
4611
- process.exit(code);
4659
+ await exitCli(code);
4612
4660
  } catch (error) {
4613
4661
  if (isWpHsError(error)) {
4614
4662
  process.stderr.write(`Error: ${error.message}
@@ -4617,11 +4665,11 @@ async function handle(fn) {
4617
4665
  process.stderr.write(`\u2192 ${error.remediation}
4618
4666
  `);
4619
4667
  }
4620
- process.exit(error.exitCode);
4668
+ await exitCli(error.exitCode);
4621
4669
  }
4622
4670
  process.stderr.write(`Error: ${error instanceof Error ? error.message : String(error)}
4623
4671
  `);
4624
- process.exit(ExitCode.INTERNAL);
4672
+ await exitCli(ExitCode.INTERNAL);
4625
4673
  }
4626
4674
  }
4627
4675
  function createProgram() {