@woodsportal/hubspot-kit 1.0.4 → 1.0.26

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.
Files changed (217) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +4 -2
  3. package/dist/{chunk-JDGIAYHX.js → chunk-GTTIJVAR.js} +43 -3
  4. package/dist/chunk-GTTIJVAR.js.map +1 -0
  5. package/dist/cli.js +1400 -338
  6. package/dist/cli.js.map +1 -1
  7. package/dist/exec-U2OJATR3.js +12 -0
  8. package/dist/index.d.ts +20 -0
  9. package/dist/index.js +8 -1
  10. package/dist/index.js.map +1 -1
  11. package/examples/greenfield/module-hybrid-cdn/README.md +40 -3
  12. package/examples/greenfield/module-hybrid-cdn/public/fields.json +44 -1
  13. package/examples/greenfield/module-hybrid-cdn/src/app.tsx +8 -0
  14. package/examples/greenfield/module-hybrid-cdn/src/assets/css/main.dev.css +6 -1
  15. package/examples/greenfield/module-hybrid-cdn/src/cdn/app-entry.ts +32 -0
  16. package/examples/greenfield/module-hybrid-cdn/src/cdn/vendor-entry.ts +28 -0
  17. package/examples/greenfield/module-hybrid-cdn/src/components/layout/section-card.tsx +20 -0
  18. package/examples/greenfield/module-hybrid-cdn/src/components/layout/starter-nav.tsx +28 -0
  19. package/examples/greenfield/module-hybrid-cdn/src/components/layout/starter-shell.tsx +18 -0
  20. package/examples/greenfield/module-hybrid-cdn/src/config/router.ts +4 -0
  21. package/examples/greenfield/module-hybrid-cdn/src/config/routes.ts +5 -0
  22. package/examples/greenfield/module-hybrid-cdn/src/defaultData.ts +2 -7
  23. package/examples/greenfield/module-hybrid-cdn/src/features/starter/components/starter-guide-page.tsx +93 -0
  24. package/examples/greenfield/module-hybrid-cdn/src/features/starter/components/starter-home-page.tsx +21 -0
  25. package/examples/greenfield/module-hybrid-cdn/src/features/starter/components/starter-home.tsx +52 -0
  26. package/examples/greenfield/module-hybrid-cdn/src/features/starter/components/starter-single-page.tsx +17 -0
  27. package/examples/greenfield/module-hybrid-cdn/src/features/starter/content/woodscli-guide-content.ts +86 -0
  28. package/examples/greenfield/module-hybrid-cdn/src/features/starter/hooks/use-starter-preview.ts +47 -0
  29. package/examples/greenfield/module-hybrid-cdn/src/features/starter/index.ts +6 -0
  30. package/examples/greenfield/module-hybrid-cdn/src/hubspot/bootstrap.ts +10 -0
  31. package/examples/greenfield/module-hybrid-cdn/src/hubspot/default-data.ts +7 -0
  32. package/examples/greenfield/module-hybrid-cdn/src/integrations/tanstack-router/router.tsx +25 -0
  33. package/examples/greenfield/module-hybrid-cdn/src/main.tsx +4 -3
  34. package/examples/greenfield/module-hybrid-cdn/src/portal/mount-dev-preview.tsx +8 -0
  35. package/examples/greenfield/module-hybrid-cdn/src/portal/mount-portal.tsx +12 -0
  36. package/examples/greenfield/module-hybrid-cdn/src/routeTree.gen.ts +77 -0
  37. package/examples/greenfield/module-hybrid-cdn/src/routes/__root.tsx +15 -0
  38. package/examples/greenfield/module-hybrid-cdn/src/routes/guide.tsx +7 -0
  39. package/examples/greenfield/module-hybrid-cdn/src/routes/index.tsx +7 -0
  40. package/examples/greenfield/module-hybrid-cdn/tsconfig.json +20 -0
  41. package/examples/greenfield/module-monolith/public/fields.json +44 -1
  42. package/examples/greenfield/module-monolith/src/app.tsx +8 -0
  43. package/examples/greenfield/module-monolith/src/assets/css/main.dev.css +8 -0
  44. package/examples/greenfield/module-monolith/src/components/layout/section-card.tsx +20 -0
  45. package/examples/greenfield/module-monolith/src/components/layout/starter-nav.tsx +28 -0
  46. package/examples/greenfield/module-monolith/src/components/layout/starter-shell.tsx +18 -0
  47. package/examples/greenfield/module-monolith/src/config/router.ts +4 -0
  48. package/examples/greenfield/module-monolith/src/config/routes.ts +5 -0
  49. package/examples/greenfield/module-monolith/src/defaultData.ts +2 -7
  50. package/examples/greenfield/module-monolith/src/features/starter/components/starter-guide-page.tsx +93 -0
  51. package/examples/greenfield/module-monolith/src/features/starter/components/starter-home-page.tsx +21 -0
  52. package/examples/greenfield/module-monolith/src/features/starter/components/starter-home.tsx +52 -0
  53. package/examples/greenfield/module-monolith/src/features/starter/components/starter-single-page.tsx +17 -0
  54. package/examples/greenfield/module-monolith/src/features/starter/content/woodscli-guide-content.ts +86 -0
  55. package/examples/greenfield/module-monolith/src/features/starter/hooks/use-starter-preview.ts +47 -0
  56. package/examples/greenfield/module-monolith/src/features/starter/index.ts +6 -0
  57. package/examples/greenfield/module-monolith/src/hubspot/default-data.ts +7 -0
  58. package/examples/greenfield/module-monolith/src/hubspot/types.ts +17 -0
  59. package/examples/greenfield/module-monolith/src/integrations/tanstack-router/router.tsx +25 -0
  60. package/examples/greenfield/module-monolith/src/main.tsx +5 -0
  61. package/examples/greenfield/module-monolith/src/portal/mount-dev-preview.tsx +8 -0
  62. package/examples/greenfield/module-monolith/src/portal/mount-portal.tsx +12 -0
  63. package/examples/greenfield/module-monolith/src/routeTree.gen.ts +77 -0
  64. package/examples/greenfield/module-monolith/src/routes/__root.tsx +15 -0
  65. package/examples/greenfield/module-monolith/src/routes/guide.tsx +7 -0
  66. package/examples/greenfield/module-monolith/src/routes/index.tsx +7 -0
  67. package/examples/greenfield/module-monolith/tsconfig.json +20 -0
  68. package/package.json +4 -1
  69. package/scripts/check-hubspot-bundle-size.mjs +5 -2
  70. package/scripts/ensure-wphs-workspace.mjs +2 -1
  71. package/scripts/hubspot-module-postbuild.mjs +1 -1
  72. package/scripts/read-woodsportal-config.mjs +4 -1
  73. package/scripts/run-hubspot-module-postbuild.mjs +9 -4
  74. package/scripts/vite/bootstrap-shim-resolver.js +47 -0
  75. package/scripts/vite/ensure-dev-kit-shims.js +7 -1
  76. package/scripts/vite/react-dev-plugin-options.js +2 -1
  77. package/scripts/watch-hubspot.mjs +107 -17
  78. package/scripts/wphs-paths.mjs +1 -0
  79. package/scripts/write-audit-artifacts.mjs +2 -1
  80. package/src/dev/bootstrap-greenfield.tsx +59 -0
  81. package/src/dev/bootstrap.tsx +46 -0
  82. package/src/dev-module-config/__tests__/fixtures/fields.json +3014 -0
  83. package/src/dev-module-config/__tests__/fixtures/golden-module.fixture.ts +102 -0
  84. package/src/dev-module-config/__tests__/module-config-storage.test.ts +40 -0
  85. package/src/dev-module-config/__tests__/module-config-transform.test.ts +55 -0
  86. package/src/dev-module-config/__tests__/normalize-repeater-items.test.ts +46 -0
  87. package/src/dev-module-config/dev-module-display-name.ts +5 -0
  88. package/src/dev-module-config/runtime/module-config-bridge.ts +27 -0
  89. package/src/dev-module-config/runtime/module-config-runtime.ts +185 -0
  90. package/src/dev-module-config/schema/build-default-item.ts +7 -0
  91. package/src/dev-module-config/schema/deep-merge.ts +27 -0
  92. package/src/dev-module-config/schema/extract-defaults.ts +62 -0
  93. package/src/dev-module-config/schema/normalize-choices.ts +18 -0
  94. package/src/dev-module-config/schema/normalize-repeater-items.ts +31 -0
  95. package/src/dev-module-config/schema/parse-fields-json.ts +2 -0
  96. package/src/dev-module-config/schema/types.ts +75 -0
  97. package/src/dev-module-config/schema/validate-module-config.ts +81 -0
  98. package/src/dev-module-config/schema/visibility.ts +130 -0
  99. package/src/dev-module-config/storage/config-json.ts +46 -0
  100. package/src/dev-module-config/storage/workspace-store.ts +139 -0
  101. package/src/dev-module-config/transform/build-hubspot-data.ts +409 -0
  102. package/src/dev-module-config/ui/FieldRenderer.tsx +353 -0
  103. package/src/dev-module-config/ui/ModuleConfigAnimatedPresence.tsx +72 -0
  104. package/src/dev-module-config/ui/ModuleConfigCollapsible.tsx +43 -0
  105. package/src/dev-module-config/ui/ModuleConfigDockSideIcon.tsx +27 -0
  106. package/src/dev-module-config/ui/ModuleConfigOverlay.tsx +134 -0
  107. package/src/dev-module-config/ui/ModuleConfigPersistenceStatus.tsx +89 -0
  108. package/src/dev-module-config/ui/ModuleConfigShell.tsx +355 -0
  109. package/src/dev-module-config/ui/ModuleConfigThemeToggle.tsx +32 -0
  110. package/src/dev-module-config/ui/ModuleConfigUiContext.tsx +65 -0
  111. package/src/dev-module-config/ui/editors/DeveloperOptionsEditor.tsx +61 -0
  112. package/src/dev-module-config/ui/editors/MenuOptionsEditor.tsx +103 -0
  113. package/src/dev-module-config/ui/hubspot/HsColorField.tsx +106 -0
  114. package/src/dev-module-config/ui/hubspot/HsColorPickerPopover.tsx +210 -0
  115. package/src/dev-module-config/ui/hubspot/HsFieldLabel.tsx +24 -0
  116. package/src/dev-module-config/ui/hubspot/HsFieldWidgets.tsx +248 -0
  117. package/src/dev-module-config/ui/hubspot/HsImageField.tsx +112 -0
  118. package/src/dev-module-config/ui/hubspot/HsOccurrenceGroupEditor.tsx +243 -0
  119. package/src/dev-module-config/ui/hubspot/HsRepeaterList.tsx +178 -0
  120. package/src/dev-module-config/ui/hubspot/HsSectionHeader.tsx +20 -0
  121. package/src/dev-module-config/ui/hubspot/HsToggle.tsx +39 -0
  122. package/src/dev-module-config/ui/hubspot/HsToggleRow.tsx +30 -0
  123. package/src/dev-module-config/ui/hubspot/hs-color-palette.ts +11 -0
  124. package/src/dev-module-config/ui/hubspot/module-config-typography.ts +26 -0
  125. package/src/dev-module-config/ui/module-config-motion.css +134 -0
  126. package/src/dev-module-config/ui/module-config-motion.ts +28 -0
  127. package/src/dev-module-config/ui/module-config-overlay-side.ts +29 -0
  128. package/src/dev-module-config/ui/module-config-surfaces.ts +27 -0
  129. package/src/dev-module-config/ui/module-config-theme.css +489 -0
  130. package/src/dev-module-config/ui/module-config-toolbar-theme.ts +35 -0
  131. package/src/dev-module-config/ui/navigation/ModuleConfigNavContext.tsx +194 -0
  132. package/src/dev-module-config/ui/use-dev-module-config-live-revision.ts +22 -0
  133. package/src/dev-module-config/ui/use-hubspot-live-exports.ts +27 -0
  134. package/src/dev-module-config/ui/use-module-config.ts +124 -0
  135. package/src/routing/build-api-routes.ts +37 -0
  136. package/src/routing/route-format.ts +19 -0
  137. package/templates/module-hybrid-cdn/README.md +53 -3
  138. package/templates/module-hybrid-cdn/index.html +12 -1
  139. package/templates/module-hybrid-cdn/package.json +6 -2
  140. package/templates/module-hybrid-cdn/public/fields.json +44 -1
  141. package/templates/module-hybrid-cdn/src/app.tsx +8 -0
  142. package/templates/module-hybrid-cdn/src/assets/css/main.dev.css +6 -1
  143. package/templates/module-hybrid-cdn/src/cdn/app-entry.ts +32 -0
  144. package/templates/module-hybrid-cdn/src/cdn/vendor-entry.ts +28 -0
  145. package/templates/module-hybrid-cdn/src/components/layout/section-card.tsx +20 -0
  146. package/templates/module-hybrid-cdn/src/components/layout/starter-nav.tsx +28 -0
  147. package/templates/module-hybrid-cdn/src/components/layout/starter-shell.tsx +18 -0
  148. package/templates/module-hybrid-cdn/src/config/router.ts +4 -0
  149. package/templates/module-hybrid-cdn/src/config/routes.ts +5 -0
  150. package/templates/module-hybrid-cdn/src/defaultData.ts +2 -7
  151. package/templates/module-hybrid-cdn/src/features/starter/components/starter-guide-page.tsx +93 -0
  152. package/templates/module-hybrid-cdn/src/features/starter/components/starter-home-page.tsx +21 -0
  153. package/templates/module-hybrid-cdn/src/features/starter/components/starter-home.tsx +52 -0
  154. package/templates/module-hybrid-cdn/src/features/starter/components/starter-single-page.tsx +17 -0
  155. package/templates/module-hybrid-cdn/src/features/starter/content/woodscli-guide-content.ts +86 -0
  156. package/templates/module-hybrid-cdn/src/features/starter/hooks/starter-preview-values.ts +42 -0
  157. package/templates/module-hybrid-cdn/src/features/starter/hooks/use-starter-preview.dev.ts +17 -0
  158. package/templates/module-hybrid-cdn/src/features/starter/hooks/use-starter-preview.ts +30 -0
  159. package/templates/module-hybrid-cdn/src/features/starter/index.ts +6 -0
  160. package/templates/module-hybrid-cdn/src/hubspot/bootstrap.ts +10 -0
  161. package/templates/module-hybrid-cdn/src/hubspot/default-data.ts +7 -0
  162. package/templates/module-hybrid-cdn/src/hubspot/module-entry.tsx +9 -0
  163. package/templates/module-hybrid-cdn/src/integrations/tanstack-router/router.tsx +25 -0
  164. package/templates/module-hybrid-cdn/src/main.tsx +4 -3
  165. package/templates/module-hybrid-cdn/src/portal/mount-dev-preview.tsx +8 -0
  166. package/templates/module-hybrid-cdn/src/portal/mount-portal.tsx +12 -0
  167. package/templates/module-hybrid-cdn/src/routeTree.gen.ts +77 -0
  168. package/templates/module-hybrid-cdn/src/routes/__root.tsx +15 -0
  169. package/templates/module-hybrid-cdn/src/routes/guide.tsx +7 -0
  170. package/templates/module-hybrid-cdn/src/routes/index.tsx +7 -0
  171. package/templates/module-hybrid-cdn/tsconfig.json +20 -0
  172. package/templates/module-hybrid-cdn/vite.config.monolith.mjs +8 -0
  173. package/templates/module-monolith/README.md +59 -0
  174. package/templates/module-monolith/index.html +12 -0
  175. package/templates/module-monolith/package.json +10 -3
  176. package/templates/module-monolith/public/fields.json +44 -1
  177. package/templates/module-monolith/public/module.html +1 -0
  178. package/templates/module-monolith/src/app.tsx +8 -0
  179. package/templates/module-monolith/src/assets/css/main.dev.css +6 -1
  180. package/templates/module-monolith/src/components/layout/section-card.tsx +20 -0
  181. package/templates/module-monolith/src/components/layout/starter-nav.tsx +28 -0
  182. package/templates/module-monolith/src/components/layout/starter-shell.tsx +18 -0
  183. package/templates/module-monolith/src/config/router.ts +4 -0
  184. package/templates/module-monolith/src/config/routes.ts +5 -0
  185. package/templates/module-monolith/src/defaultData.ts +2 -7
  186. package/templates/module-monolith/src/features/starter/components/starter-guide-page.tsx +93 -0
  187. package/templates/module-monolith/src/features/starter/components/starter-home-page.tsx +21 -0
  188. package/templates/module-monolith/src/features/starter/components/starter-home.tsx +52 -0
  189. package/templates/module-monolith/src/features/starter/components/starter-single-page.tsx +17 -0
  190. package/templates/module-monolith/src/features/starter/content/woodscli-guide-content.ts +86 -0
  191. package/templates/module-monolith/src/features/starter/hooks/starter-preview-values.ts +42 -0
  192. package/templates/module-monolith/src/features/starter/hooks/use-starter-preview.dev.ts +17 -0
  193. package/templates/module-monolith/src/features/starter/hooks/use-starter-preview.ts +30 -0
  194. package/templates/module-monolith/src/features/starter/index.ts +6 -0
  195. package/templates/module-monolith/src/hubspot/default-data.ts +7 -0
  196. package/templates/module-monolith/src/hubspot/module-entry.tsx +9 -0
  197. package/templates/module-monolith/src/integrations/tanstack-router/router.tsx +25 -0
  198. package/templates/module-monolith/src/main.tsx +4 -3
  199. package/templates/module-monolith/src/portal/mount-dev-preview.tsx +8 -0
  200. package/templates/module-monolith/src/portal/mount-portal.tsx +12 -0
  201. package/templates/module-monolith/src/routeTree.gen.ts +77 -0
  202. package/templates/module-monolith/src/routes/__root.tsx +15 -0
  203. package/templates/module-monolith/src/routes/guide.tsx +7 -0
  204. package/templates/module-monolith/src/routes/index.tsx +7 -0
  205. package/templates/module-monolith/tsconfig.json +20 -0
  206. package/templates/module-monolith/vite.config.monolith.mjs +8 -0
  207. package/templates/module-monolith/woodscli.json +1 -0
  208. package/templates/shared/.env.dev.example +5 -0
  209. package/templates/shared/.env.prod.example +5 -0
  210. package/templates/shared/.env.stg.example +3 -0
  211. package/templates/shared/portal.cdn.json.example +8 -0
  212. package/templates/theme-starter/package.json +5 -2
  213. package/vite/vite.config.dev.mjs +9 -1
  214. package/vite/vite.config.monolith.mjs +22 -17
  215. package/dist/chunk-JDGIAYHX.js.map +0 -1
  216. package/dist/exec-K5C6LZP4.js +0 -10
  217. /package/dist/{exec-K5C6LZP4.js.map → exec-U2OJATR3.js.map} +0 -0
package/dist/cli.js CHANGED
@@ -4,12 +4,13 @@ import {
4
4
  WpHsError,
5
5
  isWpHsError,
6
6
  runCommand,
7
- runNodeScript
8
- } from "./chunk-JDGIAYHX.js";
7
+ runNodeScript,
8
+ runNodeScriptLongRunning
9
+ } from "./chunk-GTTIJVAR.js";
9
10
 
10
11
  // src/cli/program.ts
11
- import { readFileSync as readFileSync10 } from "fs";
12
- import { resolve as resolve17 } from "path";
12
+ import { readFileSync as readFileSync14 } from "fs";
13
+ import { resolve as resolve21 } from "path";
13
14
  import { Command } from "commander";
14
15
 
15
16
  // src/lib/config.ts
@@ -94,6 +95,8 @@ var WoodsPortalHsSchema = z.object({
94
95
  app: z.string().default("src/cdn/app-entry.ts"),
95
96
  vendor: z.string().default("src/cdn/vendor-entry.ts"),
96
97
  hubspotBootstrap: z.string().default("src/hubspot/bootstrap.ts"),
98
+ /** Monolith production IIFE entry (wp build --hubspot-only) */
99
+ module: z.string().default("src/hubspot/module-entry.tsx"),
97
100
  localDev: z.string().default("src/main.tsx")
98
101
  }).default({}),
99
102
  env: z.object({
@@ -104,7 +107,11 @@ var WoodsPortalHsSchema = z.object({
104
107
  adapter: z.string().default("./wphs.adapter.ts"),
105
108
  fieldsJson: z.string().default("public/fields.json"),
106
109
  themeFieldsJson: z.string().optional()
107
- }).default({})
110
+ }).default({}),
111
+ scaffold: z.object({
112
+ /** hash = woodsportal-client-frontend (HubSpot CMS); browser = local clean URLs; none = single page */
113
+ router: z.enum(["hash", "browser", "none"]).default("hash")
114
+ }).optional()
108
115
  });
109
116
  var DEFAULT_WOODSPORTAL_HS = {
110
117
  configVersion: CONFIG_VERSION,
@@ -123,6 +130,7 @@ var DEFAULT_WOODSPORTAL_HS = {
123
130
  app: "src/cdn/app-entry.ts",
124
131
  vendor: "src/cdn/vendor-entry.ts",
125
132
  hubspotBootstrap: "src/hubspot/bootstrap.ts",
133
+ module: "src/hubspot/module-entry.tsx",
126
134
  localDev: "src/main.tsx"
127
135
  },
128
136
  env: {
@@ -285,22 +293,66 @@ function defaultBaselineDir(projectRoot, config) {
285
293
  }
286
294
 
287
295
  // src/cli/commands/doctor.ts
288
- import { existsSync as existsSync4 } from "fs";
296
+ import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
289
297
  import { join, resolve as resolve5 } from "path";
290
298
 
299
+ // src/lib/cli-spinner.ts
300
+ import * as p from "@clack/prompts";
301
+ import pc2 from "picocolors";
302
+ function isInteractiveCli(ctx) {
303
+ const { json, quiet, ci } = ctx.global;
304
+ return !json && !quiet && !ci && process.stdout.isTTY === true;
305
+ }
306
+ function shouldShowProgress(ctx, override) {
307
+ if (override === false) return false;
308
+ return isInteractiveCli(ctx);
309
+ }
310
+ async function runCliTasks(ctx, tasks2, options = {}) {
311
+ if (!shouldShowProgress(ctx, options.progress)) {
312
+ for (const step of tasks2) {
313
+ await step.task();
314
+ }
315
+ return;
316
+ }
317
+ await p.tasks(tasks2);
318
+ }
319
+ function printCommandDone(ctx, message) {
320
+ if (isInteractiveCli(ctx)) {
321
+ process.stdout.write(`${pc2.green("\u25C6")} ${message}
322
+ `);
323
+ return;
324
+ }
325
+ ctx.logger.success(message);
326
+ }
327
+
291
328
  // src/lib/hubspot-cli-setup.ts
292
329
  import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
293
330
  import { resolve as resolve4 } from "path";
294
331
  import { spawnSync } from "child_process";
295
332
  var HUBSPOT_CONFIG_FILE = "hubspot.config.yml";
296
333
  var HUBSPOT_CONFIG_EXAMPLE = "hubspot.config.yml.example";
334
+ var HS_VERSION_TIMEOUT_MS = 5e3;
335
+ var HS_ACCOUNTS_TIMEOUT_MS = 1e4;
297
336
  var PLACEHOLDER_PATTERNS = [
298
337
  /YOUR_PAT_HERE/i,
299
338
  /your-portal-id/i,
300
339
  /personalAccessKey:\s*$/m,
301
340
  /portalId:\s*12345678\b/
302
341
  ];
303
- function assessHubSpotCliSetup(projectRoot) {
342
+ function spawnHs(args, options = {}) {
343
+ return spawnSync("hs", args, {
344
+ cwd: options.cwd,
345
+ encoding: "utf8",
346
+ timeout: options.timeoutMs ?? HS_ACCOUNTS_TIMEOUT_MS,
347
+ // Keep HubSpot CLI off the interactive TTY (avoids hangs under @clack spinners)
348
+ stdio: ["ignore", "pipe", "pipe"]
349
+ });
350
+ }
351
+ function hsTimedOut(result) {
352
+ const err = result.error;
353
+ return err?.code === "ETIMEDOUT";
354
+ }
355
+ function assessHubSpotConfigFiles(projectRoot) {
304
356
  const configPath = resolve4(projectRoot, HUBSPOT_CONFIG_FILE);
305
357
  const examplePath = resolve4(projectRoot, HUBSPOT_CONFIG_EXAMPLE);
306
358
  const configPresent = existsSync3(configPath);
@@ -312,43 +364,47 @@ function assessHubSpotCliSetup(projectRoot) {
312
364
  } else {
313
365
  looksLikePlaceholder = true;
314
366
  }
315
- const hsVersion = spawnSync("hs", ["--version"], { encoding: "utf8" });
316
- const hsCliInstalled = hsVersion.status === 0;
367
+ return {
368
+ configPath,
369
+ examplePath,
370
+ configPresent,
371
+ examplePresent,
372
+ looksLikePlaceholder
373
+ };
374
+ }
375
+ function runHubSpotCliChecks(projectRoot, options = {}) {
376
+ const checkAccounts = options.checkAccounts !== false;
377
+ const hsVersion = spawnHs(["--version"], { timeoutMs: HS_VERSION_TIMEOUT_MS });
378
+ const hsCliInstalled = hsVersion.status === 0 && !hsTimedOut(hsVersion);
317
379
  const hsCliVersion = hsCliInstalled ? (hsVersion.stdout || hsVersion.stderr || "").trim() : null;
318
380
  let authenticated = false;
319
381
  let accountsHint = null;
320
- if (hsCliInstalled) {
321
- const accounts = spawnSync("hs", ["accounts", "list"], {
382
+ if (hsCliInstalled && checkAccounts) {
383
+ const accounts = spawnHs(["accounts", "list"], {
322
384
  cwd: projectRoot,
323
- encoding: "utf8",
324
- timeout: 15e3
385
+ timeoutMs: HS_ACCOUNTS_TIMEOUT_MS
325
386
  });
326
387
  const output = `${accounts.stdout || ""}
327
388
  ${accounts.stderr || ""}`.trim();
328
- accountsHint = output || null;
329
- authenticated = accounts.status === 0 && /Account:|portalId|portal/i.test(output) && !/No accounts/i.test(output);
389
+ accountsHint = hsTimedOut(accounts) ? "timed out" : output || null;
390
+ authenticated = !hsTimedOut(accounts) && accounts.status === 0 && /Account:|portalId|portal/i.test(output) && !/No accounts/i.test(output);
330
391
  }
392
+ return { hsCliInstalled, hsCliVersion, authenticated, accountsHint };
393
+ }
394
+ function assessHubSpotCliSetup(projectRoot, options = {}) {
331
395
  return {
332
- configPath,
333
- examplePath,
334
- configPresent,
335
- examplePresent,
336
- looksLikePlaceholder,
337
- hsCliInstalled,
338
- hsCliVersion,
339
- authenticated,
340
- accountsHint
396
+ ...assessHubSpotConfigFiles(projectRoot),
397
+ ...runHubSpotCliChecks(projectRoot, options)
341
398
  };
342
399
  }
343
- function hubSpotSetupRemediation(projectRoot) {
344
- const assessment = assessHubSpotCliSetup(projectRoot);
400
+ function hubSpotSetupRemediation(projectRoot, assessment = assessHubSpotCliSetup(projectRoot)) {
345
401
  const steps = [];
346
402
  if (!assessment.hsCliInstalled) {
347
403
  steps.push("Install HubSpot CLI: npm i -g @hubspot/cli");
348
404
  }
349
405
  if (!assessment.configPresent) {
350
406
  if (assessment.examplePresent) {
351
- steps.push(`Copy ${HUBSPOT_CONFIG_EXAMPLE} \u2192 ${HUBSPOT_CONFIG_FILE} and add your portal + PAT`);
407
+ steps.push(`Run wp setup (copies ${HUBSPOT_CONFIG_EXAMPLE} \u2192 ${HUBSPOT_CONFIG_FILE}) and add portal + PAT`);
352
408
  } else {
353
409
  steps.push(`Create ${HUBSPOT_CONFIG_FILE} (see docs/HUBSPOT-CLI-SETUP.md)`);
354
410
  }
@@ -361,29 +417,67 @@ function hubSpotSetupRemediation(projectRoot) {
361
417
  }
362
418
  return steps;
363
419
  }
420
+ function hubSpotLocalDevRemediation(files) {
421
+ const steps = [];
422
+ if (!files.configPresent) {
423
+ if (files.examplePresent) {
424
+ steps.push(`Run wp setup (copies ${HUBSPOT_CONFIG_EXAMPLE} \u2192 ${HUBSPOT_CONFIG_FILE}) and add portal + PAT`);
425
+ } else {
426
+ steps.push(`Create ${HUBSPOT_CONFIG_FILE} (see docs/HUBSPOT-CLI-SETUP.md)`);
427
+ }
428
+ steps.push("Generate a PAT: https://app.hubspot.com/l/personal-access-key");
429
+ } else if (files.looksLikePlaceholder) {
430
+ steps.push(`Edit ${HUBSPOT_CONFIG_FILE} \u2014 replace placeholder portalId and personalAccessKey`);
431
+ }
432
+ return steps;
433
+ }
434
+ function cmsEnsureAssessment(projectRoot) {
435
+ return assessHubSpotCliSetup(projectRoot, { checkAccounts: false });
436
+ }
437
+ function throwCmsConfigError(_ctx, files) {
438
+ const steps = hubSpotLocalDevRemediation(files);
439
+ throw new WpHsError("HubSpot config is not ready for cms watch/upload in this project.", {
440
+ exitCode: ExitCode.PREREQUISITE,
441
+ remediation: steps.join("; ")
442
+ });
443
+ }
444
+ function ensureHubSpotConfigForCms(ctx) {
445
+ if (ctx.global.dryRun || ctx.global.json) return;
446
+ const files = assessHubSpotConfigFiles(ctx.projectRoot);
447
+ if (files.configPresent && !files.looksLikePlaceholder) return;
448
+ throwCmsConfigError(ctx, files);
449
+ }
364
450
  function ensureHubSpotCliSetup(ctx, mode) {
365
451
  if (ctx.global.dryRun || ctx.global.json) return;
366
- const assessment = assessHubSpotCliSetup(ctx.projectRoot);
367
- const needsSetup = !assessment.hsCliInstalled || !assessment.configPresent || assessment.looksLikePlaceholder || !assessment.authenticated;
368
- if (!needsSetup) return;
369
- const steps = hubSpotSetupRemediation(ctx.projectRoot);
370
- const headline = mode === "local" ? "HubSpot upload/watch is not configured for this project (local preview still works)." : "HubSpot CLI is not ready for cms upload/watch in this project.";
371
452
  if (mode === "local") {
372
- ctx.logger.warn(headline);
373
- for (const step of steps) {
453
+ const files = assessHubSpotConfigFiles(ctx.projectRoot);
454
+ if (files.configPresent && !files.looksLikePlaceholder) {
455
+ return;
456
+ }
457
+ const steps2 = hubSpotLocalDevRemediation(files);
458
+ ctx.logger.warn(
459
+ "HubSpot upload/watch is not configured for this project (local preview still works)."
460
+ );
461
+ for (const step of steps2) {
374
462
  ctx.logger.info(` \u2192 ${step}`);
375
463
  }
464
+ ctx.logger.info(" \u2192 Full HubSpot CLI checks: wp doctor");
376
465
  ctx.logger.info(" \u2192 Docs: @woodsportal/hubspot-kit/docs/HUBSPOT-CLI-SETUP.md");
377
466
  return;
378
467
  }
468
+ const assessment = cmsEnsureAssessment(ctx.projectRoot);
469
+ const needsSetup = !assessment.hsCliInstalled || !assessment.configPresent || assessment.looksLikePlaceholder;
470
+ if (!needsSetup) return;
471
+ const steps = hubSpotSetupRemediation(ctx.projectRoot, assessment);
472
+ const headline = "HubSpot CLI is not ready for cms upload/watch in this project.";
379
473
  throw new WpHsError(headline, {
380
474
  exitCode: ExitCode.PREREQUISITE,
381
475
  remediation: steps.join("; ")
382
476
  });
383
477
  }
384
478
  function doctorHubSpotChecks(projectRoot) {
385
- const assessment = assessHubSpotCliSetup(projectRoot);
386
- const steps = hubSpotSetupRemediation(projectRoot);
479
+ const assessment = assessHubSpotCliSetup(projectRoot, { checkAccounts: true });
480
+ const steps = hubSpotSetupRemediation(projectRoot, assessment);
387
481
  const remediation = steps.length > 0 ? steps.join(" \xB7 ") : void 0;
388
482
  return [
389
483
  {
@@ -403,13 +497,42 @@ function doctorHubSpotChecks(projectRoot) {
403
497
  {
404
498
  name: "hubspot-auth",
405
499
  ok: assessment.authenticated,
406
- message: assessment.authenticated ? "HubSpot CLI authenticated for this project" : "HubSpot CLI not authenticated (or no portal in hubspot.config.yml)",
500
+ message: assessment.authenticated ? "HubSpot CLI authenticated for this project" : assessment.accountsHint === "timed out" ? "HubSpot CLI auth check timed out \u2014 run hs accounts list manually" : "HubSpot CLI not authenticated (or no portal in hubspot.config.yml)",
407
501
  remediation: assessment.authenticated ? void 0 : "wp auth \xB7 hs accounts list",
408
502
  severity: "warn"
409
503
  }
410
504
  ];
411
505
  }
412
506
 
507
+ // src/lib/template-dev-deps.ts
508
+ var VITE_DEV_PEER_DEPS = {
509
+ vite: "^6.1.0",
510
+ "@vitejs/plugin-react": "^4.3.4",
511
+ "babel-plugin-react-compiler": "^1.0.0",
512
+ "@tailwindcss/vite": "^4.0.6",
513
+ "@tanstack/router-plugin": "^1.121.2",
514
+ "@tanstack/react-router": "^1.121.2",
515
+ "@dnd-kit/core": "^6.3.1",
516
+ "@dnd-kit/sortable": "^10.0.0",
517
+ "@dnd-kit/utilities": "^3.2.2",
518
+ react: "^19.0.0",
519
+ "react-dom": "^19.0.0",
520
+ tailwindcss: "^4.0.6"
521
+ };
522
+ var KIT_DEV_DEP = "@woodsportal/hubspot-kit";
523
+ var KIT_DEV_DEP_VERSION = "^1.0.26";
524
+ var MODULE_CONFIG_UI_DEPS = [
525
+ "@dnd-kit/core",
526
+ "@dnd-kit/sortable",
527
+ "@dnd-kit/utilities"
528
+ ];
529
+ function templateDevDependencies() {
530
+ return {
531
+ [KIT_DEV_DEP]: KIT_DEV_DEP_VERSION,
532
+ ...VITE_DEV_PEER_DEPS
533
+ };
534
+ }
535
+
413
536
  // src/cli/commands/doctor.ts
414
537
  function checkNode() {
415
538
  const major = Number(process.versions.node.split(".")[0]);
@@ -476,13 +599,12 @@ function checkEnvFiles(ctx) {
476
599
  return ctx.config.env.tiers.map((tier) => {
477
600
  const file = tier === "dev" ? ".env.dev" : tier === "stg" ? ".env.stg" : ".env.prod";
478
601
  const path = resolve5(ctx.projectRoot, file);
479
- const example = resolve5(ctx.projectRoot, `${file}.example`);
480
602
  const ok = existsSync4(path);
481
603
  return {
482
604
  name: `env-${tier}`,
483
605
  ok,
484
606
  message: ok ? `${file} present` : `${file} missing`,
485
- remediation: ok ? void 0 : existsSync4(example) ? `cp ${file}.example ${file}` : `Create ${file}`,
607
+ remediation: ok ? void 0 : "wp setup (copies from .example templates)",
486
608
  severity: "warn"
487
609
  };
488
610
  });
@@ -495,7 +617,7 @@ function checkBuildId(ctx) {
495
617
  name: "build-id",
496
618
  ok,
497
619
  message: ok ? `${ctx.config.cdn.buildIdFile} present` : `${ctx.config.cdn.buildIdFile} missing`,
498
- remediation: ok ? void 0 : `Create ${ctx.config.cdn.buildIdFile} with your release tag (e.g. v0.0.1-dev.1)`,
620
+ remediation: ok ? void 0 : "wp setup (creates default) or set your release tag (e.g. v0.0.1-dev.1)",
499
621
  severity: "warn"
500
622
  };
501
623
  }
@@ -510,6 +632,43 @@ function checkDevViteConfig(ctx) {
510
632
  severity: "error"
511
633
  };
512
634
  }
635
+ function checkDevEntry(ctx) {
636
+ const checks = [];
637
+ const indexHtml = resolve5(ctx.projectRoot, "index.html");
638
+ const hasIndex = existsSync4(indexHtml);
639
+ checks.push({
640
+ name: "index-html",
641
+ ok: hasIndex,
642
+ message: hasIndex ? "index.html present" : "index.html missing",
643
+ remediation: hasIndex ? void 0 : "Re-run wp init or copy index.html from kit templates",
644
+ severity: "error"
645
+ });
646
+ const localDev = resolve5(ctx.projectRoot, ctx.config.entries.localDev);
647
+ const hasEntry = existsSync4(localDev);
648
+ checks.push({
649
+ name: "local-dev-entry",
650
+ ok: hasEntry,
651
+ message: hasEntry ? `${ctx.config.entries.localDev} present` : `${ctx.config.entries.localDev} missing`,
652
+ remediation: hasEntry ? void 0 : "Re-run wp init or restore src/main.tsx from kit templates",
653
+ severity: "error"
654
+ });
655
+ if (hasEntry) {
656
+ try {
657
+ const source = readFileSync4(localDev, "utf8");
658
+ const usesKitNodeBootstrap = source.includes("@woodsportal/hubspot-kit/dev/bootstrap-greenfield") || source.includes("@woodsportal/hubspot-kit/dev/bootstrap");
659
+ const usesWphsShim = source.includes(".wphs/.kit-bootstrap-greenfield") || source.includes(".wphs/.kit-bootstrap") || source.includes("@wphs/bootstrap-greenfield") || source.includes("@wphs/bootstrap");
660
+ checks.push({
661
+ name: "local-dev-bootstrap-import",
662
+ ok: !usesKitNodeBootstrap || usesWphsShim,
663
+ message: usesKitNodeBootstrap ? `${ctx.config.entries.localDev} imports kit bootstrap via node_modules (breaks React JSX in dev)` : "local dev bootstrap import uses .wphs shim",
664
+ remediation: usesKitNodeBootstrap ? `Use import from '../.wphs/.kit-bootstrap-greenfield.tsx' (greenfield) or '@wphs/bootstrap' (full portal)` : void 0,
665
+ severity: "error"
666
+ });
667
+ } catch {
668
+ }
669
+ }
670
+ return checks;
671
+ }
513
672
  function checkKitScripts() {
514
673
  const script = kitPath("scripts", "build-cdn.mjs");
515
674
  const ok = existsSync4(script);
@@ -581,6 +740,28 @@ function checkAdapter(ctx) {
581
740
  severity: "warn"
582
741
  };
583
742
  }
743
+ function checkModuleConfigUiDeps(ctx) {
744
+ if (isThemeProject(ctx.config)) return [];
745
+ const pkgPath = resolve5(ctx.projectRoot, "package.json");
746
+ if (!existsSync4(pkgPath)) return [];
747
+ let deps = {};
748
+ try {
749
+ const pkg = JSON.parse(readFileSync4(pkgPath, "utf8"));
750
+ deps = { ...pkg.dependencies, ...pkg.devDependencies };
751
+ } catch {
752
+ return [];
753
+ }
754
+ return MODULE_CONFIG_UI_DEPS.map((name) => {
755
+ const ok = Boolean(deps[name]);
756
+ return {
757
+ name: `module-config-${name.replace(/[@/]/g, "-")}`,
758
+ ok,
759
+ message: ok ? `${name} installed` : `${name} missing (module-config overlay)`,
760
+ remediation: ok ? void 0 : "Run yarn install after wp init, or add @dnd-kit/core @dnd-kit/sortable @dnd-kit/utilities to devDependencies",
761
+ severity: "error"
762
+ };
763
+ });
764
+ }
584
765
  function checkViteInstalled(ctx) {
585
766
  if (isThemeProject(ctx.config) && !existsSync4(resolve5(ctx.projectRoot, "vite.config.dev.mjs"))) {
586
767
  return null;
@@ -595,29 +776,80 @@ function checkViteInstalled(ctx) {
595
776
  severity: "error"
596
777
  };
597
778
  }
598
- async function runDoctor(ctx) {
599
- if (!ctx.global.dryRun) {
600
- try {
601
- runNodeScript(kitPath("scripts/ensure-wphs-workspace.mjs"), [], {
602
- cwd: ctx.projectRoot,
603
- logger: ctx.logger,
604
- dryRun: false
605
- });
606
- } catch {
779
+ async function gatherDoctorChecks(ctx) {
780
+ const checks = [];
781
+ await runCliTasks(ctx, [
782
+ {
783
+ title: "Preparing .wphs workspace",
784
+ task: async () => {
785
+ if (ctx.global.dryRun) return;
786
+ try {
787
+ runNodeScript(kitPath("scripts", "ensure-wphs-workspace.mjs"), [], {
788
+ cwd: ctx.projectRoot,
789
+ logger: ctx.logger,
790
+ dryRun: false
791
+ });
792
+ } catch {
793
+ }
794
+ }
795
+ },
796
+ {
797
+ title: "Checking Node, Vite, and kit scripts",
798
+ task: async () => {
799
+ checks.push(checkNode(), checkKitScripts(), checkDevViteConfig(ctx), ...checkDevEntry(ctx));
800
+ const viteCheck = checkViteInstalled(ctx);
801
+ if (viteCheck) checks.push(viteCheck);
802
+ checks.push(...checkModuleConfigUiDeps(ctx));
803
+ }
804
+ },
805
+ {
806
+ title: "Checking woodscli.json and project layout",
807
+ task: async () => {
808
+ checks.push(...checkConfig(ctx), ...checkWphsWorkspace(ctx));
809
+ const adapterCheck = checkAdapter(ctx);
810
+ if (adapterCheck) checks.push(adapterCheck);
811
+ }
812
+ },
813
+ {
814
+ title: "Checking HubSpot CLI (this can take a few seconds)",
815
+ task: async () => {
816
+ checks.push(...doctorHubSpotChecks(ctx.projectRoot));
817
+ }
818
+ },
819
+ {
820
+ title: "Checking environment and CDN files",
821
+ task: async () => {
822
+ const buildIdCheck = checkBuildId(ctx);
823
+ if (buildIdCheck) checks.push(buildIdCheck);
824
+ checks.push(...checkEnvFiles(ctx));
825
+ }
826
+ }
827
+ ]);
828
+ return checks.filter((c) => c !== null);
829
+ }
830
+ function printDoctorResults(ctx, checks) {
831
+ if (ctx.global.json) return;
832
+ const interactive = isInteractiveCli(ctx);
833
+ const verbose = ctx.global.verbose;
834
+ const showPassing = !interactive || verbose;
835
+ if (interactive) {
836
+ process.stdout.write("\n");
837
+ }
838
+ for (const check of checks) {
839
+ if (check.ok && !showPassing) continue;
840
+ if (check.ok) {
841
+ ctx.logger.success(check.message);
842
+ } else if (check.severity === "warn") {
843
+ ctx.logger.warn(check.message);
844
+ if (check.remediation) ctx.logger.info(` \u2192 ${check.remediation}`);
845
+ } else {
846
+ ctx.logger.error(check.message);
847
+ if (check.remediation) ctx.logger.info(` \u2192 ${check.remediation}`);
607
848
  }
608
849
  }
609
- const checks = [
610
- checkNode(),
611
- checkKitScripts(),
612
- checkDevViteConfig(ctx),
613
- checkViteInstalled(ctx),
614
- ...checkConfig(ctx),
615
- ...checkWphsWorkspace(ctx),
616
- checkAdapter(ctx),
617
- ...doctorHubSpotChecks(ctx.projectRoot),
618
- checkBuildId(ctx),
619
- ...checkEnvFiles(ctx)
620
- ].filter((c) => c !== null);
850
+ }
851
+ async function runDoctor(ctx) {
852
+ const checks = await gatherDoctorChecks(ctx);
621
853
  const failed = checks.filter((c) => !c.ok && (c.severity ?? "error") === "error");
622
854
  const warnings = checks.filter((c) => !c.ok && c.severity === "warn");
623
855
  if (ctx.global.json) {
@@ -628,16 +860,10 @@ async function runDoctor(ctx) {
628
860
  checks
629
861
  });
630
862
  } else {
631
- for (const check of checks) {
632
- if (check.ok) {
633
- ctx.logger.success(check.message);
634
- } else if (check.severity === "warn") {
635
- ctx.logger.warn(check.message);
636
- if (check.remediation) ctx.logger.info(` \u2192 ${check.remediation}`);
637
- } else {
638
- ctx.logger.error(check.message);
639
- if (check.remediation) ctx.logger.info(` \u2192 ${check.remediation}`);
640
- }
863
+ printDoctorResults(ctx, checks);
864
+ if (!ctx.global.quiet && failed.length === 0) {
865
+ const summary = warnings.length > 0 ? `${checks.length} checks passed \xB7 ${warnings.length} warning(s)` : `${checks.length} checks passed`;
866
+ printCommandDone(ctx, summary);
641
867
  }
642
868
  }
643
869
  if (failed.length > 0) {
@@ -675,43 +901,43 @@ async function runConfigValidate(ctx) {
675
901
 
676
902
  // src/cli/commands/build.ts
677
903
  import { resolve as resolve6 } from "path";
904
+
905
+ // src/lib/build-tier.ts
906
+ var DEFAULT_BUILD_TIER = "dev";
907
+ function parseBuildTier(value) {
908
+ if (value === "dev" || value === "stg" || value === "prod") return value;
909
+ return DEFAULT_BUILD_TIER;
910
+ }
911
+
912
+ // src/cli/commands/build.ts
678
913
  function tierToStgFlag(tier) {
679
914
  return tier === "dev" || tier === "stg" ? ["--stg"] : [];
680
915
  }
681
- async function runBuild(ctx, options) {
682
- const tier = options.tier ?? "prod";
683
- const env = { ...process.env, WP_HS_PROJECT_ROOT: ctx.projectRoot };
916
+ function buildTasks(ctx, options, run) {
917
+ const tier = options.tier ?? DEFAULT_BUILD_TIER;
684
918
  const isTheme = isThemeProject(ctx.config);
685
919
  const useCdn = usesCdnPipeline(ctx.config) && !isTheme;
686
- const execOpts = {
687
- cwd: ctx.projectRoot,
688
- env,
689
- logger: ctx.logger,
690
- dryRun: ctx.global.dryRun
691
- };
692
- const run = (script, args = []) => {
693
- runNodeScript(kitPath("scripts", script), args, execOpts);
694
- };
695
- try {
696
- if (isTheme && !options.cdnOnly) {
697
- ctx.logger.info("Theme project \u2014 skipping CDN/module.js build (use wp upload for theme assets)");
698
- if (ctx.global.json) {
699
- ctx.logger.json({ command: "build", tier, ok: true, projectType: ctx.config.projectType });
700
- } else {
701
- ctx.logger.success("Theme build complete (no-op)");
920
+ const tasks2 = [];
921
+ if (isTheme && !options.cdnOnly) {
922
+ return tasks2;
923
+ }
924
+ if (ctx.config.projectType === "module-monolith" && !options.cdnOnly) {
925
+ tasks2.push({
926
+ title: `Building monolith module (${tier})`,
927
+ task: async () => {
928
+ run("run-vite-build.mjs", [
929
+ resolve6(ctx.projectRoot, "vite.config.monolith.mjs"),
930
+ ...tierToStgFlag(tier)
931
+ ]);
932
+ run("run-hubspot-module-postbuild.mjs");
702
933
  }
703
- return ExitCode.SUCCESS;
704
- }
705
- if (ctx.config.projectType === "module-monolith" && !options.cdnOnly) {
706
- ctx.logger.info(`Building monolith module (${tier})\u2026`);
707
- run("run-vite-build.mjs", [
708
- resolve6(ctx.projectRoot, "vite.config.monolith.mjs"),
709
- ...tierToStgFlag(tier)
710
- ]);
711
- run("run-hubspot-module-postbuild.mjs");
712
- } else {
713
- if (!options.hubspotOnly && useCdn) {
714
- ctx.logger.info(`Building CDN (${tier})\u2026`);
934
+ });
935
+ return tasks2;
936
+ }
937
+ if (!options.hubspotOnly && useCdn) {
938
+ tasks2.push({
939
+ title: `Building CDN (${tier})`,
940
+ task: async () => {
715
941
  if (options.cdnEsm) {
716
942
  run("run-vite-build.mjs", [
717
943
  resolve6(ctx.projectRoot, "vite.config.cdn-esm.mjs"),
@@ -722,19 +948,64 @@ async function runBuild(ctx, options) {
722
948
  }
723
949
  run("generate-cdn-manifest.mjs");
724
950
  }
725
- if (!options.cdnOnly) {
726
- ctx.logger.info(`Building HubSpot module (${tier})\u2026`);
951
+ });
952
+ }
953
+ if (!options.cdnOnly) {
954
+ tasks2.push({
955
+ title: `Building HubSpot module (${tier})`,
956
+ task: async () => {
727
957
  run("run-vite-build.mjs", [
728
958
  resolve6(ctx.projectRoot, "vite.config.hubspot.mjs"),
729
959
  ...tierToStgFlag(tier)
730
960
  ]);
731
961
  run("run-hubspot-module-postbuild.mjs");
732
962
  }
963
+ });
964
+ }
965
+ if (!options.cdnOnly && !options.hubspotOnly) {
966
+ tasks2.push({
967
+ title: "Running bundle checks",
968
+ task: async () => {
969
+ run("check-hubspot-bundle-size.mjs");
970
+ run("write-audit-artifacts.mjs");
971
+ }
972
+ });
973
+ }
974
+ return tasks2;
975
+ }
976
+ async function runBuild(ctx, options) {
977
+ const tier = options.tier ?? DEFAULT_BUILD_TIER;
978
+ const env = { ...process.env, WP_HS_PROJECT_ROOT: ctx.projectRoot };
979
+ const isTheme = isThemeProject(ctx.config);
980
+ const execOpts = {
981
+ cwd: ctx.projectRoot,
982
+ env,
983
+ logger: ctx.logger,
984
+ dryRun: ctx.global.dryRun
985
+ };
986
+ const run = (script, args = []) => {
987
+ runNodeScript(kitPath("scripts", script), args, execOpts);
988
+ };
989
+ try {
990
+ if (isTheme && !options.cdnOnly) {
991
+ if (ctx.global.json) {
992
+ ctx.logger.json({ command: "build", tier, ok: true, projectType: ctx.config.projectType });
993
+ } else if (!options.progress) {
994
+ ctx.logger.info("Theme project \u2014 skipping CDN/module.js build (use wp upload for theme assets)");
995
+ printCommandDone(ctx, "Theme build complete (no-op)");
996
+ } else {
997
+ await runCliTasks(
998
+ ctx,
999
+ [{ title: "Theme project (no module build)", task: async () => {
1000
+ } }],
1001
+ options
1002
+ );
1003
+ printCommandDone(ctx, "Theme build complete (no-op)");
1004
+ }
1005
+ return ExitCode.SUCCESS;
733
1006
  }
734
- if (!options.cdnOnly && !options.hubspotOnly) {
735
- run("check-hubspot-bundle-size.mjs");
736
- run("write-audit-artifacts.mjs");
737
- }
1007
+ const tasks2 = buildTasks(ctx, options, run);
1008
+ await runCliTasks(ctx, tasks2, options);
738
1009
  if (ctx.global.json) {
739
1010
  ctx.logger.json({
740
1011
  command: "build",
@@ -743,7 +1014,7 @@ async function runBuild(ctx, options) {
743
1014
  outDir: resolve6(ctx.projectRoot, ctx.config.module.outDir)
744
1015
  });
745
1016
  } else {
746
- ctx.logger.success("Build complete");
1017
+ printCommandDone(ctx, "Build complete");
747
1018
  }
748
1019
  return ExitCode.SUCCESS;
749
1020
  } catch (error) {
@@ -755,22 +1026,142 @@ async function runBuild(ctx, options) {
755
1026
 
756
1027
  // src/cli/commands/dev.ts
757
1028
  import { existsSync as existsSync6 } from "fs";
758
- import { resolve as resolve8 } from "path";
1029
+ import { resolve as resolve9 } from "path";
759
1030
  import { spawn } from "child_process";
760
1031
 
1032
+ // src/lib/cli-banner.ts
1033
+ import pc3 from "picocolors";
1034
+
1035
+ // src/lib/kit-version.ts
1036
+ import { readFileSync as readFileSync5 } from "fs";
1037
+ import { resolve as resolve7 } from "path";
1038
+ var cachedVersion = null;
1039
+ function readKitVersion() {
1040
+ if (cachedVersion) {
1041
+ return cachedVersion;
1042
+ }
1043
+ try {
1044
+ const pkg = JSON.parse(readFileSync5(resolve7(getKitRoot(), "package.json"), "utf8"));
1045
+ cachedVersion = pkg.version ?? "0.0.0";
1046
+ } catch {
1047
+ cachedVersion = "0.0.0";
1048
+ }
1049
+ return cachedVersion;
1050
+ }
1051
+
1052
+ // src/lib/cli-banner.ts
1053
+ var WOODSCLI_ASCII_BANNER = `
1054
+ __ __ _ _____ _ _____
1055
+ \\ \\ / / | | / ____| | |_ _|
1056
+ \\ \\ /\\ / /__ ___ __| |___| | | | | |
1057
+ \\ \\/ \\/ / _ \\ / _ \\ / _\` / __| | | | | |
1058
+ \\ /\\ / (_) | (_) | (_| \\__ \\ |____| |____ _| |_
1059
+ \\/ \\/ \\___/ \\___/ \\__,_|___/\\_____|______|_____|
1060
+ `.trimEnd();
1061
+ var RULE = "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500";
1062
+ function shouldPrintCliBanner(global) {
1063
+ if (global.json || global.quiet || global.ci || global.dryRun) {
1064
+ return false;
1065
+ }
1066
+ return process.stdout.isTTY === true;
1067
+ }
1068
+ function useCliColor() {
1069
+ if (process.env.NO_COLOR !== void 0) return false;
1070
+ if (process.env.CI === "true") return false;
1071
+ return process.stdout.isTTY === true;
1072
+ }
1073
+ function formatProjectLabel(config) {
1074
+ if (isThemeProject(config)) {
1075
+ return `${config.theme?.name ?? "theme"} \xB7 theme-starter`;
1076
+ }
1077
+ const router = config.scaffold?.router;
1078
+ const routerNote = router && router !== "hash" ? ` \xB7 router ${router}` : " \xB7 hash router";
1079
+ return `${config.module.name} \xB7 ${config.projectType}${routerNote}`;
1080
+ }
1081
+ function section(color, title, body) {
1082
+ const bold = (text2) => color ? pc3.bold(text2) : text2;
1083
+ return ` ${bold(title)}
1084
+ ${body}`;
1085
+ }
1086
+ function printWoodsCliDevBanner(options) {
1087
+ if (!shouldPrintCliBanner(options.global)) {
1088
+ return;
1089
+ }
1090
+ const color = useCliColor();
1091
+ const cyan = (text2) => color ? pc3.cyan(text2) : text2;
1092
+ const dim = (text2) => color ? pc3.dim(text2) : text2;
1093
+ const bold = (text2) => color ? pc3.bold(text2) : text2;
1094
+ const yellow = (text2) => color ? pc3.yellow(text2) : text2;
1095
+ const version = options.version ?? readKitVersion();
1096
+ const project = formatProjectLabel(options.config);
1097
+ const isTheme = isThemeProject(options.config);
1098
+ const localUrl = `http://localhost:${options.port}/`;
1099
+ const docsRoot = "node_modules/@woodsportal/hubspot-kit/docs";
1100
+ const header = [
1101
+ color ? WOODSCLI_ASCII_BANNER.split("\n").map((line) => cyan(bold(line))).join("\n") : WOODSCLI_ASCII_BANNER,
1102
+ ` ${bold("HubSpot kit")}${dim(` v${version}`)} ${yellow("\xB7 dev")}`
1103
+ ].join("\n");
1104
+ const outputLine = isTheme ? " Vite HMR + theme field overlay (Ctrl+Shift+Z)" : " Vite HMR + module-config overlay (Ctrl+Shift+Z)";
1105
+ const quickChecks = isTheme ? ` wp doctor prerequisites + HubSpot CLI
1106
+ wp dev alias: wp local
1107
+ wp upload cms theme upload` : ` wp doctor prerequisites + HubSpot CLI
1108
+ wp setup copy missing config / .env templates
1109
+ wp build module.js / CDN (tier defaults dev)
1110
+ wp upload cms module upload`;
1111
+ const tips = [
1112
+ dim(RULE),
1113
+ section(color, "Output", ` ${outputLine}`),
1114
+ "",
1115
+ section(color, "Project", ` ${project}`),
1116
+ "",
1117
+ section(color, "Preview", ` ${color ? cyan(localUrl) : localUrl}`),
1118
+ "",
1119
+ section(
1120
+ color,
1121
+ "Overlay",
1122
+ ` Ctrl+Shift+Z ${isTheme ? "theme" : "module"} fields ${dim("(Mac \u2303\u21E7Z)")}`
1123
+ ),
1124
+ "",
1125
+ section(
1126
+ color,
1127
+ "Workspace",
1128
+ ` ${dim(".wphs/")} \xB7 ${options.config.dev.fieldsJson}`
1129
+ ),
1130
+ "",
1131
+ section(color, "Quick checks", `
1132
+ ${quickChecks}`),
1133
+ "",
1134
+ section(
1135
+ color,
1136
+ "Docs",
1137
+ ` ${docsRoot}/CLI-REFERENCE.md \xB7 ${docsRoot}/MODULES.md`
1138
+ ),
1139
+ dim(RULE)
1140
+ ].join("\n");
1141
+ process.stdout.write(`
1142
+ ${header}
1143
+
1144
+ ${tips}
1145
+
1146
+ `);
1147
+ process.stdout.write(`${dim("Starting Vite dev\u2026")}
1148
+
1149
+ `);
1150
+ }
1151
+
761
1152
  // src/vite/resolve-dev-config.ts
762
1153
  import { existsSync as existsSync5 } from "fs";
763
- import { resolve as resolve7 } from "path";
1154
+ import { resolve as resolve8 } from "path";
764
1155
  var CONSUMER_DEV_CONFIG = "vite.config.dev.mjs";
765
1156
  function resolveDevViteConfigPath(projectRoot) {
766
- const consumerConfig = resolve7(projectRoot, CONSUMER_DEV_CONFIG);
1157
+ const consumerConfig = resolve8(projectRoot, CONSUMER_DEV_CONFIG);
767
1158
  if (existsSync5(consumerConfig)) {
768
1159
  return consumerConfig;
769
1160
  }
770
1161
  return kitPath("vite", "vite.config.dev.mjs");
771
1162
  }
772
1163
  function resolveProjectViteBin(projectRoot) {
773
- const localBin = resolve7(projectRoot, "node_modules", ".bin", "vite");
1164
+ const localBin = resolve8(projectRoot, "node_modules", ".bin", "vite");
774
1165
  if (existsSync5(localBin)) {
775
1166
  return localBin;
776
1167
  }
@@ -779,7 +1170,6 @@ function resolveProjectViteBin(projectRoot) {
779
1170
 
780
1171
  // src/cli/commands/dev.ts
781
1172
  async function runDev(ctx, options) {
782
- ensureHubSpotCliSetup(ctx, "local");
783
1173
  const port = options.port ?? 3e3;
784
1174
  const env = {
785
1175
  ...process.env,
@@ -797,13 +1187,18 @@ async function runDev(ctx, options) {
797
1187
  remediation: "Run yarn install or npm install. If this is an older wp init project, add vite and peers to devDependencies (run wp init again or see @woodsportal/hubspot-kit templates)."
798
1188
  });
799
1189
  }
800
- const consumerConfig = resolve8(ctx.projectRoot, "vite.config.dev.mjs");
1190
+ const consumerConfig = resolve9(ctx.projectRoot, "vite.config.dev.mjs");
801
1191
  if (!existsSync6(consumerConfig)) {
802
1192
  throw new WpHsError("vite.config.dev.mjs is missing in the project root.", {
803
1193
  exitCode: ExitCode.USER_ERROR,
804
1194
  remediation: "Add vite.config.dev.mjs (see @woodsportal/hubspot-kit templates/module-hybrid-cdn)."
805
1195
  });
806
1196
  }
1197
+ printWoodsCliDevBanner({
1198
+ config: ctx.config,
1199
+ port,
1200
+ global: ctx.global
1201
+ });
807
1202
  return new Promise((resolvePromise) => {
808
1203
  const child = spawn(
809
1204
  viteBin,
@@ -814,6 +1209,7 @@ async function runDev(ctx, options) {
814
1209
  stdio: "inherit"
815
1210
  }
816
1211
  );
1212
+ setImmediate(() => ensureHubSpotCliSetup(ctx, "local"));
817
1213
  child.on("exit", (code) => {
818
1214
  resolvePromise(code ?? ExitCode.USER_ERROR);
819
1215
  });
@@ -821,11 +1217,17 @@ async function runDev(ctx, options) {
821
1217
  }
822
1218
 
823
1219
  // src/cli/commands/deploy.ts
824
- import { resolve as resolve10 } from "path";
1220
+ import { resolve as resolve11 } from "path";
825
1221
 
826
1222
  // src/cli/commands/publish.ts
827
1223
  async function runPublish(ctx, options) {
828
- const tier = options.tier ?? "prod";
1224
+ if (!usesCdnPipeline(ctx.config)) {
1225
+ throw new WpHsError("This project does not use the CDN pipeline.", {
1226
+ exitCode: ExitCode.USER_ERROR,
1227
+ remediation: "module-monolith / cdn.mode none: use wp build && wp upload (or wp deploy without CDN publish)."
1228
+ });
1229
+ }
1230
+ const tier = options.tier ?? DEFAULT_BUILD_TIER;
829
1231
  const env = { ...process.env, WP_HS_PROJECT_ROOT: ctx.projectRoot };
830
1232
  const execOpts = {
831
1233
  cwd: ctx.projectRoot,
@@ -836,16 +1238,32 @@ async function runPublish(ctx, options) {
836
1238
  const verifyFlag = tier === "prod" ? ["--prod"] : ["--stg"];
837
1239
  const publishArgs = options.dryRun ? ["--dry-run"] : [];
838
1240
  try {
839
- if (!options.dryRun) {
840
- ctx.logger.info(`Verifying CDN API endpoint (${tier})\u2026`);
841
- runNodeScript(kitPath("scripts", "verify-cdn-api-endpoint.mjs"), verifyFlag, execOpts);
842
- }
843
- ctx.logger.info(options.dryRun ? "CDN publish dry-run\u2026" : `Publishing CDN (${tier})\u2026`);
844
- runNodeScript(kitPath("scripts", "publish-cdn-github.mjs"), publishArgs, execOpts);
1241
+ await runCliTasks(
1242
+ ctx,
1243
+ [
1244
+ {
1245
+ title: `Verifying CDN API endpoint (${tier})`,
1246
+ task: async () => {
1247
+ if (options.dryRun) return;
1248
+ runNodeScript(kitPath("scripts", "verify-cdn-api-endpoint.mjs"), verifyFlag, execOpts);
1249
+ }
1250
+ },
1251
+ {
1252
+ title: options.dryRun ? `CDN publish dry-run (${tier})` : `Publishing CDN (${tier})`,
1253
+ task: async () => {
1254
+ runNodeScript(kitPath("scripts", "publish-cdn-github.mjs"), publishArgs, execOpts);
1255
+ }
1256
+ }
1257
+ ],
1258
+ options
1259
+ );
845
1260
  if (ctx.global.json) {
846
1261
  ctx.logger.json({ command: "publish", tier, dryRun: Boolean(options.dryRun), ok: true });
847
1262
  } else {
848
- ctx.logger.success(options.dryRun ? "CDN publish dry-run complete" : "CDN publish complete");
1263
+ printCommandDone(
1264
+ ctx,
1265
+ options.dryRun ? "CDN publish dry-run complete" : "CDN publish complete"
1266
+ );
849
1267
  }
850
1268
  return ExitCode.SUCCESS;
851
1269
  } catch (error) {
@@ -857,10 +1275,32 @@ async function runPublish(ctx, options) {
857
1275
  }
858
1276
 
859
1277
  // src/cli/commands/upload.ts
860
- import { resolve as resolve9 } from "path";
1278
+ import { resolve as resolve10 } from "path";
1279
+ async function uploadModuleOrTheme(ctx) {
1280
+ if (isThemeProject(ctx.config)) {
1281
+ const theme = ctx.config.theme;
1282
+ if (!theme) {
1283
+ throw new WpHsError("theme block missing in woodscli.json", { exitCode: ExitCode.USER_ERROR });
1284
+ }
1285
+ const srcDir = resolve10(ctx.projectRoot, theme.srcDir);
1286
+ runCommand("hs", ["cms", "upload", srcDir, theme.uploadPath], {
1287
+ cwd: ctx.projectRoot,
1288
+ logger: ctx.logger,
1289
+ dryRun: ctx.global.dryRun
1290
+ });
1291
+ return `Uploaded theme ${srcDir} \u2192 ${theme.uploadPath}`;
1292
+ }
1293
+ const outDir = resolve10(ctx.projectRoot, ctx.config.module.outDir);
1294
+ const uploadPath = ctx.config.module.uploadPath;
1295
+ runCommand("hs", ["cms", "upload", outDir, uploadPath], {
1296
+ cwd: ctx.projectRoot,
1297
+ logger: ctx.logger,
1298
+ dryRun: ctx.global.dryRun
1299
+ });
1300
+ return `Uploaded ${outDir} \u2192 ${uploadPath}`;
1301
+ }
861
1302
  async function runUpload(ctx, options = {}) {
862
- ensureHubSpotCliSetup(ctx, "cms");
863
- const tier = options.tier ?? "prod";
1303
+ const tier = options.tier ?? DEFAULT_BUILD_TIER;
864
1304
  if (tier === "prod" && !ctx.global.yes && !ctx.global.ci) {
865
1305
  const confirmed = process.env.WP_HS_CONFIRM_PROD === "1";
866
1306
  if (!confirmed) {
@@ -870,39 +1310,25 @@ async function runUpload(ctx, options = {}) {
870
1310
  });
871
1311
  }
872
1312
  }
873
- if (isThemeProject(ctx.config)) {
874
- const theme = ctx.config.theme;
875
- if (!theme) {
876
- throw new WpHsError("theme block missing in woodscli.json", { exitCode: ExitCode.USER_ERROR });
877
- }
878
- const srcDir = resolve9(ctx.projectRoot, theme.srcDir);
879
- try {
880
- runCommand("hs", ["cms", "upload", srcDir, theme.uploadPath], {
881
- cwd: ctx.projectRoot,
882
- logger: ctx.logger,
883
- dryRun: ctx.global.dryRun
884
- });
885
- if (!ctx.global.json && !ctx.global.quiet) {
886
- ctx.logger.success(`Uploaded theme ${srcDir} \u2192 ${theme.uploadPath}`);
887
- }
888
- return ExitCode.SUCCESS;
889
- } catch (error) {
890
- throw new WpHsError("HubSpot theme upload failed", {
891
- exitCode: ExitCode.PUBLISH_FAILED,
892
- cause: error
893
- });
894
- }
1313
+ if (!options.skipPrereqCheck) {
1314
+ ensureHubSpotCliSetup(ctx, "cms");
895
1315
  }
896
- const outDir = resolve9(ctx.projectRoot, ctx.config.module.outDir);
897
- const uploadPath = ctx.config.module.uploadPath;
898
1316
  try {
899
- runCommand("hs", ["cms", "upload", outDir, uploadPath], {
900
- cwd: ctx.projectRoot,
901
- logger: ctx.logger,
902
- dryRun: ctx.global.dryRun
903
- });
904
- if (!ctx.global.json && !ctx.global.quiet) {
905
- ctx.logger.success(`Uploaded ${outDir} \u2192 ${uploadPath}`);
1317
+ let successMessage = "";
1318
+ await runCliTasks(
1319
+ ctx,
1320
+ [
1321
+ {
1322
+ title: isThemeProject(ctx.config) ? "Uploading theme to HubSpot" : "Uploading module to HubSpot",
1323
+ task: async () => {
1324
+ successMessage = await uploadModuleOrTheme(ctx);
1325
+ }
1326
+ }
1327
+ ],
1328
+ options
1329
+ );
1330
+ if (!ctx.global.json && !options.quiet) {
1331
+ printCommandDone(ctx, successMessage);
906
1332
  }
907
1333
  return ExitCode.SUCCESS;
908
1334
  } catch (error) {
@@ -917,7 +1343,7 @@ async function runUpload(ctx, options = {}) {
917
1343
  // src/cli/commands/deploy.ts
918
1344
  async function runDeploy(ctx, options) {
919
1345
  ensureHubSpotCliSetup(ctx, "cms");
920
- const tier = options.tier ?? "prod";
1346
+ const tier = options.tier ?? DEFAULT_BUILD_TIER;
921
1347
  const env = { ...process.env, WP_HS_PROJECT_ROOT: ctx.projectRoot };
922
1348
  const execOpts = {
923
1349
  cwd: ctx.projectRoot,
@@ -925,24 +1351,67 @@ async function runDeploy(ctx, options) {
925
1351
  logger: ctx.logger,
926
1352
  dryRun: ctx.global.dryRun
927
1353
  };
928
- await runBuild(ctx, { tier, cdnOnly: true });
929
- if (!options.skipPublish) {
930
- await runPublish(ctx, { tier });
931
- }
932
- await runBuild(ctx, { tier, hubspotOnly: true });
933
- runNodeScript(kitPath("scripts", "check-hubspot-bundle-size.mjs"), [], execOpts);
934
- runNodeScript(kitPath("scripts", "write-audit-artifacts.mjs"), [], execOpts);
935
- await runUpload(ctx, { tier });
1354
+ const useCdn = usesCdnPipeline(ctx.config) && !isThemeProject(ctx.config);
1355
+ const nested = { progress: false };
1356
+ const tasks2 = [];
1357
+ if (useCdn) {
1358
+ tasks2.push({
1359
+ title: `Building CDN (${tier})`,
1360
+ task: async () => {
1361
+ await runBuild(ctx, { tier, cdnOnly: true, ...nested });
1362
+ }
1363
+ });
1364
+ if (!options.skipPublish) {
1365
+ tasks2.push({
1366
+ title: `Publishing CDN (${tier})`,
1367
+ task: async () => {
1368
+ await runPublish(ctx, { tier, ...nested });
1369
+ }
1370
+ });
1371
+ }
1372
+ tasks2.push({
1373
+ title: `Building HubSpot module (${tier})`,
1374
+ task: async () => {
1375
+ await runBuild(ctx, { tier, hubspotOnly: true, ...nested });
1376
+ }
1377
+ });
1378
+ } else {
1379
+ tasks2.push({
1380
+ title: `Building module (${tier})`,
1381
+ task: async () => {
1382
+ await runBuild(ctx, { tier, ...nested });
1383
+ }
1384
+ });
1385
+ }
1386
+ tasks2.push(
1387
+ {
1388
+ title: "Running bundle checks",
1389
+ task: async () => {
1390
+ runNodeScript(kitPath("scripts", "check-hubspot-bundle-size.mjs"), [], execOpts);
1391
+ runNodeScript(kitPath("scripts", "write-audit-artifacts.mjs"), [], execOpts);
1392
+ }
1393
+ },
1394
+ {
1395
+ title: "Uploading to HubSpot",
1396
+ task: async () => {
1397
+ await runUpload(ctx, { tier, ...nested, quiet: true, skipPrereqCheck: true });
1398
+ }
1399
+ }
1400
+ );
1401
+ await runCliTasks(ctx, tasks2, options);
936
1402
  if (!ctx.global.json) {
937
- ctx.logger.success(`Deploy complete (${resolve10(ctx.projectRoot, ctx.config.module.outDir)} \u2192 ${ctx.config.module.uploadPath})`);
1403
+ printCommandDone(
1404
+ ctx,
1405
+ `Deploy complete (${resolve11(ctx.projectRoot, ctx.config.module.outDir)} \u2192 ${ctx.config.module.uploadPath})`
1406
+ );
938
1407
  }
939
1408
  return ExitCode.SUCCESS;
940
1409
  }
941
1410
 
942
1411
  // src/cli/commands/audit.ts
943
1412
  import { createHash } from "crypto";
944
- import { cpSync, existsSync as existsSync7, mkdirSync, readFileSync as readFileSync4, readdirSync, rmSync, statSync } from "fs";
945
- import { resolve as resolve11, join as join2 } from "path";
1413
+ import { cpSync, existsSync as existsSync7, mkdirSync, readFileSync as readFileSync6, readdirSync, rmSync, statSync } from "fs";
1414
+ import { resolve as resolve12, join as join2 } from "path";
946
1415
  var FORBIDDEN_SHIP_TOKENS = [
947
1416
  "ModuleConfigOverlay",
948
1417
  "module-config-store",
@@ -951,7 +1420,7 @@ var FORBIDDEN_SHIP_TOKENS = [
951
1420
  "dev.module-config"
952
1421
  ];
953
1422
  function hashFile(path) {
954
- return createHash("sha256").update(readFileSync4(path)).digest("hex");
1423
+ return createHash("sha256").update(readFileSync6(path)).digest("hex");
955
1424
  }
956
1425
  function walkFiles(dir) {
957
1426
  if (!existsSync7(dir)) return [];
@@ -970,14 +1439,14 @@ function walkFiles(dir) {
970
1439
  async function runAuditShipBundle(ctx) {
971
1440
  const root = ctx.projectRoot;
972
1441
  const targets = [
973
- resolve11(root, "dist/cdn"),
974
- resolve11(root, ctx.config.module.outDir)
1442
+ resolve12(root, "dist/cdn"),
1443
+ resolve12(root, ctx.config.module.outDir)
975
1444
  ];
976
1445
  const violations = [];
977
1446
  for (const dir of targets) {
978
1447
  for (const file of walkFiles(dir)) {
979
1448
  if (!/\.(js|css|html)$/i.test(file)) continue;
980
- const content = readFileSync4(file, "utf8");
1449
+ const content = readFileSync6(file, "utf8");
981
1450
  for (const token of FORBIDDEN_SHIP_TOKENS) {
982
1451
  if (content.includes(token)) {
983
1452
  violations.push(`${file}: contains forbidden token "${token}"`);
@@ -1004,8 +1473,8 @@ async function runAuditBaselineCapture(ctx, options) {
1004
1473
  const root = ctx.projectRoot;
1005
1474
  const baseline = options.baselineDir ?? defaultBaselineDir(root, ctx.config);
1006
1475
  const sources = [
1007
- { label: "cdn", path: resolve11(root, "dist/cdn") },
1008
- { label: "hubspot", path: resolve11(root, ctx.config.module.outDir) }
1476
+ { label: "cdn", path: resolve12(root, "dist/cdn") },
1477
+ { label: "hubspot", path: resolve12(root, ctx.config.module.outDir) }
1009
1478
  ];
1010
1479
  if (ctx.global.dryRun) {
1011
1480
  ctx.logger.info(`[dry-run] Would capture baseline to ${baseline}`);
@@ -1018,7 +1487,7 @@ async function runAuditBaselineCapture(ctx, options) {
1018
1487
  remediation: "Run wp build before wp audit baseline --capture."
1019
1488
  });
1020
1489
  }
1021
- const target = resolve11(baseline, label);
1490
+ const target = resolve12(baseline, label);
1022
1491
  rmSync(target, { recursive: true, force: true });
1023
1492
  mkdirSync(baseline, { recursive: true });
1024
1493
  cpSync(path, target, { recursive: true });
@@ -1034,12 +1503,12 @@ async function runAuditParity(ctx, options) {
1034
1503
  const root = ctx.projectRoot;
1035
1504
  const baseline = options.baselineDir ?? defaultBaselineDir(root, ctx.config);
1036
1505
  const currentDirs = [
1037
- { label: "cdn", path: resolve11(root, "dist/cdn") },
1038
- { label: "hubspot", path: resolve11(root, ctx.config.module.outDir) }
1506
+ { label: "cdn", path: resolve12(root, "dist/cdn") },
1507
+ { label: "hubspot", path: resolve12(root, ctx.config.module.outDir) }
1039
1508
  ];
1040
1509
  const mismatches = [];
1041
1510
  for (const { label, path } of currentDirs) {
1042
- const baselinePath = resolve11(baseline, label);
1511
+ const baselinePath = resolve12(baseline, label);
1043
1512
  if (!existsSync7(baselinePath)) {
1044
1513
  ctx.logger.warn(`No baseline at ${baselinePath} \u2014 skipping ${label} parity`);
1045
1514
  continue;
@@ -1055,7 +1524,7 @@ async function runAuditParity(ctx, options) {
1055
1524
  }
1056
1525
  for (const file of baselineFiles) {
1057
1526
  const rel = file.replace(baselinePath, "");
1058
- const current = resolve11(path, rel.slice(1));
1527
+ const current = resolve12(path, rel.slice(1));
1059
1528
  if (!existsSync7(current)) continue;
1060
1529
  const bh = hashFile(file);
1061
1530
  const ch = hashFile(current);
@@ -1083,13 +1552,13 @@ var SECRET_PATTERNS = [
1083
1552
  ];
1084
1553
  async function runAuditSecrets(ctx) {
1085
1554
  const root = ctx.projectRoot;
1086
- const scanRoots = ["src", "public", ".wphs/config"].map((d) => resolve11(root, d));
1555
+ const scanRoots = ["src", "public", ".wphs/config"].map((d) => resolve12(root, d));
1087
1556
  const findings = [];
1088
1557
  for (const dir of scanRoots) {
1089
1558
  for (const file of walkFiles(dir)) {
1090
1559
  if (!/\.(ts|tsx|js|jsx|json|env|yml|yaml|html)$/i.test(file)) continue;
1091
1560
  if (file.includes("node_modules")) continue;
1092
- const content = readFileSync4(file, "utf8");
1561
+ const content = readFileSync6(file, "utf8");
1093
1562
  for (const pattern of SECRET_PATTERNS) {
1094
1563
  pattern.lastIndex = 0;
1095
1564
  if (pattern.test(content)) {
@@ -1115,29 +1584,340 @@ async function runAuditSecrets(ctx) {
1115
1584
  }
1116
1585
 
1117
1586
  // src/cli/commands/init.ts
1118
- import { cpSync as cpSync2, existsSync as existsSync8, mkdirSync as mkdirSync2, readdirSync as readdirSync2, readFileSync as readFileSync5, writeFileSync } from "fs";
1119
- import { basename, resolve as resolve12 } from "path";
1120
- import * as p from "@clack/prompts";
1587
+ import { cpSync as cpSync2, existsSync as existsSync10, mkdirSync as mkdirSync3, readdirSync as readdirSync2, readFileSync as readFileSync8, writeFileSync as writeFileSync3 } from "fs";
1588
+ import { basename as basename2, resolve as resolve15 } from "path";
1589
+ import * as p2 from "@clack/prompts";
1121
1590
 
1122
- // src/lib/template-dev-deps.ts
1123
- var VITE_DEV_PEER_DEPS = {
1124
- vite: "^6.1.0",
1125
- "@vitejs/plugin-react": "^4.3.4",
1126
- "babel-plugin-react-compiler": "^1.0.0",
1127
- "@tailwindcss/vite": "^4.0.6",
1128
- "@tanstack/router-plugin": "^1.121.2",
1129
- react: "^19.0.0",
1130
- "react-dom": "^19.0.0",
1131
- tailwindcss: "^4.0.6"
1132
- };
1133
- var KIT_DEV_DEP = "@woodsportal/hubspot-kit";
1134
- var KIT_DEV_DEP_VERSION = "^1.0.4";
1135
- function templateDevDependencies() {
1591
+ // src/lib/init-router-scaffold.ts
1592
+ import { existsSync as existsSync8, mkdirSync as mkdirSync2, readFileSync as readFileSync7, rmSync as rmSync2, writeFileSync } from "fs";
1593
+ import { resolve as resolve13 } from "path";
1594
+ var ROUTER_FILES = [
1595
+ "src/routes",
1596
+ "src/routeTree.gen.ts",
1597
+ "src/integrations/tanstack-router",
1598
+ "src/config/routes.ts",
1599
+ "src/config/router.ts",
1600
+ "src/app.tsx"
1601
+ ];
1602
+ var TANSTACK_DEPS = ["@tanstack/react-router", "@tanstack/router-plugin"];
1603
+ function isModuleTemplate(template) {
1604
+ return template === "module-hybrid-cdn" || template === "module-monolith";
1605
+ }
1606
+ function writeRouterConfig(targetDir, mode) {
1607
+ const configDir = resolve13(targetDir, "src/config");
1608
+ mkdirSync2(configDir, { recursive: true });
1609
+ writeFileSync(
1610
+ resolve13(configDir, "router.ts"),
1611
+ `/** Router history \u2014 set by \`wp init\`. Hash matches woodsportal-client-frontend (HubSpot CMS embeds). */
1612
+ export type RouterHistoryMode = 'hash' | 'browser'
1613
+
1614
+ export const ROUTER_HISTORY: RouterHistoryMode = '${mode}'
1615
+ `
1616
+ );
1617
+ }
1618
+ function writeMountDevPreviewRouter(targetDir) {
1619
+ writeFileSync(
1620
+ resolve13(targetDir, "src/portal/mount-dev-preview.tsx"),
1621
+ `import { mountGreenfieldDev } from '../../.wphs/.kit-bootstrap-greenfield.tsx'
1622
+
1623
+ import { App } from '@/app'
1624
+
1625
+ /** Local dev: module-config overlay + routed starter preview. */
1626
+ export async function mountDevPreview() {
1627
+ await mountGreenfieldDev({ preview: <App /> })
1628
+ }
1629
+ `
1630
+ );
1631
+ }
1632
+ function writeMountDevPreviewSinglePage(targetDir) {
1633
+ writeFileSync(
1634
+ resolve13(targetDir, "src/portal/mount-dev-preview.tsx"),
1635
+ `import { mountGreenfieldDev } from '../../.wphs/.kit-bootstrap-greenfield.tsx'
1636
+
1637
+ import { StarterSinglePage } from '@/features/starter'
1638
+
1639
+ /** Local dev: module-config overlay + single-page starter (no TanStack Router). */
1640
+ export async function mountDevPreview() {
1641
+ await mountGreenfieldDev({ preview: <StarterSinglePage /> })
1642
+ }
1643
+ `
1644
+ );
1645
+ }
1646
+ function writeMountPortalRouter(targetDir) {
1647
+ writeFileSync(
1648
+ resolve13(targetDir, "src/portal/mount-portal.tsx"),
1649
+ `import ReactDOM from 'react-dom/client'
1650
+
1651
+ import { App } from '@/app'
1652
+
1653
+ /** Production mount \u2014 same App shell as woodsportal-client-frontend CDN app chunk. */
1654
+ export async function mountPortal() {
1655
+ const root = document.getElementById('app')
1656
+ if (!root) {
1657
+ return
1658
+ }
1659
+ ReactDOM.createRoot(root).render(<App />)
1660
+ }
1661
+ `
1662
+ );
1663
+ }
1664
+ function writeMountPortalSinglePage(targetDir) {
1665
+ writeFileSync(
1666
+ resolve13(targetDir, "src/portal/mount-portal.tsx"),
1667
+ `import ReactDOM from 'react-dom/client'
1668
+
1669
+ import { StarterSinglePage } from '@/features/starter'
1670
+
1671
+ /** Production mount \u2014 single-page starter when router scaffold is \`none\`. */
1672
+ export async function mountPortal() {
1673
+ const root = document.getElementById('app')
1674
+ if (!root) {
1675
+ return
1676
+ }
1677
+ ReactDOM.createRoot(root).render(<StarterSinglePage />)
1678
+ }
1679
+ `
1680
+ );
1681
+ }
1682
+ function writeStarterSinglePage(targetDir) {
1683
+ const file = resolve13(targetDir, "src/features/starter/components/starter-single-page.tsx");
1684
+ if (existsSync8(file)) {
1685
+ return;
1686
+ }
1687
+ writeFileSync(
1688
+ file,
1689
+ `import { StarterShell } from '@/components/layout/starter-shell'
1690
+
1691
+ import { StarterGuidePage } from './starter-guide-page'
1692
+ import { StarterHome } from './starter-home'
1693
+
1694
+ /** Single-page starter when \`wp init --router none\` (no TanStack Router). */
1695
+ export function StarterSinglePage() {
1696
+ return (
1697
+ <StarterShell showNav={false}>
1698
+ <StarterHome />
1699
+ <div className="mx-auto max-w-4xl px-6 sm:px-10">
1700
+ <hr className="my-10 border-[#cbd6e2]" />
1701
+ </div>
1702
+ <StarterGuidePage />
1703
+ </StarterShell>
1704
+ )
1705
+ }
1706
+ `
1707
+ );
1708
+ }
1709
+ function removePath(targetDir, relativePath) {
1710
+ const full = resolve13(targetDir, relativePath);
1711
+ if (!existsSync8(full)) {
1712
+ return;
1713
+ }
1714
+ rmSync2(full, { recursive: true, force: true });
1715
+ }
1716
+ function stripTanStackFromPackage(targetDir) {
1717
+ const pkgPath = resolve13(targetDir, "package.json");
1718
+ if (!existsSync8(pkgPath)) {
1719
+ return;
1720
+ }
1721
+ const pkg = JSON.parse(readFileSync7(pkgPath, "utf8"));
1722
+ if (!pkg.devDependencies) {
1723
+ return;
1724
+ }
1725
+ for (const dep of TANSTACK_DEPS) {
1726
+ delete pkg.devDependencies[dep];
1727
+ }
1728
+ writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}
1729
+ `);
1730
+ }
1731
+ function applyRouterScaffold(targetDir, mode, template) {
1732
+ if (!isModuleTemplate(template)) {
1733
+ return;
1734
+ }
1735
+ if (mode === "none") {
1736
+ for (const relativePath of ROUTER_FILES) {
1737
+ removePath(targetDir, relativePath);
1738
+ }
1739
+ writeStarterSinglePage(targetDir);
1740
+ writeMountDevPreviewSinglePage(targetDir);
1741
+ writeMountPortalSinglePage(targetDir);
1742
+ stripTanStackFromPackage(targetDir);
1743
+ return;
1744
+ }
1745
+ writeRouterConfig(targetDir, mode);
1746
+ writeMountDevPreviewRouter(targetDir);
1747
+ writeMountPortalRouter(targetDir);
1748
+ const featuresIndex = resolve13(targetDir, "src/features/starter/index.ts");
1749
+ if (existsSync8(featuresIndex)) {
1750
+ let content = readFileSync7(featuresIndex, "utf8");
1751
+ if (!content.includes("StarterSinglePage")) {
1752
+ content = content.replace(
1753
+ "export type { StarterPreviewValues } from './hooks/use-starter-preview'\n",
1754
+ "export type { StarterPreviewValues } from './hooks/use-starter-preview'\nexport { StarterSinglePage } from './components/starter-single-page'\n"
1755
+ );
1756
+ writeFileSync(featuresIndex, content);
1757
+ }
1758
+ }
1759
+ }
1760
+
1761
+ // src/lib/project-setup.ts
1762
+ import { copyFileSync, existsSync as existsSync9, writeFileSync as writeFileSync2 } from "fs";
1763
+ import { basename, resolve as resolve14 } from "path";
1764
+ function envFileForTier(tier) {
1765
+ return tier === "dev" ? ".env.dev" : tier === "stg" ? ".env.stg" : ".env.prod";
1766
+ }
1767
+ function tiersToSetup(config, tier) {
1768
+ if (tier) return [tier];
1769
+ return config.env.tiers;
1770
+ }
1771
+ function firstExisting(paths) {
1772
+ for (const path of paths) {
1773
+ if (existsSync9(path)) return path;
1774
+ }
1775
+ return null;
1776
+ }
1777
+ function copyMissingFile(projectRoot, targetName, sourceCandidates, dryRun) {
1778
+ const target = resolve14(projectRoot, targetName);
1779
+ if (existsSync9(target)) {
1780
+ return { action: "skipped", path: targetName, detail: "already present" };
1781
+ }
1782
+ const source = firstExisting(sourceCandidates);
1783
+ if (!source) {
1784
+ return {
1785
+ action: "manual",
1786
+ path: targetName,
1787
+ detail: `no template found \u2014 create ${targetName} manually`
1788
+ };
1789
+ }
1790
+ if (!dryRun) {
1791
+ copyFileSync(source, target);
1792
+ }
1793
+ const from = basename(source);
1136
1794
  return {
1137
- [KIT_DEV_DEP]: KIT_DEV_DEP_VERSION,
1138
- ...VITE_DEV_PEER_DEPS
1795
+ action: "copied",
1796
+ path: targetName,
1797
+ detail: dryRun ? `[dry-run] would copy ${from} \u2192 ${targetName}` : `copied ${from} \u2192 ${targetName}`
1798
+ };
1799
+ }
1800
+ function writeMissingFile(projectRoot, targetName, content, dryRun) {
1801
+ const target = resolve14(projectRoot, targetName);
1802
+ if (existsSync9(target)) {
1803
+ return { action: "skipped", path: targetName, detail: "already present" };
1804
+ }
1805
+ if (!dryRun) {
1806
+ writeFileSync2(target, content);
1807
+ }
1808
+ return {
1809
+ action: "copied",
1810
+ path: targetName,
1811
+ detail: dryRun ? `[dry-run] would create ${targetName}` : `created ${targetName}`
1139
1812
  };
1140
1813
  }
1814
+ function templateViteFiles(config) {
1815
+ if (isThemeProject(config)) return ["vite.config.dev.mjs"];
1816
+ if (config.projectType === "module-monolith") {
1817
+ return ["vite.config.dev.mjs", "vite.config.monolith.mjs"];
1818
+ }
1819
+ return ["vite.config.dev.mjs", "vite.config.hubspot.mjs", "vite.config.monolith.mjs"];
1820
+ }
1821
+ function kitTemplateDir(config) {
1822
+ return kitPath("templates", config.projectType);
1823
+ }
1824
+ function applyProjectSetup(ctx, options = {}) {
1825
+ const { projectRoot, config } = ctx;
1826
+ const dryRun = options.dryRun ?? false;
1827
+ const results = [];
1828
+ const sharedDir = kitPath("templates", "shared");
1829
+ const templateDir = kitTemplateDir(config);
1830
+ results.push(
1831
+ copyMissingFile(projectRoot, HUBSPOT_CONFIG_FILE, [
1832
+ resolve14(projectRoot, HUBSPOT_CONFIG_EXAMPLE),
1833
+ resolve14(templateDir, HUBSPOT_CONFIG_EXAMPLE),
1834
+ kitPath("templates", HUBSPOT_CONFIG_EXAMPLE)
1835
+ ], dryRun)
1836
+ );
1837
+ for (const tier of tiersToSetup(config, options.tier)) {
1838
+ const envFile = envFileForTier(tier);
1839
+ const envExample = `${envFile}.example`;
1840
+ results.push(
1841
+ copyMissingFile(projectRoot, envFile, [
1842
+ resolve14(projectRoot, envExample),
1843
+ resolve14(sharedDir, envExample)
1844
+ ], dryRun)
1845
+ );
1846
+ }
1847
+ if (usesCdnPipeline(config) && !isThemeProject(config)) {
1848
+ const mirror = config.cdn.mirror;
1849
+ results.push(
1850
+ copyMissingFile(projectRoot, mirror, [
1851
+ resolve14(projectRoot, `${mirror}.example`),
1852
+ resolve14(sharedDir, "portal.cdn.json.example")
1853
+ ], dryRun)
1854
+ );
1855
+ const buildIdFile = config.cdn.buildIdFile;
1856
+ results.push(
1857
+ writeMissingFile(projectRoot, buildIdFile, "v0.0.1-dev.1\n", dryRun)
1858
+ );
1859
+ }
1860
+ for (const file of templateViteFiles(config)) {
1861
+ results.push(
1862
+ copyMissingFile(projectRoot, file, [resolve14(templateDir, file)], dryRun)
1863
+ );
1864
+ }
1865
+ if (isModuleProject(config) && !isThemeProject(config)) {
1866
+ const adapter = config.dev.adapter;
1867
+ const adapterPath = resolve14(projectRoot, adapter);
1868
+ if (!existsSync9(adapterPath)) {
1869
+ const templateAdapter = resolve14(templateDir, "wphs.adapter.ts");
1870
+ if (existsSync9(templateAdapter)) {
1871
+ results.push(copyMissingFile(projectRoot, adapter, [templateAdapter], dryRun));
1872
+ } else {
1873
+ results.push({
1874
+ action: "manual",
1875
+ path: adapter,
1876
+ detail: "create wphs.adapter.ts (see docs/ADAPTER.md)"
1877
+ });
1878
+ }
1879
+ } else {
1880
+ results.push({ action: "skipped", path: adapter, detail: "already present" });
1881
+ }
1882
+ }
1883
+ return results;
1884
+ }
1885
+ function copySharedSetupExamples(targetDir) {
1886
+ const sharedDir = kitPath("templates", "shared");
1887
+ if (!existsSync9(sharedDir)) return;
1888
+ for (const name of [
1889
+ ".env.dev.example",
1890
+ ".env.stg.example",
1891
+ ".env.prod.example",
1892
+ "portal.cdn.json.example"
1893
+ ]) {
1894
+ const src = resolve14(sharedDir, name);
1895
+ const dest = resolve14(targetDir, name);
1896
+ if (existsSync9(src) && !existsSync9(dest)) {
1897
+ copyFileSync(src, dest);
1898
+ }
1899
+ }
1900
+ const hubspotExample = firstExisting([
1901
+ resolve14(sharedDir, HUBSPOT_CONFIG_EXAMPLE),
1902
+ kitPath("templates", HUBSPOT_CONFIG_EXAMPLE)
1903
+ ]);
1904
+ if (!hubspotExample) return;
1905
+ const destHubspot = resolve14(targetDir, HUBSPOT_CONFIG_EXAMPLE);
1906
+ if (!existsSync9(destHubspot) && !existsSync9(resolve14(targetDir, HUBSPOT_CONFIG_FILE))) {
1907
+ copyFileSync(hubspotExample, destHubspot);
1908
+ }
1909
+ }
1910
+ function formatSetupHint(results) {
1911
+ const hubspot = results.find((r) => r.path === HUBSPOT_CONFIG_FILE && r.action === "copied");
1912
+ if (hubspot) {
1913
+ return `Edit ${HUBSPOT_CONFIG_FILE} \u2014 set portalId and personalAccessKey (https://app.hubspot.com/l/personal-access-key)`;
1914
+ }
1915
+ const envCopied = results.some((r) => r.path.startsWith(".env.") && r.action === "copied");
1916
+ if (envCopied) {
1917
+ return "Edit .env.* files \u2014 set VITE_PUBLIC_REST_API_ENDPOINT and portal IDs for your environment";
1918
+ }
1919
+ return void 0;
1920
+ }
1141
1921
 
1142
1922
  // src/lib/project-name.ts
1143
1923
  var PROJECT_NAME_PATTERN = /^[a-z][a-z0-9-]*$/;
@@ -1171,7 +1951,11 @@ var TEMPLATE_SCRIPTS = {
1171
1951
  "module-monolith": {
1172
1952
  dev: "wp dev",
1173
1953
  local: "wp dev",
1174
- "build:prod": "wp build --tier prod --hubspot-only",
1954
+ "build:dev": "wp build --tier dev",
1955
+ "build:prod": "wp build --tier prod",
1956
+ "deploy:dev": "wp deploy --tier dev --yes",
1957
+ "deploy:prod": "wp deploy --tier prod --yes",
1958
+ watch: "wp watch --rebuild",
1175
1959
  doctor: "wp doctor"
1176
1960
  },
1177
1961
  "theme-starter": {
@@ -1180,7 +1964,7 @@ var TEMPLATE_SCRIPTS = {
1180
1964
  doctor: "wp doctor"
1181
1965
  }
1182
1966
  };
1183
- function buildConfigForProject(template, projectName) {
1967
+ function buildConfigForProject(template, projectName, router) {
1184
1968
  const base = { ...DEFAULT_WOODSPORTAL_HS, projectType: template };
1185
1969
  if (template === "theme-starter") {
1186
1970
  return {
@@ -1193,7 +1977,7 @@ function buildConfigForProject(template, projectName) {
1193
1977
  }
1194
1978
  };
1195
1979
  }
1196
- return {
1980
+ const config = {
1197
1981
  ...base,
1198
1982
  module: {
1199
1983
  name: projectName,
@@ -1205,9 +1989,13 @@ function buildConfigForProject(template, projectName) {
1205
1989
  mode: template === "module-monolith" ? "none" : base.cdn.mode
1206
1990
  }
1207
1991
  };
1992
+ if (isModuleTemplate(template) && router) {
1993
+ config.scaffold = { router };
1994
+ }
1995
+ return config;
1208
1996
  }
1209
1997
  function assertTargetDirIsScaffoldable(targetDir) {
1210
- if (!existsSync8(targetDir)) return;
1998
+ if (!existsSync10(targetDir)) return;
1211
1999
  const entries = readdirSync2(targetDir).filter((entry) => entry !== ".DS_Store");
1212
2000
  if (entries.length > 0) {
1213
2001
  throw new WpHsError(`Target directory is not empty: ${targetDir}`, {
@@ -1218,7 +2006,7 @@ function assertTargetDirIsScaffoldable(targetDir) {
1218
2006
  }
1219
2007
  function resolveInitTargetDir(options, interactiveName) {
1220
2008
  if (options.directory) {
1221
- return resolve12(options.directory);
2009
+ return resolve15(options.directory);
1222
2010
  }
1223
2011
  const parentDir = process.cwd();
1224
2012
  const slug = options.name ?? interactiveName;
@@ -1232,15 +2020,16 @@ function resolveInitTargetDir(options, interactiveName) {
1232
2020
  if (error) {
1233
2021
  throw new WpHsError(error, { exitCode: ExitCode.USER_ERROR });
1234
2022
  }
1235
- return resolve12(parentDir, slug);
2023
+ return resolve15(parentDir, slug);
1236
2024
  }
1237
2025
  async function runInit(ctx, options) {
1238
2026
  const nonInteractive = ctx.global.ci || ctx.global.yes || ctx.global.json;
1239
2027
  let template = options.template;
1240
2028
  let projectName = options.name ? normalizeProjectName(options.name) : void 0;
2029
+ let router = options.router;
1241
2030
  if (!nonInteractive) {
1242
2031
  if (!template) {
1243
- const type = await p.select({
2032
+ const type = await p2.select({
1244
2033
  message: "Project type",
1245
2034
  options: [
1246
2035
  {
@@ -1252,58 +2041,92 @@ async function runInit(ctx, options) {
1252
2041
  { value: "theme-starter", label: "Theme starter", hint: "CMS theme templates" }
1253
2042
  ]
1254
2043
  });
1255
- if (p.isCancel(type)) throw new WpHsError("Init cancelled", { exitCode: ExitCode.USER_ERROR });
2044
+ if (p2.isCancel(type)) throw new WpHsError("Init cancelled", { exitCode: ExitCode.USER_ERROR });
1256
2045
  template = type;
1257
2046
  }
1258
2047
  if (!options.directory && !projectName) {
1259
- const nameAnswer = await p.text({
2048
+ const nameAnswer = await p2.text({
1260
2049
  message: "Project name (folder name)",
1261
2050
  placeholder: "my-portal-module",
1262
2051
  validate: (value) => validateProjectName(normalizeProjectName(value ?? ""))
1263
2052
  });
1264
- if (p.isCancel(nameAnswer)) {
2053
+ if (p2.isCancel(nameAnswer)) {
1265
2054
  throw new WpHsError("Init cancelled", { exitCode: ExitCode.USER_ERROR });
1266
2055
  }
1267
2056
  projectName = normalizeProjectName(nameAnswer);
1268
2057
  }
2058
+ if (!router && template && isModuleTemplate(template)) {
2059
+ const routerAnswer = await p2.select({
2060
+ message: "Client-side routing",
2061
+ initialValue: "hash",
2062
+ options: [
2063
+ {
2064
+ value: "hash",
2065
+ label: "Hash history (recommended)",
2066
+ hint: "Same as woodsportal-client-frontend \u2014 works in HubSpot CMS embeds"
2067
+ },
2068
+ {
2069
+ value: "browser",
2070
+ label: "Browser history",
2071
+ hint: "Clean /guide URLs for local dev (not for HubSpot iframe embeds)"
2072
+ },
2073
+ {
2074
+ value: "none",
2075
+ label: "No router",
2076
+ hint: "Single-page starter \u2014 add TanStack Router later"
2077
+ }
2078
+ ]
2079
+ });
2080
+ if (p2.isCancel(routerAnswer)) {
2081
+ throw new WpHsError("Init cancelled", { exitCode: ExitCode.USER_ERROR });
2082
+ }
2083
+ router = routerAnswer;
2084
+ }
1269
2085
  }
1270
2086
  if (!template) {
1271
2087
  template = "module-hybrid-cdn";
1272
2088
  }
2089
+ if (!router && isModuleTemplate(template)) {
2090
+ router = "hash";
2091
+ }
1273
2092
  const targetDir = resolveInitTargetDir(options, projectName);
1274
- const slug = projectName ?? basename(targetDir);
2093
+ const slug = projectName ?? basename2(targetDir);
1275
2094
  assertTargetDirIsScaffoldable(targetDir);
1276
2095
  const templateDir = kitPath("templates", template);
1277
- if (!existsSync8(templateDir)) {
2096
+ if (!existsSync10(templateDir)) {
1278
2097
  throw new WpHsError(`Template not found: ${template}`, { exitCode: ExitCode.USER_ERROR });
1279
2098
  }
1280
2099
  if (ctx.global.dryRun) {
1281
2100
  ctx.logger.info(`[dry-run] Would scaffold ${template} in ${targetDir}`);
1282
2101
  return ExitCode.SUCCESS;
1283
2102
  }
1284
- mkdirSync2(targetDir, { recursive: true });
2103
+ mkdirSync3(targetDir, { recursive: true });
1285
2104
  cpSync2(templateDir, targetDir, { recursive: true });
1286
- const config = buildConfigForProject(template, slug);
1287
- writeFileSync(resolve12(targetDir, WOODSCLI_CONFIG_FILE), `${JSON.stringify(config, null, 2)}
2105
+ copySharedSetupExamples(targetDir);
2106
+ const config = buildConfigForProject(template, slug, router);
2107
+ writeFileSync3(resolve15(targetDir, WOODSCLI_CONFIG_FILE), `${JSON.stringify(config, null, 2)}
1288
2108
  `);
1289
- const pkgPath = resolve12(targetDir, "package.json");
1290
- if (existsSync8(pkgPath)) {
1291
- const pkg = JSON.parse(readFileSync5(pkgPath, "utf8"));
2109
+ const pkgPath = resolve15(targetDir, "package.json");
2110
+ if (existsSync10(pkgPath)) {
2111
+ const pkg = JSON.parse(readFileSync8(pkgPath, "utf8"));
1292
2112
  pkg.name = slug;
1293
2113
  pkg.scripts = { ...pkg.scripts, ...TEMPLATE_SCRIPTS[template] };
1294
2114
  pkg.devDependencies = {
1295
2115
  ...pkg.devDependencies,
1296
2116
  ...templateDevDependencies()
1297
2117
  };
1298
- writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}
2118
+ writeFileSync3(pkgPath, `${JSON.stringify(pkg, null, 2)}
1299
2119
  `);
1300
2120
  }
2121
+ if (router && isModuleTemplate(template)) {
2122
+ applyRouterScaffold(targetDir, router, template);
2123
+ }
1301
2124
  runNodeScript(kitPath("scripts/ensure-wphs-workspace.mjs"), [], {
1302
2125
  cwd: targetDir,
1303
2126
  logger: ctx.logger,
1304
2127
  dryRun: false
1305
2128
  });
1306
- const gitignorePath = resolve12(targetDir, ".gitignore");
2129
+ const gitignorePath = resolve15(targetDir, ".gitignore");
1307
2130
  const gitignoreExtras = [
1308
2131
  "hubspot.config.yml",
1309
2132
  ".wphs/config/module-config.local.json",
@@ -1313,18 +2136,19 @@ async function runInit(ctx, options) {
1313
2136
  ".wphs/state/",
1314
2137
  ".wphs/fixtures/hubspot-data.json",
1315
2138
  ".wphs/.kit-module-config",
1316
- ".wphs/.kit-bootstrap.tsx"
2139
+ ".wphs/.kit-bootstrap.tsx",
2140
+ ".wphs/.kit-bootstrap-greenfield.tsx"
1317
2141
  ];
1318
- const existing = existsSync8(gitignorePath) ? readFileSync5(gitignorePath, "utf8") : "";
2142
+ const existing = existsSync10(gitignorePath) ? readFileSync8(gitignorePath, "utf8") : "";
1319
2143
  const toAppend = gitignoreExtras.filter((line) => !existing.includes(line)).join("\n");
1320
2144
  if (toAppend) {
1321
- writeFileSync(gitignorePath, `${existing.trimEnd()}
2145
+ writeFileSync3(gitignorePath, `${existing.trimEnd()}
1322
2146
  ${toAppend}
1323
2147
  `);
1324
2148
  }
1325
2149
  if (!nonInteractive) {
1326
- p.log.success(`Scaffolded ${template} in ${targetDir}`);
1327
- p.note(`cd ${slug} && yarn install && wp doctor && wp dev`, "Getting started");
2150
+ p2.log.success(`Scaffolded ${template} in ${targetDir}`);
2151
+ p2.note(`cd ${slug} && yarn install && wp setup && wp doctor && wp dev`, "Getting started");
1328
2152
  } else {
1329
2153
  ctx.logger.success(`Scaffolded ${template} in ${targetDir}`);
1330
2154
  }
@@ -1332,10 +2156,10 @@ ${toAppend}
1332
2156
  }
1333
2157
 
1334
2158
  // src/cli/commands/adopt.ts
1335
- import { existsSync as existsSync9, readFileSync as readFileSync6, writeFileSync as writeFileSync2 } from "fs";
1336
- import { resolve as resolve13 } from "path";
2159
+ import { existsSync as existsSync11, readFileSync as readFileSync9, writeFileSync as writeFileSync4 } from "fs";
2160
+ import { resolve as resolve16 } from "path";
1337
2161
  import { pathToFileURL } from "url";
1338
- import * as p2 from "@clack/prompts";
2162
+ import * as p3 from "@clack/prompts";
1339
2163
  async function loadDetectLayout() {
1340
2164
  const mod = await import(pathToFileURL(kitPath("scripts", "detect-layout.mjs")).href);
1341
2165
  return mod.detectProjectLayout;
@@ -1343,7 +2167,7 @@ async function loadDetectLayout() {
1343
2167
  async function runAdopt(ctx, _options = {}) {
1344
2168
  const detectProjectLayout = await loadDetectLayout();
1345
2169
  const layout = detectProjectLayout(ctx.projectRoot);
1346
- const configPath = resolve13(ctx.projectRoot, WOODSCLI_CONFIG_FILE);
2170
+ const configPath = resolve16(ctx.projectRoot, WOODSCLI_CONFIG_FILE);
1347
2171
  if (layout.track === "unknown") {
1348
2172
  throw new WpHsError("Could not detect module or theme layout.", {
1349
2173
  exitCode: ExitCode.USER_ERROR,
@@ -1355,7 +2179,7 @@ async function runAdopt(ctx, _options = {}) {
1355
2179
  projectType = layout.projectType;
1356
2180
  } else if (layout.track === "theme") {
1357
2181
  projectType = "theme-starter";
1358
- } else if (existsSync9(resolve13(ctx.projectRoot, "vite.config.monolith.mjs"))) {
2182
+ } else if (existsSync11(resolve16(ctx.projectRoot, "vite.config.monolith.mjs"))) {
1359
2183
  projectType = "module-monolith";
1360
2184
  } else {
1361
2185
  projectType = "module-hybrid-cdn";
@@ -1376,13 +2200,13 @@ async function runAdopt(ctx, _options = {}) {
1376
2200
  ctx.logger.info(`[dry-run] Would adopt as ${projectType} (${layout.track})`);
1377
2201
  return ExitCode.SUCCESS;
1378
2202
  }
1379
- if (!existsSync9(configPath)) {
1380
- writeFileSync2(configPath, `${JSON.stringify(config, null, 2)}
2203
+ if (!existsSync11(configPath)) {
2204
+ writeFileSync4(configPath, `${JSON.stringify(config, null, 2)}
1381
2205
  `);
1382
2206
  ctx.logger.success(`Created ${WOODSCLI_CONFIG_FILE}`);
1383
2207
  } else if (!layout.projectType) {
1384
- const existing = JSON.parse(readFileSync6(configPath, "utf8"));
1385
- writeFileSync2(configPath, `${JSON.stringify({ ...existing, projectType }, null, 2)}
2208
+ const existing = JSON.parse(readFileSync9(configPath, "utf8"));
2209
+ writeFileSync4(configPath, `${JSON.stringify({ ...existing, projectType }, null, 2)}
1386
2210
  `);
1387
2211
  ctx.logger.info(`Set projectType \u2192 ${projectType}`);
1388
2212
  }
@@ -1391,9 +2215,9 @@ async function runAdopt(ctx, _options = {}) {
1391
2215
  logger: ctx.logger,
1392
2216
  dryRun: false
1393
2217
  });
1394
- const adapterPath = resolve13(ctx.projectRoot, "wphs.adapter.ts");
1395
- if (!existsSync9(adapterPath) && isModuleProject(config)) {
1396
- writeFileSync2(
2218
+ const adapterPath = resolve16(ctx.projectRoot, "wphs.adapter.ts");
2219
+ if (!existsSync11(adapterPath) && isModuleProject(config)) {
2220
+ writeFileSync4(
1397
2221
  adapterPath,
1398
2222
  `import type { HubSpotKitAdapter } from '@woodsportal/hubspot-kit'
1399
2223
 
@@ -1409,11 +2233,11 @@ export default adapter
1409
2233
  ctx.logger.info("Created wphs.adapter.ts stub");
1410
2234
  }
1411
2235
  if (!ctx.global.ci && !ctx.global.quiet) {
1412
- p2.note(
2236
+ p3.note(
1413
2237
  [
1414
2238
  `Review ${WOODSCLI_CONFIG_FILE} upload paths`,
1415
2239
  "Implement wphs.adapter.ts for your app (if module)",
1416
- "Add vite.config.dev.mjs re-export if missing",
2240
+ "Run wp setup (copies hubspot.config.yml, .env.*, vite shims)",
1417
2241
  "Patch main.dev.css @source to .wphs/.kit-module-config/**"
1418
2242
  ].join("\n"),
1419
2243
  "Manual follow-ups"
@@ -1424,67 +2248,67 @@ export default adapter
1424
2248
  }
1425
2249
 
1426
2250
  // src/cli/commands/migrate.ts
1427
- import { cpSync as cpSync3, existsSync as existsSync10, mkdirSync as mkdirSync3, readFileSync as readFileSync7, readdirSync as readdirSync3, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
1428
- import { join as join3, resolve as resolve14 } from "path";
2251
+ import { cpSync as cpSync3, existsSync as existsSync12, mkdirSync as mkdirSync4, readFileSync as readFileSync10, readdirSync as readdirSync3, renameSync, rmSync as rmSync3, writeFileSync as writeFileSync5 } from "fs";
2252
+ import { join as join3, resolve as resolve17 } from "path";
1429
2253
  var LEGACY_DIRS = [".dev", ".woodsportal"];
1430
2254
  var LEGACY_BASELINE = ".wp-hs-baseline";
1431
2255
  async function runMigrate(ctx, _options = {}) {
1432
2256
  const root = ctx.projectRoot;
1433
- const wphsRoot = resolve14(root, WPHS_WORKSPACE_DIR);
2257
+ const wphsRoot = resolve17(root, WPHS_WORKSPACE_DIR);
1434
2258
  const actions = [];
1435
2259
  for (const legacy of LEGACY_DIRS) {
1436
- const legacyPath = resolve14(root, legacy);
1437
- if (!existsSync10(legacyPath)) continue;
2260
+ const legacyPath = resolve17(root, legacy);
2261
+ if (!existsSync12(legacyPath)) continue;
1438
2262
  actions.push(`Move ${legacy}/ \u2192 .wphs/`);
1439
2263
  if (ctx.global.dryRun) continue;
1440
- mkdirSync3(join3(wphsRoot, "config"), { recursive: true });
1441
- mkdirSync3(join3(wphsRoot, "assets", "branding"), { recursive: true });
2264
+ mkdirSync4(join3(wphsRoot, "config"), { recursive: true });
2265
+ mkdirSync4(join3(wphsRoot, "assets", "branding"), { recursive: true });
1442
2266
  const localConfig = join3(legacyPath, "module-config.local.json");
1443
2267
  const exampleConfig = join3(legacyPath, "module-config.example.json");
1444
- if (existsSync10(localConfig)) {
2268
+ if (existsSync12(localConfig)) {
1445
2269
  cpSync3(localConfig, join3(wphsRoot, "config", "module-config.local.json"));
1446
2270
  }
1447
- if (existsSync10(exampleConfig)) {
2271
+ if (existsSync12(exampleConfig)) {
1448
2272
  cpSync3(exampleConfig, join3(wphsRoot, "config", "module-config.example.json"));
1449
2273
  }
1450
2274
  const branding = join3(legacyPath, "branding-assets");
1451
- if (existsSync10(branding)) {
2275
+ if (existsSync12(branding)) {
1452
2276
  for (const file of readdirSync3(branding)) {
1453
2277
  cpSync3(join3(branding, file), join3(wphsRoot, "assets", "branding", file));
1454
2278
  }
1455
2279
  }
1456
- rmSync2(legacyPath, { recursive: true, force: true });
2280
+ rmSync3(legacyPath, { recursive: true, force: true });
1457
2281
  }
1458
- const legacyBaseline = resolve14(root, LEGACY_BASELINE);
2282
+ const legacyBaseline = resolve17(root, LEGACY_BASELINE);
1459
2283
  const targetBaseline = join3(wphsRoot, "audit", "baseline");
1460
- if (existsSync10(legacyBaseline)) {
2284
+ if (existsSync12(legacyBaseline)) {
1461
2285
  actions.push(`Move ${LEGACY_BASELINE}/ \u2192 .wphs/audit/baseline/`);
1462
2286
  if (!ctx.global.dryRun) {
1463
- mkdirSync3(targetBaseline, { recursive: true });
2287
+ mkdirSync4(targetBaseline, { recursive: true });
1464
2288
  cpSync3(legacyBaseline, targetBaseline, { recursive: true });
1465
- rmSync2(legacyBaseline, { recursive: true, force: true });
2289
+ rmSync3(legacyBaseline, { recursive: true, force: true });
1466
2290
  }
1467
2291
  }
1468
- const legacyConfigPath2 = resolve14(root, LEGACY_WOODSPORTAL_HS_FILE);
1469
- const woodscliPath = resolve14(root, WOODSCLI_CONFIG_FILE);
1470
- if (existsSync10(legacyConfigPath2) && !existsSync10(woodscliPath)) {
2292
+ const legacyConfigPath2 = resolve17(root, LEGACY_WOODSPORTAL_HS_FILE);
2293
+ const woodscliPath = resolve17(root, WOODSCLI_CONFIG_FILE);
2294
+ if (existsSync12(legacyConfigPath2) && !existsSync12(woodscliPath)) {
1471
2295
  actions.push(`Rename ${LEGACY_WOODSPORTAL_HS_FILE} \u2192 ${WOODSCLI_CONFIG_FILE}`);
1472
2296
  if (!ctx.global.dryRun) {
1473
2297
  renameSync(legacyConfigPath2, woodscliPath);
1474
2298
  }
1475
- } else if (existsSync10(legacyConfigPath2) && existsSync10(woodscliPath)) {
2299
+ } else if (existsSync12(legacyConfigPath2) && existsSync12(woodscliPath)) {
1476
2300
  actions.push(`Remove duplicate ${LEGACY_WOODSPORTAL_HS_FILE}`);
1477
2301
  if (!ctx.global.dryRun) {
1478
- rmSync2(legacyConfigPath2, { force: true });
2302
+ rmSync3(legacyConfigPath2, { force: true });
1479
2303
  }
1480
2304
  }
1481
- if (existsSync10(woodscliPath)) {
1482
- const raw = JSON.parse(readFileSync7(woodscliPath, "utf8"));
2305
+ if (existsSync12(woodscliPath)) {
2306
+ const raw = JSON.parse(readFileSync10(woodscliPath, "utf8"));
1483
2307
  if (raw?.dev?.moduleConfigDir && raw.dev.moduleConfigDir !== WPHS_WORKSPACE_DIR) {
1484
2308
  actions.push(`Update dev.moduleConfigDir \u2192 .wphs in project config`);
1485
2309
  if (!ctx.global.dryRun) {
1486
2310
  raw.dev.moduleConfigDir = WPHS_WORKSPACE_DIR;
1487
- writeFileSync3(woodscliPath, `${JSON.stringify(raw, null, 2)}
2311
+ writeFileSync5(woodscliPath, `${JSON.stringify(raw, null, 2)}
1488
2312
  `);
1489
2313
  }
1490
2314
  }
@@ -1513,10 +2337,10 @@ async function runMigrate(ctx, _options = {}) {
1513
2337
  }
1514
2338
 
1515
2339
  // src/cli/commands/examples.ts
1516
- import { readFileSync as readFileSync8 } from "fs";
2340
+ import { readFileSync as readFileSync11 } from "fs";
1517
2341
  function loadManifest() {
1518
2342
  const manifestPath = kitPath("examples", "manifest.json");
1519
- const raw = JSON.parse(readFileSync8(manifestPath, "utf8"));
2343
+ const raw = JSON.parse(readFileSync11(manifestPath, "utf8"));
1520
2344
  return raw.scenarios;
1521
2345
  }
1522
2346
  async function runExamplesList(ctx, options = {}) {
@@ -1536,7 +2360,7 @@ async function runExamplesSmoke(ctx, options = {}) {
1536
2360
  const tier = options.tier ?? "static";
1537
2361
  const args = ["--tier", tier];
1538
2362
  if (options.scenario) args.push("--scenario", options.scenario);
1539
- const { runNodeScript: runNodeScript2 } = await import("./exec-K5C6LZP4.js");
2363
+ const { runNodeScript: runNodeScript2 } = await import("./exec-U2OJATR3.js");
1540
2364
  runNodeScript2(kitPath("examples", "smoke", "run-matrix.mjs"), args, {
1541
2365
  cwd: ctx.projectRoot,
1542
2366
  logger: ctx.logger,
@@ -1549,15 +2373,15 @@ async function runExamplesSmoke(ctx, options = {}) {
1549
2373
  }
1550
2374
 
1551
2375
  // src/cli/commands/fields.ts
1552
- import { cpSync as cpSync4, existsSync as existsSync11, readFileSync as readFileSync9, writeFileSync as writeFileSync4 } from "fs";
1553
- import { resolve as resolve15 } from "path";
2376
+ import { cpSync as cpSync4, existsSync as existsSync13, readFileSync as readFileSync12, writeFileSync as writeFileSync6 } from "fs";
2377
+ import { resolve as resolve18 } from "path";
1554
2378
  async function runFieldsSync(ctx, options = {}) {
1555
2379
  const direction = options.direction ?? "pull";
1556
- const fieldsPath = resolve15(ctx.projectRoot, ctx.config.dev.fieldsJson);
1557
- const cacheDir = resolve15(ctx.projectRoot, ctx.config.dev.moduleConfigDir, "cache", "fields-sync");
1558
- const cachePath = resolve15(cacheDir, "fields.json");
2380
+ const fieldsPath = resolve18(ctx.projectRoot, ctx.config.dev.fieldsJson);
2381
+ const cacheDir = resolve18(ctx.projectRoot, ctx.config.dev.moduleConfigDir, "cache", "fields-sync");
2382
+ const cachePath = resolve18(cacheDir, "fields.json");
1559
2383
  if (direction === "pull") {
1560
- if (!existsSync11(fieldsPath)) {
2384
+ if (!existsSync13(fieldsPath)) {
1561
2385
  throw new WpHsError(`Missing ${ctx.config.dev.fieldsJson}`, {
1562
2386
  exitCode: ExitCode.USER_ERROR,
1563
2387
  remediation: "Add fields.json or run wp init."
@@ -1567,13 +2391,13 @@ async function runFieldsSync(ctx, options = {}) {
1567
2391
  ctx.logger.info(`[dry-run] Would cache ${fieldsPath} \u2192 ${cachePath}`);
1568
2392
  return ExitCode.SUCCESS;
1569
2393
  }
1570
- const { mkdirSync: mkdirSync4 } = await import("fs");
1571
- mkdirSync4(cacheDir, { recursive: true });
2394
+ const { mkdirSync: mkdirSync5 } = await import("fs");
2395
+ mkdirSync5(cacheDir, { recursive: true });
1572
2396
  cpSync4(fieldsPath, cachePath);
1573
2397
  ctx.logger.success(`Cached fields \u2192 ${cachePath}`);
1574
2398
  return ExitCode.SUCCESS;
1575
2399
  }
1576
- if (!existsSync11(cachePath)) {
2400
+ if (!existsSync13(cachePath)) {
1577
2401
  throw new WpHsError("No cached fields \u2014 run wp fields sync pull first.", {
1578
2402
  exitCode: ExitCode.USER_ERROR
1579
2403
  });
@@ -1582,14 +2406,14 @@ async function runFieldsSync(ctx, options = {}) {
1582
2406
  ctx.logger.info(`[dry-run] Would write ${cachePath} \u2192 ${fieldsPath}`);
1583
2407
  return ExitCode.SUCCESS;
1584
2408
  }
1585
- writeFileSync4(fieldsPath, readFileSync9(cachePath, "utf8"));
2409
+ writeFileSync6(fieldsPath, readFileSync12(cachePath, "utf8"));
1586
2410
  ctx.logger.success(`Restored fields from cache \u2192 ${fieldsPath}`);
1587
2411
  return ExitCode.SUCCESS;
1588
2412
  }
1589
2413
 
1590
2414
  // src/cli/commands/regression.ts
1591
- import { existsSync as existsSync12 } from "fs";
1592
- import { resolve as resolve16 } from "path";
2415
+ import { existsSync as existsSync14 } from "fs";
2416
+ import { resolve as resolve19 } from "path";
1593
2417
  var LEGACY_SHIM_SCRIPTS = [
1594
2418
  "build-cdn.mjs",
1595
2419
  "run-vite-build.mjs",
@@ -1612,21 +2436,21 @@ async function runRegressionSmoke(ctx, options = {}) {
1612
2436
  if (!validateOk) failures.push("wp config validate failed");
1613
2437
  if (options.checkLegacyShims !== false) {
1614
2438
  for (const script of LEGACY_SHIM_SCRIPTS) {
1615
- const shim = resolve16(ctx.projectRoot, "scripts", script);
2439
+ const shim = resolve19(ctx.projectRoot, "scripts", script);
1616
2440
  const kitScript = kitPath("scripts", script);
1617
- const shimOk = existsSync12(shim) && existsSync12(kitScript);
2441
+ const shimOk = existsSync14(shim) && existsSync14(kitScript);
1618
2442
  checks.push({ name: `shim:${script}`, ok: shimOk });
1619
2443
  if (!shimOk) failures.push(`Missing legacy shim or kit script: ${script}`);
1620
2444
  }
1621
2445
  }
1622
2446
  for (const rel of FORBIDDEN_CONSUMER_PATHS) {
1623
- const forbidden = resolve16(ctx.projectRoot, rel);
1624
- const absent = !existsSync12(forbidden);
2447
+ const forbidden = resolve19(ctx.projectRoot, rel);
2448
+ const absent = !existsSync14(forbidden);
1625
2449
  checks.push({ name: `no-${rel}`, ok: absent });
1626
2450
  if (!absent) failures.push(`Forbidden dev path still present: ${rel}`);
1627
2451
  }
1628
- const woodscliConfig = resolve16(ctx.projectRoot, WOODSCLI_CONFIG_FILE);
1629
- const configOk = existsSync12(woodscliConfig);
2452
+ const woodscliConfig = resolve19(ctx.projectRoot, WOODSCLI_CONFIG_FILE);
2453
+ const configOk = existsSync14(woodscliConfig);
1630
2454
  checks.push({ name: WOODSCLI_CONFIG_FILE, ok: configOk });
1631
2455
  if (!configOk) failures.push(`${WOODSCLI_CONFIG_FILE} missing`);
1632
2456
  if (ctx.global.json) {
@@ -1650,43 +2474,260 @@ async function runRegressionSmoke(ctx, options = {}) {
1650
2474
  return ExitCode.SUCCESS;
1651
2475
  }
1652
2476
 
2477
+ // src/cli/commands/setup.ts
2478
+ async function runSetup(ctx, options = {}) {
2479
+ let results = [];
2480
+ await runCliTasks(
2481
+ ctx,
2482
+ [
2483
+ {
2484
+ title: "Preparing .wphs workspace",
2485
+ task: async () => {
2486
+ if (ctx.global.dryRun) return;
2487
+ try {
2488
+ runNodeScript(kitPath("scripts", "ensure-wphs-workspace.mjs"), [], {
2489
+ cwd: ctx.projectRoot,
2490
+ logger: ctx.logger,
2491
+ dryRun: false
2492
+ });
2493
+ } catch {
2494
+ }
2495
+ }
2496
+ },
2497
+ {
2498
+ title: "Copying missing config files",
2499
+ task: async () => {
2500
+ results = applyProjectSetup(ctx, {
2501
+ tier: options.tier,
2502
+ dryRun: ctx.global.dryRun
2503
+ });
2504
+ }
2505
+ }
2506
+ ],
2507
+ options
2508
+ );
2509
+ const copied = results.filter((r) => r.action === "copied");
2510
+ const manual = results.filter((r) => r.action === "manual");
2511
+ if (ctx.global.json) {
2512
+ ctx.logger.json({
2513
+ command: "setup",
2514
+ ok: manual.length === 0,
2515
+ copied: copied.length,
2516
+ skipped: results.filter((r) => r.action === "skipped").length,
2517
+ manual,
2518
+ results
2519
+ });
2520
+ } else {
2521
+ for (const result of results) {
2522
+ if (result.action === "skipped") continue;
2523
+ if (result.action === "copied") {
2524
+ ctx.logger.success(result.detail);
2525
+ } else {
2526
+ ctx.logger.warn(`${result.path}: ${result.detail}`);
2527
+ }
2528
+ }
2529
+ if (copied.length === 0 && manual.length === 0) {
2530
+ printCommandDone(ctx, "Nothing to copy \u2014 required files already present");
2531
+ } else if (copied.length > 0) {
2532
+ printCommandDone(ctx, `Setup complete \u2014 ${copied.length} file(s) copied`);
2533
+ }
2534
+ const hint = formatSetupHint(results);
2535
+ if (hint) {
2536
+ ctx.logger.info(`\u2192 ${hint}`);
2537
+ }
2538
+ if (manual.length > 0) {
2539
+ ctx.logger.info("\u2192 Run wp doctor for the full checklist");
2540
+ }
2541
+ }
2542
+ return ExitCode.SUCCESS;
2543
+ }
2544
+
2545
+ // src/cli/commands/portal.ts
2546
+ import * as p4 from "@clack/prompts";
2547
+
2548
+ // src/lib/hubspot-portals.ts
2549
+ import { existsSync as existsSync15, readFileSync as readFileSync13 } from "fs";
2550
+ import { resolve as resolve20 } from "path";
2551
+ function stripYamlScalar(value) {
2552
+ const trimmed = value.trim();
2553
+ if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
2554
+ return trimmed.slice(1, -1);
2555
+ }
2556
+ return trimmed;
2557
+ }
2558
+ function parseHubSpotConfigPortals(raw) {
2559
+ const defaultMatch = raw.match(/^defaultPortal:\s*(.+)\s*$/m);
2560
+ const defaultPortal = defaultMatch ? stripYamlScalar(defaultMatch[1]) : null;
2561
+ const portals = [];
2562
+ let current = null;
2563
+ let inPortals = false;
2564
+ for (const line of raw.split("\n")) {
2565
+ const trimmed = line.trim();
2566
+ if (trimmed === "portals:") {
2567
+ inPortals = true;
2568
+ continue;
2569
+ }
2570
+ if (!inPortals) continue;
2571
+ const listItemName = line.match(/^\s+-\s+name:\s+(.+)\s*$/);
2572
+ const nestedName = line.match(/^\s+name:\s+(.+)\s*$/);
2573
+ const portalId = line.match(/^\s+portalId:\s+(\d+)\s*$/);
2574
+ if (listItemName || nestedName && !line.match(/^\s+-\s/)) {
2575
+ if (current) portals.push(current);
2576
+ const nameValue = listItemName?.[1] ?? nestedName?.[1];
2577
+ current = nameValue ? { name: stripYamlScalar(nameValue) } : null;
2578
+ continue;
2579
+ }
2580
+ if (portalId && current) {
2581
+ current.portalId = portalId[1];
2582
+ }
2583
+ }
2584
+ if (current) portals.push(current);
2585
+ return { defaultPortal, portals };
2586
+ }
2587
+ function readHubSpotPortals(projectRoot) {
2588
+ const configPath = resolve20(projectRoot, HUBSPOT_CONFIG_FILE);
2589
+ if (!existsSync15(configPath)) {
2590
+ throw new WpHsError(`${HUBSPOT_CONFIG_FILE} not found in project root.`, {
2591
+ exitCode: ExitCode.USER_ERROR,
2592
+ remediation: "Run wp setup, then add your portal + PAT."
2593
+ });
2594
+ }
2595
+ const raw = readFileSync13(configPath, "utf8");
2596
+ const { defaultPortal, portals } = parseHubSpotConfigPortals(raw);
2597
+ return { configPath, defaultPortal, portals };
2598
+ }
2599
+ function resolvePortalName(portals, nameOrId) {
2600
+ const needle = nameOrId.trim();
2601
+ return portals.find((p5) => p5.name === needle) ?? portals.find((p5) => p5.portalId === needle) ?? null;
2602
+ }
2603
+
2604
+ // src/cli/commands/portal.ts
2605
+ function formatPortalLine(portal, defaultPortal) {
2606
+ const id = portal.portalId ? `portalId ${portal.portalId}` : "portalId ?";
2607
+ const mark = portal.name === defaultPortal ? " \xB7 default" : "";
2608
+ return `${portal.name} (${id})${mark}`;
2609
+ }
2610
+ async function runPortalList(ctx, _options = {}) {
2611
+ const data = readHubSpotPortals(ctx.projectRoot);
2612
+ if (ctx.global.json) {
2613
+ ctx.logger.json({
2614
+ command: "portal:list",
2615
+ configPath: data.configPath,
2616
+ defaultPortal: data.defaultPortal,
2617
+ portals: data.portals
2618
+ });
2619
+ return ExitCode.SUCCESS;
2620
+ }
2621
+ if (data.portals.length === 0) {
2622
+ ctx.logger.warn(`No portals[] entries found in ${data.configPath}`);
2623
+ ctx.logger.info("\u2192 Add portals under hubspot.config.yml or run hs auth");
2624
+ return ExitCode.SUCCESS;
2625
+ }
2626
+ if (data.defaultPortal) {
2627
+ ctx.logger.info(`Default: ${data.defaultPortal}`);
2628
+ }
2629
+ for (const portal of data.portals) {
2630
+ ctx.logger.info(formatPortalLine(portal, data.defaultPortal));
2631
+ }
2632
+ return ExitCode.SUCCESS;
2633
+ }
2634
+ async function runPortalUse(ctx, options = {}) {
2635
+ const data = readHubSpotPortals(ctx.projectRoot);
2636
+ if (data.portals.length === 0) {
2637
+ throw new WpHsError("No portals configured in hubspot.config.yml", {
2638
+ exitCode: ExitCode.USER_ERROR,
2639
+ remediation: "Run wp auth or add portals[] to hubspot.config.yml"
2640
+ });
2641
+ }
2642
+ let targetName = options.name?.trim();
2643
+ if (!targetName) {
2644
+ if (ctx.global.ci || ctx.global.json) {
2645
+ throw new WpHsError("Portal name or id required in non-interactive mode", {
2646
+ exitCode: ExitCode.USER_ERROR,
2647
+ remediation: "wp portal use <name>"
2648
+ });
2649
+ }
2650
+ if (!isInteractiveCli(ctx)) {
2651
+ throw new WpHsError("Portal name or id required", {
2652
+ exitCode: ExitCode.USER_ERROR,
2653
+ remediation: "wp portal use <name> \xB7 wp portal list"
2654
+ });
2655
+ }
2656
+ const answer = await p4.select({
2657
+ message: "Set default HubSpot portal",
2658
+ options: data.portals.map((portal2) => ({
2659
+ value: portal2.name,
2660
+ label: portal2.name,
2661
+ hint: portal2.name === data.defaultPortal ? `current default \xB7 ${portal2.portalId ?? "no id"}` : portal2.portalId ?? void 0
2662
+ }))
2663
+ });
2664
+ if (p4.isCancel(answer)) {
2665
+ throw new WpHsError("Portal selection cancelled", { exitCode: ExitCode.USER_ERROR });
2666
+ }
2667
+ targetName = answer;
2668
+ }
2669
+ const portal = resolvePortalName(data.portals, targetName);
2670
+ if (!portal) {
2671
+ throw new WpHsError(`Unknown portal: ${targetName}`, {
2672
+ exitCode: ExitCode.USER_ERROR,
2673
+ remediation: "wp portal list"
2674
+ });
2675
+ }
2676
+ if (ctx.global.dryRun) {
2677
+ ctx.logger.info(`[dry-run] hs accounts use ${portal.name}`);
2678
+ return ExitCode.SUCCESS;
2679
+ }
2680
+ runCommand("hs", ["accounts", "use", portal.name], {
2681
+ cwd: ctx.projectRoot,
2682
+ logger: ctx.logger,
2683
+ dryRun: false
2684
+ });
2685
+ if (!ctx.global.json && !ctx.global.quiet) {
2686
+ printCommandDone(ctx, `Default portal \u2192 ${portal.name}`);
2687
+ } else if (ctx.global.json) {
2688
+ ctx.logger.json({
2689
+ command: "portal:use",
2690
+ ok: true,
2691
+ defaultPortal: portal.name,
2692
+ portalId: portal.portalId ?? null
2693
+ });
2694
+ }
2695
+ return ExitCode.SUCCESS;
2696
+ }
2697
+
1653
2698
  // src/cli/commands/watch.ts
1654
2699
  async function runWatch(ctx, options) {
1655
- ensureHubSpotCliSetup(ctx, "cms");
2700
+ ensureHubSpotConfigForCms(ctx);
1656
2701
  const tier = options.tier ?? "dev";
1657
- const args = [];
1658
- if (tier === "prod") args.push("--prod");
2702
+ const args = ["--tier", tier];
1659
2703
  if (options.rebuild) args.push("--rebuild");
1660
- try {
1661
- runNodeScript(kitPath("scripts", "watch-hubspot.mjs"), args, {
1662
- cwd: ctx.projectRoot,
1663
- env: { ...process.env, WP_HS_PROJECT_ROOT: ctx.projectRoot },
1664
- logger: ctx.logger,
1665
- dryRun: ctx.global.dryRun
1666
- });
1667
- return ExitCode.SUCCESS;
1668
- } catch (error) {
1669
- throw new WpHsError("Watch failed", {
2704
+ const code = await runNodeScriptLongRunning(kitPath("scripts", "watch-hubspot.mjs"), args, {
2705
+ cwd: ctx.projectRoot,
2706
+ env: { ...process.env, WP_HS_PROJECT_ROOT: ctx.projectRoot },
2707
+ logger: ctx.logger,
2708
+ dryRun: ctx.global.dryRun
2709
+ });
2710
+ if (code === 130 || code === 143) {
2711
+ return code;
2712
+ }
2713
+ if (code !== ExitCode.SUCCESS) {
2714
+ throw new WpHsError(`Watch exited with code ${code}`, {
1670
2715
  exitCode: ExitCode.BUILD_FAILED,
1671
- cause: error,
1672
- remediation: "Ensure HubSpot module is built once before watch, or pass --rebuild."
2716
+ remediation: "Run wp build once, then wp watch. For src/ changes use wp watch --rebuild. Check hubspot.config.yml and hs cms watch output."
1673
2717
  });
1674
2718
  }
2719
+ return ExitCode.SUCCESS;
1675
2720
  }
1676
2721
 
1677
2722
  // src/cli/program.ts
1678
2723
  function readVersion() {
1679
2724
  try {
1680
- const pkg = JSON.parse(readFileSync10(resolve17(getKitRoot(), "package.json"), "utf8"));
2725
+ const pkg = JSON.parse(readFileSync14(resolve21(getKitRoot(), "package.json"), "utf8"));
1681
2726
  return pkg.version ?? "0.0.0";
1682
2727
  } catch {
1683
2728
  return "0.0.0";
1684
2729
  }
1685
2730
  }
1686
- function parseTier(value) {
1687
- if (value === "dev" || value === "stg" || value === "prod") return value;
1688
- return "prod";
1689
- }
1690
2731
  function buildContext(global) {
1691
2732
  const logger = createLogger({
1692
2733
  verbose: global.verbose,
@@ -1727,33 +2768,45 @@ function createProgram() {
1727
2768
  const global = program2.opts();
1728
2769
  await handle(() => runDoctor(buildContext(global)));
1729
2770
  });
1730
- const configCmd = program2.command("config").description("Project configuration");
1731
- configCmd.command("validate").description("Validate woodscli.json").action(async () => {
1732
- const global = program2.opts();
1733
- await handle(() => runConfigValidate(buildContext(global)));
1734
- });
1735
- 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)").action(async (opts) => {
2771
+ program2.command("setup").description("Copy missing hubspot.config.yml, .env.*, CDN config, and vite shims from kit templates").option("--tier <tier>", "dev | stg | prod \u2014 copy only this .env file (default: all env.tiers)").action(async (opts) => {
1736
2772
  const global = program2.opts();
1737
- const logger = createLogger({
1738
- verbose: global.verbose,
1739
- quiet: global.quiet,
1740
- json: global.json,
1741
- ci: global.ci
1742
- });
1743
- const ctx = {
1744
- projectRoot: process.cwd(),
1745
- config: DEFAULT_WOODSPORTAL_HS,
1746
- logger,
1747
- global
1748
- };
1749
2773
  await handle(
1750
- () => runInit(ctx, {
1751
- template: opts.template,
1752
- name: opts.name,
1753
- directory: opts.directory
2774
+ () => runSetup(buildContext(global), {
2775
+ tier: opts.tier ? parseBuildTier(opts.tier) : void 0
1754
2776
  })
1755
2777
  );
1756
2778
  });
2779
+ const configCmd = program2.command("config").description("Project configuration");
2780
+ configCmd.command("validate").description("Validate woodscli.json").action(async () => {
2781
+ const global = program2.opts();
2782
+ await handle(() => runConfigValidate(buildContext(global)));
2783
+ });
2784
+ 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(
2785
+ async (opts) => {
2786
+ const global = program2.opts();
2787
+ const logger = createLogger({
2788
+ verbose: global.verbose,
2789
+ quiet: global.quiet,
2790
+ json: global.json,
2791
+ ci: global.ci
2792
+ });
2793
+ const ctx = {
2794
+ projectRoot: process.cwd(),
2795
+ config: DEFAULT_WOODSPORTAL_HS,
2796
+ logger,
2797
+ global
2798
+ };
2799
+ const routerMode = opts.router === "browser" || opts.router === "none" || opts.router === "hash" ? opts.router : void 0;
2800
+ await handle(
2801
+ () => runInit(ctx, {
2802
+ template: opts.template,
2803
+ name: opts.name,
2804
+ directory: opts.directory,
2805
+ router: routerMode
2806
+ })
2807
+ );
2808
+ }
2809
+ );
1757
2810
  program2.command("adopt").description("Detect existing module/theme repo and add WoodsCLI config + .wphs/").action(async () => {
1758
2811
  const global = program2.opts();
1759
2812
  await handle(() => runAdopt(buildContext(global)));
@@ -1766,12 +2819,12 @@ function createProgram() {
1766
2819
  const global = program2.opts();
1767
2820
  await handle(() => runDev(buildContext(global), { port: Number(opts.port) }));
1768
2821
  });
1769
- program2.command("build").description("Build CDN + HubSpot module (or theme no-op)").option("--tier <tier>", "dev | stg | prod", "prod").option("--cdn-only", "CDN chunks only").option("--hubspot-only", "HubSpot module only").option("--cdn-esm", "Build CDN via ESM preset").action(
2822
+ program2.command("build").description("Build CDN + HubSpot module (or theme no-op)").option("--tier <tier>", "dev | stg | prod", DEFAULT_BUILD_TIER).option("--cdn-only", "CDN chunks only").option("--hubspot-only", "HubSpot module only").option("--cdn-esm", "Build CDN via ESM preset").action(
1770
2823
  async (opts) => {
1771
2824
  const global = program2.opts();
1772
2825
  await handle(
1773
2826
  () => runBuild(buildContext(global), {
1774
- tier: parseTier(opts.tier),
2827
+ tier: parseBuildTier(opts.tier),
1775
2828
  cdnOnly: opts.cdnOnly,
1776
2829
  hubspotOnly: opts.hubspotOnly,
1777
2830
  cdnEsm: opts.cdnEsm
@@ -1779,20 +2832,20 @@ function createProgram() {
1779
2832
  );
1780
2833
  }
1781
2834
  );
1782
- program2.command("deploy").description("Build, publish CDN, upload HubSpot module").option("--tier <tier>", "dev | stg | prod", "prod").option("--skip-publish", "Skip CDN publish").action(async (opts) => {
2835
+ program2.command("deploy").description("Build, publish CDN, upload HubSpot module").option("--tier <tier>", "dev | stg | prod", DEFAULT_BUILD_TIER).option("--skip-publish", "Skip CDN publish").action(async (opts) => {
1783
2836
  const global = program2.opts();
1784
2837
  await handle(
1785
2838
  () => runDeploy(buildContext(global), {
1786
- tier: parseTier(opts.tier),
2839
+ tier: parseBuildTier(opts.tier),
1787
2840
  skipPublish: opts.skipPublish
1788
2841
  })
1789
2842
  );
1790
2843
  });
1791
- program2.command("publish").description("Publish CDN artifacts to jsDelivr mirror").option("--tier <tier>", "dev | stg | prod", "prod").option("--dry-run", "Print publish plan without pushing").action(async (opts) => {
2844
+ program2.command("publish").description("Publish CDN artifacts to jsDelivr mirror").option("--tier <tier>", "dev | stg | prod", DEFAULT_BUILD_TIER).option("--dry-run", "Print publish plan without pushing").action(async (opts) => {
1792
2845
  const global = program2.opts();
1793
2846
  await handle(
1794
2847
  () => runPublish(buildContext(global), {
1795
- tier: parseTier(opts.tier),
2848
+ tier: parseBuildTier(opts.tier),
1796
2849
  dryRun: opts.dryRun
1797
2850
  })
1798
2851
  );
@@ -1848,9 +2901,9 @@ function createProgram() {
1848
2901
  () => runRegressionSmoke(buildContext(global), { skipDoctor: opts.skipDoctor })
1849
2902
  );
1850
2903
  });
1851
- program2.command("watch").description("Watch HubSpot module or theme folder and re-upload on change").option("--tier <tier>", "dev | stg | prod", "dev").option("--rebuild", "Rebuild module before watch").action(async (opts) => {
2904
+ program2.command("watch").description("Watch HubSpot module or theme folder and re-upload on change").option("--tier <tier>", "dev | stg | prod", DEFAULT_BUILD_TIER).option("--rebuild", "Rebuild module before watch").action(async (opts) => {
1852
2905
  const global = program2.opts();
1853
- const tier = parseTier(opts.tier);
2906
+ const tier = parseBuildTier(opts.tier);
1854
2907
  await handle(
1855
2908
  () => runWatch(buildContext(global), {
1856
2909
  tier: tier === "prod" ? "prod" : tier === "stg" ? "stg" : "dev",
@@ -1858,16 +2911,16 @@ function createProgram() {
1858
2911
  })
1859
2912
  );
1860
2913
  });
1861
- program2.command("upload").description("Upload built HubSpot module or theme (hs cms upload)").option("--tier <tier>", "dev | stg | prod", "prod").action(async (opts) => {
2914
+ program2.command("upload").description("Upload built HubSpot module or theme (hs cms upload)").option("--tier <tier>", "dev | stg | prod", DEFAULT_BUILD_TIER).action(async (opts) => {
1862
2915
  const global = program2.opts();
1863
2916
  await handle(
1864
- () => runUpload(buildContext(global), { tier: parseTier(opts.tier) })
2917
+ () => runUpload(buildContext(global), { tier: parseBuildTier(opts.tier) })
1865
2918
  );
1866
2919
  });
1867
2920
  program2.command("auth").description("Authenticate HubSpot CLI (hs auth)").action(async () => {
1868
2921
  const global = program2.opts();
1869
2922
  const ctx = buildContext(global);
1870
- const { runCommand: runCommand2 } = await import("./exec-K5C6LZP4.js");
2923
+ const { runCommand: runCommand2 } = await import("./exec-U2OJATR3.js");
1871
2924
  runCommand2("hs", ["auth"], {
1872
2925
  cwd: ctx.projectRoot,
1873
2926
  logger: ctx.logger,
@@ -1875,6 +2928,15 @@ function createProgram() {
1875
2928
  });
1876
2929
  process.exit(ExitCode.SUCCESS);
1877
2930
  });
2931
+ const portal = program2.command("portal").description("HubSpot default portal selection");
2932
+ portal.command("list").alias("ls").description("List portals from hubspot.config.yml").action(async () => {
2933
+ const global = program2.opts();
2934
+ await handle(() => runPortalList(buildContext(global)));
2935
+ });
2936
+ portal.command("use [name]").description("Set defaultPortal (hs accounts use)").action(async (name) => {
2937
+ const global = program2.opts();
2938
+ await handle(() => runPortalUse(buildContext(global), { name }));
2939
+ });
1878
2940
  return program2;
1879
2941
  }
1880
2942