@woodsportal/hubspot-kit 1.0.41 → 1.0.42-dev.1

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 (613) hide show
  1. package/CHANGELOG.md +20 -1
  2. package/README.md +191 -157
  3. package/dist/chunk-4UFWXPQG.js +554 -0
  4. package/dist/chunk-4UFWXPQG.js.map +1 -0
  5. package/dist/{chunk-4GOJPOW3.js → chunk-GXYUGXF5.js} +82 -23
  6. package/dist/chunk-GXYUGXF5.js.map +1 -0
  7. package/dist/{chunk-S2YJDWZU.js → chunk-NWBK3XBZ.js} +1 -1
  8. package/dist/chunk-NWBK3XBZ.js.map +1 -0
  9. package/dist/cli.js +1826 -877
  10. package/dist/cli.js.map +1 -1
  11. package/dist/{exec-VY6BGWF3.js → exec-NS4GKXZS.js} +2 -2
  12. package/dist/index.d.ts +12 -0
  13. package/dist/index.js +6 -2
  14. package/dist/index.js.map +1 -1
  15. package/dist/scaffold-page-lifecycle-53AWWGFR.js +194 -0
  16. package/dist/scaffold-page-lifecycle-53AWWGFR.js.map +1 -0
  17. package/dist/vite/index.js +1 -1
  18. package/examples/greenfield/module-hybrid-cdn/README.md +2 -1
  19. package/examples/greenfield/module-hybrid-cdn/public/fields.json +73 -28
  20. package/examples/greenfield/module-hybrid-cdn/public/module.html +21 -5
  21. package/examples/greenfield/module-hybrid-cdn/src/assets/css/main.dev.css +15 -0
  22. package/examples/greenfield/module-hybrid-cdn/src/components/layout/starter-nav.tsx +12 -15
  23. package/examples/greenfield/module-hybrid-cdn/src/config/starter-nav.ts +9 -0
  24. package/examples/greenfield/module-hybrid-cdn/src/features/starter/components/starter-home.tsx +25 -29
  25. package/examples/greenfield/module-hybrid-cdn/src/features/starter/hooks/starter-preview-values.ts +60 -0
  26. package/examples/greenfield/module-hybrid-cdn/src/features/starter/hooks/use-starter-preview.dev.ts +18 -0
  27. package/examples/greenfield/module-hybrid-cdn/src/features/starter/hooks/use-starter-preview.ts +20 -37
  28. package/examples/greenfield/module-hybrid-cdn-auth-full/.env.dev.example +3 -0
  29. package/examples/greenfield/module-hybrid-cdn-auth-full/.env.local.example +6 -0
  30. package/examples/greenfield/module-hybrid-cdn-auth-full/.env.prod.example +5 -0
  31. package/examples/greenfield/module-hybrid-cdn-auth-full/.env.stg.example +3 -0
  32. package/examples/greenfield/module-hybrid-cdn-auth-full/.wphs/README.md +17 -0
  33. package/examples/greenfield/module-hybrid-cdn-auth-full/README.md +60 -0
  34. package/examples/greenfield/module-hybrid-cdn-auth-full/hubspot.config.yml.example +10 -0
  35. package/examples/greenfield/module-hybrid-cdn-auth-full/index.html +12 -0
  36. package/examples/greenfield/module-hybrid-cdn-auth-full/package.json +34 -0
  37. package/examples/greenfield/module-hybrid-cdn-auth-full/portal.cdn.json.example +8 -0
  38. package/examples/greenfield/module-hybrid-cdn-auth-full/public/fields.json +100 -0
  39. package/examples/greenfield/module-hybrid-cdn-auth-full/public/meta.json +9 -0
  40. package/examples/greenfield/module-hybrid-cdn-auth-full/public/module.html +38 -0
  41. package/examples/greenfield/module-hybrid-cdn-auth-full/src/app.tsx +172 -0
  42. package/examples/greenfield/module-hybrid-cdn-auth-full/src/assets/css/main.dev.css +23 -0
  43. package/examples/greenfield/module-hybrid-cdn-auth-full/src/assets/css/main.prod.css +12 -0
  44. package/examples/greenfield/module-hybrid-cdn-auth-full/src/assets/css/tailwind.css.config.css +7 -0
  45. package/examples/greenfield/module-hybrid-cdn-auth-full/src/cdn/app-entry.ts +48 -0
  46. package/examples/greenfield/module-hybrid-cdn-auth-full/src/cdn/vendor-entry.ts +28 -0
  47. package/examples/greenfield/module-hybrid-cdn-auth-full/src/components/Layouts/AppLayoutWrapper.tsx +38 -0
  48. package/examples/greenfield/module-hybrid-cdn-auth-full/src/components/Layouts/AuthLayout.tsx +7 -0
  49. package/examples/greenfield/module-hybrid-cdn-auth-full/src/components/Layouts/DefaultLayout.tsx +5 -0
  50. package/examples/greenfield/module-hybrid-cdn-auth-full/src/components/Layouts/MainLayout.tsx +42 -0
  51. package/examples/greenfield/module-hybrid-cdn-auth-full/src/components/Layouts/UnauthorizedLayout.tsx +5 -0
  52. package/examples/greenfield/module-hybrid-cdn-auth-full/src/components/Layouts/index.ts +13 -0
  53. package/examples/greenfield/module-hybrid-cdn-auth-full/src/components/layout/section-card.tsx +20 -0
  54. package/examples/greenfield/module-hybrid-cdn-auth-full/src/components/layout/starter-nav.tsx +28 -0
  55. package/examples/greenfield/module-hybrid-cdn-auth-full/src/components/layout/starter-shell.tsx +18 -0
  56. package/examples/greenfield/module-hybrid-cdn-auth-full/src/components/ui/Button.tsx +22 -0
  57. package/examples/greenfield/module-hybrid-cdn-auth-full/src/components/ui/Form.tsx +22 -0
  58. package/examples/greenfield/module-hybrid-cdn-auth-full/src/components/ui/Input.tsx +12 -0
  59. package/examples/greenfield/module-hybrid-cdn-auth-full/src/components/ui/Toaster.tsx +5 -0
  60. package/examples/greenfield/module-hybrid-cdn-auth-full/src/components/ui/auth/SsoLoginButton.tsx +34 -0
  61. package/examples/greenfield/module-hybrid-cdn-auth-full/src/components/ui/link.tsx +10 -0
  62. package/examples/greenfield/module-hybrid-cdn-auth-full/src/components/ui/loader/loader.tsx +18 -0
  63. package/examples/greenfield/module-hybrid-cdn-auth-full/src/config/auth-tier.ts +4 -0
  64. package/examples/greenfield/module-hybrid-cdn-auth-full/src/config/nav-scaffold.ts +4 -0
  65. package/examples/greenfield/module-hybrid-cdn-auth-full/src/config/portal-nav.ts +9 -0
  66. package/examples/greenfield/module-hybrid-cdn-auth-full/src/config/router.ts +1 -0
  67. package/examples/greenfield/module-hybrid-cdn-auth-full/src/config/routes.ts +10 -0
  68. package/examples/greenfield/module-hybrid-cdn-auth-full/src/data/hubSpotContext.ts +57 -0
  69. package/examples/greenfield/module-hybrid-cdn-auth-full/src/data/sync-sdk-hub-context.ts +44 -0
  70. package/examples/greenfield/module-hybrid-cdn-auth-full/src/defaultData.ts +2 -0
  71. package/examples/greenfield/module-hybrid-cdn-auth-full/src/env.ts +59 -0
  72. package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/auth/app-bootstrap-guards.ts +14 -0
  73. package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/auth/auth-navigation.ts +81 -0
  74. package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/auth/auth-route-paths.ts +29 -0
  75. package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/auth/auth-session-events.ts +2 -0
  76. package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/auth/auth-session.ts +51 -0
  77. package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/auth/clear-client-auth-session.ts +62 -0
  78. package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/auth/components/starter-login.tsx +79 -0
  79. package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/auth/login-email-storage.ts +64 -0
  80. package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/auth/login-session-bridge.ts +74 -0
  81. package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/auth/mfa-otp-utils.ts +24 -0
  82. package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/auth/mfa-pending-storage.ts +108 -0
  83. package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/auth/mfa-portal.ts +45 -0
  84. package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/auth/post-login-path.ts +17 -0
  85. package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/auth/resolve-auth-login-error-message.ts +52 -0
  86. package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/auth/resolve-post-login-path.ts +21 -0
  87. package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/auth/sso-callback.ts +126 -0
  88. package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/auth/use-mfa-login-otp-flow.ts +78 -0
  89. package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/auth/use-portal-session.ts +25 -0
  90. package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/auth/use-signed-in-email.ts +55 -0
  91. package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/auth/use-sso-callback-handler.ts +92 -0
  92. package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/starter/components/starter-guide-page.tsx +210 -0
  93. package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/starter/components/starter-home-page.tsx +21 -0
  94. package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/starter/components/starter-home.tsx +48 -0
  95. package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/starter/components/starter-page-frame.tsx +25 -0
  96. package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/starter/components/starter-protected-page.tsx +54 -0
  97. package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/starter/components/starter-single-page.tsx +17 -0
  98. package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/starter/content/auth-guide-content.ts +85 -0
  99. package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/starter/content/woodscli-guide-content.ts +86 -0
  100. package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/starter/hooks/starter-preview-values.ts +60 -0
  101. package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/starter/hooks/use-starter-preview.dev.ts +18 -0
  102. package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/starter/hooks/use-starter-preview.ts +30 -0
  103. package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/starter/index.ts +6 -0
  104. package/examples/greenfield/module-hybrid-cdn-auth-full/src/hubspot/bootstrap.ts +16 -0
  105. package/examples/greenfield/module-hybrid-cdn-auth-full/src/hubspot/default-data.ts +7 -0
  106. package/examples/greenfield/module-hybrid-cdn-auth-full/src/hubspot/is-cms-editor.ts +78 -0
  107. package/examples/greenfield/module-hybrid-cdn-auth-full/src/hubspot/module-entry.tsx +9 -0
  108. package/examples/greenfield/module-hybrid-cdn-auth-full/src/hubspot/types.ts +17 -0
  109. package/examples/greenfield/module-hybrid-cdn-auth-full/src/integrations/configure-woodsportal-sdk.ts +98 -0
  110. package/examples/greenfield/module-hybrid-cdn-auth-full/src/integrations/tanstack-query/root-provider.tsx +18 -0
  111. package/examples/greenfield/module-hybrid-cdn-auth-full/src/integrations/tanstack-router/router.tsx +39 -0
  112. package/examples/greenfield/module-hybrid-cdn-auth-full/src/integrations/woodsportal-sdk.ts +37 -0
  113. package/examples/greenfield/module-hybrid-cdn-auth-full/src/main.tsx +5 -0
  114. package/examples/greenfield/module-hybrid-cdn-auth-full/src/portal/api-endpoint.ts +11 -0
  115. package/examples/greenfield/module-hybrid-cdn-auth-full/src/portal/ensure-portal-cdn-stylesheet.ts +115 -0
  116. package/examples/greenfield/module-hybrid-cdn-auth-full/src/portal/mount-dev-preview.tsx +12 -0
  117. package/examples/greenfield/module-hybrid-cdn-auth-full/src/portal/mount-portal.tsx +57 -0
  118. package/examples/greenfield/module-hybrid-cdn-auth-full/src/routeTree.gen.ts +182 -0
  119. package/examples/greenfield/module-hybrid-cdn-auth-full/src/routes/__root.tsx +12 -0
  120. package/examples/greenfield/module-hybrid-cdn-auth-full/src/routes/_app/portal.tsx +10 -0
  121. package/examples/greenfield/module-hybrid-cdn-auth-full/src/routes/_auth/login/sso.tsx +27 -0
  122. package/examples/greenfield/module-hybrid-cdn-auth-full/src/routes/_auth/login.tsx +10 -0
  123. package/examples/greenfield/module-hybrid-cdn-auth-full/src/routes/_auth/two-fa.tsx +91 -0
  124. package/examples/greenfield/module-hybrid-cdn-auth-full/src/routes/guide.tsx +9 -0
  125. package/examples/greenfield/module-hybrid-cdn-auth-full/src/routes/index.tsx +8 -0
  126. package/examples/greenfield/module-hybrid-cdn-auth-full/src/routes/unauthorized.tsx +50 -0
  127. package/examples/greenfield/module-hybrid-cdn-auth-full/src/utils/ValidationSchema.ts +1 -0
  128. package/examples/greenfield/module-hybrid-cdn-auth-full/src/utils/cookie.ts +23 -0
  129. package/examples/greenfield/module-hybrid-cdn-auth-full/src/utils/normalize-router-path.ts +10 -0
  130. package/examples/greenfield/module-hybrid-cdn-auth-full/src/utils/private-route.tsx +100 -0
  131. package/examples/greenfield/module-hybrid-cdn-auth-full/src/utils/public-route.tsx +64 -0
  132. package/examples/greenfield/module-hybrid-cdn-auth-full/src/utils/resolve-api-base-url.ts +44 -0
  133. package/examples/greenfield/module-hybrid-cdn-auth-full/src/utils/safe-router-navigation.ts +82 -0
  134. package/examples/greenfield/module-hybrid-cdn-auth-full/tsconfig.json +20 -0
  135. package/examples/greenfield/module-hybrid-cdn-auth-full/vite.config.cdn-app.mjs +1 -0
  136. package/examples/greenfield/module-hybrid-cdn-auth-full/vite.config.cdn-esm.mjs +1 -0
  137. package/examples/greenfield/module-hybrid-cdn-auth-full/vite.config.cdn-vendor.mjs +1 -0
  138. package/examples/greenfield/module-hybrid-cdn-auth-full/vite.config.dev.mjs +1 -0
  139. package/examples/greenfield/module-hybrid-cdn-auth-full/vite.config.hubspot.mjs +1 -0
  140. package/examples/greenfield/module-hybrid-cdn-auth-full/vite.config.monolith.mjs +8 -0
  141. package/examples/greenfield/module-hybrid-cdn-auth-full/woodscli.json +38 -0
  142. package/examples/greenfield/module-hybrid-cdn-auth-full/wphs.adapter.ts +7 -0
  143. package/examples/greenfield/module-hybrid-cdn-auth-sso/.env.dev.example +3 -0
  144. package/examples/greenfield/module-hybrid-cdn-auth-sso/.env.local.example +6 -0
  145. package/examples/greenfield/module-hybrid-cdn-auth-sso/.env.prod.example +5 -0
  146. package/examples/greenfield/module-hybrid-cdn-auth-sso/.env.stg.example +3 -0
  147. package/examples/greenfield/module-hybrid-cdn-auth-sso/.wphs/README.md +17 -0
  148. package/examples/greenfield/module-hybrid-cdn-auth-sso/README.md +60 -0
  149. package/examples/greenfield/module-hybrid-cdn-auth-sso/hubspot.config.yml.example +10 -0
  150. package/examples/greenfield/module-hybrid-cdn-auth-sso/index.html +12 -0
  151. package/examples/greenfield/module-hybrid-cdn-auth-sso/package.json +34 -0
  152. package/examples/greenfield/module-hybrid-cdn-auth-sso/portal.cdn.json.example +8 -0
  153. package/examples/greenfield/module-hybrid-cdn-auth-sso/public/fields.json +100 -0
  154. package/examples/greenfield/module-hybrid-cdn-auth-sso/public/meta.json +9 -0
  155. package/examples/greenfield/module-hybrid-cdn-auth-sso/public/module.html +38 -0
  156. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/app.tsx +180 -0
  157. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/assets/css/main.dev.css +23 -0
  158. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/assets/css/main.prod.css +12 -0
  159. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/assets/css/tailwind.css.config.css +7 -0
  160. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/cdn/app-entry.ts +48 -0
  161. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/cdn/vendor-entry.ts +28 -0
  162. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/components/Layouts/AppLayoutWrapper.tsx +38 -0
  163. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/components/Layouts/AuthLayout.tsx +7 -0
  164. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/components/Layouts/DefaultLayout.tsx +5 -0
  165. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/components/Layouts/MainLayout.tsx +42 -0
  166. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/components/Layouts/UnauthorizedLayout.tsx +5 -0
  167. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/components/Layouts/index.ts +13 -0
  168. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/components/layout/section-card.tsx +20 -0
  169. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/components/layout/starter-nav.tsx +28 -0
  170. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/components/layout/starter-shell.tsx +18 -0
  171. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/components/ui/Button.tsx +22 -0
  172. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/components/ui/Form.tsx +22 -0
  173. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/components/ui/Input.tsx +12 -0
  174. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/components/ui/Toaster.tsx +5 -0
  175. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/components/ui/auth/SsoLoginButton.tsx +34 -0
  176. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/components/ui/link.tsx +10 -0
  177. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/components/ui/loader/loader.tsx +18 -0
  178. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/config/auth-tier.ts +4 -0
  179. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/config/nav-scaffold.ts +4 -0
  180. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/config/portal-nav.ts +9 -0
  181. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/config/router.ts +1 -0
  182. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/config/routes.ts +10 -0
  183. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/data/hubSpotContext.ts +57 -0
  184. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/data/sync-sdk-hub-context.ts +44 -0
  185. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/defaultData.ts +2 -0
  186. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/env.ts +59 -0
  187. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/auth/app-bootstrap-guards.ts +14 -0
  188. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/auth/auth-navigation.ts +81 -0
  189. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/auth/auth-route-paths.ts +29 -0
  190. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/auth/auth-session-events.ts +2 -0
  191. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/auth/auth-session.ts +26 -0
  192. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/auth/clear-client-auth-session.ts +62 -0
  193. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/auth/components/starter-login.tsx +83 -0
  194. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/auth/login-email-storage.ts +64 -0
  195. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/auth/login-session-bridge.ts +72 -0
  196. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/auth/post-login-path.ts +17 -0
  197. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/auth/resolve-auth-login-error-message.ts +52 -0
  198. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/auth/resolve-post-login-path.ts +21 -0
  199. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/auth/sso-callback.ts +126 -0
  200. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/auth/use-portal-session.ts +25 -0
  201. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/auth/use-signed-in-email.ts +55 -0
  202. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/auth/use-sso-callback-handler.ts +92 -0
  203. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/starter/components/starter-guide-page.tsx +210 -0
  204. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/starter/components/starter-home-page.tsx +21 -0
  205. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/starter/components/starter-home.tsx +48 -0
  206. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/starter/components/starter-page-frame.tsx +25 -0
  207. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/starter/components/starter-protected-page.tsx +54 -0
  208. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/starter/components/starter-single-page.tsx +17 -0
  209. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/starter/content/auth-guide-content.ts +85 -0
  210. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/starter/content/woodscli-guide-content.ts +86 -0
  211. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/starter/hooks/starter-preview-values.ts +60 -0
  212. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/starter/hooks/use-starter-preview.dev.ts +18 -0
  213. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/starter/hooks/use-starter-preview.ts +30 -0
  214. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/starter/index.ts +6 -0
  215. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/hubspot/bootstrap.ts +16 -0
  216. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/hubspot/default-data.ts +7 -0
  217. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/hubspot/is-cms-editor.ts +78 -0
  218. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/hubspot/module-entry.tsx +9 -0
  219. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/hubspot/types.ts +17 -0
  220. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/integrations/configure-woodsportal-sdk.ts +94 -0
  221. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/integrations/tanstack-query/root-provider.tsx +18 -0
  222. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/integrations/tanstack-router/router.tsx +39 -0
  223. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/integrations/woodsportal-sdk.ts +37 -0
  224. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/main.tsx +5 -0
  225. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/portal/api-endpoint.ts +11 -0
  226. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/portal/ensure-portal-cdn-stylesheet.ts +115 -0
  227. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/portal/mount-dev-preview.tsx +12 -0
  228. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/portal/mount-portal.tsx +57 -0
  229. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/routeTree.gen.ts +155 -0
  230. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/routes/__root.tsx +12 -0
  231. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/routes/_app/portal.tsx +10 -0
  232. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/routes/_auth/login/sso.tsx +27 -0
  233. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/routes/_auth/login.tsx +10 -0
  234. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/routes/guide.tsx +9 -0
  235. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/routes/index.tsx +8 -0
  236. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/routes/unauthorized.tsx +50 -0
  237. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/utils/ValidationSchema.ts +1 -0
  238. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/utils/cookie.ts +23 -0
  239. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/utils/normalize-router-path.ts +10 -0
  240. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/utils/private-route.tsx +100 -0
  241. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/utils/public-route.tsx +64 -0
  242. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/utils/resolve-api-base-url.ts +44 -0
  243. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/utils/safe-router-navigation.ts +82 -0
  244. package/examples/greenfield/module-hybrid-cdn-auth-sso/tsconfig.json +20 -0
  245. package/examples/greenfield/module-hybrid-cdn-auth-sso/vite.config.cdn-app.mjs +1 -0
  246. package/examples/greenfield/module-hybrid-cdn-auth-sso/vite.config.cdn-esm.mjs +1 -0
  247. package/examples/greenfield/module-hybrid-cdn-auth-sso/vite.config.cdn-vendor.mjs +1 -0
  248. package/examples/greenfield/module-hybrid-cdn-auth-sso/vite.config.dev.mjs +1 -0
  249. package/examples/greenfield/module-hybrid-cdn-auth-sso/vite.config.hubspot.mjs +1 -0
  250. package/examples/greenfield/module-hybrid-cdn-auth-sso/vite.config.monolith.mjs +8 -0
  251. package/examples/greenfield/module-hybrid-cdn-auth-sso/woodscli.json +38 -0
  252. package/examples/greenfield/module-hybrid-cdn-auth-sso/wphs.adapter.ts +7 -0
  253. package/examples/greenfield/module-hybrid-cdn-auth-starter/.env.dev.example +3 -0
  254. package/examples/greenfield/module-hybrid-cdn-auth-starter/.env.local.example +6 -0
  255. package/examples/greenfield/module-hybrid-cdn-auth-starter/.env.prod.example +5 -0
  256. package/examples/greenfield/module-hybrid-cdn-auth-starter/.env.stg.example +3 -0
  257. package/examples/greenfield/module-hybrid-cdn-auth-starter/.wphs/README.md +17 -0
  258. package/examples/greenfield/module-hybrid-cdn-auth-starter/README.md +60 -0
  259. package/examples/greenfield/module-hybrid-cdn-auth-starter/hubspot.config.yml.example +10 -0
  260. package/examples/greenfield/module-hybrid-cdn-auth-starter/index.html +12 -0
  261. package/examples/greenfield/module-hybrid-cdn-auth-starter/package.json +34 -0
  262. package/examples/greenfield/module-hybrid-cdn-auth-starter/portal.cdn.json.example +8 -0
  263. package/examples/greenfield/module-hybrid-cdn-auth-starter/public/fields.json +100 -0
  264. package/examples/greenfield/module-hybrid-cdn-auth-starter/public/meta.json +9 -0
  265. package/examples/greenfield/module-hybrid-cdn-auth-starter/public/module.html +38 -0
  266. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/app.tsx +173 -0
  267. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/assets/css/main.dev.css +23 -0
  268. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/assets/css/main.prod.css +12 -0
  269. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/assets/css/tailwind.css.config.css +7 -0
  270. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/cdn/app-entry.ts +48 -0
  271. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/cdn/vendor-entry.ts +28 -0
  272. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/components/Layouts/AppLayoutWrapper.tsx +38 -0
  273. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/components/Layouts/AuthLayout.tsx +7 -0
  274. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/components/Layouts/DefaultLayout.tsx +5 -0
  275. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/components/Layouts/MainLayout.tsx +42 -0
  276. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/components/Layouts/UnauthorizedLayout.tsx +5 -0
  277. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/components/Layouts/index.ts +13 -0
  278. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/components/layout/section-card.tsx +20 -0
  279. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/components/layout/starter-nav.tsx +28 -0
  280. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/components/layout/starter-shell.tsx +18 -0
  281. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/components/ui/Button.tsx +22 -0
  282. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/components/ui/Form.tsx +22 -0
  283. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/components/ui/Input.tsx +12 -0
  284. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/components/ui/Toaster.tsx +5 -0
  285. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/components/ui/link.tsx +10 -0
  286. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/components/ui/loader/loader.tsx +18 -0
  287. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/config/auth-tier.ts +4 -0
  288. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/config/nav-scaffold.ts +4 -0
  289. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/config/portal-nav.ts +9 -0
  290. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/config/router.ts +1 -0
  291. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/config/routes.ts +10 -0
  292. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/data/hubSpotContext.ts +57 -0
  293. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/data/sync-sdk-hub-context.ts +44 -0
  294. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/defaultData.ts +2 -0
  295. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/env.ts +59 -0
  296. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/auth/app-bootstrap-guards.ts +14 -0
  297. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/auth/auth-navigation.ts +81 -0
  298. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/auth/auth-route-paths.ts +29 -0
  299. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/auth/auth-session-events.ts +2 -0
  300. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/auth/auth-session.ts +26 -0
  301. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/auth/clear-client-auth-session.ts +62 -0
  302. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/auth/components/starter-login.tsx +76 -0
  303. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/auth/login-email-storage.ts +64 -0
  304. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/auth/login-session-bridge.ts +72 -0
  305. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/auth/post-login-path.ts +17 -0
  306. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/auth/resolve-auth-login-error-message.ts +52 -0
  307. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/auth/resolve-post-login-path.ts +21 -0
  308. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/auth/use-portal-session.ts +25 -0
  309. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/auth/use-signed-in-email.ts +55 -0
  310. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/starter/components/starter-guide-page.tsx +218 -0
  311. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/starter/components/starter-home-page.tsx +21 -0
  312. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/starter/components/starter-home.tsx +48 -0
  313. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/starter/components/starter-page-frame.tsx +25 -0
  314. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/starter/components/starter-protected-page.tsx +54 -0
  315. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/starter/components/starter-single-page.tsx +17 -0
  316. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/starter/content/auth-guide-content.ts +91 -0
  317. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/starter/content/woodscli-guide-content.ts +86 -0
  318. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/starter/hooks/starter-preview-values.ts +60 -0
  319. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/starter/hooks/use-starter-preview.dev.ts +18 -0
  320. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/starter/hooks/use-starter-preview.ts +30 -0
  321. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/starter/index.ts +6 -0
  322. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/hubspot/bootstrap.ts +16 -0
  323. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/hubspot/default-data.ts +7 -0
  324. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/hubspot/is-cms-editor.ts +78 -0
  325. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/hubspot/module-entry.tsx +9 -0
  326. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/hubspot/types.ts +17 -0
  327. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/integrations/configure-woodsportal-sdk.ts +94 -0
  328. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/integrations/tanstack-query/root-provider.tsx +18 -0
  329. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/integrations/tanstack-router/router.tsx +39 -0
  330. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/integrations/woodsportal-sdk.ts +37 -0
  331. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/main.tsx +5 -0
  332. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/portal/api-endpoint.ts +11 -0
  333. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/portal/ensure-portal-cdn-stylesheet.ts +115 -0
  334. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/portal/mount-dev-preview.tsx +12 -0
  335. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/portal/mount-portal.tsx +52 -0
  336. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/routeTree.gen.ts +134 -0
  337. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/routes/__root.tsx +12 -0
  338. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/routes/_app/portal.tsx +10 -0
  339. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/routes/_auth/login.tsx +10 -0
  340. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/routes/guide.tsx +9 -0
  341. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/routes/index.tsx +8 -0
  342. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/routes/unauthorized.tsx +50 -0
  343. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/utils/ValidationSchema.ts +1 -0
  344. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/utils/cookie.ts +23 -0
  345. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/utils/normalize-router-path.ts +10 -0
  346. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/utils/private-route.tsx +100 -0
  347. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/utils/public-route.tsx +64 -0
  348. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/utils/resolve-api-base-url.ts +44 -0
  349. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/utils/safe-router-navigation.ts +82 -0
  350. package/examples/greenfield/module-hybrid-cdn-auth-starter/tsconfig.json +20 -0
  351. package/examples/greenfield/module-hybrid-cdn-auth-starter/vite.config.cdn-app.mjs +1 -0
  352. package/examples/greenfield/module-hybrid-cdn-auth-starter/vite.config.cdn-esm.mjs +1 -0
  353. package/examples/greenfield/module-hybrid-cdn-auth-starter/vite.config.cdn-vendor.mjs +1 -0
  354. package/examples/greenfield/module-hybrid-cdn-auth-starter/vite.config.dev.mjs +1 -0
  355. package/examples/greenfield/module-hybrid-cdn-auth-starter/vite.config.hubspot.mjs +1 -0
  356. package/examples/greenfield/module-hybrid-cdn-auth-starter/vite.config.monolith.mjs +8 -0
  357. package/examples/greenfield/module-hybrid-cdn-auth-starter/woodscli.json +38 -0
  358. package/examples/greenfield/module-hybrid-cdn-auth-starter/wphs.adapter.ts +7 -0
  359. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/.env.dev.example +3 -0
  360. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/.env.local.example +6 -0
  361. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/.env.prod.example +5 -0
  362. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/.env.stg.example +3 -0
  363. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/.wphs/README.md +17 -0
  364. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/README.md +60 -0
  365. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/hubspot.config.yml.example +10 -0
  366. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/index.html +12 -0
  367. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/package.json +34 -0
  368. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/portal.cdn.json.example +8 -0
  369. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/public/fields.json +100 -0
  370. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/public/meta.json +9 -0
  371. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/public/module.html +38 -0
  372. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/app.tsx +173 -0
  373. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/assets/css/main.dev.css +23 -0
  374. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/assets/css/main.prod.css +12 -0
  375. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/assets/css/tailwind.css.config.css +7 -0
  376. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/cdn/app-entry.ts +48 -0
  377. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/cdn/vendor-entry.ts +28 -0
  378. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/components/Layouts/AppLayoutWrapper.tsx +38 -0
  379. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/components/Layouts/AuthLayout.tsx +7 -0
  380. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/components/Layouts/DefaultLayout.tsx +5 -0
  381. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/components/Layouts/MainLayout.tsx +7 -0
  382. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/components/Layouts/UnauthorizedLayout.tsx +5 -0
  383. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/components/Layouts/index.ts +13 -0
  384. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/components/layout/portal-sidebar-nav.tsx +33 -0
  385. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/components/layout/portal-sidebar.tsx +139 -0
  386. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/components/layout/section-card.tsx +20 -0
  387. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/components/layout/starter-nav.tsx +28 -0
  388. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/components/layout/starter-shell.tsx +18 -0
  389. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/components/ui/Button.tsx +22 -0
  390. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/components/ui/Form.tsx +22 -0
  391. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/components/ui/Input.tsx +12 -0
  392. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/components/ui/Toaster.tsx +5 -0
  393. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/components/ui/link.tsx +10 -0
  394. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/components/ui/loader/loader.tsx +18 -0
  395. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/config/auth-tier.ts +4 -0
  396. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/config/nav-scaffold.ts +4 -0
  397. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/config/portal-nav.ts +9 -0
  398. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/config/router.ts +1 -0
  399. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/config/routes.ts +10 -0
  400. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/data/hubSpotContext.ts +57 -0
  401. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/data/sync-sdk-hub-context.ts +44 -0
  402. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/defaultData.ts +2 -0
  403. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/env.ts +59 -0
  404. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/auth/app-bootstrap-guards.ts +14 -0
  405. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/auth/auth-navigation.ts +81 -0
  406. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/auth/auth-route-paths.ts +29 -0
  407. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/auth/auth-session-events.ts +2 -0
  408. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/auth/auth-session.ts +26 -0
  409. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/auth/clear-client-auth-session.ts +62 -0
  410. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/auth/components/starter-login.tsx +76 -0
  411. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/auth/login-email-storage.ts +64 -0
  412. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/auth/login-session-bridge.ts +72 -0
  413. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/auth/post-login-path.ts +17 -0
  414. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/auth/resolve-auth-login-error-message.ts +52 -0
  415. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/auth/resolve-post-login-path.ts +21 -0
  416. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/auth/use-portal-session.ts +25 -0
  417. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/auth/use-signed-in-email.ts +55 -0
  418. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/starter/components/starter-guide-page.tsx +210 -0
  419. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/starter/components/starter-home-page.tsx +21 -0
  420. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/starter/components/starter-home.tsx +48 -0
  421. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/starter/components/starter-page-frame.tsx +25 -0
  422. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/starter/components/starter-protected-page.tsx +54 -0
  423. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/starter/components/starter-single-page.tsx +17 -0
  424. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/starter/content/auth-guide-content.ts +85 -0
  425. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/starter/content/woodscli-guide-content.ts +86 -0
  426. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/starter/hooks/starter-preview-values.ts +60 -0
  427. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/starter/hooks/use-starter-preview.dev.ts +18 -0
  428. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/starter/hooks/use-starter-preview.ts +30 -0
  429. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/starter/index.ts +6 -0
  430. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/hubspot/bootstrap.ts +16 -0
  431. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/hubspot/default-data.ts +7 -0
  432. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/hubspot/is-cms-editor.ts +78 -0
  433. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/hubspot/module-entry.tsx +9 -0
  434. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/hubspot/types.ts +17 -0
  435. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/integrations/configure-woodsportal-sdk.ts +94 -0
  436. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/integrations/tanstack-query/root-provider.tsx +18 -0
  437. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/integrations/tanstack-router/router.tsx +39 -0
  438. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/integrations/woodsportal-sdk.ts +37 -0
  439. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/main.tsx +5 -0
  440. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/portal/api-endpoint.ts +11 -0
  441. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/portal/ensure-portal-cdn-stylesheet.ts +115 -0
  442. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/portal/mount-dev-preview.tsx +12 -0
  443. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/portal/mount-portal.tsx +52 -0
  444. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/routeTree.gen.ts +134 -0
  445. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/routes/__root.tsx +12 -0
  446. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/routes/_app/portal.tsx +10 -0
  447. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/routes/_auth/login.tsx +10 -0
  448. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/routes/guide.tsx +9 -0
  449. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/routes/index.tsx +8 -0
  450. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/routes/unauthorized.tsx +50 -0
  451. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/utils/ValidationSchema.ts +1 -0
  452. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/utils/cookie.ts +23 -0
  453. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/utils/normalize-router-path.ts +10 -0
  454. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/utils/private-route.tsx +100 -0
  455. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/utils/public-route.tsx +64 -0
  456. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/utils/resolve-api-base-url.ts +44 -0
  457. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/utils/safe-router-navigation.ts +82 -0
  458. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/tsconfig.json +20 -0
  459. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/vite.config.cdn-app.mjs +1 -0
  460. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/vite.config.cdn-esm.mjs +1 -0
  461. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/vite.config.cdn-vendor.mjs +1 -0
  462. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/vite.config.dev.mjs +1 -0
  463. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/vite.config.hubspot.mjs +1 -0
  464. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/vite.config.monolith.mjs +8 -0
  465. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/woodscli.json +38 -0
  466. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/wphs.adapter.ts +7 -0
  467. package/examples/greenfield/module-monolith/public/fields.json +66 -16
  468. package/examples/greenfield/module-monolith/src/assets/css/main.dev.css +7 -0
  469. package/examples/greenfield/module-monolith/src/components/layout/starter-nav.tsx +12 -15
  470. package/examples/greenfield/module-monolith/src/config/starter-nav.ts +9 -0
  471. package/examples/greenfield/module-monolith/src/features/starter/components/starter-home.tsx +25 -29
  472. package/examples/greenfield/module-monolith/src/features/starter/hooks/starter-preview-values.ts +60 -0
  473. package/examples/greenfield/module-monolith/src/features/starter/hooks/use-starter-preview.dev.ts +18 -0
  474. package/examples/greenfield/module-monolith/src/features/starter/hooks/use-starter-preview.ts +20 -37
  475. package/examples/manifest.json +5 -1
  476. package/examples/smoke/run-matrix.mjs +37 -0
  477. package/package.json +30 -13
  478. package/scripts/audit-tdz-split.mjs +11 -5
  479. package/scripts/build-cdn.mjs +6 -5
  480. package/scripts/check-cdn-size-regression.mjs +49 -0
  481. package/scripts/check-hubspot-bundle-size.mjs +18 -4
  482. package/scripts/cli-smoke-helpers.mjs +150 -0
  483. package/scripts/dist-cli-pack-smoke.test.mjs +79 -0
  484. package/scripts/ensure-wphs-workspace.mjs +7 -2
  485. package/scripts/generate-cdn-bundle-breakdown.mjs +61 -0
  486. package/scripts/generate-cdn-manifest.mjs +23 -8
  487. package/scripts/portal-vite-env.mjs +76 -2
  488. package/scripts/publish-cdn-github.mjs +79 -14
  489. package/scripts/refresh-artifact-sizes.mjs +77 -0
  490. package/scripts/regenerate-route-tree.mjs +71 -0
  491. package/scripts/resolve-hubspot-cli.mjs +51 -0
  492. package/scripts/resolve-vite-config.mjs +2 -3
  493. package/scripts/resolve-vite-spawn.mjs +48 -0
  494. package/scripts/run-vite-build.mjs +5 -6
  495. package/scripts/sync-auth-scaffold-from-client.mjs +41 -0
  496. package/scripts/verify-cdn-api-endpoint.mjs +17 -10
  497. package/scripts/vite/create-dev-server-config.mjs +7 -1
  498. package/scripts/vite/portal-build-shared.js +62 -2
  499. package/scripts/watch-hubspot.mjs +11 -13
  500. package/scripts/wp-cli-smoke.test.mjs +263 -0
  501. package/src/dev/bootstrap-greenfield.tsx +6 -0
  502. package/src/dev-module-config/ui/module-config-overlay-side.ts +13 -3
  503. package/src/dev-module-config/ui/module-config-toolbar-theme.ts +12 -3
  504. package/templates/module-hybrid-cdn/README.md +2 -1
  505. package/templates/module-hybrid-cdn/public/fields.json +47 -7
  506. package/templates/module-hybrid-cdn/public/module.html +7 -6
  507. package/templates/module-hybrid-cdn/src/assets/css/main.dev.css +7 -0
  508. package/templates/module-hybrid-cdn/src/components/layout/starter-nav.tsx +12 -15
  509. package/templates/module-hybrid-cdn/src/config/starter-nav.ts +9 -0
  510. package/templates/module-hybrid-cdn/src/features/starter/components/starter-home.tsx +25 -27
  511. package/templates/module-hybrid-cdn/src/features/starter/hooks/starter-preview-values.ts +5 -5
  512. package/templates/module-monolith/README.md +2 -1
  513. package/templates/module-monolith/public/fields.json +49 -6
  514. package/templates/module-monolith/src/assets/css/main.dev.css +7 -0
  515. package/templates/module-monolith/src/components/layout/starter-nav.tsx +12 -15
  516. package/templates/module-monolith/src/config/starter-nav.ts +9 -0
  517. package/templates/module-monolith/src/features/starter/components/starter-home.tsx +25 -27
  518. package/templates/module-monolith/src/features/starter/hooks/starter-preview-values.ts +5 -5
  519. package/templates/scaffold/auth/_full/src/app.tsx +172 -0
  520. package/templates/scaffold/auth/_full/src/config/auth-tier.ts +4 -0
  521. package/templates/scaffold/auth/_full/src/features/auth/auth-session.ts +51 -0
  522. package/templates/scaffold/auth/_full/src/features/auth/components/starter-login.tsx +79 -0
  523. package/templates/scaffold/auth/_full/src/features/auth/login-session-bridge.ts +74 -0
  524. package/templates/scaffold/auth/_full/src/features/auth/mfa-otp-utils.ts +24 -0
  525. package/templates/scaffold/auth/_full/src/features/auth/mfa-pending-storage.ts +108 -0
  526. package/templates/scaffold/auth/_full/src/features/auth/mfa-portal.ts +45 -0
  527. package/templates/scaffold/auth/_full/src/features/auth/use-mfa-login-otp-flow.ts +78 -0
  528. package/templates/scaffold/auth/_full/src/routeTree.gen.ts +182 -0
  529. package/templates/scaffold/auth/_full/src/routes/_auth/two-fa.tsx +91 -0
  530. package/templates/scaffold/auth/_nav-sidebar/src/components/Layouts/MainLayout.tsx +7 -0
  531. package/templates/scaffold/auth/_nav-sidebar/src/components/layout/portal-sidebar-nav.tsx +33 -0
  532. package/templates/scaffold/auth/_nav-sidebar/src/components/layout/portal-sidebar.tsx +139 -0
  533. package/templates/scaffold/auth/_nav-top/src/components/Layouts/MainLayout.tsx +42 -0
  534. package/templates/scaffold/auth/_shared/src/app.tsx +178 -0
  535. package/templates/scaffold/auth/_shared/src/components/Layouts/AppLayoutWrapper.tsx +38 -0
  536. package/templates/scaffold/auth/_shared/src/components/Layouts/AuthLayout.tsx +7 -0
  537. package/templates/scaffold/auth/_shared/src/components/Layouts/DefaultLayout.tsx +5 -0
  538. package/templates/scaffold/auth/_shared/src/components/Layouts/UnauthorizedLayout.tsx +5 -0
  539. package/templates/scaffold/auth/_shared/src/components/Layouts/index.ts +13 -0
  540. package/templates/scaffold/auth/_shared/src/components/ui/Button.tsx +22 -0
  541. package/templates/scaffold/auth/_shared/src/components/ui/Form.tsx +22 -0
  542. package/templates/scaffold/auth/_shared/src/components/ui/Input.tsx +12 -0
  543. package/templates/scaffold/auth/_shared/src/components/ui/Toaster.tsx +5 -0
  544. package/templates/scaffold/auth/_shared/src/components/ui/link.tsx +10 -0
  545. package/templates/scaffold/auth/_shared/src/components/ui/loader/loader.tsx +18 -0
  546. package/templates/scaffold/auth/_shared/src/config/auth-tier.ts +4 -0
  547. package/templates/scaffold/auth/_shared/src/config/nav-scaffold.ts +4 -0
  548. package/templates/scaffold/auth/_shared/src/config/portal-nav.ts +9 -0
  549. package/templates/scaffold/auth/_shared/src/config/router.ts +1 -0
  550. package/templates/scaffold/auth/_shared/src/config/routes.ts +10 -0
  551. package/templates/scaffold/auth/_shared/src/data/hubSpotContext.ts +57 -0
  552. package/templates/scaffold/auth/_shared/src/data/sync-sdk-hub-context.ts +44 -0
  553. package/templates/scaffold/auth/_shared/src/env.ts +59 -0
  554. package/templates/scaffold/auth/_shared/src/features/auth/app-bootstrap-guards.ts +14 -0
  555. package/templates/scaffold/auth/_shared/src/features/auth/auth-navigation.ts +81 -0
  556. package/templates/scaffold/auth/_shared/src/features/auth/auth-route-paths.ts +29 -0
  557. package/templates/scaffold/auth/_shared/src/features/auth/auth-session-events.ts +2 -0
  558. package/templates/scaffold/auth/_shared/src/features/auth/auth-session.ts +26 -0
  559. package/templates/scaffold/auth/_shared/src/features/auth/clear-client-auth-session.ts +62 -0
  560. package/templates/scaffold/auth/_shared/src/features/auth/components/starter-login.tsx +83 -0
  561. package/templates/scaffold/auth/_shared/src/features/auth/login-email-storage.ts +64 -0
  562. package/templates/scaffold/auth/_shared/src/features/auth/login-session-bridge.ts +72 -0
  563. package/templates/scaffold/auth/_shared/src/features/auth/post-login-path.ts +17 -0
  564. package/templates/scaffold/auth/_shared/src/features/auth/resolve-auth-login-error-message.ts +52 -0
  565. package/templates/scaffold/auth/_shared/src/features/auth/resolve-post-login-path.ts +21 -0
  566. package/templates/scaffold/auth/_shared/src/features/auth/use-portal-session.ts +25 -0
  567. package/templates/scaffold/auth/_shared/src/features/auth/use-signed-in-email.ts +55 -0
  568. package/templates/scaffold/auth/_shared/src/features/starter/components/starter-guide-page.tsx +218 -0
  569. package/templates/scaffold/auth/_shared/src/features/starter/components/starter-page-frame.tsx +25 -0
  570. package/templates/scaffold/auth/_shared/src/features/starter/components/starter-protected-page.tsx +54 -0
  571. package/templates/scaffold/auth/_shared/src/features/starter/content/auth-guide-content.ts +91 -0
  572. package/templates/scaffold/auth/_shared/src/hubspot/is-cms-editor.ts +78 -0
  573. package/templates/scaffold/auth/_shared/src/integrations/configure-woodsportal-sdk.ts +100 -0
  574. package/templates/scaffold/auth/_shared/src/integrations/tanstack-query/root-provider.tsx +18 -0
  575. package/templates/scaffold/auth/_shared/src/integrations/tanstack-router/router.tsx +39 -0
  576. package/templates/scaffold/auth/_shared/src/integrations/woodsportal-sdk.ts +37 -0
  577. package/templates/scaffold/auth/_shared/src/portal/mount-portal.tsx +59 -0
  578. package/templates/scaffold/auth/_shared/src/routeTree.gen.ts +134 -0
  579. package/templates/scaffold/auth/_shared/src/routes/__root.tsx +12 -0
  580. package/templates/scaffold/auth/_shared/src/routes/_app/portal.tsx +10 -0
  581. package/templates/scaffold/auth/_shared/src/routes/_auth/login.tsx +10 -0
  582. package/templates/scaffold/auth/_shared/src/routes/guide.tsx +9 -0
  583. package/templates/scaffold/auth/_shared/src/routes/index.tsx +8 -0
  584. package/templates/scaffold/auth/_shared/src/routes/unauthorized.tsx +50 -0
  585. package/templates/scaffold/auth/_shared/src/utils/ValidationSchema.ts +1 -0
  586. package/templates/scaffold/auth/_shared/src/utils/cookie.ts +23 -0
  587. package/templates/scaffold/auth/_shared/src/utils/normalize-router-path.ts +10 -0
  588. package/templates/scaffold/auth/_shared/src/utils/private-route.tsx +100 -0
  589. package/templates/scaffold/auth/_shared/src/utils/public-route.tsx +64 -0
  590. package/templates/scaffold/auth/_shared/src/utils/resolve-api-base-url.ts +44 -0
  591. package/templates/scaffold/auth/_shared/src/utils/safe-router-navigation.ts +82 -0
  592. package/templates/scaffold/auth/_sso/src/app.tsx +180 -0
  593. package/templates/scaffold/auth/_sso/src/components/ui/auth/SsoLoginButton.tsx +34 -0
  594. package/templates/scaffold/auth/_sso/src/config/auth-tier.ts +4 -0
  595. package/templates/scaffold/auth/_sso/src/features/auth/components/starter-login.tsx +90 -0
  596. package/templates/scaffold/auth/_sso/src/features/auth/sso-callback.ts +126 -0
  597. package/templates/scaffold/auth/_sso/src/features/auth/use-sso-callback-handler.ts +92 -0
  598. package/templates/scaffold/auth/_sso/src/routeTree.gen.ts +155 -0
  599. package/templates/scaffold/auth/_sso/src/routes/_auth/login/sso.tsx +27 -0
  600. package/templates/scaffold/auth/nav-manifest.json +8 -0
  601. package/templates/scaffold/auth/sync-manifest.json +16 -0
  602. package/templates/scaffold/auth/tier-manifest.json +18 -0
  603. package/templates/scaffold/page/page-component-auth.tsx.tpl +10 -0
  604. package/templates/scaffold/page/page-component-plain.tsx.tpl +11 -0
  605. package/templates/scaffold/page/route-protected-auth.tsx.tpl +10 -0
  606. package/templates/scaffold/page/route-public-auth.tsx.tpl +9 -0
  607. package/templates/scaffold/page/route-public-plain.tsx.tpl +6 -0
  608. package/vite/vite.config.cdn-app.mjs +43 -30
  609. package/vite/vite.config.cdn-audit.mjs +34 -0
  610. package/vite/vite.config.cdn-esm.mjs +1 -1
  611. package/dist/chunk-4GOJPOW3.js.map +0 -1
  612. package/dist/chunk-S2YJDWZU.js.map +0 -1
  613. /package/dist/{exec-VY6BGWF3.js.map → exec-NS4GKXZS.js.map} +0 -0
package/dist/cli.js CHANGED
@@ -1,208 +1,43 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ DEFAULT_WOODSPORTAL_HS,
4
+ KIT_PACKAGE_NAME,
5
+ LEGACY_WOODSPORTAL_HS_FILE,
6
+ WOODSCLI_CONFIG_FILE,
7
+ WPHS_WORKSPACE_DIR,
8
+ WoodsPortalHsSchema,
9
+ findProjectRoot,
10
+ getKitRoot,
11
+ isModuleProject,
12
+ isThemeProject,
13
+ kitPath,
14
+ legacyConfigConflict,
15
+ navConfigFileExists,
16
+ normalizePagePath,
17
+ normalizePageSlug,
18
+ resolveProjectConfig,
19
+ scaffoldPage,
20
+ titleFromSlug,
21
+ usesCdnPipeline
22
+ } from "./chunk-4UFWXPQG.js";
2
23
  import {
3
24
  ExitCode,
4
25
  WpHsError,
5
26
  isWpHsError,
27
+ resolveHubSpotCliSpawn,
6
28
  runCommand,
7
29
  runCommandAsync,
8
- runNodeScript,
9
30
  runNodeScriptAsync,
10
31
  runNodeScriptLongRunning
11
- } from "./chunk-4GOJPOW3.js";
32
+ } from "./chunk-GXYUGXF5.js";
12
33
 
13
34
  // src/cli/program.ts
14
- import { readFileSync as readFileSync16 } from "fs";
15
- import { resolve as resolve23 } from "path";
16
35
  import { Command } from "commander";
17
36
 
18
37
  // src/lib/config.ts
19
- import { readFileSync as readFileSync2 } from "fs";
20
-
21
- // src/lib/config-files.ts
22
- import { existsSync } from "fs";
23
- import { resolve } from "path";
24
- var WOODSCLI_CONFIG_FILE = "woodscli.json";
25
- var LEGACY_WOODSPORTAL_HS_FILE = "woodsportal.hs.json";
26
- function envConfigPath(projectRoot) {
27
- const fromEnv = process.env.WOODSCLI_CONFIG?.trim() || process.env.WP_HS_CONFIG?.trim() || null;
28
- if (!fromEnv) return null;
29
- return resolve(projectRoot, fromEnv);
30
- }
31
- function legacyConfigPath(projectRoot) {
32
- return resolve(projectRoot, LEGACY_WOODSPORTAL_HS_FILE);
33
- }
34
- function woodscliConfigPath(projectRoot) {
35
- return resolve(projectRoot, WOODSCLI_CONFIG_FILE);
36
- }
37
- function legacyConfigConflict(projectRoot) {
38
- const hasWoodscli = existsSync(woodscliConfigPath(projectRoot));
39
- const hasLegacy = existsSync(legacyConfigPath(projectRoot));
40
- if (hasLegacy && hasWoodscli) return "duplicate";
41
- if (hasLegacy && !hasWoodscli) return "legacy-only";
42
- return null;
43
- }
44
- function resolveProjectConfig(projectRoot) {
45
- const envPath = envConfigPath(projectRoot);
46
- if (envPath) {
47
- if (!existsSync(envPath)) return null;
48
- return { path: envPath, fileName: WOODSCLI_CONFIG_FILE };
49
- }
50
- const path = woodscliConfigPath(projectRoot);
51
- if (existsSync(path)) {
52
- return { path, fileName: WOODSCLI_CONFIG_FILE };
53
- }
54
- return null;
55
- }
56
- function findProjectRoot(from = process.cwd()) {
57
- if (process.env.WP_HS_PROJECT_ROOT?.trim()) {
58
- return resolve(process.env.WP_HS_PROJECT_ROOT.trim());
59
- }
60
- let dir = resolve(from);
61
- while (true) {
62
- if (resolveProjectConfig(dir)) {
63
- return dir;
64
- }
65
- const parent = resolve(dir, "..");
66
- if (parent === dir) {
67
- return null;
68
- }
69
- dir = parent;
70
- }
71
- }
72
-
73
- // src/schema/woodsportal-hs.ts
74
- import { z } from "zod";
75
- var CONFIG_VERSION = 1;
76
- var WPHS_WORKSPACE_DIR = ".wphs";
77
- var WoodsPortalHsSchema = z.object({
78
- configVersion: z.number().int().default(CONFIG_VERSION),
79
- projectType: z.enum(["module-hybrid-cdn", "module-monolith", "theme-starter"]),
80
- module: z.object({
81
- name: z.string().min(1),
82
- uploadPath: z.string().min(1),
83
- outDir: z.string().default("dist/WoodsPortal.Module")
84
- }),
85
- theme: z.object({
86
- name: z.string().min(1),
87
- uploadPath: z.string().min(1),
88
- srcDir: z.string().default("src/theme"),
89
- themeJson: z.string().default("src/theme/theme.json")
90
- }).optional(),
91
- cdn: z.object({
92
- mode: z.enum(["jsdelivr", "custom", "local", "none"]).default("jsdelivr"),
93
- buildIdFile: z.string().default(".module-build-id"),
94
- mirror: z.string().default("portal.cdn.json")
95
- }).default({}),
96
- entries: z.object({
97
- app: z.string().default("src/cdn/app-entry.ts"),
98
- vendor: z.string().default("src/cdn/vendor-entry.ts"),
99
- hubspotBootstrap: z.string().default("src/hubspot/bootstrap.ts"),
100
- /** Monolith production IIFE entry (wp build --hubspot-only) */
101
- module: z.string().default("src/hubspot/module-entry.tsx"),
102
- localDev: z.string().default("src/main.tsx")
103
- }).default({}),
104
- env: z.object({
105
- tiers: z.array(z.enum(["dev", "stg", "prod"])).default(["dev", "stg", "prod"])
106
- }).default({}),
107
- dev: z.object({
108
- moduleConfigDir: z.literal(WPHS_WORKSPACE_DIR).default(WPHS_WORKSPACE_DIR),
109
- adapter: z.string().default("./wphs.adapter.ts"),
110
- fieldsJson: z.string().default("public/fields.json"),
111
- themeFieldsJson: z.string().optional()
112
- }).default({}),
113
- scaffold: z.object({
114
- /** hash = woodsportal-client-frontend (HubSpot CMS); browser = local clean URLs; none = single page */
115
- router: z.enum(["hash", "browser", "none"]).default("hash")
116
- }).optional()
117
- });
118
- var DEFAULT_WOODSPORTAL_HS = {
119
- configVersion: CONFIG_VERSION,
120
- projectType: "module-hybrid-cdn",
121
- module: {
122
- name: "woodsportal-module",
123
- uploadPath: "dw-modules/woodsportal-module.module",
124
- outDir: "dist/WoodsPortal.Module"
125
- },
126
- cdn: {
127
- mode: "jsdelivr",
128
- buildIdFile: ".module-build-id",
129
- mirror: "portal.cdn.json"
130
- },
131
- entries: {
132
- app: "src/cdn/app-entry.ts",
133
- vendor: "src/cdn/vendor-entry.ts",
134
- hubspotBootstrap: "src/hubspot/bootstrap.ts",
135
- module: "src/hubspot/module-entry.tsx",
136
- localDev: "src/main.tsx"
137
- },
138
- env: {
139
- tiers: ["dev", "stg", "prod"]
140
- },
141
- dev: {
142
- moduleConfigDir: WPHS_WORKSPACE_DIR,
143
- adapter: "./wphs.adapter.ts",
144
- fieldsJson: "public/fields.json"
145
- }
146
- };
147
- function isModuleProject(config) {
148
- return config.projectType === "module-hybrid-cdn" || config.projectType === "module-monolith";
149
- }
150
- function isThemeProject(config) {
151
- return config.projectType === "theme-starter";
152
- }
153
- function usesCdnPipeline(config) {
154
- return config.projectType === "module-hybrid-cdn" && config.cdn.mode !== "none";
155
- }
156
-
157
- // src/lib/paths.ts
158
- import { existsSync as existsSync2, readFileSync } from "fs";
159
- import { dirname, resolve as resolve2 } from "path";
160
- import { fileURLToPath } from "url";
161
- var KIT_PACKAGE_NAME = "@woodsportal/hubspot-kit";
162
- function resolveKitRoot() {
163
- let dir = dirname(fileURLToPath(import.meta.url));
164
- while (true) {
165
- const pkgPath = resolve2(dir, "package.json");
166
- if (existsSync2(pkgPath)) {
167
- try {
168
- const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
169
- if (pkg.name === KIT_PACKAGE_NAME) {
170
- return dir;
171
- }
172
- } catch {
173
- }
174
- }
175
- const parent = dirname(dir);
176
- if (parent === dir) {
177
- break;
178
- }
179
- dir = parent;
180
- }
181
- throw new WpHsError("Could not resolve @woodsportal/hubspot-kit package root.", {
182
- exitCode: ExitCode.INTERNAL
183
- });
184
- }
185
- var kitRoot = resolveKitRoot();
186
- function getKitRoot() {
187
- return kitRoot;
188
- }
189
- function findProjectRoot2(from = process.cwd()) {
190
- const resolved = findProjectRoot(from);
191
- if (!resolved) {
192
- throw new WpHsError("Could not find woodscli.json in this directory or any parent.", {
193
- exitCode: ExitCode.USER_ERROR,
194
- remediation: "Run wp init, or pass --project <path>. Legacy config: wp migrate --yes."
195
- });
196
- }
197
- return resolved;
198
- }
199
- function kitPath(...segments) {
200
- return resolve2(kitRoot, ...segments);
201
- }
202
-
203
- // src/lib/config.ts
38
+ import { readFileSync } from "fs";
204
39
  function loadConfig(projectRoot) {
205
- const root = projectRoot ?? findProjectRoot2();
40
+ const root = projectRoot ?? findProjectRoot();
206
41
  const resolved = resolveProjectConfig(root);
207
42
  if (!resolved) {
208
43
  throw new WpHsError(`Missing ${WOODSCLI_CONFIG_FILE}`, {
@@ -212,7 +47,7 @@ function loadConfig(projectRoot) {
212
47
  }
213
48
  let raw;
214
49
  try {
215
- raw = JSON.parse(readFileSync2(resolved.path, "utf8"));
50
+ raw = JSON.parse(readFileSync(resolved.path, "utf8"));
216
51
  } catch (cause) {
217
52
  throw new WpHsError(`Invalid JSON in ${resolved.fileName}`, {
218
53
  exitCode: ExitCode.USER_ERROR,
@@ -294,20 +129,171 @@ function createLogger(options = {}) {
294
129
  };
295
130
  }
296
131
 
132
+ // src/lib/kit-version-check.ts
133
+ import { existsSync, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
134
+ import { homedir } from "os";
135
+ import { dirname, join } from "path";
136
+ import pc2 from "picocolors";
137
+ import semver from "semver";
138
+ var NPM_REGISTRY_URL = "https://registry.npmjs.org/@woodsportal%2Fhubspot-kit";
139
+ var VERSION_CHECK_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
140
+ var VERSION_CHECK_FETCH_TIMEOUT_MS = 2500;
141
+ function getVersionCheckCachePath() {
142
+ if (process.env.WP_VERSION_CHECK_CACHE) {
143
+ return process.env.WP_VERSION_CHECK_CACHE;
144
+ }
145
+ const base = process.env.XDG_CACHE_HOME ? join(process.env.XDG_CACHE_HOME, "woodsportal") : join(homedir(), ".cache", "woodsportal");
146
+ return join(base, "wp-kit-version.json");
147
+ }
148
+ function readVersionCheckCache(now = Date.now()) {
149
+ const cachePath = getVersionCheckCachePath();
150
+ if (!existsSync(cachePath)) {
151
+ return null;
152
+ }
153
+ try {
154
+ const parsed = JSON.parse(readFileSync2(cachePath, "utf8"));
155
+ if (!parsed.latest || typeof parsed.fetchedAt !== "number") {
156
+ return null;
157
+ }
158
+ if (now - parsed.fetchedAt > VERSION_CHECK_CACHE_TTL_MS) {
159
+ return null;
160
+ }
161
+ return parsed;
162
+ } catch {
163
+ return null;
164
+ }
165
+ }
166
+ function writeVersionCheckCache(cache) {
167
+ const cachePath = getVersionCheckCachePath();
168
+ mkdirSync(dirname(cachePath), { recursive: true });
169
+ writeFileSync(cachePath, `${JSON.stringify(cache)}
170
+ `, "utf8");
171
+ }
172
+ async function fetchLatestKitVersion() {
173
+ try {
174
+ const response = await fetch(NPM_REGISTRY_URL, {
175
+ signal: AbortSignal.timeout(VERSION_CHECK_FETCH_TIMEOUT_MS),
176
+ headers: { accept: "application/json" }
177
+ });
178
+ if (!response.ok) {
179
+ return null;
180
+ }
181
+ const body = await response.json();
182
+ const latest = body["dist-tags"]?.latest;
183
+ return typeof latest === "string" && latest.length > 0 ? latest : null;
184
+ } catch {
185
+ return null;
186
+ }
187
+ }
188
+ async function resolveLatestKitVersion(now = Date.now()) {
189
+ const cached = readVersionCheckCache(now);
190
+ if (cached) {
191
+ return cached.latest;
192
+ }
193
+ const latest = await fetchLatestKitVersion();
194
+ if (!latest) {
195
+ return null;
196
+ }
197
+ writeVersionCheckCache({ latest, fetchedAt: now });
198
+ return latest;
199
+ }
200
+ function isKitUpdateAvailable(current, latest) {
201
+ const currentCoerced = semver.coerce(current);
202
+ const latestCoerced = semver.coerce(latest);
203
+ if (!currentCoerced || !latestCoerced) {
204
+ return false;
205
+ }
206
+ return semver.lt(currentCoerced, latestCoerced);
207
+ }
208
+ function formatKitUpgradeCommand(projectRoot) {
209
+ const root = projectRoot || process.cwd();
210
+ const spec = `${KIT_PACKAGE_NAME}@latest`;
211
+ if (existsSync(join(root, "yarn.lock"))) {
212
+ return `yarn add -D ${spec}`;
213
+ }
214
+ if (existsSync(join(root, "pnpm-lock.yaml"))) {
215
+ return `pnpm add -D ${spec}`;
216
+ }
217
+ return `npm install -D ${spec}`;
218
+ }
219
+ function shouldUseWarnColor() {
220
+ if (process.env.NO_COLOR !== void 0) return false;
221
+ if (process.env.CI === "true") return false;
222
+ return process.stderr.isTTY === true;
223
+ }
224
+ function shouldSkipVersionCheck(options) {
225
+ if (options.ci || process.env.CI === "true") return true;
226
+ if (options.json || options.quiet) return true;
227
+ if (process.env.WP_SKIP_VERSION_CHECK === "1") return true;
228
+ return false;
229
+ }
230
+ function formatKitUpdateWarning(current, latest, projectRoot) {
231
+ const upgrade = formatKitUpgradeCommand(projectRoot);
232
+ const useColor = shouldUseWarnColor();
233
+ const warn = useColor ? pc2.yellow("warn") : "warn";
234
+ const dim = useColor ? pc2.dim : (text5) => text5;
235
+ return [
236
+ `[${warn}] ${KIT_PACKAGE_NAME} v${current} is installed; latest is v${latest}.`,
237
+ `${dim(" ")}Upgrade: ${upgrade}`
238
+ ].join("\n");
239
+ }
240
+ async function maybeWarnKitUpdate(options) {
241
+ try {
242
+ if (shouldSkipVersionCheck(options)) {
243
+ return;
244
+ }
245
+ const latest = await resolveLatestKitVersion();
246
+ if (!latest || !isKitUpdateAvailable(options.current, latest)) {
247
+ return;
248
+ }
249
+ process.stderr.write(`${formatKitUpdateWarning(options.current, latest, options.projectRoot)}
250
+ `);
251
+ } catch {
252
+ }
253
+ }
254
+
255
+ // src/lib/kit-version.ts
256
+ import { readFileSync as readFileSync3 } from "fs";
257
+ import { resolve } from "path";
258
+ var cachedVersion = null;
259
+ function readKitVersion() {
260
+ if (cachedVersion) {
261
+ return cachedVersion;
262
+ }
263
+ try {
264
+ const pkg = JSON.parse(readFileSync3(resolve(getKitRoot(), "package.json"), "utf8"));
265
+ cachedVersion = pkg.version ?? "0.0.0";
266
+ } catch {
267
+ cachedVersion = "0.0.0";
268
+ }
269
+ return cachedVersion;
270
+ }
271
+
297
272
  // src/lib/wphs-baseline.ts
298
- import { resolve as resolve3 } from "path";
273
+ import { resolve as resolve2 } from "path";
299
274
  function defaultBaselineDir(projectRoot, config) {
300
275
  const workspace = config.dev.moduleConfigDir ?? WPHS_WORKSPACE_DIR;
301
- return resolve3(projectRoot, workspace, "audit", "baseline");
276
+ return resolve2(projectRoot, workspace, "audit", "baseline");
277
+ }
278
+
279
+ // src/lib/exit-cli.ts
280
+ async function exitCli(code) {
281
+ await new Promise((resolve23) => {
282
+ setImmediate(resolve23);
283
+ });
284
+ await new Promise((resolve23) => {
285
+ setTimeout(resolve23, 0);
286
+ });
287
+ process.exit(code);
302
288
  }
303
289
 
304
290
  // src/cli/commands/doctor.ts
305
- import { existsSync as existsSync7, readFileSync as readFileSync5 } from "fs";
306
- import { join, resolve as resolve8 } from "path";
291
+ import { existsSync as existsSync8, readFileSync as readFileSync8 } from "fs";
292
+ import { join as join2, resolve as resolve9 } from "path";
307
293
 
308
294
  // src/lib/cli-spinner.ts
309
295
  import * as p from "@clack/prompts";
310
- import pc2 from "picocolors";
296
+ import pc3 from "picocolors";
311
297
  function isInteractiveCli(ctx) {
312
298
  const { json, quiet, ci } = ctx.global;
313
299
  return !json && !quiet && !ci && process.stdout.isTTY === true;
@@ -321,11 +307,11 @@ function isVerboseStepLog(ctx) {
321
307
  return ctx.global.verbose === true && !ctx.global.quiet && !ctx.global.json;
322
308
  }
323
309
  async function yieldForSpinnerPaint() {
324
- await new Promise((resolve24) => {
325
- setImmediate(resolve24);
310
+ await new Promise((resolve23) => {
311
+ setImmediate(resolve23);
326
312
  });
327
- await new Promise((resolve24) => {
328
- setTimeout(resolve24, 48);
313
+ await new Promise((resolve23) => {
314
+ setTimeout(resolve23, 48);
329
315
  });
330
316
  }
331
317
  async function runCliTasks(ctx, tasks, options = {}) {
@@ -372,7 +358,7 @@ async function runCliTasks(ctx, tasks, options = {}) {
372
358
  await step.task();
373
359
  spinner2.stop(step.title);
374
360
  } catch (error) {
375
- spinner2.stop(pc2.red(step.title));
361
+ spinner2.stop(pc3.red(step.title));
376
362
  throw error;
377
363
  }
378
364
  }
@@ -384,7 +370,7 @@ function shouldQuietSubprocess(ctx, options = {}) {
384
370
  }
385
371
  function printCommandDone(ctx, message) {
386
372
  if (isInteractiveCli(ctx)) {
387
- process.stdout.write(`${pc2.green("\u25C6")} ${message}
373
+ process.stdout.write(`${pc3.green("\u25C6")} ${message}
388
374
  `);
389
375
  return;
390
376
  }
@@ -392,8 +378,8 @@ function printCommandDone(ctx, message) {
392
378
  }
393
379
 
394
380
  // src/lib/hubspot-cli-setup.ts
395
- import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
396
- import { resolve as resolve4 } from "path";
381
+ import { existsSync as existsSync2, readFileSync as readFileSync4 } from "fs";
382
+ import { resolve as resolve3 } from "path";
397
383
  import { spawnSync } from "child_process";
398
384
  var HUBSPOT_CONFIG_FILE = "hubspot.config.yml";
399
385
  var HUBSPOT_CONFIG_EXAMPLE = "hubspot.config.yml.example";
@@ -406,12 +392,14 @@ var PLACEHOLDER_PATTERNS = [
406
392
  /portalId:\s*12345678\b/
407
393
  ];
408
394
  function spawnHs(args, options = {}) {
409
- return spawnSync("hs", args, {
395
+ const spawn = resolveHubSpotCliSpawn(options.cwd);
396
+ return spawnSync(spawn.command, [...spawn.prefixArgs, ...args], {
410
397
  cwd: options.cwd,
411
398
  encoding: "utf8",
412
399
  timeout: options.timeoutMs ?? HS_ACCOUNTS_TIMEOUT_MS,
413
400
  // Keep HubSpot CLI off the interactive TTY (avoids hangs under @clack spinners)
414
- stdio: ["ignore", "pipe", "pipe"]
401
+ stdio: ["ignore", "pipe", "pipe"],
402
+ shell: spawn.shell
415
403
  });
416
404
  }
417
405
  function hsTimedOut(result) {
@@ -419,13 +407,13 @@ function hsTimedOut(result) {
419
407
  return err?.code === "ETIMEDOUT";
420
408
  }
421
409
  function assessHubSpotConfigFiles(projectRoot) {
422
- const configPath = resolve4(projectRoot, HUBSPOT_CONFIG_FILE);
423
- const examplePath = resolve4(projectRoot, HUBSPOT_CONFIG_EXAMPLE);
424
- const configPresent = existsSync3(configPath);
425
- const examplePresent = existsSync3(examplePath);
410
+ const configPath = resolve3(projectRoot, HUBSPOT_CONFIG_FILE);
411
+ const examplePath = resolve3(projectRoot, HUBSPOT_CONFIG_EXAMPLE);
412
+ const configPresent = existsSync2(configPath);
413
+ const examplePresent = existsSync2(examplePath);
426
414
  let looksLikePlaceholder = false;
427
415
  if (configPresent) {
428
- const raw = readFileSync3(configPath, "utf8");
416
+ const raw = readFileSync4(configPath, "utf8");
429
417
  looksLikePlaceholder = PLACEHOLDER_PATTERNS.some((pattern) => pattern.test(raw));
430
418
  } else {
431
419
  looksLikePlaceholder = true;
@@ -551,7 +539,7 @@ function doctorHubSpotChecks(projectRoot) {
551
539
  ok: assessment.hsCliInstalled,
552
540
  message: assessment.hsCliInstalled ? `HubSpot CLI ${assessment.hsCliVersion ?? ""}`.trim() : "HubSpot CLI not installed",
553
541
  remediation: assessment.hsCliInstalled ? void 0 : "npm i -g @hubspot/cli",
554
- severity: "error"
542
+ severity: "warn"
555
543
  },
556
544
  {
557
545
  name: "hubspot-config",
@@ -570,136 +558,352 @@ function doctorHubSpotChecks(projectRoot) {
570
558
  ];
571
559
  }
572
560
 
573
- // src/lib/template-dev-deps.ts
574
- var VITE_DEV_PEER_DEPS = {
575
- vite: "^6.1.0",
576
- "@vitejs/plugin-react": "^4.3.4",
577
- "babel-plugin-react-compiler": "^1.0.0",
578
- "@tailwindcss/vite": "^4.0.6",
579
- "@tanstack/router-plugin": "^1.121.2",
580
- "@tanstack/react-router": "^1.121.2",
581
- "@dnd-kit/core": "^6.3.1",
582
- "@dnd-kit/sortable": "^10.0.0",
583
- "@dnd-kit/utilities": "^3.2.2",
584
- react: "^19.0.0",
585
- "react-dom": "^19.0.0",
586
- tailwindcss: "^4.0.6"
587
- };
588
- var KIT_DEV_DEP = "@woodsportal/hubspot-kit";
589
- var KIT_DEV_DEP_VERSION = "^1.0.30";
590
- var MODULE_CONFIG_UI_DEPS = [
591
- "@dnd-kit/core",
592
- "@dnd-kit/sortable",
593
- "@dnd-kit/utilities"
594
- ];
595
- function templateDevDependencies() {
596
- return {
597
- [KIT_DEV_DEP]: KIT_DEV_DEP_VERSION,
598
- ...VITE_DEV_PEER_DEPS
599
- };
600
- }
561
+ // src/lib/cdn-github-setup.ts
562
+ import { existsSync as existsSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
563
+ import { resolve as resolve5 } from "path";
564
+ import * as p2 from "@clack/prompts";
601
565
 
602
- // src/lib/hubspot-fields-json.ts
603
- var HUBSPOT_MODULE_FIELD_TYPES = /* @__PURE__ */ new Set([
604
- "boolean",
605
- "choice",
606
- "color",
607
- "cta",
608
- "date",
609
- "datetime",
610
- "email",
611
- "embed",
612
- "file",
613
- "followup_email",
614
- "font",
615
- "form",
616
- "group",
617
- "html",
618
- "hubdb_row",
619
- "hubdb_table",
620
- "icon",
621
- "image",
622
- "link",
623
- "logo",
624
- "meeting",
625
- "menu",
626
- "number",
627
- "page",
628
- "payment",
629
- "richtext",
630
- "salesforce_campaign",
631
- "simple_menu",
632
- "spacing",
633
- "tag",
634
- "text",
635
- "url",
636
- "video",
637
- "workflow"
638
- ]);
639
- function isFieldRecord(value) {
640
- return typeof value === "object" && value !== null && !Array.isArray(value);
641
- }
642
- function validateFieldNode(node, path, issues) {
643
- if (!isFieldRecord(node)) {
644
- issues.push({ path, message: "expected a field object" });
645
- return;
646
- }
647
- const type = node.type;
648
- if (typeof type !== "string" || type.length === 0) {
649
- issues.push({ path: `${path}.type`, message: "missing field type" });
650
- } else if (!HUBSPOT_MODULE_FIELD_TYPES.has(type)) {
651
- issues.push({
652
- path: `${path}.type`,
653
- message: type === "textarea" ? `'textarea' is not a HubSpot field type \u2014 use "type": "text" with "display": "textarea"` : `'${type}' is not a valid HubSpot field type`
654
- });
655
- }
656
- const tab = node.tab;
657
- if (tab === "STYLE" && type !== "group") {
658
- issues.push({
659
- path: `${path}.tab`,
660
- message: 'STYLE tab fields must live inside a "type": "group" with "tab": "STYLE" (HubSpot page editor Styles tab)'
661
- });
662
- }
663
- if (type !== "group") {
664
- const name = node.name;
665
- if (typeof name !== "string" || name.trim().length === 0) {
666
- issues.push({ path: `${path}.name`, message: "missing field name" });
667
- }
668
- const label = node.label;
669
- if (typeof label !== "string" || label.trim().length === 0) {
670
- issues.push({ path: `${path}.label`, message: "missing field label" });
566
+ // src/lib/project-env-files.ts
567
+ import { existsSync as existsSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
568
+ import { resolve as resolve4 } from "path";
569
+ var ENV_LOCAL_FILE = ".env.local";
570
+ var ENV_GITIGNORE_LINES = [".env", ".env.*", "!.env*.example", ENV_LOCAL_FILE];
571
+ function parseEnvFile(content) {
572
+ const result = {};
573
+ for (const line of content.split("\n")) {
574
+ const trimmed = line.trim();
575
+ if (!trimmed || trimmed.startsWith("#")) continue;
576
+ const eq = trimmed.indexOf("=");
577
+ if (eq <= 0) continue;
578
+ const key = trimmed.slice(0, eq).trim();
579
+ let value = trimmed.slice(eq + 1).trim();
580
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
581
+ value = value.slice(1, -1);
671
582
  }
672
- } else {
673
- const children = node.children;
674
- if (!Array.isArray(children)) {
675
- issues.push({ path: `${path}.children`, message: "group fields require a children array" });
583
+ result[key] = value;
584
+ }
585
+ return result;
586
+ }
587
+ function readEnvLocal(projectRoot) {
588
+ const path = resolve4(projectRoot, ENV_LOCAL_FILE);
589
+ if (!existsSync3(path)) return {};
590
+ return parseEnvFile(readFileSync5(path, "utf8"));
591
+ }
592
+ function formatEnvLocal(entries) {
593
+ const lines = [
594
+ "# Local secrets \u2014 gitignored. Created by wp init / wp setup (hybrid CDN).",
595
+ "# PORTAL_CDN_PUBLISH_TOKEN needs repo push on your portal.cdn.json mirror.",
596
+ ""
597
+ ];
598
+ for (const [key, value] of Object.entries(entries)) {
599
+ if (value.includes(" ") || value.includes("#")) {
600
+ lines.push(`${key}="${value.replace(/"/g, '\\"')}"`);
676
601
  } else {
677
- children.forEach((child, index) => {
678
- validateFieldNode(child, `${path}.children[${index}]`, issues);
679
- });
602
+ lines.push(`${key}=${value}`);
680
603
  }
681
604
  }
605
+ return `${lines.join("\n").trimEnd()}
606
+ `;
682
607
  }
683
- function validateHubSpotFieldsJson(fields) {
684
- if (!Array.isArray(fields)) {
685
- return [{ path: "$", message: "fields.json must be a top-level array" }];
686
- }
687
- const issues = [];
688
- fields.forEach((field, index) => {
608
+ function writeEnvLocal(projectRoot, entries) {
609
+ const path = resolve4(projectRoot, ENV_LOCAL_FILE);
610
+ const existing = existsSync3(path) ? parseEnvFile(readFileSync5(path, "utf8")) : {};
611
+ writeFileSync2(path, formatEnvLocal({ ...existing, ...entries }));
612
+ }
613
+ function mergeProjectEnv(projectRoot, base = process.env) {
614
+ const local = readEnvLocal(projectRoot);
615
+ const merged = { ...base };
616
+ for (const [key, value] of Object.entries(local)) {
617
+ if (merged[key] == null || merged[key] === "") {
618
+ merged[key] = value;
619
+ }
620
+ }
621
+ return merged;
622
+ }
623
+ function ensureEnvFilesGitignore(projectRoot) {
624
+ const gitignorePath = resolve4(projectRoot, ".gitignore");
625
+ const existing = existsSync3(gitignorePath) ? readFileSync5(gitignorePath, "utf8") : "";
626
+ const toAppend = ENV_GITIGNORE_LINES.filter((line) => !existing.includes(line)).join("\n");
627
+ if (!toAppend) return;
628
+ writeFileSync2(gitignorePath, `${existing.trimEnd()}
629
+
630
+ # Env files (secrets)
631
+ ${toAppend}
632
+ `);
633
+ }
634
+
635
+ // src/lib/cdn-github-setup.ts
636
+ var PORTAL_CDN_PUBLISH_TOKEN_ENV = "PORTAL_CDN_PUBLISH_TOKEN";
637
+ var PORTAL_CDN_JSON = "portal.cdn.json";
638
+ var PLACEHOLDER_GITHUB = /your-org|your-portal-cdn-mirror/i;
639
+ function isPlaceholderGithubSlug(github) {
640
+ return PLACEHOLDER_GITHUB.test(github);
641
+ }
642
+ function defaultPortalCdnJson() {
643
+ const examplePath = kitPath("templates", "shared", "portal.cdn.json.example");
644
+ if (existsSync4(examplePath)) {
645
+ return JSON.parse(readFileSync6(examplePath, "utf8"));
646
+ }
647
+ return {
648
+ publishMode: "jsdelivr",
649
+ github: "your-org/your-portal-cdn-mirror",
650
+ gitRemote: "https://github.com/your-org/your-portal-cdn-mirror.git",
651
+ cdnPathInRepo: "dist/cdn",
652
+ defaultBranch: "main"
653
+ };
654
+ }
655
+ function readPortalCdnJson(projectRoot) {
656
+ const path = resolve5(projectRoot, PORTAL_CDN_JSON);
657
+ if (!existsSync4(path)) return null;
658
+ try {
659
+ return JSON.parse(readFileSync6(path, "utf8"));
660
+ } catch {
661
+ return null;
662
+ }
663
+ }
664
+ function normalizeGithubSlug(input) {
665
+ const trimmed = input.trim().replace(/^https:\/\/github\.com\//, "").replace(/\.git$/, "");
666
+ const parts = trimmed.split("/").filter(Boolean);
667
+ if (parts.length !== 2) return null;
668
+ return `${parts[0]}/${parts[1]}`;
669
+ }
670
+ function portalCdnJsonFromGithub(github, base) {
671
+ const slug = normalizeGithubSlug(github);
672
+ if (!slug) {
673
+ throw new Error(`Invalid GitHub repo \u2014 use org/repo (got "${github}")`);
674
+ }
675
+ return {
676
+ publishMode: base?.publishMode ?? "jsdelivr",
677
+ github: slug,
678
+ gitRemote: `https://github.com/${slug}.git`,
679
+ cdnPathInRepo: base?.cdnPathInRepo ?? "dist/cdn",
680
+ defaultBranch: base?.defaultBranch ?? "main"
681
+ };
682
+ }
683
+ function hasCdnPublishToken(projectRoot) {
684
+ if (process.env[PORTAL_CDN_PUBLISH_TOKEN_ENV]?.trim()) return true;
685
+ if (process.env.GITHUB_TOKEN?.trim()) return true;
686
+ return Boolean(readEnvLocal(projectRoot)[PORTAL_CDN_PUBLISH_TOKEN_ENV]?.trim());
687
+ }
688
+ function hasConfiguredCdnGithubMirror(projectRoot) {
689
+ const config = readPortalCdnJson(projectRoot);
690
+ const github = config?.github ?? "";
691
+ return Boolean(github) && !PLACEHOLDER_GITHUB.test(github);
692
+ }
693
+ function githubDefaultForPrompt(projectRoot) {
694
+ const existing = readPortalCdnJson(projectRoot);
695
+ if (existing?.github && !PLACEHOLDER_GITHUB.test(existing.github)) {
696
+ return existing.github;
697
+ }
698
+ return defaultPortalCdnJson().github;
699
+ }
700
+ function needsCdnGitHubSetup(projectRoot) {
701
+ return !hasConfiguredCdnGithubMirror(projectRoot) || !hasCdnPublishToken(projectRoot);
702
+ }
703
+ function applyCdnGitHubSetup(projectRoot, input) {
704
+ const existing = readPortalCdnJson(projectRoot) ?? defaultPortalCdnJson();
705
+ const updated = portalCdnJsonFromGithub(input.github, existing);
706
+ writeFileSync3(resolve5(projectRoot, PORTAL_CDN_JSON), `${JSON.stringify(updated, null, 2)}
707
+ `);
708
+ if (input.token?.trim()) {
709
+ writeEnvLocal(projectRoot, { [PORTAL_CDN_PUBLISH_TOKEN_ENV]: input.token.trim() });
710
+ }
711
+ ensureEnvFilesGitignore(projectRoot);
712
+ }
713
+ async function promptCdnGitHubSetup(options = {}) {
714
+ if (options.skip) return null;
715
+ const projectRoot = options.projectRoot ?? process.cwd();
716
+ if (!needsCdnGitHubSetup(projectRoot)) {
717
+ return null;
718
+ }
719
+ let github = options.github?.trim();
720
+ if (!github && hasConfiguredCdnGithubMirror(projectRoot)) {
721
+ github = readPortalCdnJson(projectRoot)?.github;
722
+ }
723
+ if (!github) {
724
+ const answer = await p2.text({
725
+ message: "Public GitHub CDN mirror (org/repo)",
726
+ placeholder: options.githubDefault ?? "your-org/your-portal-cdn-mirror",
727
+ initialValue: options.githubDefault,
728
+ validate: (value) => {
729
+ const slug = normalizeGithubSlug(value ?? "");
730
+ return slug ? void 0 : "Enter a public repo as org/repo (jsDelivr serves from it)";
731
+ }
732
+ });
733
+ if (p2.isCancel(answer)) return null;
734
+ github = normalizeGithubSlug(answer) ?? void 0;
735
+ }
736
+ if (!github) return null;
737
+ if (hasCdnPublishToken(projectRoot)) {
738
+ return { github };
739
+ }
740
+ const tokenAnswer = await p2.password({
741
+ message: "GitHub token (repo push on CDN mirror)",
742
+ validate: (value) => {
743
+ if (!value?.trim()) return "Required for wp publish \u2014 create a fine-grained PAT with Contents: Read and write";
744
+ return void 0;
745
+ }
746
+ });
747
+ if (p2.isCancel(tokenAnswer)) return null;
748
+ return { github, token: tokenAnswer.trim() };
749
+ }
750
+ function formatCdnSetupNote(_projectRoot) {
751
+ return `CDN config written \u2014 edit ${PORTAL_CDN_JSON} if needed; token saved to ${ENV_LOCAL_FILE} (gitignored)`;
752
+ }
753
+
754
+ // src/lib/wphs-workspace.ts
755
+ import { pathToFileURL } from "url";
756
+ var ensureModulePromise;
757
+ function ensureScriptUrl() {
758
+ return pathToFileURL(kitPath("scripts", "ensure-wphs-workspace.mjs")).href;
759
+ }
760
+ function loadEnsureModule() {
761
+ if (!ensureModulePromise) {
762
+ const scriptUrl = ensureScriptUrl();
763
+ ensureModulePromise = import(
764
+ /* webpackIgnore: true */
765
+ scriptUrl
766
+ ).then(
767
+ (mod) => mod
768
+ );
769
+ }
770
+ return ensureModulePromise;
771
+ }
772
+ async function ensureProjectWphsWorkspace(projectRoot) {
773
+ const { ensureWphsWorkspace } = await loadEnsureModule();
774
+ return ensureWphsWorkspace(projectRoot, getKitRoot());
775
+ }
776
+
777
+ // src/lib/template-dev-deps.ts
778
+ var VITE_DEV_PEER_DEPS = {
779
+ vite: "^6.1.0",
780
+ "@vitejs/plugin-react": "^4.3.4",
781
+ "babel-plugin-react-compiler": "^1.0.0",
782
+ "@tailwindcss/vite": "^4.0.6",
783
+ "@tanstack/router-plugin": "^1.121.2",
784
+ "@tanstack/react-router": "^1.121.2",
785
+ "@dnd-kit/core": "^6.3.1",
786
+ "@dnd-kit/sortable": "^10.0.0",
787
+ "@dnd-kit/utilities": "^3.2.2",
788
+ react: "^19.0.0",
789
+ "react-dom": "^19.0.0",
790
+ tailwindcss: "^4.0.6"
791
+ };
792
+ var KIT_DEV_DEP = "@woodsportal/hubspot-kit";
793
+ var KIT_DEV_DEP_VERSION = "^1.0.30";
794
+ var MODULE_CONFIG_UI_DEPS = [
795
+ "@dnd-kit/core",
796
+ "@dnd-kit/sortable",
797
+ "@dnd-kit/utilities"
798
+ ];
799
+ function templateDevDependencies() {
800
+ return {
801
+ [KIT_DEV_DEP]: KIT_DEV_DEP_VERSION,
802
+ ...VITE_DEV_PEER_DEPS
803
+ };
804
+ }
805
+
806
+ // src/lib/hubspot-fields-json.ts
807
+ var HUBSPOT_MODULE_FIELD_TYPES = /* @__PURE__ */ new Set([
808
+ "boolean",
809
+ "choice",
810
+ "color",
811
+ "cta",
812
+ "date",
813
+ "datetime",
814
+ "email",
815
+ "embed",
816
+ "file",
817
+ "followup_email",
818
+ "font",
819
+ "form",
820
+ "group",
821
+ "html",
822
+ "hubdb_row",
823
+ "hubdb_table",
824
+ "icon",
825
+ "image",
826
+ "link",
827
+ "logo",
828
+ "meeting",
829
+ "menu",
830
+ "number",
831
+ "page",
832
+ "payment",
833
+ "richtext",
834
+ "salesforce_campaign",
835
+ "simple_menu",
836
+ "spacing",
837
+ "tag",
838
+ "text",
839
+ "url",
840
+ "video",
841
+ "workflow"
842
+ ]);
843
+ function isFieldRecord(value) {
844
+ return typeof value === "object" && value !== null && !Array.isArray(value);
845
+ }
846
+ function validateFieldNode(node, path, issues) {
847
+ if (!isFieldRecord(node)) {
848
+ issues.push({ path, message: "expected a field object" });
849
+ return;
850
+ }
851
+ const type = node.type;
852
+ if (typeof type !== "string" || type.length === 0) {
853
+ issues.push({ path: `${path}.type`, message: "missing field type" });
854
+ } else if (!HUBSPOT_MODULE_FIELD_TYPES.has(type)) {
855
+ issues.push({
856
+ path: `${path}.type`,
857
+ message: type === "textarea" ? `'textarea' is not a HubSpot field type \u2014 use "type": "text" with "display": "textarea"` : `'${type}' is not a valid HubSpot field type`
858
+ });
859
+ }
860
+ const tab = node.tab;
861
+ if (tab === "STYLE" && type !== "group") {
862
+ issues.push({
863
+ path: `${path}.tab`,
864
+ message: 'STYLE tab fields must live inside a "type": "group" with "tab": "STYLE" (HubSpot page editor Styles tab)'
865
+ });
866
+ }
867
+ if (type !== "group") {
868
+ const name = node.name;
869
+ if (typeof name !== "string" || name.trim().length === 0) {
870
+ issues.push({ path: `${path}.name`, message: "missing field name" });
871
+ }
872
+ const label = node.label;
873
+ if (typeof label !== "string" || label.trim().length === 0) {
874
+ issues.push({ path: `${path}.label`, message: "missing field label" });
875
+ }
876
+ } else {
877
+ const children = node.children;
878
+ if (!Array.isArray(children)) {
879
+ issues.push({ path: `${path}.children`, message: "group fields require a children array" });
880
+ } else {
881
+ children.forEach((child, index) => {
882
+ validateFieldNode(child, `${path}.children[${index}]`, issues);
883
+ });
884
+ }
885
+ }
886
+ }
887
+ function validateHubSpotFieldsJson(fields) {
888
+ if (!Array.isArray(fields)) {
889
+ return [{ path: "$", message: "fields.json must be a top-level array" }];
890
+ }
891
+ const issues = [];
892
+ fields.forEach((field, index) => {
689
893
  validateFieldNode(field, `[${index}]`, issues);
690
894
  });
691
895
  return issues;
692
896
  }
693
897
 
694
898
  // src/lib/developer-slug.ts
695
- import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync4, writeFileSync } from "fs";
696
- import { resolve as resolve5 } from "path";
697
- import * as p2 from "@clack/prompts";
899
+ import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
900
+ import { resolve as resolve6 } from "path";
901
+ import * as p3 from "@clack/prompts";
698
902
  var SLUG_MAX_LEN = 12;
699
903
  var SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9-]{0,10}[a-z0-9])?$/;
700
904
  var DEVELOPER_CONFIG_FILE = "developer.json";
701
905
  function developerConfigPath(projectRoot) {
702
- return resolve5(projectRoot, ".wphs", "config", DEVELOPER_CONFIG_FILE);
906
+ return resolve6(projectRoot, ".wphs", "config", DEVELOPER_CONFIG_FILE);
703
907
  }
704
908
  function sanitizeDevSlug(input) {
705
909
  if (!input?.trim()) return void 0;
@@ -711,9 +915,9 @@ function readDeveloperSlug(projectRoot) {
711
915
  const fromEnv = sanitizeDevSlug(process.env.PORTAL_CDN_DEV_SLUG) ?? sanitizeDevSlug(process.env.MODULE_CDN_DEV_SLUG);
712
916
  if (fromEnv) return fromEnv;
713
917
  const path = developerConfigPath(projectRoot);
714
- if (!existsSync4(path)) return void 0;
918
+ if (!existsSync5(path)) return void 0;
715
919
  try {
716
- const raw = JSON.parse(readFileSync4(path, "utf8"));
920
+ const raw = JSON.parse(readFileSync7(path, "utf8"));
717
921
  return sanitizeDevSlug(raw.slug);
718
922
  } catch {
719
923
  return void 0;
@@ -725,8 +929,8 @@ function writeDeveloperSlug(projectRoot, slug) {
725
929
  throw new Error("Invalid CDN dev slug \u2014 use lowercase letters, numbers, and hyphens (max 12 chars)");
726
930
  }
727
931
  const path = developerConfigPath(projectRoot);
728
- mkdirSync(resolve5(path, ".."), { recursive: true });
729
- writeFileSync(path, `${JSON.stringify({ slug: normalized }, null, 2)}
932
+ mkdirSync2(resolve6(path, ".."), { recursive: true });
933
+ writeFileSync4(path, `${JSON.stringify({ slug: normalized }, null, 2)}
730
934
  `);
731
935
  }
732
936
  function needsDeveloperSlugSetup(projectRoot) {
@@ -736,7 +940,7 @@ async function promptDeveloperSlug(projectRoot) {
736
940
  if (!needsDeveloperSlugSetup(projectRoot)) {
737
941
  return readDeveloperSlug(projectRoot);
738
942
  }
739
- const slug = await p2.text({
943
+ const slug = await p3.text({
740
944
  message: "CDN dev slug (unique per developer for shared CDN mirror \u2014 e.g. your first name)",
741
945
  placeholder: "manab",
742
946
  validate: (value) => {
@@ -746,7 +950,7 @@ async function promptDeveloperSlug(projectRoot) {
746
950
  return void 0;
747
951
  }
748
952
  });
749
- if (p2.isCancel(slug) || typeof slug !== "string") {
953
+ if (p3.isCancel(slug) || typeof slug !== "string") {
750
954
  return void 0;
751
955
  }
752
956
  writeDeveloperSlug(projectRoot, slug);
@@ -788,37 +992,37 @@ function resolveModuleBuildIdMetaForCli(projectRoot, env = process.env) {
788
992
  }
789
993
 
790
994
  // src/lib/upload-only-module.ts
791
- import { existsSync as existsSync5 } from "fs";
792
- import { resolve as resolve6 } from "path";
995
+ import { existsSync as existsSync6 } from "fs";
996
+ import { resolve as resolve7 } from "path";
793
997
  function isUploadOnlyPreexistingProject(projectRoot, moduleArtifactDirs) {
794
998
  if (moduleArtifactDirs.length === 0) return false;
795
- if (existsSync5(resolve6(projectRoot, "package.json"))) return false;
796
- if (existsSync5(resolve6(projectRoot, "src"))) return false;
797
- return moduleArtifactDirs.some((dir) => existsSync5(resolve6(dir, "fields.json")));
999
+ if (existsSync6(resolve7(projectRoot, "package.json"))) return false;
1000
+ if (existsSync6(resolve7(projectRoot, "src"))) return false;
1001
+ return moduleArtifactDirs.some((dir) => existsSync6(resolve7(dir, "fields.json")));
798
1002
  }
799
1003
 
800
1004
  // src/vite/resolve-dev-config.ts
801
- import { existsSync as existsSync6 } from "fs";
1005
+ import { existsSync as existsSync7 } from "fs";
802
1006
  import { platform } from "os";
803
- import { resolve as resolve7 } from "path";
1007
+ import { resolve as resolve8 } from "path";
804
1008
  var VITE_JS_SEGMENTS = ["node_modules", "vite", "bin", "vite.js"];
805
1009
  function isProjectViteInstalled(projectRoot) {
806
- return existsSync6(resolve7(projectRoot, ...VITE_JS_SEGMENTS));
1010
+ return existsSync7(resolve8(projectRoot, ...VITE_JS_SEGMENTS));
807
1011
  }
808
1012
  function resolveProjectViteSpawn(projectRoot) {
809
- const viteJs = resolve7(projectRoot, ...VITE_JS_SEGMENTS);
810
- if (existsSync6(viteJs)) {
1013
+ const viteJs = resolve8(projectRoot, ...VITE_JS_SEGMENTS);
1014
+ if (existsSync7(viteJs)) {
811
1015
  return { command: process.execPath, prefixArgs: [viteJs] };
812
1016
  }
813
- const binDir = resolve7(projectRoot, "node_modules", ".bin");
1017
+ const binDir = resolve8(projectRoot, "node_modules", ".bin");
814
1018
  if (platform() === "win32") {
815
- const winCmd = resolve7(binDir, "vite.cmd");
816
- if (existsSync6(winCmd)) {
1019
+ const winCmd = resolve8(binDir, "vite.cmd");
1020
+ if (existsSync7(winCmd)) {
817
1021
  return { command: winCmd, prefixArgs: [] };
818
1022
  }
819
1023
  }
820
- const posixShim = resolve7(binDir, "vite");
821
- if (existsSync6(posixShim)) {
1024
+ const posixShim = resolve8(binDir, "vite");
1025
+ if (existsSync7(posixShim)) {
822
1026
  return { command: posixShim, prefixArgs: [] };
823
1027
  }
824
1028
  return null;
@@ -905,10 +1109,10 @@ function envFileForTier(tier) {
905
1109
  return ".env.prod";
906
1110
  }
907
1111
  function readEnvFileValue(projectRoot, file, key) {
908
- const path = resolve8(projectRoot, file);
909
- if (!existsSync7(path)) return void 0;
1112
+ const path = resolve9(projectRoot, file);
1113
+ if (!existsSync8(path)) return void 0;
910
1114
  try {
911
- const content = readFileSync5(path, "utf8");
1115
+ const content = readFileSync8(path, "utf8");
912
1116
  const match = content.match(new RegExp(`^${key}=(.*)$`, "m"));
913
1117
  return match?.[1]?.trim();
914
1118
  } catch {
@@ -917,8 +1121,8 @@ function readEnvFileValue(projectRoot, file, key) {
917
1121
  }
918
1122
  function checkEnvFiles(ctx) {
919
1123
  const checks = [];
920
- const localPath = resolve8(ctx.projectRoot, ".env.local");
921
- const localOk = existsSync7(localPath);
1124
+ const localPath = resolve9(ctx.projectRoot, ".env.local");
1125
+ const localOk = existsSync8(localPath);
922
1126
  checks.push({
923
1127
  name: "env-local",
924
1128
  ok: localOk,
@@ -926,8 +1130,8 @@ function checkEnvFiles(ctx) {
926
1130
  remediation: localOk ? void 0 : "wp setup or copy .env.local.example \u2192 .env.local",
927
1131
  severity: "warn"
928
1132
  });
929
- const legacyLocalDev = resolve8(ctx.projectRoot, ".env.dev");
930
- if (!localOk && existsSync7(legacyLocalDev)) {
1133
+ const legacyLocalDev = resolve9(ctx.projectRoot, ".env.dev");
1134
+ if (!localOk && existsSync8(legacyLocalDev)) {
931
1135
  const nodeEnv = readEnvFileValue(ctx.projectRoot, ".env.dev", "VITE_NODE_ENV");
932
1136
  const hasDevMode = readEnvFileValue(ctx.projectRoot, ".env.dev", "VITE_DEV_MODE") != null;
933
1137
  const looksLikeLocal = hasDevMode || nodeEnv === "local" || nodeEnv == null || nodeEnv === "development";
@@ -943,7 +1147,7 @@ function checkEnvFiles(ctx) {
943
1147
  }
944
1148
  for (const tier of ctx.config.env.tiers) {
945
1149
  const file = envFileForTier(tier);
946
- const ok = existsSync7(resolve8(ctx.projectRoot, file));
1150
+ const ok = existsSync8(resolve9(ctx.projectRoot, file));
947
1151
  if (tier === "stg") {
948
1152
  checks.push({
949
1153
  name: "env-stg",
@@ -966,8 +1170,8 @@ function checkEnvFiles(ctx) {
966
1170
  }
967
1171
  function checkLegacyBuildIdFile(ctx) {
968
1172
  if (isThemeProject(ctx.config) || !usesCdnPipeline(ctx.config)) return null;
969
- const legacy = resolve8(ctx.projectRoot, ".portal-build-id");
970
- if (!existsSync7(legacy)) return null;
1173
+ const legacy = resolve9(ctx.projectRoot, ".portal-build-id");
1174
+ if (!existsSync8(legacy)) return null;
971
1175
  return {
972
1176
  name: "legacy-build-id",
973
1177
  ok: false,
@@ -978,8 +1182,8 @@ function checkLegacyBuildIdFile(ctx) {
978
1182
  }
979
1183
  function checkBuildId(ctx) {
980
1184
  if (isThemeProject(ctx.config) || !usesCdnPipeline(ctx.config)) return null;
981
- const file = resolve8(ctx.projectRoot, ctx.config.cdn.buildIdFile);
982
- const ok = existsSync7(file);
1185
+ const file = resolve9(ctx.projectRoot, ctx.config.cdn.buildIdFile);
1186
+ const ok = existsSync8(file);
983
1187
  return {
984
1188
  name: "build-id",
985
1189
  ok,
@@ -1033,7 +1237,7 @@ function checkDevSlug(ctx) {
1033
1237
  };
1034
1238
  }
1035
1239
  function isUploadOnlyProject(ctx) {
1036
- const moduleDirs = [resolve8(ctx.projectRoot, ctx.config.module.uploadPath)];
1240
+ const moduleDirs = [resolve9(ctx.projectRoot, ctx.config.module.uploadPath)];
1037
1241
  return isUploadOnlyPreexistingProject(ctx.projectRoot, moduleDirs);
1038
1242
  }
1039
1243
  function checkUploadOnlyNote(ctx) {
@@ -1047,8 +1251,8 @@ function checkUploadOnlyNote(ctx) {
1047
1251
  };
1048
1252
  }
1049
1253
  function checkDevViteConfig(ctx) {
1050
- const path = resolve8(ctx.projectRoot, "vite.config.dev.mjs");
1051
- const ok = existsSync7(path);
1254
+ const path = resolve9(ctx.projectRoot, "vite.config.dev.mjs");
1255
+ const ok = existsSync8(path);
1052
1256
  return {
1053
1257
  name: "vite-dev-config",
1054
1258
  ok,
@@ -1059,8 +1263,8 @@ function checkDevViteConfig(ctx) {
1059
1263
  }
1060
1264
  function checkMetaJson(ctx) {
1061
1265
  if (isThemeProject(ctx.config)) return null;
1062
- const path = resolve8(ctx.projectRoot, "public/meta.json");
1063
- if (!existsSync7(path)) {
1266
+ const path = resolve9(ctx.projectRoot, "public/meta.json");
1267
+ if (!existsSync8(path)) {
1064
1268
  return {
1065
1269
  name: "meta-json",
1066
1270
  ok: false,
@@ -1070,7 +1274,7 @@ function checkMetaJson(ctx) {
1070
1274
  };
1071
1275
  }
1072
1276
  try {
1073
- const meta = JSON.parse(readFileSync5(path, "utf8"));
1277
+ const meta = JSON.parse(readFileSync8(path, "utf8"));
1074
1278
  const hasContentTypes = Array.isArray(meta.content_types) && meta.content_types.length > 0;
1075
1279
  return {
1076
1280
  name: "meta-json",
@@ -1092,8 +1296,8 @@ function checkMetaJson(ctx) {
1092
1296
  }
1093
1297
  async function checkCdnMirrorPublic(ctx) {
1094
1298
  if (isThemeProject(ctx.config) || !usesCdnPipeline(ctx.config)) return null;
1095
- const mirrorPath = resolve8(ctx.projectRoot, ctx.config.cdn?.mirror ?? "portal.cdn.json");
1096
- if (!existsSync7(mirrorPath)) {
1299
+ const mirrorPath = resolve9(ctx.projectRoot, ctx.config.cdn?.mirror ?? "portal.cdn.json");
1300
+ if (!existsSync8(mirrorPath)) {
1097
1301
  return {
1098
1302
  name: "cdn-mirror-public",
1099
1303
  ok: false,
@@ -1104,7 +1308,7 @@ async function checkCdnMirrorPublic(ctx) {
1104
1308
  }
1105
1309
  let github;
1106
1310
  try {
1107
- const mirror = JSON.parse(readFileSync5(mirrorPath, "utf8"));
1311
+ const mirror = JSON.parse(readFileSync8(mirrorPath, "utf8"));
1108
1312
  github = mirror.github?.trim();
1109
1313
  } catch {
1110
1314
  return {
@@ -1124,6 +1328,15 @@ async function checkCdnMirrorPublic(ctx) {
1124
1328
  severity: "error"
1125
1329
  };
1126
1330
  }
1331
+ if (isPlaceholderGithubSlug(github)) {
1332
+ return {
1333
+ name: "cdn-mirror-public",
1334
+ ok: false,
1335
+ message: `portal.cdn.json still has placeholder github slug (${github})`,
1336
+ remediation: "Run wp setup and set a real public org/repo for jsDelivr",
1337
+ severity: "warn"
1338
+ };
1339
+ }
1127
1340
  try {
1128
1341
  const res = await fetch(`https://api.github.com/repos/${github}`, {
1129
1342
  headers: { Accept: "application/vnd.github+json" }
@@ -1156,8 +1369,8 @@ async function checkCdnMirrorPublic(ctx) {
1156
1369
  severity: "warn"
1157
1370
  };
1158
1371
  }
1159
- const manifestPath = resolve8(ctx.projectRoot, "dist/cdn/manifest.json");
1160
- if (!existsSync7(manifestPath)) {
1372
+ const manifestPath = resolve9(ctx.projectRoot, "dist/cdn/manifest.json");
1373
+ if (!existsSync8(manifestPath)) {
1161
1374
  return {
1162
1375
  name: "cdn-mirror-public",
1163
1376
  ok: true,
@@ -1166,7 +1379,7 @@ async function checkCdnMirrorPublic(ctx) {
1166
1379
  };
1167
1380
  }
1168
1381
  try {
1169
- const manifest = JSON.parse(readFileSync5(manifestPath, "utf8"));
1382
+ const manifest = JSON.parse(readFileSync8(manifestPath, "utf8"));
1170
1383
  const appUrl = manifest.scripts?.find((s) => s.name === "app")?.url;
1171
1384
  if (!appUrl) {
1172
1385
  return {
@@ -1176,20 +1389,36 @@ async function checkCdnMirrorPublic(ctx) {
1176
1389
  severity: "error"
1177
1390
  };
1178
1391
  }
1179
- const head = await fetch(appUrl, { method: "HEAD" });
1180
- if (!head.ok) {
1392
+ const appHead = await fetch(appUrl, { method: "HEAD" });
1393
+ if (!appHead.ok) {
1181
1394
  return {
1182
1395
  name: "cdn-mirror-public",
1183
1396
  ok: false,
1184
- message: `jsDelivr app chunk not reachable (HTTP ${head.status}) \u2014 HubSpot preview will be blank`,
1397
+ message: `jsDelivr app chunk not reachable (HTTP ${appHead.status}) \u2014 HubSpot preview will be blank`,
1185
1398
  remediation: "Run wp deploy (publish CDN + upload module) after making the mirror repo public",
1186
1399
  severity: "error"
1187
1400
  };
1188
1401
  }
1402
+ const lazyChunks = (manifest.scripts ?? []).filter(
1403
+ (entry) => typeof entry.url === "string" && (entry.file?.startsWith("chunks/") || entry.url.includes("/chunks/"))
1404
+ );
1405
+ for (const chunk of lazyChunks) {
1406
+ const chunkHead = await fetch(chunk.url, { method: "HEAD" });
1407
+ if (!chunkHead.ok) {
1408
+ return {
1409
+ name: "cdn-mirror-public",
1410
+ ok: false,
1411
+ message: `jsDelivr lazy chunk not reachable: ${chunk.file ?? chunk.url} (HTTP ${chunkHead.status})`,
1412
+ remediation: "Run wp deploy to republish dist/cdn (including chunks/) and wait 5\u201315 min for jsDelivr propagation",
1413
+ severity: "error"
1414
+ };
1415
+ }
1416
+ }
1417
+ const lazyChunkNote = lazyChunks.length > 0 ? ` and ${lazyChunks.length} lazy chunk(s) are reachable` : "";
1189
1418
  return {
1190
1419
  name: "cdn-mirror-public",
1191
1420
  ok: true,
1192
- message: `CDN mirror ${github} is public and jsDelivr app chunk is reachable`,
1421
+ message: `CDN mirror ${github} is public and jsDelivr app chunk${lazyChunkNote}`,
1193
1422
  severity: "error"
1194
1423
  };
1195
1424
  } catch {
@@ -1203,8 +1432,8 @@ async function checkCdnMirrorPublic(ctx) {
1203
1432
  }
1204
1433
  function checkFieldsJson(ctx) {
1205
1434
  if (isThemeProject(ctx.config)) return null;
1206
- const path = resolve8(ctx.projectRoot, "public/fields.json");
1207
- if (!existsSync7(path)) {
1435
+ const path = resolve9(ctx.projectRoot, "public/fields.json");
1436
+ if (!existsSync8(path)) {
1208
1437
  return {
1209
1438
  name: "fields-json",
1210
1439
  ok: false,
@@ -1214,7 +1443,7 @@ function checkFieldsJson(ctx) {
1214
1443
  };
1215
1444
  }
1216
1445
  try {
1217
- const fields = JSON.parse(readFileSync5(path, "utf8"));
1446
+ const fields = JSON.parse(readFileSync8(path, "utf8"));
1218
1447
  const issues = validateHubSpotFieldsJson(fields);
1219
1448
  if (issues.length === 0) {
1220
1449
  return {
@@ -1244,11 +1473,11 @@ function checkFieldsJson(ctx) {
1244
1473
  }
1245
1474
  }
1246
1475
  function readProjectFile(projectRoot, relativePath) {
1247
- const path = resolve8(projectRoot, relativePath);
1248
- if (!existsSync7(path)) {
1476
+ const path = resolve9(projectRoot, relativePath);
1477
+ if (!existsSync8(path)) {
1249
1478
  return null;
1250
1479
  }
1251
- return readFileSync5(path, "utf8");
1480
+ return readFileSync8(path, "utf8");
1252
1481
  }
1253
1482
  function checkCssPipeline(ctx) {
1254
1483
  if (isThemeProject(ctx.config) || isUploadOnlyProject(ctx)) {
@@ -1315,9 +1544,9 @@ function checkCssPipeline(ctx) {
1315
1544
  }
1316
1545
  const moduleOutDir = ctx.config.module?.outDir;
1317
1546
  if (moduleOutDir && usesCdnPipeline(ctx.config)) {
1318
- const moduleCssPath = resolve8(ctx.projectRoot, moduleOutDir, "module.css");
1319
- if (existsSync7(moduleCssPath)) {
1320
- const css = readFileSync5(moduleCssPath, "utf8");
1547
+ const moduleCssPath = resolve9(ctx.projectRoot, moduleOutDir, "module.css");
1548
+ if (existsSync8(moduleCssPath)) {
1549
+ const css = readFileSync8(moduleCssPath, "utf8");
1321
1550
  const hasDwUtilities = /dw\\:/.test(css) || /\.dw:/.test(css);
1322
1551
  checks.push({
1323
1552
  name: "css-module-dist-prefixed",
@@ -1332,8 +1561,8 @@ function checkCssPipeline(ctx) {
1332
1561
  }
1333
1562
  function checkHybridModuleHtml(ctx) {
1334
1563
  if (isThemeProject(ctx.config) || !usesCdnPipeline(ctx.config)) return null;
1335
- const path = resolve8(ctx.projectRoot, "public/module.html");
1336
- if (!existsSync7(path)) {
1564
+ const path = resolve9(ctx.projectRoot, "public/module.html");
1565
+ if (!existsSync8(path)) {
1337
1566
  return {
1338
1567
  name: "module-html",
1339
1568
  ok: false,
@@ -1342,7 +1571,7 @@ function checkHybridModuleHtml(ctx) {
1342
1571
  severity: "error"
1343
1572
  };
1344
1573
  }
1345
- const html = readFileSync5(path, "utf8");
1574
+ const html = readFileSync8(path, "utf8");
1346
1575
  const needs = ["__PORTAL_CDN_BASE__", "require_js", "hubSpotData", 'id="app"'];
1347
1576
  const missing = needs.filter((token) => !html.includes(token));
1348
1577
  const usesUndeclaredCdnFields = /module\.portal_(cdn_base_url|vendor_script|app_script|app_css)/.test(
@@ -1360,8 +1589,8 @@ function checkHybridModuleHtml(ctx) {
1360
1589
  function checkCdnViteConfigs(ctx) {
1361
1590
  if (isThemeProject(ctx.config) || !usesCdnPipeline(ctx.config)) return [];
1362
1591
  return ["vite.config.cdn-vendor.mjs", "vite.config.cdn-app.mjs"].map((file) => {
1363
- const path = resolve8(ctx.projectRoot, file);
1364
- const ok = existsSync7(path);
1592
+ const path = resolve9(ctx.projectRoot, file);
1593
+ const ok = existsSync8(path);
1365
1594
  return {
1366
1595
  name: file.replace(/\./g, "-"),
1367
1596
  ok,
@@ -1373,8 +1602,8 @@ function checkCdnViteConfigs(ctx) {
1373
1602
  }
1374
1603
  function checkDevEntry(ctx) {
1375
1604
  const checks = [];
1376
- const indexHtml = resolve8(ctx.projectRoot, "index.html");
1377
- const hasIndex = existsSync7(indexHtml);
1605
+ const indexHtml = resolve9(ctx.projectRoot, "index.html");
1606
+ const hasIndex = existsSync8(indexHtml);
1378
1607
  checks.push({
1379
1608
  name: "index-html",
1380
1609
  ok: hasIndex,
@@ -1382,8 +1611,8 @@ function checkDevEntry(ctx) {
1382
1611
  remediation: hasIndex ? void 0 : "Re-run wp init or copy index.html from kit templates",
1383
1612
  severity: "error"
1384
1613
  });
1385
- const localDev = resolve8(ctx.projectRoot, ctx.config.entries.localDev);
1386
- const hasEntry = existsSync7(localDev);
1614
+ const localDev = resolve9(ctx.projectRoot, ctx.config.entries.localDev);
1615
+ const hasEntry = existsSync8(localDev);
1387
1616
  checks.push({
1388
1617
  name: "local-dev-entry",
1389
1618
  ok: hasEntry,
@@ -1393,7 +1622,7 @@ function checkDevEntry(ctx) {
1393
1622
  });
1394
1623
  if (hasEntry) {
1395
1624
  try {
1396
- const source = readFileSync5(localDev, "utf8");
1625
+ const source = readFileSync8(localDev, "utf8");
1397
1626
  const usesKitNodeBootstrap = source.includes("@woodsportal/hubspot-kit/dev/bootstrap-greenfield") || source.includes("@woodsportal/hubspot-kit/dev/bootstrap");
1398
1627
  const usesWphsShim = source.includes(".wphs/.kit-bootstrap-greenfield") || source.includes(".wphs/.kit-bootstrap") || source.includes("@wphs/bootstrap-greenfield") || source.includes("@wphs/bootstrap");
1399
1628
  checks.push({
@@ -1410,7 +1639,7 @@ function checkDevEntry(ctx) {
1410
1639
  }
1411
1640
  function checkKitScripts() {
1412
1641
  const script = kitPath("scripts", "build-cdn.mjs");
1413
- const ok = existsSync7(script);
1642
+ const ok = existsSync8(script);
1414
1643
  return {
1415
1644
  name: "kit-scripts",
1416
1645
  ok,
@@ -1419,12 +1648,38 @@ function checkKitScripts() {
1419
1648
  severity: "error"
1420
1649
  };
1421
1650
  }
1651
+ function checkWphsScaffold(scaffoldError) {
1652
+ if (!scaffoldError) return null;
1653
+ return {
1654
+ name: "wphs-scaffold",
1655
+ ok: false,
1656
+ message: `.wphs workspace scaffold failed \u2014 ${scaffoldError}`,
1657
+ remediation: "Run wp doctor --verbose or wp setup; check write permissions on the project root (Windows: enable Developer Mode if junction creation fails)",
1658
+ severity: "error"
1659
+ };
1660
+ }
1661
+ async function runWphsScaffold(ctx) {
1662
+ if (ctx.global.dryRun) return void 0;
1663
+ try {
1664
+ const paths = await ensureProjectWphsWorkspace(ctx.projectRoot);
1665
+ if (ctx.global.verbose) {
1666
+ ctx.logger.info(`.wphs workspace ready at ${paths.workspaceRoot}`);
1667
+ }
1668
+ return void 0;
1669
+ } catch (error) {
1670
+ const message = error instanceof Error ? error.message : String(error);
1671
+ if (ctx.global.verbose) {
1672
+ ctx.logger.error(`Could not scaffold .wphs workspace: ${message}`);
1673
+ }
1674
+ return message;
1675
+ }
1676
+ }
1422
1677
  function checkWphsWorkspace(ctx) {
1423
1678
  const workspace = ctx.config.dev.moduleConfigDir;
1424
1679
  const checks = [];
1425
1680
  for (const legacy of [".dev", ".woodsportal"]) {
1426
- const legacyPath = resolve8(ctx.projectRoot, legacy);
1427
- if (existsSync7(legacyPath)) {
1681
+ const legacyPath = resolve9(ctx.projectRoot, legacy);
1682
+ if (existsSync8(legacyPath)) {
1428
1683
  checks.push({
1429
1684
  name: `legacy-${legacy}`,
1430
1685
  ok: false,
@@ -1434,7 +1689,7 @@ function checkWphsWorkspace(ctx) {
1434
1689
  });
1435
1690
  }
1436
1691
  }
1437
- if (existsSync7(resolve8(ctx.projectRoot, ".wp-hs-baseline"))) {
1692
+ if (existsSync8(resolve9(ctx.projectRoot, ".wp-hs-baseline"))) {
1438
1693
  checks.push({
1439
1694
  name: "legacy-baseline",
1440
1695
  ok: false,
@@ -1453,15 +1708,15 @@ function checkWphsWorkspace(ctx) {
1453
1708
  });
1454
1709
  return checks;
1455
1710
  }
1456
- const wphsRoot = resolve8(ctx.projectRoot, workspace);
1711
+ const wphsRoot = resolve9(ctx.projectRoot, workspace);
1457
1712
  const required = ["config", "assets/branding", "fixtures", "cache", "audit/baseline", "state"];
1458
1713
  for (const sub of required) {
1459
- const ok = existsSync7(join(wphsRoot, sub));
1714
+ const ok = existsSync8(join2(wphsRoot, sub));
1460
1715
  checks.push({
1461
1716
  name: `wphs-${sub.replace(/\//g, "-")}`,
1462
1717
  ok,
1463
1718
  message: ok ? `.wphs/${sub}/ present` : `.wphs/${sub}/ missing`,
1464
- remediation: ok ? void 0 : "Run wp doctor (auto-scaffolds) or wp init",
1719
+ remediation: ok ? void 0 : "Run wp setup or wp doctor --verbose; check write permissions on the project root",
1465
1720
  severity: "error"
1466
1721
  });
1467
1722
  }
@@ -1469,8 +1724,8 @@ function checkWphsWorkspace(ctx) {
1469
1724
  }
1470
1725
  function checkAdapter(ctx) {
1471
1726
  if (isThemeProject(ctx.config)) return null;
1472
- const adapterPath = resolve8(ctx.projectRoot, ctx.config.dev.adapter);
1473
- const ok = existsSync7(adapterPath);
1727
+ const adapterPath = resolve9(ctx.projectRoot, ctx.config.dev.adapter);
1728
+ const ok = existsSync8(adapterPath);
1474
1729
  return {
1475
1730
  name: "wphs-adapter",
1476
1731
  ok,
@@ -1481,11 +1736,11 @@ function checkAdapter(ctx) {
1481
1736
  }
1482
1737
  function checkModuleConfigUiDeps(ctx) {
1483
1738
  if (isThemeProject(ctx.config)) return [];
1484
- const pkgPath = resolve8(ctx.projectRoot, "package.json");
1485
- if (!existsSync7(pkgPath)) return [];
1739
+ const pkgPath = resolve9(ctx.projectRoot, "package.json");
1740
+ if (!existsSync8(pkgPath)) return [];
1486
1741
  let deps = {};
1487
1742
  try {
1488
- const pkg = JSON.parse(readFileSync5(pkgPath, "utf8"));
1743
+ const pkg = JSON.parse(readFileSync8(pkgPath, "utf8"));
1489
1744
  deps = { ...pkg.dependencies, ...pkg.devDependencies };
1490
1745
  } catch {
1491
1746
  return [];
@@ -1512,7 +1767,7 @@ function checkNodeLoaderInterop() {
1512
1767
  };
1513
1768
  }
1514
1769
  function checkViteInstalled(ctx) {
1515
- if (isThemeProject(ctx.config) && !existsSync7(resolve8(ctx.projectRoot, "vite.config.dev.mjs"))) {
1770
+ if (isThemeProject(ctx.config) && !existsSync8(resolve9(ctx.projectRoot, "vite.config.dev.mjs"))) {
1516
1771
  return null;
1517
1772
  }
1518
1773
  const ok = isProjectViteInstalled(ctx.projectRoot);
@@ -1526,19 +1781,12 @@ function checkViteInstalled(ctx) {
1526
1781
  }
1527
1782
  async function gatherDoctorChecks(ctx) {
1528
1783
  const checks = [];
1784
+ let wphsScaffoldError;
1529
1785
  await runCliTasks(ctx, [
1530
1786
  {
1531
1787
  title: "Preparing .wphs workspace",
1532
1788
  task: async () => {
1533
- if (ctx.global.dryRun) return;
1534
- try {
1535
- runNodeScript(kitPath("scripts", "ensure-wphs-workspace.mjs"), [], {
1536
- cwd: ctx.projectRoot,
1537
- logger: ctx.logger,
1538
- dryRun: false
1539
- });
1540
- } catch {
1541
- }
1789
+ wphsScaffoldError = await runWphsScaffold(ctx);
1542
1790
  }
1543
1791
  },
1544
1792
  {
@@ -1562,7 +1810,11 @@ async function gatherDoctorChecks(ctx) {
1562
1810
  {
1563
1811
  title: "Checking woodscli.json and project layout",
1564
1812
  task: async () => {
1565
- checks.push(...checkConfig(ctx), ...checkWphsWorkspace(ctx));
1813
+ wphsScaffoldError = await runWphsScaffold(ctx) ?? wphsScaffoldError;
1814
+ checks.push(...checkConfig(ctx));
1815
+ const scaffoldCheck = checkWphsScaffold(wphsScaffoldError);
1816
+ if (scaffoldCheck) checks.push(scaffoldCheck);
1817
+ checks.push(...checkWphsWorkspace(ctx));
1566
1818
  const fieldsCheck = checkFieldsJson(ctx);
1567
1819
  if (fieldsCheck) checks.push(fieldsCheck);
1568
1820
  const metaCheck = checkMetaJson(ctx);
@@ -1672,26 +1924,7 @@ async function runConfigValidate(ctx) {
1672
1924
  import { resolve as resolve10 } from "path";
1673
1925
 
1674
1926
  // src/lib/cli-banner.ts
1675
- import pc3 from "picocolors";
1676
-
1677
- // src/lib/kit-version.ts
1678
- import { readFileSync as readFileSync6 } from "fs";
1679
- import { resolve as resolve9 } from "path";
1680
- var cachedVersion = null;
1681
- function readKitVersion() {
1682
- if (cachedVersion) {
1683
- return cachedVersion;
1684
- }
1685
- try {
1686
- const pkg = JSON.parse(readFileSync6(resolve9(getKitRoot(), "package.json"), "utf8"));
1687
- cachedVersion = pkg.version ?? "0.0.0";
1688
- } catch {
1689
- cachedVersion = "0.0.0";
1690
- }
1691
- return cachedVersion;
1692
- }
1693
-
1694
- // src/lib/cli-banner.ts
1927
+ import pc4 from "picocolors";
1695
1928
  var WOODSCLI_ASCII_BANNER = `
1696
1929
  __ __ _ _____ _ _____
1697
1930
  \\ \\ / / | | / ____| | |_ _|
@@ -1721,7 +1954,7 @@ function formatProjectLabel(config) {
1721
1954
  return `${config.module.name} \xB7 ${config.projectType}${routerNote}`;
1722
1955
  }
1723
1956
  function section(color, title, body) {
1724
- const bold = (text4) => color ? pc3.bold(text4) : text4;
1957
+ const bold = (text5) => color ? pc4.bold(text5) : text5;
1725
1958
  return ` ${bold(title)}
1726
1959
  ${body}`;
1727
1960
  }
@@ -1730,10 +1963,10 @@ function printWoodsCliDevBanner(options) {
1730
1963
  return;
1731
1964
  }
1732
1965
  const color = useCliColor();
1733
- const cyan = (text4) => color ? pc3.cyan(text4) : text4;
1734
- const dim = (text4) => color ? pc3.dim(text4) : text4;
1735
- const bold = (text4) => color ? pc3.bold(text4) : text4;
1736
- const yellow = (text4) => color ? pc3.yellow(text4) : text4;
1966
+ const cyan = (text5) => color ? pc4.cyan(text5) : text5;
1967
+ const dim = (text5) => color ? pc4.dim(text5) : text5;
1968
+ const bold = (text5) => color ? pc4.bold(text5) : text5;
1969
+ const yellow = (text5) => color ? pc4.yellow(text5) : text5;
1737
1970
  const version = options.version ?? readKitVersion();
1738
1971
  const project = formatProjectLabel(options.config);
1739
1972
  const isTheme = isThemeProject(options.config);
@@ -1994,86 +2227,17 @@ async function runDev(ctx, options) {
1994
2227
  }
1995
2228
 
1996
2229
  // src/cli/commands/deploy.ts
1997
- import { resolve as resolve13 } from "path";
1998
-
1999
- // src/lib/project-env-files.ts
2000
- import { existsSync as existsSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync2 } from "fs";
2001
- import { resolve as resolve11 } from "path";
2002
- var ENV_LOCAL_FILE = ".env.local";
2003
- var ENV_GITIGNORE_LINES = [".env", ".env.*", "!.env*.example", ENV_LOCAL_FILE];
2004
- function parseEnvFile(content) {
2005
- const result = {};
2006
- for (const line of content.split("\n")) {
2007
- const trimmed = line.trim();
2008
- if (!trimmed || trimmed.startsWith("#")) continue;
2009
- const eq = trimmed.indexOf("=");
2010
- if (eq <= 0) continue;
2011
- const key = trimmed.slice(0, eq).trim();
2012
- let value = trimmed.slice(eq + 1).trim();
2013
- if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
2014
- value = value.slice(1, -1);
2015
- }
2016
- result[key] = value;
2017
- }
2018
- return result;
2019
- }
2020
- function readEnvLocal(projectRoot) {
2021
- const path = resolve11(projectRoot, ENV_LOCAL_FILE);
2022
- if (!existsSync8(path)) return {};
2023
- return parseEnvFile(readFileSync7(path, "utf8"));
2024
- }
2025
- function formatEnvLocal(entries) {
2026
- const lines = [
2027
- "# Local secrets \u2014 gitignored. Created by wp init / wp setup (hybrid CDN).",
2028
- "# PORTAL_CDN_PUBLISH_TOKEN needs repo push on your portal.cdn.json mirror.",
2029
- ""
2030
- ];
2031
- for (const [key, value] of Object.entries(entries)) {
2032
- if (value.includes(" ") || value.includes("#")) {
2033
- lines.push(`${key}="${value.replace(/"/g, '\\"')}"`);
2034
- } else {
2035
- lines.push(`${key}=${value}`);
2036
- }
2037
- }
2038
- return `${lines.join("\n").trimEnd()}
2039
- `;
2040
- }
2041
- function writeEnvLocal(projectRoot, entries) {
2042
- const path = resolve11(projectRoot, ENV_LOCAL_FILE);
2043
- const existing = existsSync8(path) ? parseEnvFile(readFileSync7(path, "utf8")) : {};
2044
- writeFileSync2(path, formatEnvLocal({ ...existing, ...entries }));
2045
- }
2046
- function mergeProjectEnv(projectRoot, base = process.env) {
2047
- const local = readEnvLocal(projectRoot);
2048
- const merged = { ...base };
2049
- for (const [key, value] of Object.entries(local)) {
2050
- if (merged[key] == null || merged[key] === "") {
2051
- merged[key] = value;
2052
- }
2053
- }
2054
- return merged;
2055
- }
2056
- function ensureEnvFilesGitignore(projectRoot) {
2057
- const gitignorePath = resolve11(projectRoot, ".gitignore");
2058
- const existing = existsSync8(gitignorePath) ? readFileSync7(gitignorePath, "utf8") : "";
2059
- const toAppend = ENV_GITIGNORE_LINES.filter((line) => !existing.includes(line)).join("\n");
2060
- if (!toAppend) return;
2061
- writeFileSync2(gitignorePath, `${existing.trimEnd()}
2062
-
2063
- # Env files (secrets)
2064
- ${toAppend}
2065
- `);
2066
- }
2230
+ import { resolve as resolve12 } from "path";
2067
2231
 
2068
2232
  // src/cli/commands/upload.ts
2069
- import { resolve as resolve12 } from "path";
2233
+ import { resolve as resolve11 } from "path";
2070
2234
  async function uploadModuleOrTheme(ctx) {
2071
2235
  if (isThemeProject(ctx.config)) {
2072
2236
  const theme = ctx.config.theme;
2073
2237
  if (!theme) {
2074
2238
  throw new WpHsError("theme block missing in woodscli.json", { exitCode: ExitCode.USER_ERROR });
2075
2239
  }
2076
- const srcDir = resolve12(ctx.projectRoot, theme.srcDir);
2240
+ const srcDir = resolve11(ctx.projectRoot, theme.srcDir);
2077
2241
  await runCommandAsync("hs", ["cms", "upload", srcDir, theme.uploadPath], {
2078
2242
  cwd: ctx.projectRoot,
2079
2243
  projectRoot: ctx.projectRoot,
@@ -2083,7 +2247,7 @@ async function uploadModuleOrTheme(ctx) {
2083
2247
  });
2084
2248
  return `Uploaded theme ${srcDir} \u2192 ${theme.uploadPath}`;
2085
2249
  }
2086
- const outDir = resolve12(ctx.projectRoot, ctx.config.module.outDir);
2250
+ const outDir = resolve11(ctx.projectRoot, ctx.config.module.outDir);
2087
2251
  const uploadPath = ctx.config.module.uploadPath;
2088
2252
  await runCommandAsync("hs", ["cms", "upload", outDir, uploadPath], {
2089
2253
  cwd: ctx.projectRoot,
@@ -2260,7 +2424,7 @@ async function runDeploy(ctx, options) {
2260
2424
  if (!ctx.global.json) {
2261
2425
  printCommandDone(
2262
2426
  ctx,
2263
- `Deploy complete (${resolve13(ctx.projectRoot, ctx.config.module.outDir)} \u2192 ${ctx.config.module.uploadPath})`
2427
+ `Deploy complete (${resolve12(ctx.projectRoot, ctx.config.module.outDir)} \u2192 ${ctx.config.module.uploadPath})`
2264
2428
  );
2265
2429
  }
2266
2430
  return ExitCode.SUCCESS;
@@ -2333,8 +2497,8 @@ async function runPublish(ctx, options) {
2333
2497
 
2334
2498
  // src/cli/commands/audit.ts
2335
2499
  import { createHash } from "crypto";
2336
- import { cpSync, existsSync as existsSync9, mkdirSync as mkdirSync2, readFileSync as readFileSync8, readdirSync, rmSync, statSync } from "fs";
2337
- import { resolve as resolve14, join as join2 } from "path";
2500
+ import { cpSync, existsSync as existsSync9, mkdirSync as mkdirSync3, readFileSync as readFileSync9, readdirSync, rmSync, statSync } from "fs";
2501
+ import { resolve as resolve13, join as join3 } from "path";
2338
2502
  var FORBIDDEN_SHIP_TOKENS = [
2339
2503
  "ModuleConfigOverlay",
2340
2504
  "module-config-store",
@@ -2343,15 +2507,16 @@ var FORBIDDEN_SHIP_TOKENS = [
2343
2507
  "dev.module-config"
2344
2508
  ];
2345
2509
  var FORBIDDEN_FIXTURE_MENU_TOKENS = ["User Home", "Primary Company Deals"];
2510
+ var FORBIDDEN_PREVIEW_FIXTURE_TOKENS = ["preview.editor@example.com", "CMS_EDITOR_PREVIEW"];
2346
2511
  function hashFile(path) {
2347
- return createHash("sha256").update(readFileSync8(path)).digest("hex");
2512
+ return createHash("sha256").update(readFileSync9(path)).digest("hex");
2348
2513
  }
2349
2514
  function walkFiles(dir) {
2350
2515
  if (!existsSync9(dir)) return [];
2351
2516
  const entries = readdirSync(dir);
2352
2517
  const files = [];
2353
2518
  for (const entry of entries) {
2354
- const full = join2(dir, entry);
2519
+ const full = join3(dir, entry);
2355
2520
  if (statSync(full).isDirectory()) {
2356
2521
  files.push(...walkFiles(full));
2357
2522
  } else {
@@ -2363,26 +2528,31 @@ function walkFiles(dir) {
2363
2528
  async function runAuditShipBundle(ctx) {
2364
2529
  const root = ctx.projectRoot;
2365
2530
  const targets = [
2366
- resolve14(root, "dist/cdn"),
2367
- resolve14(root, ctx.config.module.outDir)
2531
+ resolve13(root, "dist/cdn"),
2532
+ resolve13(root, ctx.config.module.outDir)
2368
2533
  ];
2369
2534
  const violations = [];
2370
2535
  for (const dir of targets) {
2371
2536
  for (const file of walkFiles(dir)) {
2372
2537
  if (!/\.(js|css|html)$/i.test(file)) continue;
2373
- const content = readFileSync8(file, "utf8");
2538
+ const content = readFileSync9(file, "utf8");
2374
2539
  for (const token of FORBIDDEN_SHIP_TOKENS) {
2375
2540
  if (content.includes(token)) {
2376
2541
  violations.push(`${file}: contains forbidden token "${token}"`);
2377
2542
  }
2378
2543
  }
2379
- const isCdnAppBundle = /[/\\]dist[/\\]cdn[/\\]app\..*\.js$/i.test(file);
2380
- if (isCdnAppBundle) {
2544
+ const isCdnAppEntry = /[/\\]dist[/\\]cdn[/\\]app\..*\.js$/i.test(file);
2545
+ if (isCdnAppEntry) {
2381
2546
  for (const token of FORBIDDEN_FIXTURE_MENU_TOKENS) {
2382
2547
  if (content.includes(token)) {
2383
2548
  violations.push(`${file}: contains fixture menu marker "${token}"`);
2384
2549
  }
2385
2550
  }
2551
+ for (const token of FORBIDDEN_PREVIEW_FIXTURE_TOKENS) {
2552
+ if (content.includes(token)) {
2553
+ violations.push(`${file}: contains preview fixture marker "${token}"`);
2554
+ }
2555
+ }
2386
2556
  }
2387
2557
  }
2388
2558
  }
@@ -2405,8 +2575,8 @@ async function runAuditBaselineCapture(ctx, options) {
2405
2575
  const root = ctx.projectRoot;
2406
2576
  const baseline = options.baselineDir ?? defaultBaselineDir(root, ctx.config);
2407
2577
  const sources = [
2408
- { label: "cdn", path: resolve14(root, "dist/cdn") },
2409
- { label: "hubspot", path: resolve14(root, ctx.config.module.outDir) }
2578
+ { label: "cdn", path: resolve13(root, "dist/cdn") },
2579
+ { label: "hubspot", path: resolve13(root, ctx.config.module.outDir) }
2410
2580
  ];
2411
2581
  if (ctx.global.dryRun) {
2412
2582
  ctx.logger.info(`[dry-run] Would capture baseline to ${baseline}`);
@@ -2419,9 +2589,9 @@ async function runAuditBaselineCapture(ctx, options) {
2419
2589
  remediation: "Run wp build before wp audit baseline --capture."
2420
2590
  });
2421
2591
  }
2422
- const target = resolve14(baseline, label);
2592
+ const target = resolve13(baseline, label);
2423
2593
  rmSync(target, { recursive: true, force: true });
2424
- mkdirSync2(baseline, { recursive: true });
2594
+ mkdirSync3(baseline, { recursive: true });
2425
2595
  cpSync(path, target, { recursive: true });
2426
2596
  }
2427
2597
  if (ctx.global.json) {
@@ -2435,12 +2605,12 @@ async function runAuditParity(ctx, options) {
2435
2605
  const root = ctx.projectRoot;
2436
2606
  const baseline = options.baselineDir ?? defaultBaselineDir(root, ctx.config);
2437
2607
  const currentDirs = [
2438
- { label: "cdn", path: resolve14(root, "dist/cdn") },
2439
- { label: "hubspot", path: resolve14(root, ctx.config.module.outDir) }
2608
+ { label: "cdn", path: resolve13(root, "dist/cdn") },
2609
+ { label: "hubspot", path: resolve13(root, ctx.config.module.outDir) }
2440
2610
  ];
2441
2611
  const mismatches = [];
2442
2612
  for (const { label, path } of currentDirs) {
2443
- const baselinePath = resolve14(baseline, label);
2613
+ const baselinePath = resolve13(baseline, label);
2444
2614
  if (!existsSync9(baselinePath)) {
2445
2615
  ctx.logger.warn(`No baseline at ${baselinePath} \u2014 skipping ${label} parity`);
2446
2616
  continue;
@@ -2456,7 +2626,7 @@ async function runAuditParity(ctx, options) {
2456
2626
  }
2457
2627
  for (const file of baselineFiles) {
2458
2628
  const rel = file.replace(baselinePath, "");
2459
- const current = resolve14(path, rel.slice(1));
2629
+ const current = resolve13(path, rel.slice(1));
2460
2630
  if (!existsSync9(current)) continue;
2461
2631
  const bh = hashFile(file);
2462
2632
  const ch = hashFile(current);
@@ -2484,13 +2654,13 @@ var SECRET_PATTERNS = [
2484
2654
  ];
2485
2655
  async function runAuditSecrets(ctx) {
2486
2656
  const root = ctx.projectRoot;
2487
- const scanRoots = ["src", "public", ".wphs/config"].map((d) => resolve14(root, d));
2657
+ const scanRoots = ["src", "public", ".wphs/config"].map((d) => resolve13(root, d));
2488
2658
  const findings = [];
2489
2659
  for (const dir of scanRoots) {
2490
2660
  for (const file of walkFiles(dir)) {
2491
2661
  if (!/\.(ts|tsx|js|jsx|json|env|yml|yaml|html)$/i.test(file)) continue;
2492
2662
  if (file.includes("node_modules")) continue;
2493
- const content = readFileSync8(file, "utf8");
2663
+ const content = readFileSync9(file, "utf8");
2494
2664
  for (const pattern of SECRET_PATTERNS) {
2495
2665
  pattern.lastIndex = 0;
2496
2666
  if (pattern.test(content)) {
@@ -2516,13 +2686,13 @@ async function runAuditSecrets(ctx) {
2516
2686
  }
2517
2687
 
2518
2688
  // src/cli/commands/init.ts
2519
- import { cpSync as cpSync2, existsSync as existsSync13, mkdirSync as mkdirSync4, readdirSync as readdirSync2, readFileSync as readFileSync11, writeFileSync as writeFileSync6 } from "fs";
2520
- import { basename as basename2, resolve as resolve18 } from "path";
2521
- import * as p4 from "@clack/prompts";
2689
+ import { cpSync as cpSync3, existsSync as existsSync13, mkdirSync as mkdirSync5, readdirSync as readdirSync2, readFileSync as readFileSync12, writeFileSync as writeFileSync8 } from "fs";
2690
+ import { basename as basename3, resolve as resolve17 } from "path";
2691
+ import * as p5 from "@clack/prompts";
2522
2692
 
2523
2693
  // src/lib/init-router-scaffold.ts
2524
- import { existsSync as existsSync10, mkdirSync as mkdirSync3, readFileSync as readFileSync9, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
2525
- import { resolve as resolve15 } from "path";
2694
+ import { existsSync as existsSync10, mkdirSync as mkdirSync4, readFileSync as readFileSync10, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "fs";
2695
+ import { resolve as resolve14 } from "path";
2526
2696
  var ROUTER_FILES = [
2527
2697
  "src/routes",
2528
2698
  "src/routeTree.gen.ts",
@@ -2536,10 +2706,10 @@ function isModuleTemplate(template) {
2536
2706
  return template === "module-hybrid-cdn" || template === "module-monolith";
2537
2707
  }
2538
2708
  function writeRouterConfig(targetDir, mode) {
2539
- const configDir = resolve15(targetDir, "src/config");
2540
- mkdirSync3(configDir, { recursive: true });
2541
- writeFileSync3(
2542
- resolve15(configDir, "router.ts"),
2709
+ const configDir = resolve14(targetDir, "src/config");
2710
+ mkdirSync4(configDir, { recursive: true });
2711
+ writeFileSync5(
2712
+ resolve14(configDir, "router.ts"),
2543
2713
  `/** Router history \u2014 set by \`wp init\`. Hash matches woodsportal-client-frontend (HubSpot CMS embeds). */
2544
2714
  export type RouterHistoryMode = 'hash' | 'browser'
2545
2715
 
@@ -2548,8 +2718,8 @@ export const ROUTER_HISTORY: RouterHistoryMode = '${mode}'
2548
2718
  );
2549
2719
  }
2550
2720
  function writeMountDevPreviewRouter(targetDir) {
2551
- writeFileSync3(
2552
- resolve15(targetDir, "src/portal/mount-dev-preview.tsx"),
2721
+ writeFileSync5(
2722
+ resolve14(targetDir, "src/portal/mount-dev-preview.tsx"),
2553
2723
  `import { mountGreenfieldDev } from '../../.wphs/.kit-bootstrap-greenfield.tsx'
2554
2724
 
2555
2725
  import { App } from '@/app'
@@ -2562,8 +2732,8 @@ export async function mountDevPreview() {
2562
2732
  );
2563
2733
  }
2564
2734
  function writeMountDevPreviewSinglePage(targetDir) {
2565
- writeFileSync3(
2566
- resolve15(targetDir, "src/portal/mount-dev-preview.tsx"),
2735
+ writeFileSync5(
2736
+ resolve14(targetDir, "src/portal/mount-dev-preview.tsx"),
2567
2737
  `import { mountGreenfieldDev } from '../../.wphs/.kit-bootstrap-greenfield.tsx'
2568
2738
 
2569
2739
  import { StarterSinglePage } from '@/features/starter'
@@ -2576,8 +2746,8 @@ export async function mountDevPreview() {
2576
2746
  );
2577
2747
  }
2578
2748
  function writeMountPortalRouter(targetDir) {
2579
- writeFileSync3(
2580
- resolve15(targetDir, "src/portal/mount-portal.tsx"),
2749
+ writeFileSync5(
2750
+ resolve14(targetDir, "src/portal/mount-portal.tsx"),
2581
2751
  `import ReactDOM from 'react-dom/client'
2582
2752
 
2583
2753
  import { App } from '@/app'
@@ -2594,8 +2764,8 @@ export async function mountPortal() {
2594
2764
  );
2595
2765
  }
2596
2766
  function writeMountPortalSinglePage(targetDir) {
2597
- writeFileSync3(
2598
- resolve15(targetDir, "src/portal/mount-portal.tsx"),
2767
+ writeFileSync5(
2768
+ resolve14(targetDir, "src/portal/mount-portal.tsx"),
2599
2769
  `import ReactDOM from 'react-dom/client'
2600
2770
 
2601
2771
  import { StarterSinglePage } from '@/features/starter'
@@ -2612,11 +2782,11 @@ export async function mountPortal() {
2612
2782
  );
2613
2783
  }
2614
2784
  function writeStarterSinglePage(targetDir) {
2615
- const file = resolve15(targetDir, "src/features/starter/components/starter-single-page.tsx");
2785
+ const file = resolve14(targetDir, "src/features/starter/components/starter-single-page.tsx");
2616
2786
  if (existsSync10(file)) {
2617
2787
  return;
2618
2788
  }
2619
- writeFileSync3(
2789
+ writeFileSync5(
2620
2790
  file,
2621
2791
  `import { StarterShell } from '@/components/layout/starter-shell'
2622
2792
 
@@ -2639,25 +2809,25 @@ export function StarterSinglePage() {
2639
2809
  );
2640
2810
  }
2641
2811
  function removePath(targetDir, relativePath) {
2642
- const full = resolve15(targetDir, relativePath);
2812
+ const full = resolve14(targetDir, relativePath);
2643
2813
  if (!existsSync10(full)) {
2644
2814
  return;
2645
2815
  }
2646
2816
  rmSync2(full, { recursive: true, force: true });
2647
2817
  }
2648
2818
  function stripTanStackFromPackage(targetDir) {
2649
- const pkgPath = resolve15(targetDir, "package.json");
2819
+ const pkgPath = resolve14(targetDir, "package.json");
2650
2820
  if (!existsSync10(pkgPath)) {
2651
2821
  return;
2652
2822
  }
2653
- const pkg = JSON.parse(readFileSync9(pkgPath, "utf8"));
2823
+ const pkg = JSON.parse(readFileSync10(pkgPath, "utf8"));
2654
2824
  if (!pkg.devDependencies) {
2655
2825
  return;
2656
2826
  }
2657
2827
  for (const dep of TANSTACK_DEPS) {
2658
2828
  delete pkg.devDependencies[dep];
2659
2829
  }
2660
- writeFileSync3(pkgPath, `${JSON.stringify(pkg, null, 2)}
2830
+ writeFileSync5(pkgPath, `${JSON.stringify(pkg, null, 2)}
2661
2831
  `);
2662
2832
  }
2663
2833
  function applyRouterScaffold(targetDir, mode, template) {
@@ -2677,141 +2847,602 @@ function applyRouterScaffold(targetDir, mode, template) {
2677
2847
  writeRouterConfig(targetDir, mode);
2678
2848
  writeMountDevPreviewRouter(targetDir);
2679
2849
  writeMountPortalRouter(targetDir);
2680
- const featuresIndex = resolve15(targetDir, "src/features/starter/index.ts");
2850
+ const featuresIndex = resolve14(targetDir, "src/features/starter/index.ts");
2681
2851
  if (existsSync10(featuresIndex)) {
2682
- let content = readFileSync9(featuresIndex, "utf8");
2852
+ let content = readFileSync10(featuresIndex, "utf8");
2683
2853
  if (!content.includes("StarterSinglePage")) {
2684
2854
  content = content.replace(
2685
2855
  "export type { StarterPreviewValues } from './hooks/use-starter-preview'\n",
2686
2856
  "export type { StarterPreviewValues } from './hooks/use-starter-preview'\nexport { StarterSinglePage } from './components/starter-single-page'\n"
2687
2857
  );
2688
- writeFileSync3(featuresIndex, content);
2858
+ writeFileSync5(featuresIndex, content);
2689
2859
  }
2690
2860
  }
2691
2861
  }
2692
2862
 
2693
- // src/lib/cdn-github-setup.ts
2694
- import { existsSync as existsSync11, readFileSync as readFileSync10, writeFileSync as writeFileSync4 } from "fs";
2695
- import { resolve as resolve16 } from "path";
2696
- import * as p3 from "@clack/prompts";
2697
- var PORTAL_CDN_PUBLISH_TOKEN_ENV = "PORTAL_CDN_PUBLISH_TOKEN";
2698
- var PORTAL_CDN_JSON = "portal.cdn.json";
2699
- var PLACEHOLDER_GITHUB = /your-org|your-portal-cdn-mirror/i;
2700
- function defaultPortalCdnJson() {
2701
- const examplePath = kitPath("templates", "shared", "portal.cdn.json.example");
2702
- if (existsSync11(examplePath)) {
2703
- return JSON.parse(readFileSync10(examplePath, "utf8"));
2704
- }
2863
+ // src/lib/init-auth-scaffold.ts
2864
+ import { cpSync as cpSync2, existsSync as existsSync11, readFileSync as readFileSync11, rmSync as rmSync3, writeFileSync as writeFileSync6 } from "fs";
2865
+ import { resolve as resolve15 } from "path";
2866
+
2867
+ // src/lib/template-auth-deps.ts
2868
+ var AUTH_SCAFFOLD_DEPS = {
2869
+ "woodsportal-client-sdk": "^4.0.7-dev.11",
2870
+ "@tanstack/react-query": "^5.66.9",
2871
+ "@t3-oss/env-core": "^0.12.0",
2872
+ zod: "^3.24.2",
2873
+ sonner: "^2.0.1"
2874
+ };
2875
+ function mergeAuthDevDependencies(existing) {
2705
2876
  return {
2706
- publishMode: "jsdelivr",
2707
- github: "your-org/your-portal-cdn-mirror",
2708
- gitRemote: "https://github.com/your-org/your-portal-cdn-mirror.git",
2709
- cdnPathInRepo: "dist/cdn",
2710
- defaultBranch: "main"
2877
+ ...existing,
2878
+ ...AUTH_SCAFFOLD_DEPS
2711
2879
  };
2712
2880
  }
2713
- function readPortalCdnJson(projectRoot) {
2714
- const path = resolve16(projectRoot, PORTAL_CDN_JSON);
2715
- if (!existsSync11(path)) return null;
2716
- try {
2717
- return JSON.parse(readFileSync10(path, "utf8"));
2718
- } catch {
2719
- return null;
2881
+
2882
+ // src/lib/init-auth-scaffold.ts
2883
+ var AUTH_SCAFFOLD_ROOT = kitPath("templates", "scaffold", "auth");
2884
+ function readTierManifest() {
2885
+ const path = resolve15(AUTH_SCAFFOLD_ROOT, "tier-manifest.json");
2886
+ return JSON.parse(readFileSync11(path, "utf8"));
2887
+ }
2888
+ function readNavManifest() {
2889
+ const path = resolve15(AUTH_SCAFFOLD_ROOT, "nav-manifest.json");
2890
+ return JSON.parse(readFileSync11(path, "utf8"));
2891
+ }
2892
+ function layersForMode(mode) {
2893
+ const manifest = readTierManifest();
2894
+ const entry = manifest.tiers[mode];
2895
+ if (!entry?.layers?.length) {
2896
+ throw new Error(`Unknown auth scaffold tier in tier-manifest.json: ${mode}`);
2720
2897
  }
2898
+ return entry.layers;
2721
2899
  }
2722
- function normalizeGithubSlug(input) {
2723
- const trimmed = input.trim().replace(/^https:\/\/github\.com\//, "").replace(/\.git$/, "");
2724
- const parts = trimmed.split("/").filter(Boolean);
2725
- if (parts.length !== 2) return null;
2726
- return `${parts[0]}/${parts[1]}`;
2900
+ function copyLayer(targetDir, layerName) {
2901
+ const layerRoot = resolve15(AUTH_SCAFFOLD_ROOT, layerName);
2902
+ if (!existsSync11(layerRoot)) {
2903
+ throw new Error(`Auth scaffold layer not found: ${layerName}`);
2904
+ }
2905
+ cpSync2(layerRoot, targetDir, { recursive: true, force: true });
2727
2906
  }
2728
- function portalCdnJsonFromGithub(github, base) {
2729
- const slug = normalizeGithubSlug(github);
2730
- if (!slug) {
2731
- throw new Error(`Invalid GitHub repo \u2014 use org/repo (got "${github}")`);
2907
+ function layerForNavMode(nav) {
2908
+ const manifest = readNavManifest();
2909
+ const entry = manifest.modes[nav];
2910
+ if (!entry?.layer) {
2911
+ throw new Error(`Unknown nav scaffold mode in nav-manifest.json: ${nav}`);
2732
2912
  }
2733
- return {
2734
- publishMode: base?.publishMode ?? "jsdelivr",
2735
- github: slug,
2736
- gitRemote: `https://github.com/${slug}.git`,
2737
- cdnPathInRepo: base?.cdnPathInRepo ?? "dist/cdn",
2738
- defaultBranch: base?.defaultBranch ?? "main"
2739
- };
2913
+ return entry.layer;
2740
2914
  }
2741
- function hasCdnPublishToken(projectRoot) {
2742
- if (process.env[PORTAL_CDN_PUBLISH_TOKEN_ENV]?.trim()) return true;
2743
- if (process.env.GITHUB_TOKEN?.trim()) return true;
2744
- return Boolean(readEnvLocal(projectRoot)[PORTAL_CDN_PUBLISH_TOKEN_ENV]?.trim());
2915
+ function copyNavLayer(targetDir, nav) {
2916
+ copyLayer(targetDir, layerForNavMode(nav));
2745
2917
  }
2746
- function hasConfiguredCdnGithubMirror(projectRoot) {
2747
- const config = readPortalCdnJson(projectRoot);
2748
- const github = config?.github ?? "";
2749
- return Boolean(github) && !PLACEHOLDER_GITHUB.test(github);
2918
+ function patchNavScaffoldFile(targetDir, nav) {
2919
+ const navScaffoldPath = resolve15(targetDir, "src/config/nav-scaffold.ts");
2920
+ if (!existsSync11(navScaffoldPath)) {
2921
+ return;
2922
+ }
2923
+ const content = `/** Set by wp init --nav ${nav}. */
2924
+ export const NAV_SCAFFOLD_MODE = '${nav}' as const
2925
+
2926
+ export type NavScaffoldMode = typeof NAV_SCAFFOLD_MODE
2927
+ `;
2928
+ writeFileSync6(navScaffoldPath, content);
2750
2929
  }
2751
- function githubDefaultForPrompt(projectRoot) {
2752
- const existing = readPortalCdnJson(projectRoot);
2753
- if (existing?.github && !PLACEHOLDER_GITHUB.test(existing.github)) {
2754
- return existing.github;
2930
+ function patchAuthTierFile(targetDir, mode) {
2931
+ const authTierPath = resolve15(targetDir, "src/config/auth-tier.ts");
2932
+ if (!existsSync11(authTierPath)) {
2933
+ return;
2934
+ }
2935
+ const content = `/** Set by wp init --auth ${mode}. */
2936
+ export const AUTH_SCAFFOLD_TIER = '${mode}' as const
2937
+
2938
+ export type AuthScaffoldTier = typeof AUTH_SCAFFOLD_TIER
2939
+ `;
2940
+ writeFileSync6(authTierPath, content);
2941
+ }
2942
+ function patchStarterOnlyBlocks(targetDir, mode) {
2943
+ const hasSso = mode === "sso" || mode === "full";
2944
+ const hasFull = mode === "full";
2945
+ const filesToPatch = [
2946
+ "src/integrations/configure-woodsportal-sdk.ts",
2947
+ "src/features/auth/login-session-bridge.ts",
2948
+ "src/app.tsx",
2949
+ "src/features/auth/components/starter-login.tsx",
2950
+ "src/portal/mount-portal.tsx",
2951
+ "src/features/auth/auth-route-paths.ts"
2952
+ ];
2953
+ for (const relative of filesToPatch) {
2954
+ const filePath = resolve15(targetDir, relative);
2955
+ if (!existsSync11(filePath)) {
2956
+ continue;
2957
+ }
2958
+ let content = readFileSync11(filePath, "utf8");
2959
+ content = stripMarkedBlock(content, "WPHS_AUTH_SSO", hasSso);
2960
+ content = stripMarkedBlock(content, "WPHS_AUTH_FULL", hasFull);
2961
+ content = stripMarkedBlock(content, "WPHS_AUTH_NOT_FULL", !hasFull);
2962
+ if (!hasFull && relative === "src/integrations/configure-woodsportal-sdk.ts") {
2963
+ content = content.replace(
2964
+ /^import \{ isMfaPendingSession \} from '@\/features\/auth\/auth-session'\n/m,
2965
+ ""
2966
+ );
2967
+ }
2968
+ writeFileSync6(filePath, content);
2755
2969
  }
2756
- return defaultPortalCdnJson().github;
2757
2970
  }
2758
- function needsCdnGitHubSetup(projectRoot) {
2759
- return !hasConfiguredCdnGithubMirror(projectRoot) || !hasCdnPublishToken(projectRoot);
2971
+ function stripMarkedBlock(content, marker, keep) {
2972
+ const start = `// ${marker}`;
2973
+ const end = `// /${marker}`;
2974
+ const pattern = new RegExp(
2975
+ `${escapeRegExp(start)}[\\s\\S]*?${escapeRegExp(end)}\\n?`,
2976
+ "g"
2977
+ );
2978
+ if (keep) {
2979
+ return content.replace(new RegExp(`${escapeRegExp(start)}\\n?`, "g"), "").replace(new RegExp(`${escapeRegExp(end)}\\n?`, "g"), "");
2980
+ }
2981
+ return content.replace(pattern, "");
2760
2982
  }
2761
- function applyCdnGitHubSetup(projectRoot, input) {
2762
- const existing = readPortalCdnJson(projectRoot) ?? defaultPortalCdnJson();
2763
- const updated = portalCdnJsonFromGithub(input.github, existing);
2764
- writeFileSync4(resolve16(projectRoot, PORTAL_CDN_JSON), `${JSON.stringify(updated, null, 2)}
2983
+ function escapeRegExp(value) {
2984
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2985
+ }
2986
+ var LOWER_TIER_AUTH_GUARD_FILES = [
2987
+ "src/features/auth/login-session-bridge.ts",
2988
+ "src/integrations/configure-woodsportal-sdk.ts",
2989
+ "src/app.tsx",
2990
+ "src/portal/mount-portal.tsx",
2991
+ "src/features/auth/components/starter-login.tsx"
2992
+ ];
2993
+ function assertLowerTierAuthScaffold(targetDir, mode) {
2994
+ const violations = [];
2995
+ for (const relative of LOWER_TIER_AUTH_GUARD_FILES) {
2996
+ const filePath = resolve15(targetDir, relative);
2997
+ if (!existsSync11(filePath)) {
2998
+ continue;
2999
+ }
3000
+ const content = readFileSync11(filePath, "utf8");
3001
+ if (content.includes("WPHS_AUTH_")) {
3002
+ violations.push(`${relative}: leftover WPHS_AUTH_* strip markers`);
3003
+ }
3004
+ if (relative === "src/features/auth/login-session-bridge.ts") {
3005
+ if (content.includes("mfa-pending-storage")) {
3006
+ violations.push(`${relative}: must not import mfa-pending-storage on --auth ${mode}`);
3007
+ }
3008
+ if (content.includes("navigateToMfaGate")) {
3009
+ violations.push(`${relative}: must not reference navigateToMfaGate on --auth ${mode}`);
3010
+ }
3011
+ }
3012
+ }
3013
+ if (existsSync11(resolve15(targetDir, "src/routes/_auth/two-fa.tsx"))) {
3014
+ violations.push("src/routes/_auth/two-fa.tsx: MFA route must not exist on --auth " + mode);
3015
+ }
3016
+ if (violations.length > 0) {
3017
+ throw new WpHsError(`Auth scaffold tier "${mode}" still contains full-tier artifacts:
3018
+ - ${violations.join("\n- ")}`, {
3019
+ exitCode: ExitCode.INTERNAL,
3020
+ remediation: "Fix strip markers in templates/scaffold/auth/_shared or report a hubspot-kit bug."
3021
+ });
3022
+ }
3023
+ }
3024
+ function assertFullTierAuthScaffold(targetDir) {
3025
+ const required = [
3026
+ "src/features/auth/mfa-pending-storage.ts",
3027
+ "src/routes/_auth/two-fa.tsx"
3028
+ ];
3029
+ const missing = required.filter((relative) => !existsSync11(resolve15(targetDir, relative)));
3030
+ if (missing.length > 0) {
3031
+ throw new WpHsError(`Auth scaffold tier "full" is missing required files:
3032
+ - ${missing.join("\n- ")}`, {
3033
+ exitCode: ExitCode.INTERNAL
3034
+ });
3035
+ }
3036
+ const bridge = readFileSync11(resolve15(targetDir, "src/features/auth/login-session-bridge.ts"), "utf8");
3037
+ if (bridge.includes("WPHS_AUTH_")) {
3038
+ throw new WpHsError('Auth scaffold tier "full" left WPHS_AUTH_* markers in login-session-bridge.ts', {
3039
+ exitCode: ExitCode.INTERNAL
3040
+ });
3041
+ }
3042
+ if (!bridge.includes("navigateToMfaGate") || !bridge.includes("mfa-pending-storage")) {
3043
+ throw new WpHsError('Auth scaffold tier "full" login-session-bridge.ts is missing MFA wiring', {
3044
+ exitCode: ExitCode.INTERNAL
3045
+ });
3046
+ }
3047
+ const starterLoginPath = resolve15(targetDir, "src/features/auth/components/starter-login.tsx");
3048
+ if (existsSync11(starterLoginPath)) {
3049
+ const starterLogin = readFileSync11(starterLoginPath, "utf8");
3050
+ if (starterLogin.includes("WPHS_AUTH_")) {
3051
+ throw new WpHsError('Auth scaffold tier "full" left WPHS_AUTH_* markers in starter-login.tsx', {
3052
+ exitCode: ExitCode.INTERNAL
3053
+ });
3054
+ }
3055
+ if (starterLogin.includes("MFA_REQUIRED_SCAFFOLD_MESSAGE")) {
3056
+ throw new WpHsError(
3057
+ 'Auth scaffold tier "full" starter-login.tsx must not import MFA_REQUIRED_SCAFFOLD_MESSAGE',
3058
+ { exitCode: ExitCode.INTERNAL }
3059
+ );
3060
+ }
3061
+ }
3062
+ }
3063
+ function patchPackageJson(targetDir) {
3064
+ const pkgPath = resolve15(targetDir, "package.json");
3065
+ if (!existsSync11(pkgPath)) {
3066
+ return;
3067
+ }
3068
+ const pkg = JSON.parse(readFileSync11(pkgPath, "utf8"));
3069
+ pkg.devDependencies = mergeAuthDevDependencies(pkg.devDependencies);
3070
+ writeFileSync6(pkgPath, `${JSON.stringify(pkg, null, 2)}
2765
3071
  `);
2766
- if (input.token?.trim()) {
2767
- writeEnvLocal(projectRoot, { [PORTAL_CDN_PUBLISH_TOKEN_ENV]: input.token.trim() });
3072
+ }
3073
+ function writeAuthMountDevPreview(targetDir) {
3074
+ writeFileSync6(
3075
+ resolve15(targetDir, "src/portal/mount-dev-preview.tsx"),
3076
+ `import { mountGreenfieldDev } from '../../.wphs/.kit-bootstrap-greenfield.tsx'
3077
+
3078
+ import { App } from '@/app'
3079
+ import { getPortalRouter, mountPortal } from '@/portal/mount-portal'
3080
+
3081
+ /** Local dev: module-config overlay + auth-aware App shell. */
3082
+ export async function mountDevPreview() {
3083
+ await mountGreenfieldDev({
3084
+ beforePreview: () => mountPortal({ skipAppRender: true }),
3085
+ preview: <App router={getPortalRouter()} />,
3086
+ })
3087
+ }
3088
+ `
3089
+ );
3090
+ }
3091
+ function validateAuthRouterCombo(auth, router) {
3092
+ if (auth === "none") {
3093
+ return;
3094
+ }
3095
+ if (router === "none") {
3096
+ throw new Error(
3097
+ "Portal auth scaffold requires client-side routing. Use --router hash or --router browser."
3098
+ );
2768
3099
  }
2769
- ensureEnvFilesGitignore(projectRoot);
2770
3100
  }
2771
- async function promptCdnGitHubSetup(options = {}) {
2772
- if (options.skip) return null;
2773
- const projectRoot = options.projectRoot ?? process.cwd();
2774
- if (!needsCdnGitHubSetup(projectRoot)) {
2775
- return null;
3101
+ function validateNavAuthCombo(auth, nav) {
3102
+ if (!nav || nav === "top") {
3103
+ return;
2776
3104
  }
2777
- let github = options.github?.trim();
2778
- if (!github && hasConfiguredCdnGithubMirror(projectRoot)) {
2779
- github = readPortalCdnJson(projectRoot)?.github;
3105
+ if (auth === "none") {
3106
+ throw new Error(
3107
+ "Portal navigation shell requires auth scaffold. Use --auth starter|sso|full or omit --nav."
3108
+ );
2780
3109
  }
2781
- if (!github) {
2782
- const answer = await p3.text({
2783
- message: "Public GitHub CDN mirror (org/repo)",
2784
- placeholder: options.githubDefault ?? "your-org/your-portal-cdn-mirror",
2785
- initialValue: options.githubDefault,
2786
- validate: (value) => {
2787
- const slug = normalizeGithubSlug(value ?? "");
2788
- return slug ? void 0 : "Enter a public repo as org/repo (jsDelivr serves from it)";
3110
+ }
3111
+ function assertNavScaffold(targetDir, nav) {
3112
+ const mainLayoutPath = resolve15(targetDir, "src/components/Layouts/MainLayout.tsx");
3113
+ if (!existsSync11(mainLayoutPath)) {
3114
+ throw new WpHsError("Auth scaffold is missing MainLayout.tsx after nav layer copy", {
3115
+ exitCode: ExitCode.INTERNAL
3116
+ });
3117
+ }
3118
+ const sidebarPath = resolve15(targetDir, "src/components/layout/portal-sidebar.tsx");
3119
+ if (nav === "sidebar") {
3120
+ if (!existsSync11(sidebarPath)) {
3121
+ throw new WpHsError('Nav scaffold mode "sidebar" is missing portal-sidebar.tsx', {
3122
+ exitCode: ExitCode.INTERNAL
3123
+ });
3124
+ }
3125
+ } else if (existsSync11(sidebarPath)) {
3126
+ throw new WpHsError('Nav scaffold mode "top" must not include portal-sidebar.tsx', {
3127
+ exitCode: ExitCode.INTERNAL
3128
+ });
3129
+ }
3130
+ const navScaffold = readFileSync11(resolve15(targetDir, "src/config/nav-scaffold.ts"), "utf8");
3131
+ if (!navScaffold.includes(`'${nav}'`)) {
3132
+ throw new WpHsError(`nav-scaffold.ts was not patched for mode "${nav}"`, {
3133
+ exitCode: ExitCode.INTERNAL
3134
+ });
3135
+ }
3136
+ }
3137
+ function applyAuthScaffold(targetDir, mode, routerMode, template, nav = "top") {
3138
+ if (mode === "none" || !isModuleTemplate(template)) {
3139
+ return;
3140
+ }
3141
+ validateAuthRouterCombo(mode, routerMode);
3142
+ validateNavAuthCombo(mode, nav);
3143
+ for (const layer of layersForMode(mode)) {
3144
+ copyLayer(targetDir, layer);
3145
+ }
3146
+ patchAuthTierFile(targetDir, mode);
3147
+ patchStarterOnlyBlocks(targetDir, mode);
3148
+ patchNavScaffoldFile(targetDir, nav);
3149
+ copyNavLayer(targetDir, nav);
3150
+ assertNavScaffold(targetDir, nav);
3151
+ if (mode === "starter" || mode === "sso") {
3152
+ assertLowerTierAuthScaffold(targetDir, mode);
3153
+ } else if (mode === "full") {
3154
+ assertFullTierAuthScaffold(targetDir);
3155
+ }
3156
+ patchPackageJson(targetDir);
3157
+ if (routerMode && routerMode !== "none") {
3158
+ writeAuthMountDevPreview(targetDir);
3159
+ }
3160
+ }
3161
+ var AUTH_TIER_ORDER = ["none", "starter", "sso", "full"];
3162
+ function authTierRank(tier) {
3163
+ return AUTH_TIER_ORDER.indexOf(tier);
3164
+ }
3165
+ function readCurrentAuthTier(targetDir) {
3166
+ const authTierPath = resolve15(targetDir, "src/config/auth-tier.ts");
3167
+ if (!existsSync11(authTierPath)) {
3168
+ return "none";
3169
+ }
3170
+ const content = readFileSync11(authTierPath, "utf8");
3171
+ const match = content.match(/AUTH_SCAFFOLD_TIER = '(starter|sso|full)'/);
3172
+ return match?.[1] ?? "none";
3173
+ }
3174
+ function applyAuthTierUpgrade(targetDir, targetTier, routerMode, template, nav = "top") {
3175
+ if (!isModuleTemplate(template)) {
3176
+ throw new WpHsError("Auth upgrade is only supported for module projects.", {
3177
+ exitCode: ExitCode.USER_ERROR
3178
+ });
3179
+ }
3180
+ validateAuthRouterCombo(targetTier, routerMode);
3181
+ const currentTier = readCurrentAuthTier(targetDir);
3182
+ if (currentTier === "none") {
3183
+ applyAuthScaffold(targetDir, targetTier, routerMode, template, nav);
3184
+ return targetTier;
3185
+ }
3186
+ if (authTierRank(targetTier) <= authTierRank(currentTier)) {
3187
+ throw new WpHsError(
3188
+ `Auth upgrade requires a higher tier than "${currentTier}" (requested "${targetTier}").`,
3189
+ {
3190
+ exitCode: ExitCode.USER_ERROR,
3191
+ remediation: "Use starter \u2192 sso \u2192 full. Re-init for downgrades."
3192
+ }
3193
+ );
3194
+ }
3195
+ const targetLayers = layersForMode(targetTier);
3196
+ const currentLayers = layersForMode(currentTier);
3197
+ const layersToCopy = targetLayers.filter((layer) => !currentLayers.includes(layer));
3198
+ for (const layer of layersToCopy) {
3199
+ copyLayer(targetDir, layer);
3200
+ }
3201
+ patchAuthTierFile(targetDir, targetTier);
3202
+ patchStarterOnlyBlocks(targetDir, targetTier);
3203
+ if (targetTier === "starter" || targetTier === "sso") {
3204
+ assertLowerTierAuthScaffold(targetDir, targetTier);
3205
+ } else {
3206
+ assertFullTierAuthScaffold(targetDir);
3207
+ }
3208
+ patchPackageJson(targetDir);
3209
+ if (routerMode && routerMode !== "none") {
3210
+ writeAuthMountDevPreview(targetDir);
3211
+ }
3212
+ return targetTier;
3213
+ }
3214
+ var SIDEBAR_NAV_FILES = [
3215
+ "src/components/layout/portal-sidebar.tsx",
3216
+ "src/components/layout/portal-sidebar-nav.tsx"
3217
+ ];
3218
+ function applyNavScaffoldUpgrade(targetDir, nav) {
3219
+ const authTierPath = resolve15(targetDir, "src/config/auth-tier.ts");
3220
+ if (!existsSync11(authTierPath)) {
3221
+ throw new WpHsError("Nav scaffold requires portal auth (missing src/config/auth-tier.ts).", {
3222
+ exitCode: ExitCode.USER_ERROR,
3223
+ remediation: "Run wp scaffold auth upgrade --tier starter first."
3224
+ });
3225
+ }
3226
+ if (nav === "top") {
3227
+ for (const relative of SIDEBAR_NAV_FILES) {
3228
+ const filePath = resolve15(targetDir, relative);
3229
+ if (existsSync11(filePath)) {
3230
+ rmSync3(filePath);
3231
+ }
3232
+ }
3233
+ }
3234
+ patchNavScaffoldFile(targetDir, nav);
3235
+ copyNavLayer(targetDir, nav);
3236
+ assertNavScaffold(targetDir, nav);
3237
+ }
3238
+ function readProjectAuthTier(targetDir) {
3239
+ return readCurrentAuthTier(targetDir);
3240
+ }
3241
+
3242
+ // src/lib/init-wizard.ts
3243
+ import * as p4 from "@clack/prompts";
3244
+ import { basename } from "path";
3245
+
3246
+ // src/lib/project-name.ts
3247
+ var PROJECT_NAME_PATTERN = /^[a-z][a-z0-9-]*$/;
3248
+ function normalizeProjectName(input) {
3249
+ return input.trim().toLowerCase().replace(/[\s_]+/g, "-").replace(/[^a-z0-9-]/g, "").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
3250
+ }
3251
+ function validateProjectName(name) {
3252
+ if (!name) return "Project name is required";
3253
+ if (!PROJECT_NAME_PATTERN.test(name)) {
3254
+ return "Use lowercase letters, numbers, and hyphens (e.g. my-portal-module)";
3255
+ }
3256
+ if (name.length > 64) return "Project name is too long (max 64 characters)";
3257
+ return void 0;
3258
+ }
3259
+ function moduleOutDirFromSlug(slug) {
3260
+ const pascal = slug.split("-").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
3261
+ return `dist/${pascal || "HubSpot"}.Module`;
3262
+ }
3263
+
3264
+ // src/lib/init-wizard.ts
3265
+ function cancelIfNeeded(value) {
3266
+ if (p4.isCancel(value)) {
3267
+ throw new WpHsError("Init cancelled", { exitCode: ExitCode.USER_ERROR });
3268
+ }
3269
+ return value;
3270
+ }
3271
+ async function runInitWizard(options) {
3272
+ let template = options.template;
3273
+ if (!template) {
3274
+ const type = cancelIfNeeded(
3275
+ await p4.select({
3276
+ message: "Project type",
3277
+ options: [
3278
+ {
3279
+ value: "module-hybrid-cdn",
3280
+ label: "Module (hybrid CDN)",
3281
+ hint: "jsDelivr + thin module.js"
3282
+ },
3283
+ {
3284
+ value: "module-monolith",
3285
+ label: "Module (monolith)",
3286
+ hint: "Single module.js IIFE"
3287
+ },
3288
+ {
3289
+ value: "theme-starter",
3290
+ label: "Theme starter",
3291
+ hint: "CMS theme templates"
3292
+ }
3293
+ ]
3294
+ })
3295
+ );
3296
+ template = type;
3297
+ }
3298
+ let projectName = options.name ? normalizeProjectName(options.name) : void 0;
3299
+ if (!options.directory && !projectName) {
3300
+ const nameAnswer = cancelIfNeeded(
3301
+ await p4.text({
3302
+ message: "Project name (folder name)",
3303
+ placeholder: "my-portal-module",
3304
+ validate: (value) => validateProjectName(normalizeProjectName(value ?? ""))
3305
+ })
3306
+ );
3307
+ projectName = normalizeProjectName(nameAnswer);
3308
+ }
3309
+ if (!projectName && options.directory) {
3310
+ projectName = normalizeProjectName(basename(options.directory) || "my-portal");
3311
+ }
3312
+ if (!projectName) {
3313
+ throw new WpHsError("Project name is required", {
3314
+ exitCode: ExitCode.USER_ERROR,
3315
+ remediation: "Pass --name or --directory."
3316
+ });
3317
+ }
3318
+ let router = options.router;
3319
+ if (!router && isModuleTemplate(template)) {
3320
+ const routerAnswer = cancelIfNeeded(
3321
+ await p4.select({
3322
+ message: "Client-side routing",
3323
+ initialValue: "hash",
3324
+ options: [
3325
+ {
3326
+ value: "hash",
3327
+ label: "Hash history (recommended)",
3328
+ hint: "Works in HubSpot CMS embeds"
3329
+ },
3330
+ {
3331
+ value: "browser",
3332
+ label: "Browser history",
3333
+ hint: "Clean URLs for local dev only"
3334
+ },
3335
+ {
3336
+ value: "none",
3337
+ label: "No router",
3338
+ hint: "Single-page starter"
3339
+ }
3340
+ ]
3341
+ })
3342
+ );
3343
+ router = routerAnswer;
3344
+ }
3345
+ let auth = options.auth ?? "none";
3346
+ if (!options.auth && isModuleTemplate(template) && router && router !== "none") {
3347
+ const authAnswer = cancelIfNeeded(
3348
+ await p4.select({
3349
+ message: "Portal auth (woodsportal-api + SDK)",
3350
+ initialValue: "none",
3351
+ options: [
3352
+ { value: "none", label: "No portal login", hint: "Routes only" },
3353
+ { value: "starter", label: "Email + password", hint: "Login + sample protected page" },
3354
+ { value: "sso", label: "Email + password + SSO", hint: "OAuth callback route" },
3355
+ {
3356
+ value: "full",
3357
+ label: "Email + password + SSO + MFA",
3358
+ hint: "Recommended for production portals"
3359
+ }
3360
+ ]
3361
+ })
3362
+ );
3363
+ auth = authAnswer;
3364
+ } else if (router === "none" && isModuleTemplate(template)) {
3365
+ p4.log.info("Portal auth requires routing \u2014 auth tier set to none.");
3366
+ auth = "none";
3367
+ }
3368
+ let nav = options.nav ?? "top";
3369
+ if (!options.nav && auth !== "none" && isModuleTemplate(template)) {
3370
+ const navAnswer = cancelIfNeeded(
3371
+ await p4.select({
3372
+ message: "Portal navigation shell",
3373
+ initialValue: "top",
3374
+ options: [
3375
+ {
3376
+ value: "top",
3377
+ label: "Top navigation (recommended)",
3378
+ hint: "Simple header links"
3379
+ },
3380
+ {
3381
+ value: "sidebar",
3382
+ label: "Sidebar navigation",
3383
+ hint: "Collapsible left menu (production-shaped)"
3384
+ }
3385
+ ]
3386
+ })
3387
+ );
3388
+ nav = navAnswer;
3389
+ } else if (auth === "none") {
3390
+ nav = "top";
3391
+ }
3392
+ let cdnSetup = null;
3393
+ if (template === "module-hybrid-cdn") {
3394
+ if (options.cdnGithub) {
3395
+ cdnSetup = { github: options.cdnGithub };
3396
+ } else {
3397
+ p4.log.step("Hybrid CDN \u2014 GitHub mirror (public repo for jsDelivr)");
3398
+ const prompted = await promptCdnGitHubSetup({
3399
+ projectRoot: options.cwd,
3400
+ githubDefault: githubDefaultForPrompt(options.cwd),
3401
+ github: options.cdnGithub
3402
+ });
3403
+ if (prompted) {
3404
+ cdnSetup = prompted;
2789
3405
  }
2790
- });
2791
- if (p3.isCancel(answer)) return null;
2792
- github = normalizeGithubSlug(answer) ?? void 0;
3406
+ }
2793
3407
  }
2794
- if (!github) return null;
2795
- if (hasCdnPublishToken(projectRoot)) {
2796
- return { github };
3408
+ const folder = options.directory ?? `${options.cwd}/${projectName}`;
3409
+ const summary = [
3410
+ `Template: ${template}`,
3411
+ `Folder: ${folder}`,
3412
+ ...isModuleTemplate(template) ? [
3413
+ `Router: ${router ?? "hash"}`,
3414
+ `Auth: ${auth}`,
3415
+ ...auth !== "none" ? [`Nav: ${nav}`] : []
3416
+ ] : [],
3417
+ ...template === "module-hybrid-cdn" ? [
3418
+ `CDN repo: ${cdnSetup?.github ?? "(skipped \u2014 configure later)"}`,
3419
+ ...cdnSetup?.github && /your-org|your-portal-cdn-mirror/i.test(cdnSetup.github) ? [" (placeholder \u2014 run wp setup before wp deploy)"] : []
3420
+ ] : []
3421
+ ].join("\n");
3422
+ p4.note(summary, "Scaffold summary");
3423
+ const confirmed = cancelIfNeeded(
3424
+ await p4.confirm({
3425
+ message: "Proceed with scaffold?",
3426
+ initialValue: true
3427
+ })
3428
+ );
3429
+ if (!confirmed) {
3430
+ throw new WpHsError("Init cancelled", { exitCode: ExitCode.USER_ERROR });
2797
3431
  }
2798
- const tokenAnswer = await p3.password({
2799
- message: "GitHub token (repo push on CDN mirror)",
2800
- validate: (value) => {
2801
- if (!value?.trim()) return "Required for wp publish \u2014 create a fine-grained PAT with Contents: Read and write";
2802
- return void 0;
2803
- }
2804
- });
2805
- if (p3.isCancel(tokenAnswer)) return null;
2806
- return { github, token: tokenAnswer.trim() };
2807
- }
2808
- function formatCdnSetupNote(_projectRoot) {
2809
- return `CDN config written \u2014 edit ${PORTAL_CDN_JSON} if needed; token saved to ${ENV_LOCAL_FILE} (gitignored)`;
3432
+ return {
3433
+ template,
3434
+ projectName,
3435
+ router,
3436
+ auth,
3437
+ nav,
3438
+ cdnSetup,
3439
+ confirmed: true
3440
+ };
2810
3441
  }
2811
3442
 
2812
3443
  // src/lib/project-setup.ts
2813
- import { copyFileSync, existsSync as existsSync12, writeFileSync as writeFileSync5 } from "fs";
2814
- import { basename, resolve as resolve17 } from "path";
3444
+ import { copyFileSync, existsSync as existsSync12, writeFileSync as writeFileSync7 } from "fs";
3445
+ import { basename as basename2, resolve as resolve16 } from "path";
2815
3446
  function envFileForTier2(tier) {
2816
3447
  return tier === "dev" ? ".env.dev" : tier === "stg" ? ".env.stg" : ".env.prod";
2817
3448
  }
@@ -2826,7 +3457,7 @@ function firstExisting(paths) {
2826
3457
  return null;
2827
3458
  }
2828
3459
  function copyMissingFile(projectRoot, targetName, sourceCandidates, dryRun) {
2829
- const target = resolve17(projectRoot, targetName);
3460
+ const target = resolve16(projectRoot, targetName);
2830
3461
  if (existsSync12(target)) {
2831
3462
  return { action: "skipped", path: targetName, detail: "already present" };
2832
3463
  }
@@ -2841,7 +3472,7 @@ function copyMissingFile(projectRoot, targetName, sourceCandidates, dryRun) {
2841
3472
  if (!dryRun) {
2842
3473
  copyFileSync(source, target);
2843
3474
  }
2844
- const from = basename(source);
3475
+ const from = basename2(source);
2845
3476
  return {
2846
3477
  action: "copied",
2847
3478
  path: targetName,
@@ -2849,12 +3480,12 @@ function copyMissingFile(projectRoot, targetName, sourceCandidates, dryRun) {
2849
3480
  };
2850
3481
  }
2851
3482
  function writeMissingFile(projectRoot, targetName, content, dryRun) {
2852
- const target = resolve17(projectRoot, targetName);
3483
+ const target = resolve16(projectRoot, targetName);
2853
3484
  if (existsSync12(target)) {
2854
3485
  return { action: "skipped", path: targetName, detail: "already present" };
2855
3486
  }
2856
3487
  if (!dryRun) {
2857
- writeFileSync5(target, content);
3488
+ writeFileSync7(target, content);
2858
3489
  }
2859
3490
  return {
2860
3491
  action: "copied",
@@ -2880,15 +3511,15 @@ function applyProjectSetup(ctx, options = {}) {
2880
3511
  const templateDir = kitTemplateDir(config);
2881
3512
  results.push(
2882
3513
  copyMissingFile(projectRoot, HUBSPOT_CONFIG_FILE, [
2883
- resolve17(projectRoot, HUBSPOT_CONFIG_EXAMPLE),
2884
- resolve17(templateDir, HUBSPOT_CONFIG_EXAMPLE),
3514
+ resolve16(projectRoot, HUBSPOT_CONFIG_EXAMPLE),
3515
+ resolve16(templateDir, HUBSPOT_CONFIG_EXAMPLE),
2885
3516
  kitPath("templates", HUBSPOT_CONFIG_EXAMPLE)
2886
3517
  ], dryRun)
2887
3518
  );
2888
3519
  results.push(
2889
3520
  copyMissingFile(projectRoot, ".env.local", [
2890
- resolve17(projectRoot, ".env.local.example"),
2891
- resolve17(sharedDir, ".env.local.example")
3521
+ resolve16(projectRoot, ".env.local.example"),
3522
+ resolve16(sharedDir, ".env.local.example")
2892
3523
  ], dryRun)
2893
3524
  );
2894
3525
  for (const tier of tiersToSetup(config, options.tier)) {
@@ -2896,8 +3527,8 @@ function applyProjectSetup(ctx, options = {}) {
2896
3527
  const envExample = `${envFile}.example`;
2897
3528
  results.push(
2898
3529
  copyMissingFile(projectRoot, envFile, [
2899
- resolve17(projectRoot, envExample),
2900
- resolve17(sharedDir, envExample)
3530
+ resolve16(projectRoot, envExample),
3531
+ resolve16(sharedDir, envExample)
2901
3532
  ], dryRun)
2902
3533
  );
2903
3534
  }
@@ -2905,8 +3536,8 @@ function applyProjectSetup(ctx, options = {}) {
2905
3536
  const mirror = config.cdn.mirror;
2906
3537
  results.push(
2907
3538
  copyMissingFile(projectRoot, mirror, [
2908
- resolve17(projectRoot, `${mirror}.example`),
2909
- resolve17(sharedDir, "portal.cdn.json.example")
3539
+ resolve16(projectRoot, `${mirror}.example`),
3540
+ resolve16(sharedDir, "portal.cdn.json.example")
2910
3541
  ], dryRun)
2911
3542
  );
2912
3543
  const buildIdFile = config.cdn.buildIdFile;
@@ -2921,14 +3552,14 @@ function applyProjectSetup(ctx, options = {}) {
2921
3552
  }
2922
3553
  for (const file of templateViteFiles(config)) {
2923
3554
  results.push(
2924
- copyMissingFile(projectRoot, file, [resolve17(templateDir, file)], dryRun)
3555
+ copyMissingFile(projectRoot, file, [resolve16(templateDir, file)], dryRun)
2925
3556
  );
2926
3557
  }
2927
3558
  if (isModuleProject(config) && !isThemeProject(config)) {
2928
3559
  const adapter = config.dev.adapter;
2929
- const adapterPath = resolve17(projectRoot, adapter);
3560
+ const adapterPath = resolve16(projectRoot, adapter);
2930
3561
  if (!existsSync12(adapterPath)) {
2931
- const templateAdapter = resolve17(templateDir, "wphs.adapter.ts");
3562
+ const templateAdapter = resolve16(templateDir, "wphs.adapter.ts");
2932
3563
  if (existsSync12(templateAdapter)) {
2933
3564
  results.push(copyMissingFile(projectRoot, adapter, [templateAdapter], dryRun));
2934
3565
  } else {
@@ -2954,19 +3585,19 @@ function copySharedSetupExamples(targetDir) {
2954
3585
  ".env.prod.example",
2955
3586
  "portal.cdn.json.example"
2956
3587
  ]) {
2957
- const src = resolve17(sharedDir, name);
2958
- const dest = resolve17(targetDir, name);
3588
+ const src = resolve16(sharedDir, name);
3589
+ const dest = resolve16(targetDir, name);
2959
3590
  if (existsSync12(src) && !existsSync12(dest)) {
2960
3591
  copyFileSync(src, dest);
2961
3592
  }
2962
3593
  }
2963
3594
  const hubspotExample = firstExisting([
2964
- resolve17(sharedDir, HUBSPOT_CONFIG_EXAMPLE),
3595
+ resolve16(sharedDir, HUBSPOT_CONFIG_EXAMPLE),
2965
3596
  kitPath("templates", HUBSPOT_CONFIG_EXAMPLE)
2966
3597
  ]);
2967
3598
  if (!hubspotExample) return;
2968
- const destHubspot = resolve17(targetDir, HUBSPOT_CONFIG_EXAMPLE);
2969
- if (!existsSync12(destHubspot) && !existsSync12(resolve17(targetDir, HUBSPOT_CONFIG_FILE))) {
3599
+ const destHubspot = resolve16(targetDir, HUBSPOT_CONFIG_EXAMPLE);
3600
+ if (!existsSync12(destHubspot) && !existsSync12(resolve16(targetDir, HUBSPOT_CONFIG_FILE))) {
2970
3601
  copyFileSync(hubspotExample, destHubspot);
2971
3602
  }
2972
3603
  }
@@ -3014,26 +3645,8 @@ var TEMPLATE_SCRIPTS = {
3014
3645
  }
3015
3646
  };
3016
3647
 
3017
- // src/lib/project-name.ts
3018
- var PROJECT_NAME_PATTERN = /^[a-z][a-z0-9-]*$/;
3019
- function normalizeProjectName(input) {
3020
- return input.trim().toLowerCase().replace(/[\s_]+/g, "-").replace(/[^a-z0-9-]/g, "").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
3021
- }
3022
- function validateProjectName(name) {
3023
- if (!name) return "Project name is required";
3024
- if (!PROJECT_NAME_PATTERN.test(name)) {
3025
- return "Use lowercase letters, numbers, and hyphens (e.g. my-portal-module)";
3026
- }
3027
- if (name.length > 64) return "Project name is too long (max 64 characters)";
3028
- return void 0;
3029
- }
3030
- function moduleOutDirFromSlug(slug) {
3031
- const pascal = slug.split("-").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
3032
- return `dist/${pascal || "HubSpot"}.Module`;
3033
- }
3034
-
3035
3648
  // src/cli/commands/init.ts
3036
- function buildConfigForProject(template, projectName, router) {
3649
+ function buildConfigForProject(template, projectName, router, auth = "none", nav = "top") {
3037
3650
  const base = { ...DEFAULT_WOODSPORTAL_HS, projectType: template };
3038
3651
  if (template === "theme-starter") {
3039
3652
  return {
@@ -3058,8 +3671,12 @@ function buildConfigForProject(template, projectName, router) {
3058
3671
  mode: template === "module-monolith" ? "none" : base.cdn.mode
3059
3672
  }
3060
3673
  };
3061
- if (isModuleTemplate(template) && router) {
3062
- config.scaffold = { router };
3674
+ if (isModuleTemplate(template)) {
3675
+ config.scaffold = {
3676
+ router: router ?? "hash",
3677
+ auth: auth ?? "none",
3678
+ ...auth !== "none" ? { nav: nav ?? "top" } : {}
3679
+ };
3063
3680
  }
3064
3681
  return config;
3065
3682
  }
@@ -3075,7 +3692,7 @@ function assertTargetDirIsScaffoldable(targetDir) {
3075
3692
  }
3076
3693
  function resolveInitTargetDir(options, interactiveName) {
3077
3694
  if (options.directory) {
3078
- return resolve18(options.directory);
3695
+ return resolve17(options.directory);
3079
3696
  }
3080
3697
  const parentDir = process.cwd();
3081
3698
  const slug = options.name ?? interactiveName;
@@ -3089,67 +3706,31 @@ function resolveInitTargetDir(options, interactiveName) {
3089
3706
  if (error) {
3090
3707
  throw new WpHsError(error, { exitCode: ExitCode.USER_ERROR });
3091
3708
  }
3092
- return resolve18(parentDir, slug);
3709
+ return resolve17(parentDir, slug);
3093
3710
  }
3094
3711
  async function runInit(ctx, options) {
3095
3712
  const nonInteractive = ctx.global.ci || ctx.global.yes || ctx.global.json;
3096
3713
  let template = options.template;
3097
3714
  let projectName = options.name ? normalizeProjectName(options.name) : void 0;
3098
3715
  let router = options.router;
3716
+ let auth = options.auth ?? "none";
3717
+ let nav = options.nav ?? "top";
3718
+ let cdnGithub = options.cdnGithub;
3719
+ let wizardCdnSetup = null;
3099
3720
  if (!nonInteractive) {
3100
- if (!template) {
3101
- const type = await p4.select({
3102
- message: "Project type",
3103
- options: [
3104
- {
3105
- value: "module-hybrid-cdn",
3106
- label: "Module (hybrid CDN)",
3107
- hint: "jsDelivr + thin module.js"
3108
- },
3109
- { value: "module-monolith", label: "Module (monolith)", hint: "Single module.js IIFE" },
3110
- { value: "theme-starter", label: "Theme starter", hint: "CMS theme templates" }
3111
- ]
3112
- });
3113
- if (p4.isCancel(type)) throw new WpHsError("Init cancelled", { exitCode: ExitCode.USER_ERROR });
3114
- template = type;
3115
- }
3116
- if (!options.directory && !projectName) {
3117
- const nameAnswer = await p4.text({
3118
- message: "Project name (folder name)",
3119
- placeholder: "my-portal-module",
3120
- validate: (value) => validateProjectName(normalizeProjectName(value ?? ""))
3121
- });
3122
- if (p4.isCancel(nameAnswer)) {
3123
- throw new WpHsError("Init cancelled", { exitCode: ExitCode.USER_ERROR });
3124
- }
3125
- projectName = normalizeProjectName(nameAnswer);
3126
- }
3127
- if (!router && template && isModuleTemplate(template)) {
3128
- const routerAnswer = await p4.select({
3129
- message: "Client-side routing",
3130
- initialValue: "hash",
3131
- options: [
3132
- {
3133
- value: "hash",
3134
- label: "Hash history (recommended)",
3135
- hint: "Same as woodsportal-client-frontend \u2014 works in HubSpot CMS embeds"
3136
- },
3137
- {
3138
- value: "browser",
3139
- label: "Browser history",
3140
- hint: "Clean /guide URLs for local dev (not for HubSpot iframe embeds)"
3141
- },
3142
- {
3143
- value: "none",
3144
- label: "No router",
3145
- hint: "Single-page starter \u2014 add TanStack Router later"
3146
- }
3147
- ]
3148
- });
3149
- if (p4.isCancel(routerAnswer)) {
3150
- throw new WpHsError("Init cancelled", { exitCode: ExitCode.USER_ERROR });
3151
- }
3152
- router = routerAnswer;
3721
+ const wizard = await runInitWizard({
3722
+ ...options,
3723
+ cwd: process.cwd(),
3724
+ auth: options.auth
3725
+ });
3726
+ template = wizard.template;
3727
+ projectName = wizard.projectName;
3728
+ router = wizard.router;
3729
+ auth = wizard.auth;
3730
+ nav = wizard.nav ?? "top";
3731
+ wizardCdnSetup = wizard.cdnSetup;
3732
+ if (wizard.cdnSetup?.github) {
3733
+ cdnGithub = wizard.cdnSetup.github;
3153
3734
  }
3154
3735
  }
3155
3736
  if (!template) {
@@ -3158,44 +3739,58 @@ async function runInit(ctx, options) {
3158
3739
  if (!router && isModuleTemplate(template)) {
3159
3740
  router = "hash";
3160
3741
  }
3742
+ if (!options.auth && nonInteractive && isModuleTemplate(template)) {
3743
+ auth = "none";
3744
+ }
3745
+ try {
3746
+ validateAuthRouterCombo(auth, router);
3747
+ validateNavAuthCombo(auth, nav);
3748
+ } catch (error) {
3749
+ const message = error instanceof Error ? error.message : String(error);
3750
+ throw new WpHsError(message, {
3751
+ exitCode: ExitCode.USER_ERROR,
3752
+ remediation: "Use --router hash or --router browser with --auth starter|sso|full. Nav shell (--nav sidebar) requires auth."
3753
+ });
3754
+ }
3161
3755
  const targetDir = resolveInitTargetDir(options, projectName);
3162
- const slug = projectName ?? basename2(targetDir);
3756
+ const slug = projectName ?? basename3(targetDir);
3163
3757
  assertTargetDirIsScaffoldable(targetDir);
3164
3758
  const templateDir = kitPath("templates", template);
3165
3759
  if (!existsSync13(templateDir)) {
3166
3760
  throw new WpHsError(`Template not found: ${template}`, { exitCode: ExitCode.USER_ERROR });
3167
3761
  }
3168
3762
  if (ctx.global.dryRun) {
3169
- ctx.logger.info(`[dry-run] Would scaffold ${template} in ${targetDir}`);
3763
+ ctx.logger.info(
3764
+ `[dry-run] Would scaffold ${template} in ${targetDir} (router=${router ?? "n/a"}, auth=${auth}, nav=${auth !== "none" ? nav : "n/a"})`
3765
+ );
3170
3766
  return ExitCode.SUCCESS;
3171
3767
  }
3172
- mkdirSync4(targetDir, { recursive: true });
3173
- cpSync2(templateDir, targetDir, { recursive: true });
3768
+ mkdirSync5(targetDir, { recursive: true });
3769
+ cpSync3(templateDir, targetDir, { recursive: true });
3174
3770
  copySharedSetupExamples(targetDir);
3175
- const config = buildConfigForProject(template, slug, router);
3176
- writeFileSync6(resolve18(targetDir, WOODSCLI_CONFIG_FILE), `${JSON.stringify(config, null, 2)}
3771
+ const config = buildConfigForProject(template, slug, router, auth, nav);
3772
+ writeFileSync8(resolve17(targetDir, WOODSCLI_CONFIG_FILE), `${JSON.stringify(config, null, 2)}
3177
3773
  `);
3178
- const pkgPath = resolve18(targetDir, "package.json");
3774
+ const pkgPath = resolve17(targetDir, "package.json");
3179
3775
  if (existsSync13(pkgPath)) {
3180
- const pkg = JSON.parse(readFileSync11(pkgPath, "utf8"));
3776
+ const pkg = JSON.parse(readFileSync12(pkgPath, "utf8"));
3181
3777
  pkg.name = slug;
3182
3778
  pkg.scripts = { ...pkg.scripts, ...TEMPLATE_SCRIPTS[template] };
3183
3779
  pkg.devDependencies = {
3184
3780
  ...pkg.devDependencies,
3185
3781
  ...templateDevDependencies()
3186
3782
  };
3187
- writeFileSync6(pkgPath, `${JSON.stringify(pkg, null, 2)}
3783
+ writeFileSync8(pkgPath, `${JSON.stringify(pkg, null, 2)}
3188
3784
  `);
3189
3785
  }
3190
3786
  if (router && isModuleTemplate(template)) {
3191
3787
  applyRouterScaffold(targetDir, router, template);
3192
3788
  }
3193
- runNodeScript(kitPath("scripts/ensure-wphs-workspace.mjs"), [], {
3194
- cwd: targetDir,
3195
- logger: ctx.logger,
3196
- dryRun: false
3197
- });
3198
- const gitignorePath = resolve18(targetDir, ".gitignore");
3789
+ if (isModuleTemplate(template)) {
3790
+ applyAuthScaffold(targetDir, auth, router, template, nav);
3791
+ }
3792
+ await ensureProjectWphsWorkspace(targetDir);
3793
+ const gitignorePath = resolve17(targetDir, ".gitignore");
3199
3794
  const gitignoreExtras = [
3200
3795
  "hubspot.config.yml",
3201
3796
  ".wphs/config/module-config.local.json",
@@ -3208,40 +3803,27 @@ async function runInit(ctx, options) {
3208
3803
  ".wphs/.kit-bootstrap.tsx",
3209
3804
  ".wphs/.kit-bootstrap-greenfield.tsx"
3210
3805
  ];
3211
- const existing = existsSync13(gitignorePath) ? readFileSync11(gitignorePath, "utf8") : "";
3806
+ const existing = existsSync13(gitignorePath) ? readFileSync12(gitignorePath, "utf8") : "";
3212
3807
  const toAppend = gitignoreExtras.filter((line) => !existing.includes(line)).join("\n");
3213
3808
  if (toAppend) {
3214
- writeFileSync6(gitignorePath, `${existing.trimEnd()}
3809
+ writeFileSync8(gitignorePath, `${existing.trimEnd()}
3215
3810
  ${toAppend}
3216
3811
  `);
3217
3812
  }
3218
- if (template === "module-hybrid-cdn" && !ctx.global.dryRun && needsCdnGitHubSetup(targetDir)) {
3219
- const githubDefault = githubDefaultForPrompt(targetDir);
3220
- let cdnSetup = null;
3221
- if (nonInteractive) {
3222
- if (options.cdnGithub) {
3223
- cdnSetup = { github: options.cdnGithub };
3224
- }
3225
- } else {
3226
- p4.log.step("Hybrid CDN \u2014 GitHub mirror (public repo for jsDelivr)");
3227
- cdnSetup = await promptCdnGitHubSetup({
3228
- projectRoot: targetDir,
3229
- githubDefault,
3230
- github: options.cdnGithub
3231
- });
3232
- }
3233
- if (cdnSetup) {
3234
- applyCdnGitHubSetup(targetDir, cdnSetup);
3813
+ if (template === "module-hybrid-cdn" && !ctx.global.dryRun) {
3814
+ const setup = wizardCdnSetup ?? (cdnGithub ? { github: cdnGithub } : null);
3815
+ if (setup) {
3816
+ applyCdnGitHubSetup(targetDir, setup);
3235
3817
  if (!nonInteractive) {
3236
- p4.log.success(formatCdnSetupNote(targetDir));
3818
+ p5.log.success(formatCdnSetupNote(targetDir));
3237
3819
  } else {
3238
3820
  ctx.logger.success(formatCdnSetupNote(targetDir));
3239
3821
  }
3240
3822
  }
3241
3823
  }
3242
3824
  if (!nonInteractive) {
3243
- p4.log.success(`Scaffolded ${template} in ${targetDir}`);
3244
- p4.note(`cd ${slug} && yarn install && wp setup && wp doctor && wp dev`, "Getting started");
3825
+ p5.log.success(`Scaffolded ${template} in ${targetDir}`);
3826
+ p5.note(`cd ${slug} && npm install && wp setup && wp doctor && wp dev`, "Getting started");
3245
3827
  } else {
3246
3828
  ctx.logger.success(`Scaffolded ${template} in ${targetDir}`);
3247
3829
  }
@@ -3249,15 +3831,15 @@ ${toAppend}
3249
3831
  }
3250
3832
 
3251
3833
  // src/cli/commands/migrate.ts
3252
- import { cpSync as cpSync3, existsSync as existsSync14, mkdirSync as mkdirSync5, readFileSync as readFileSync12, readdirSync as readdirSync3, renameSync, rmSync as rmSync3, writeFileSync as writeFileSync7 } from "fs";
3253
- import { join as join3, resolve as resolve19 } from "path";
3834
+ import { cpSync as cpSync4, existsSync as existsSync14, mkdirSync as mkdirSync6, readFileSync as readFileSync13, readdirSync as readdirSync3, renameSync, rmSync as rmSync4, writeFileSync as writeFileSync9 } from "fs";
3835
+ import { join as join4, resolve as resolve18 } from "path";
3254
3836
  var LEGACY_DIRS = [".dev", ".woodsportal"];
3255
3837
  var LEGACY_BASELINE = ".wp-hs-baseline";
3256
3838
  var LEGACY_BUILD_ID_FILE = ".portal-build-id";
3257
3839
  var MODULE_BUILD_ID_FILE = ".module-build-id";
3258
3840
  function readFirstNonCommentLine(filePath) {
3259
3841
  if (!existsSync14(filePath)) return void 0;
3260
- const line = readFileSync12(filePath, "utf8").split("\n").map((l) => l.trim()).find((l) => l.length > 0 && !l.startsWith("#"));
3842
+ const line = readFileSync13(filePath, "utf8").split("\n").map((l) => l.trim()).find((l) => l.length > 0 && !l.startsWith("#"));
3261
3843
  return line || void 0;
3262
3844
  }
3263
3845
  function stripToBaseline(raw) {
@@ -3271,71 +3853,71 @@ function stripToBaseline(raw) {
3271
3853
  }
3272
3854
  async function runMigrate(ctx, _options = {}) {
3273
3855
  const root = ctx.projectRoot;
3274
- const wphsRoot = resolve19(root, WPHS_WORKSPACE_DIR);
3856
+ const wphsRoot = resolve18(root, WPHS_WORKSPACE_DIR);
3275
3857
  const actions = [];
3276
3858
  for (const legacy of LEGACY_DIRS) {
3277
- const legacyPath = resolve19(root, legacy);
3859
+ const legacyPath = resolve18(root, legacy);
3278
3860
  if (!existsSync14(legacyPath)) continue;
3279
3861
  actions.push(`Move ${legacy}/ \u2192 .wphs/`);
3280
3862
  if (ctx.global.dryRun) continue;
3281
- mkdirSync5(join3(wphsRoot, "config"), { recursive: true });
3282
- mkdirSync5(join3(wphsRoot, "assets", "branding"), { recursive: true });
3283
- const localConfig = join3(legacyPath, "module-config.local.json");
3284
- const exampleConfig = join3(legacyPath, "module-config.example.json");
3863
+ mkdirSync6(join4(wphsRoot, "config"), { recursive: true });
3864
+ mkdirSync6(join4(wphsRoot, "assets", "branding"), { recursive: true });
3865
+ const localConfig = join4(legacyPath, "module-config.local.json");
3866
+ const exampleConfig = join4(legacyPath, "module-config.example.json");
3285
3867
  if (existsSync14(localConfig)) {
3286
- cpSync3(localConfig, join3(wphsRoot, "config", "module-config.local.json"));
3868
+ cpSync4(localConfig, join4(wphsRoot, "config", "module-config.local.json"));
3287
3869
  }
3288
3870
  if (existsSync14(exampleConfig)) {
3289
- cpSync3(exampleConfig, join3(wphsRoot, "config", "module-config.example.json"));
3871
+ cpSync4(exampleConfig, join4(wphsRoot, "config", "module-config.example.json"));
3290
3872
  }
3291
- const branding = join3(legacyPath, "branding-assets");
3873
+ const branding = join4(legacyPath, "branding-assets");
3292
3874
  if (existsSync14(branding)) {
3293
3875
  for (const file of readdirSync3(branding)) {
3294
- cpSync3(join3(branding, file), join3(wphsRoot, "assets", "branding", file));
3876
+ cpSync4(join4(branding, file), join4(wphsRoot, "assets", "branding", file));
3295
3877
  }
3296
3878
  }
3297
- rmSync3(legacyPath, { recursive: true, force: true });
3879
+ rmSync4(legacyPath, { recursive: true, force: true });
3298
3880
  }
3299
- const legacyBaseline = resolve19(root, LEGACY_BASELINE);
3300
- const targetBaseline = join3(wphsRoot, "audit", "baseline");
3881
+ const legacyBaseline = resolve18(root, LEGACY_BASELINE);
3882
+ const targetBaseline = join4(wphsRoot, "audit", "baseline");
3301
3883
  if (existsSync14(legacyBaseline)) {
3302
3884
  actions.push(`Move ${LEGACY_BASELINE}/ \u2192 .wphs/audit/baseline/`);
3303
3885
  if (!ctx.global.dryRun) {
3304
- mkdirSync5(targetBaseline, { recursive: true });
3305
- cpSync3(legacyBaseline, targetBaseline, { recursive: true });
3306
- rmSync3(legacyBaseline, { recursive: true, force: true });
3886
+ mkdirSync6(targetBaseline, { recursive: true });
3887
+ cpSync4(legacyBaseline, targetBaseline, { recursive: true });
3888
+ rmSync4(legacyBaseline, { recursive: true, force: true });
3307
3889
  }
3308
3890
  }
3309
- const legacyConfigPath2 = resolve19(root, LEGACY_WOODSPORTAL_HS_FILE);
3310
- const woodscliPath = resolve19(root, WOODSCLI_CONFIG_FILE);
3311
- if (existsSync14(legacyConfigPath2) && !existsSync14(woodscliPath)) {
3891
+ const legacyConfigPath = resolve18(root, LEGACY_WOODSPORTAL_HS_FILE);
3892
+ const woodscliPath = resolve18(root, WOODSCLI_CONFIG_FILE);
3893
+ if (existsSync14(legacyConfigPath) && !existsSync14(woodscliPath)) {
3312
3894
  actions.push(`Rename ${LEGACY_WOODSPORTAL_HS_FILE} \u2192 ${WOODSCLI_CONFIG_FILE}`);
3313
3895
  if (!ctx.global.dryRun) {
3314
- renameSync(legacyConfigPath2, woodscliPath);
3896
+ renameSync(legacyConfigPath, woodscliPath);
3315
3897
  }
3316
- } else if (existsSync14(legacyConfigPath2) && existsSync14(woodscliPath)) {
3898
+ } else if (existsSync14(legacyConfigPath) && existsSync14(woodscliPath)) {
3317
3899
  actions.push(`Remove duplicate ${LEGACY_WOODSPORTAL_HS_FILE}`);
3318
3900
  if (!ctx.global.dryRun) {
3319
- rmSync3(legacyConfigPath2, { force: true });
3901
+ rmSync4(legacyConfigPath, { force: true });
3320
3902
  }
3321
3903
  }
3322
- const legacyBuildIdPath = resolve19(root, LEGACY_BUILD_ID_FILE);
3323
- const moduleBuildIdPath = resolve19(root, MODULE_BUILD_ID_FILE);
3904
+ const legacyBuildIdPath = resolve18(root, LEGACY_BUILD_ID_FILE);
3905
+ const moduleBuildIdPath = resolve18(root, MODULE_BUILD_ID_FILE);
3324
3906
  if (existsSync14(legacyBuildIdPath) && !existsSync14(moduleBuildIdPath)) {
3325
3907
  const baseline = stripToBaseline(readFirstNonCommentLine(legacyBuildIdPath)) ?? "v0.0.1-dev";
3326
3908
  actions.push(`Migrate ${LEGACY_BUILD_ID_FILE} \u2192 ${MODULE_BUILD_ID_FILE} (baseline ${baseline})`);
3327
3909
  if (!ctx.global.dryRun) {
3328
- writeFileSync7(
3910
+ writeFileSync9(
3329
3911
  moduleBuildIdPath,
3330
3912
  `# Channel baseline for local CDN deploys (wp deploy auto-bumps per developer).
3331
3913
  ${baseline}
3332
3914
  `
3333
3915
  );
3334
- rmSync3(legacyBuildIdPath, { force: true });
3916
+ rmSync4(legacyBuildIdPath, { force: true });
3335
3917
  }
3336
3918
  }
3337
3919
  if (existsSync14(woodscliPath)) {
3338
- const raw = JSON.parse(readFileSync12(woodscliPath, "utf8"));
3920
+ const raw = JSON.parse(readFileSync13(woodscliPath, "utf8"));
3339
3921
  let configChanged = false;
3340
3922
  if (raw?.dev?.moduleConfigDir && raw.dev.moduleConfigDir !== WPHS_WORKSPACE_DIR) {
3341
3923
  actions.push(`Update dev.moduleConfigDir \u2192 .wphs in project config`);
@@ -3352,7 +3934,7 @@ ${baseline}
3352
3934
  }
3353
3935
  }
3354
3936
  if (configChanged && !ctx.global.dryRun) {
3355
- writeFileSync7(woodscliPath, `${JSON.stringify(raw, null, 2)}
3937
+ writeFileSync9(woodscliPath, `${JSON.stringify(raw, null, 2)}
3356
3938
  `);
3357
3939
  }
3358
3940
  }
@@ -3370,20 +3952,16 @@ ${baseline}
3370
3952
  remediation: "Run: wp migrate --yes"
3371
3953
  });
3372
3954
  }
3373
- runNodeScript(kitPath("scripts/ensure-wphs-workspace.mjs"), [], {
3374
- cwd: root,
3375
- logger: ctx.logger,
3376
- dryRun: false
3377
- });
3955
+ await ensureProjectWphsWorkspace(root);
3378
3956
  for (const action of actions) ctx.logger.success(action);
3379
3957
  return ExitCode.SUCCESS;
3380
3958
  }
3381
3959
 
3382
3960
  // src/cli/commands/examples.ts
3383
- import { readFileSync as readFileSync13 } from "fs";
3961
+ import { readFileSync as readFileSync14 } from "fs";
3384
3962
  function loadManifest() {
3385
3963
  const manifestPath = kitPath("examples", "manifest.json");
3386
- const raw = JSON.parse(readFileSync13(manifestPath, "utf8"));
3964
+ const raw = JSON.parse(readFileSync14(manifestPath, "utf8"));
3387
3965
  return raw.scenarios;
3388
3966
  }
3389
3967
  async function runExamplesList(ctx, options = {}) {
@@ -3403,8 +3981,8 @@ async function runExamplesSmoke(ctx, options = {}) {
3403
3981
  const tier = options.tier ?? "static";
3404
3982
  const args = ["--tier", tier];
3405
3983
  if (options.scenario) args.push("--scenario", options.scenario);
3406
- const { runNodeScript: runNodeScript2 } = await import("./exec-VY6BGWF3.js");
3407
- runNodeScript2(kitPath("examples", "smoke", "run-matrix.mjs"), args, {
3984
+ const { runNodeScript } = await import("./exec-NS4GKXZS.js");
3985
+ runNodeScript(kitPath("examples", "smoke", "run-matrix.mjs"), args, {
3408
3986
  cwd: ctx.projectRoot,
3409
3987
  logger: ctx.logger,
3410
3988
  dryRun: ctx.global.dryRun
@@ -3416,13 +3994,13 @@ async function runExamplesSmoke(ctx, options = {}) {
3416
3994
  }
3417
3995
 
3418
3996
  // src/cli/commands/fields.ts
3419
- import { cpSync as cpSync4, existsSync as existsSync15, readFileSync as readFileSync14, writeFileSync as writeFileSync8 } from "fs";
3420
- import { resolve as resolve20 } from "path";
3997
+ import { cpSync as cpSync5, existsSync as existsSync15, readFileSync as readFileSync15, writeFileSync as writeFileSync10 } from "fs";
3998
+ import { resolve as resolve19 } from "path";
3421
3999
  async function runFieldsSync(ctx, options = {}) {
3422
4000
  const direction = options.direction ?? "pull";
3423
- const fieldsPath = resolve20(ctx.projectRoot, ctx.config.dev.fieldsJson);
3424
- const cacheDir = resolve20(ctx.projectRoot, ctx.config.dev.moduleConfigDir, "cache", "fields-sync");
3425
- const cachePath = resolve20(cacheDir, "fields.json");
4001
+ const fieldsPath = resolve19(ctx.projectRoot, ctx.config.dev.fieldsJson);
4002
+ const cacheDir = resolve19(ctx.projectRoot, ctx.config.dev.moduleConfigDir, "cache", "fields-sync");
4003
+ const cachePath = resolve19(cacheDir, "fields.json");
3426
4004
  if (direction === "pull") {
3427
4005
  if (!existsSync15(fieldsPath)) {
3428
4006
  throw new WpHsError(`Missing ${ctx.config.dev.fieldsJson}`, {
@@ -3434,9 +4012,9 @@ async function runFieldsSync(ctx, options = {}) {
3434
4012
  ctx.logger.info(`[dry-run] Would cache ${fieldsPath} \u2192 ${cachePath}`);
3435
4013
  return ExitCode.SUCCESS;
3436
4014
  }
3437
- const { mkdirSync: mkdirSync6 } = await import("fs");
3438
- mkdirSync6(cacheDir, { recursive: true });
3439
- cpSync4(fieldsPath, cachePath);
4015
+ const { mkdirSync: mkdirSync7 } = await import("fs");
4016
+ mkdirSync7(cacheDir, { recursive: true });
4017
+ cpSync5(fieldsPath, cachePath);
3440
4018
  ctx.logger.success(`Cached fields \u2192 ${cachePath}`);
3441
4019
  return ExitCode.SUCCESS;
3442
4020
  }
@@ -3449,14 +4027,14 @@ async function runFieldsSync(ctx, options = {}) {
3449
4027
  ctx.logger.info(`[dry-run] Would write ${cachePath} \u2192 ${fieldsPath}`);
3450
4028
  return ExitCode.SUCCESS;
3451
4029
  }
3452
- writeFileSync8(fieldsPath, readFileSync14(cachePath, "utf8"));
4030
+ writeFileSync10(fieldsPath, readFileSync15(cachePath, "utf8"));
3453
4031
  ctx.logger.success(`Restored fields from cache \u2192 ${fieldsPath}`);
3454
4032
  return ExitCode.SUCCESS;
3455
4033
  }
3456
4034
 
3457
4035
  // src/cli/commands/regression.ts
3458
4036
  import { existsSync as existsSync16 } from "fs";
3459
- import { resolve as resolve21 } from "path";
4037
+ import { resolve as resolve20 } from "path";
3460
4038
  var LEGACY_SHIM_SCRIPTS = [
3461
4039
  "build-cdn.mjs",
3462
4040
  "run-vite-build.mjs",
@@ -3479,7 +4057,7 @@ async function runRegressionSmoke(ctx, options = {}) {
3479
4057
  if (!validateOk) failures.push("wp config validate failed");
3480
4058
  if (options.checkLegacyShims !== false) {
3481
4059
  for (const script of LEGACY_SHIM_SCRIPTS) {
3482
- const shim = resolve21(ctx.projectRoot, "scripts", script);
4060
+ const shim = resolve20(ctx.projectRoot, "scripts", script);
3483
4061
  const kitScript = kitPath("scripts", script);
3484
4062
  const shimOk = existsSync16(shim) && existsSync16(kitScript);
3485
4063
  checks.push({ name: `shim:${script}`, ok: shimOk });
@@ -3487,12 +4065,12 @@ async function runRegressionSmoke(ctx, options = {}) {
3487
4065
  }
3488
4066
  }
3489
4067
  for (const rel of FORBIDDEN_CONSUMER_PATHS) {
3490
- const forbidden = resolve21(ctx.projectRoot, rel);
4068
+ const forbidden = resolve20(ctx.projectRoot, rel);
3491
4069
  const absent = !existsSync16(forbidden);
3492
4070
  checks.push({ name: `no-${rel}`, ok: absent });
3493
4071
  if (!absent) failures.push(`Forbidden dev path still present: ${rel}`);
3494
4072
  }
3495
- const woodscliConfig = resolve21(ctx.projectRoot, WOODSCLI_CONFIG_FILE);
4073
+ const woodscliConfig = resolve20(ctx.projectRoot, WOODSCLI_CONFIG_FILE);
3496
4074
  const configOk = existsSync16(woodscliConfig);
3497
4075
  checks.push({ name: WOODSCLI_CONFIG_FILE, ok: configOk });
3498
4076
  if (!configOk) failures.push(`${WOODSCLI_CONFIG_FILE} missing`);
@@ -3527,14 +4105,7 @@ async function runSetup(ctx, options = {}) {
3527
4105
  title: "Preparing .wphs workspace",
3528
4106
  task: async () => {
3529
4107
  if (ctx.global.dryRun) return;
3530
- try {
3531
- runNodeScript(kitPath("scripts", "ensure-wphs-workspace.mjs"), [], {
3532
- cwd: ctx.projectRoot,
3533
- logger: ctx.logger,
3534
- dryRun: false
3535
- });
3536
- } catch {
3537
- }
4108
+ await ensureProjectWphsWorkspace(ctx.projectRoot);
3538
4109
  }
3539
4110
  },
3540
4111
  {
@@ -3603,11 +4174,11 @@ async function runSetup(ctx, options = {}) {
3603
4174
  }
3604
4175
 
3605
4176
  // src/cli/commands/portal.ts
3606
- import * as p5 from "@clack/prompts";
4177
+ import * as p6 from "@clack/prompts";
3607
4178
 
3608
4179
  // src/lib/hubspot-portals.ts
3609
- import { existsSync as existsSync17, readFileSync as readFileSync15 } from "fs";
3610
- import { resolve as resolve22 } from "path";
4180
+ import { existsSync as existsSync17, readFileSync as readFileSync16 } from "fs";
4181
+ import { resolve as resolve21 } from "path";
3611
4182
  function stripYamlScalar(value) {
3612
4183
  const trimmed = value.trim();
3613
4184
  if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
@@ -3645,20 +4216,20 @@ function parseHubSpotConfigPortals(raw) {
3645
4216
  return { defaultPortal, portals };
3646
4217
  }
3647
4218
  function readHubSpotPortals(projectRoot) {
3648
- const configPath = resolve22(projectRoot, HUBSPOT_CONFIG_FILE);
4219
+ const configPath = resolve21(projectRoot, HUBSPOT_CONFIG_FILE);
3649
4220
  if (!existsSync17(configPath)) {
3650
4221
  throw new WpHsError(`${HUBSPOT_CONFIG_FILE} not found in project root.`, {
3651
4222
  exitCode: ExitCode.USER_ERROR,
3652
4223
  remediation: "Run wp setup, then add your portal + PAT."
3653
4224
  });
3654
4225
  }
3655
- const raw = readFileSync15(configPath, "utf8");
4226
+ const raw = readFileSync16(configPath, "utf8");
3656
4227
  const { defaultPortal, portals } = parseHubSpotConfigPortals(raw);
3657
4228
  return { configPath, defaultPortal, portals };
3658
4229
  }
3659
4230
  function resolvePortalName(portals, nameOrId) {
3660
4231
  const needle = nameOrId.trim();
3661
- return portals.find((p6) => p6.name === needle) ?? portals.find((p6) => p6.portalId === needle) ?? null;
4232
+ return portals.find((p9) => p9.name === needle) ?? portals.find((p9) => p9.portalId === needle) ?? null;
3662
4233
  }
3663
4234
 
3664
4235
  // src/cli/commands/portal.ts
@@ -3713,7 +4284,7 @@ async function runPortalUse(ctx, options = {}) {
3713
4284
  remediation: "wp portal use <name> \xB7 wp portal list"
3714
4285
  });
3715
4286
  }
3716
- const answer = await p5.select({
4287
+ const answer = await p6.select({
3717
4288
  message: "Set default HubSpot portal",
3718
4289
  options: data.portals.map((portal2) => ({
3719
4290
  value: portal2.name,
@@ -3721,7 +4292,7 @@ async function runPortalUse(ctx, options = {}) {
3721
4292
  hint: portal2.name === data.defaultPortal ? `current default \xB7 ${portal2.portalId ?? "no id"}` : portal2.portalId ?? void 0
3722
4293
  }))
3723
4294
  });
3724
- if (p5.isCancel(answer)) {
4295
+ if (p6.isCancel(answer)) {
3725
4296
  throw new WpHsError("Portal selection cancelled", { exitCode: ExitCode.USER_ERROR });
3726
4297
  }
3727
4298
  targetName = answer;
@@ -3740,7 +4311,8 @@ async function runPortalUse(ctx, options = {}) {
3740
4311
  runCommand("hs", ["accounts", "use", portal.name], {
3741
4312
  cwd: ctx.projectRoot,
3742
4313
  logger: ctx.logger,
3743
- dryRun: false
4314
+ dryRun: false,
4315
+ projectRoot: ctx.projectRoot
3744
4316
  });
3745
4317
  if (!ctx.global.json && !ctx.global.quiet) {
3746
4318
  printCommandDone(ctx, `Default portal \u2192 ${portal.name}`);
@@ -3755,6 +4327,311 @@ async function runPortalUse(ctx, options = {}) {
3755
4327
  return ExitCode.SUCCESS;
3756
4328
  }
3757
4329
 
4330
+ // src/cli/commands/page.ts
4331
+ import * as p7 from "@clack/prompts";
4332
+ function cancelIfNeeded2(value) {
4333
+ if (p7.isCancel(value)) {
4334
+ throw new WpHsError("Page scaffold cancelled", { exitCode: ExitCode.USER_ERROR });
4335
+ }
4336
+ return value;
4337
+ }
4338
+ function resolveAccessFlags(options) {
4339
+ if (options.isPublic && options.isProtected) {
4340
+ throw new WpHsError("Use only one of --public or --protected.", {
4341
+ exitCode: ExitCode.USER_ERROR
4342
+ });
4343
+ }
4344
+ if (options.isPublic) {
4345
+ return "public";
4346
+ }
4347
+ if (options.isProtected) {
4348
+ return "protected";
4349
+ }
4350
+ return void 0;
4351
+ }
4352
+ async function promptPageAddOptions(ctx, options) {
4353
+ const nonInteractive = ctx.global.ci || ctx.global.yes || ctx.global.json;
4354
+ const hasNavConfig = navConfigFileExists(ctx.projectRoot);
4355
+ let slug = options.slug ? normalizePageSlug(options.slug) : void 0;
4356
+ if (!slug && nonInteractive) {
4357
+ throw new WpHsError("Page slug is required in non-interactive mode.", {
4358
+ exitCode: ExitCode.USER_ERROR,
4359
+ remediation: "Pass a slug: wp page add settings"
4360
+ });
4361
+ }
4362
+ if (!slug) {
4363
+ const slugAnswer = cancelIfNeeded2(
4364
+ await p7.text({
4365
+ message: "Page slug (URL segment)",
4366
+ placeholder: "settings",
4367
+ validate: (value) => {
4368
+ const normalized = normalizePageSlug(value ?? "");
4369
+ if (!normalized || !/^[a-z][a-z0-9-]*$/.test(normalized)) {
4370
+ return "Use lowercase letters, numbers, and hyphens";
4371
+ }
4372
+ return void 0;
4373
+ }
4374
+ })
4375
+ );
4376
+ slug = normalizePageSlug(slugAnswer);
4377
+ }
4378
+ let path = options.path ? normalizePagePath(options.path, slug) : void 0;
4379
+ if (!path && !nonInteractive) {
4380
+ const pathAnswer = cancelIfNeeded2(
4381
+ await p7.text({
4382
+ message: "Route path",
4383
+ initialValue: `/${slug}`,
4384
+ validate: (value) => {
4385
+ const normalized = normalizePagePath(value ?? "", slug);
4386
+ if (!normalized.startsWith("/")) {
4387
+ return "Path must start with /";
4388
+ }
4389
+ return void 0;
4390
+ }
4391
+ })
4392
+ );
4393
+ path = normalizePagePath(pathAnswer, slug);
4394
+ }
4395
+ path ??= normalizePagePath(`/${slug}`, slug);
4396
+ let title = options.title?.trim() || titleFromSlug(slug);
4397
+ if (!options.title && !nonInteractive) {
4398
+ const titleAnswer = cancelIfNeeded2(
4399
+ await p7.text({
4400
+ message: "Page title",
4401
+ initialValue: title,
4402
+ validate: (value) => value?.trim() ? void 0 : "Title is required"
4403
+ })
4404
+ );
4405
+ title = titleAnswer.trim();
4406
+ }
4407
+ let access = resolveAccessFlags(options);
4408
+ if (!access && nonInteractive) {
4409
+ throw new WpHsError("Access mode is required in non-interactive mode.", {
4410
+ exitCode: ExitCode.USER_ERROR,
4411
+ remediation: "Pass --public or --protected."
4412
+ });
4413
+ }
4414
+ if (!access) {
4415
+ const accessAnswer = cancelIfNeeded2(
4416
+ await p7.select({
4417
+ message: "Who can access this page?",
4418
+ initialValue: "public",
4419
+ options: [
4420
+ {
4421
+ value: "public",
4422
+ label: "Public",
4423
+ hint: "Visible without signing in"
4424
+ },
4425
+ {
4426
+ value: "protected",
4427
+ label: "Protected",
4428
+ hint: "Requires portal sign-in"
4429
+ }
4430
+ ]
4431
+ })
4432
+ );
4433
+ access = accessAnswer;
4434
+ }
4435
+ let addToNav;
4436
+ if (options.addToNav) {
4437
+ addToNav = true;
4438
+ } else if (options.skipNav) {
4439
+ addToNav = false;
4440
+ } else if (!hasNavConfig) {
4441
+ addToNav = false;
4442
+ } else if (nonInteractive) {
4443
+ throw new WpHsError("Navigation choice is required in non-interactive mode.", {
4444
+ exitCode: ExitCode.USER_ERROR,
4445
+ remediation: "Pass --nav or --no-nav when portal-nav.ts or starter-nav.ts exists."
4446
+ });
4447
+ } else {
4448
+ const navAnswer = cancelIfNeeded2(
4449
+ await p7.confirm({
4450
+ message: "Add to navigation menu?",
4451
+ initialValue: true
4452
+ })
4453
+ );
4454
+ addToNav = navAnswer;
4455
+ }
4456
+ return { slug, path, title, access, addToNav };
4457
+ }
4458
+ async function runPageAdd(ctx, options) {
4459
+ const resolved = await promptPageAddOptions(ctx, options);
4460
+ const result = await scaffoldPage({
4461
+ projectRoot: ctx.projectRoot,
4462
+ config: ctx.config,
4463
+ slug: resolved.slug,
4464
+ path: resolved.path,
4465
+ title: resolved.title,
4466
+ access: resolved.access,
4467
+ addToNav: resolved.addToNav,
4468
+ dryRun: ctx.global.dryRun,
4469
+ logger: ctx.logger
4470
+ });
4471
+ if (ctx.global.json) {
4472
+ ctx.logger.json({
4473
+ ok: true,
4474
+ slug: resolved.slug,
4475
+ path: resolved.path,
4476
+ access: resolved.access,
4477
+ addToNav: resolved.addToNav,
4478
+ ...result
4479
+ });
4480
+ return ExitCode.SUCCESS;
4481
+ }
4482
+ if (ctx.global.dryRun) {
4483
+ ctx.logger.info("[dry-run] Page scaffold planned.");
4484
+ return ExitCode.SUCCESS;
4485
+ }
4486
+ const lines = [
4487
+ `Created ${result.routeFile}`,
4488
+ `Created ${result.pageFile}`,
4489
+ result.routeTreeRegenerated ? "Regenerated src/routeTree.gen.ts" : result.routeTreeWarning ?? "Restart wp dev if navigation types look stale."
4490
+ ];
4491
+ if (!ctx.global.quiet) {
4492
+ p7.log.success(`Page "${resolved.title}" scaffolded at ${resolved.path}`);
4493
+ p7.note(lines.join("\n"), "Next steps");
4494
+ } else {
4495
+ ctx.logger.success(lines.join("\n"));
4496
+ }
4497
+ return ExitCode.SUCCESS;
4498
+ }
4499
+ async function runPageList(ctx) {
4500
+ const { listScaffoldedPages } = await import("./scaffold-page-lifecycle-53AWWGFR.js");
4501
+ const pages = listScaffoldedPages(ctx.projectRoot, ctx.config);
4502
+ if (ctx.global.json) {
4503
+ ctx.logger.json({ pages });
4504
+ return ExitCode.SUCCESS;
4505
+ }
4506
+ if (pages.length === 0) {
4507
+ ctx.logger.info("No scaffolded pages in src/features/pages/ (use wp page add).");
4508
+ return ExitCode.SUCCESS;
4509
+ }
4510
+ if (!ctx.global.quiet) {
4511
+ p7.log.info(`Scaffolded pages (${pages.length})`);
4512
+ for (const page of pages) {
4513
+ const navFlag = page.inNav ? "nav" : "no-nav";
4514
+ p7.log.message(
4515
+ ` ${page.slug} ${page.path} ${page.access} ${navFlag} ${page.routeFile}`
4516
+ );
4517
+ }
4518
+ } else {
4519
+ for (const page of pages) {
4520
+ ctx.logger.info(`${page.slug} ${page.path} ${page.access} ${page.inNav}`);
4521
+ }
4522
+ }
4523
+ return ExitCode.SUCCESS;
4524
+ }
4525
+ async function runPageRemove(ctx, options) {
4526
+ const { removeScaffoldedPage } = await import("./scaffold-page-lifecycle-53AWWGFR.js");
4527
+ const result = removeScaffoldedPage({
4528
+ projectRoot: ctx.projectRoot,
4529
+ config: ctx.config,
4530
+ slug: options.slug,
4531
+ removeFromNav: !options.skipNav,
4532
+ dryRun: ctx.global.dryRun,
4533
+ logger: ctx.logger
4534
+ });
4535
+ if (ctx.global.json) {
4536
+ ctx.logger.json({ ok: true, ...result });
4537
+ return ExitCode.SUCCESS;
4538
+ }
4539
+ if (ctx.global.dryRun) {
4540
+ ctx.logger.info("[dry-run] Page removal planned.");
4541
+ return ExitCode.SUCCESS;
4542
+ }
4543
+ p7.log.success(`Removed page "${result.slug}"`);
4544
+ return ExitCode.SUCCESS;
4545
+ }
4546
+
4547
+ // src/cli/commands/scaffold.ts
4548
+ import * as p8 from "@clack/prompts";
4549
+ import { writeFileSync as writeFileSync11 } from "fs";
4550
+ import { resolve as resolve22 } from "path";
4551
+ function persistScaffoldConfig(projectRoot, config, patch) {
4552
+ const auth = patch.auth ?? config.scaffold?.auth ?? "none";
4553
+ const router = patch.router ?? config.scaffold?.router ?? "hash";
4554
+ const scaffold = { router, auth };
4555
+ if (auth !== "none") {
4556
+ scaffold.nav = patch.nav ?? config.scaffold?.nav ?? "top";
4557
+ }
4558
+ const next = { ...config, scaffold };
4559
+ writeFileSync11(resolve22(projectRoot, WOODSCLI_CONFIG_FILE), `${JSON.stringify(next, null, 2)}
4560
+ `);
4561
+ return next;
4562
+ }
4563
+ function parseAuthTier(value) {
4564
+ if (value === "starter" || value === "sso" || value === "full") {
4565
+ return value;
4566
+ }
4567
+ return void 0;
4568
+ }
4569
+ function runScaffoldAuthUpgrade(ctx, options) {
4570
+ const routerMode = ctx.config.scaffold?.router ?? "hash";
4571
+ const nav = ctx.config.scaffold?.nav ?? "top";
4572
+ const current = readProjectAuthTier(ctx.projectRoot);
4573
+ if (ctx.global.dryRun) {
4574
+ ctx.logger.info(
4575
+ `[dry-run] Would upgrade auth from "${current}" to "${options.tier}" in ${ctx.projectRoot}`
4576
+ );
4577
+ return ExitCode.SUCCESS;
4578
+ }
4579
+ const applied = applyAuthTierUpgrade(
4580
+ ctx.projectRoot,
4581
+ options.tier,
4582
+ routerMode,
4583
+ ctx.config.projectType,
4584
+ nav
4585
+ );
4586
+ persistScaffoldConfig(ctx.projectRoot, ctx.config, {
4587
+ auth: applied,
4588
+ nav: applied !== "none" ? nav : void 0
4589
+ });
4590
+ if (ctx.global.json) {
4591
+ ctx.logger.json({ ok: true, previousTier: current, tier: applied });
4592
+ return ExitCode.SUCCESS;
4593
+ }
4594
+ p8.log.success(`Portal auth upgraded to "${applied}" (was "${current}")`);
4595
+ p8.note("Review new routes under src/routes/_auth/ and run wp dev.", "Next steps");
4596
+ return ExitCode.SUCCESS;
4597
+ }
4598
+ function runScaffoldNav(ctx, options) {
4599
+ const currentNav = ctx.config.scaffold?.nav ?? "top";
4600
+ if (ctx.config.scaffold?.auth === "none" || readProjectAuthTier(ctx.projectRoot) === "none") {
4601
+ throw new WpHsError("Nav scaffold requires portal auth.", {
4602
+ exitCode: ExitCode.USER_ERROR,
4603
+ remediation: "Run wp scaffold auth upgrade --tier starter first."
4604
+ });
4605
+ }
4606
+ if (ctx.global.dryRun) {
4607
+ ctx.logger.info(`[dry-run] Would apply nav shell "${options.mode}" (was "${currentNav}")`);
4608
+ return ExitCode.SUCCESS;
4609
+ }
4610
+ applyNavScaffoldUpgrade(ctx.projectRoot, options.mode);
4611
+ persistScaffoldConfig(ctx.projectRoot, ctx.config, { nav: options.mode });
4612
+ if (ctx.global.json) {
4613
+ ctx.logger.json({ ok: true, previousMode: currentNav, mode: options.mode });
4614
+ return ExitCode.SUCCESS;
4615
+ }
4616
+ p8.log.success(`Navigation shell set to "${options.mode}" (was "${currentNav}")`);
4617
+ return ExitCode.SUCCESS;
4618
+ }
4619
+ function parseScaffoldAuthTierArg(value) {
4620
+ const tier = value ? parseAuthTier(value) : void 0;
4621
+ if (!tier) {
4622
+ throw new WpHsError("Auth tier is required: starter | sso | full", {
4623
+ exitCode: ExitCode.USER_ERROR
4624
+ });
4625
+ }
4626
+ return tier;
4627
+ }
4628
+ function parseScaffoldNavModeArg(value) {
4629
+ if (value === "top" || value === "sidebar") {
4630
+ return value;
4631
+ }
4632
+ throw new WpHsError("Nav mode is required: top | sidebar", { exitCode: ExitCode.USER_ERROR });
4633
+ }
4634
+
3758
4635
  // src/cli/commands/watch.ts
3759
4636
  async function runWatch(ctx, options) {
3760
4637
  ensureHubSpotConfigForCms(ctx);
@@ -3780,14 +4657,19 @@ async function runWatch(ctx, options) {
3780
4657
  }
3781
4658
 
3782
4659
  // src/cli/program.ts
3783
- function readVersion() {
4660
+ function resolveProjectRootForVersionCheck(project) {
4661
+ if (project) {
4662
+ return project;
4663
+ }
3784
4664
  try {
3785
- const pkg = JSON.parse(readFileSync16(resolve23(getKitRoot(), "package.json"), "utf8"));
3786
- return pkg.version ?? "0.0.0";
4665
+ return findProjectRoot();
3787
4666
  } catch {
3788
- return "0.0.0";
4667
+ return process.cwd();
3789
4668
  }
3790
4669
  }
4670
+ function readVersion() {
4671
+ return readKitVersion();
4672
+ }
3791
4673
  function buildContext(global) {
3792
4674
  const logger = createLogger({
3793
4675
  verbose: global.verbose,
@@ -3795,7 +4677,7 @@ function buildContext(global) {
3795
4677
  json: global.json,
3796
4678
  ci: global.ci
3797
4679
  });
3798
- const projectRoot = global.project ?? findProjectRoot2();
4680
+ const projectRoot = global.project ?? findProjectRoot();
3799
4681
  const { config } = loadConfig(projectRoot);
3800
4682
  return { projectRoot, config, logger, global };
3801
4683
  }
@@ -3805,7 +4687,7 @@ function attachGlobals(command) {
3805
4687
  async function handle(fn) {
3806
4688
  try {
3807
4689
  const code = await fn();
3808
- process.exit(code);
4690
+ await exitCli(code);
3809
4691
  } catch (error) {
3810
4692
  if (isWpHsError(error)) {
3811
4693
  process.stderr.write(`Error: ${error.message}
@@ -3814,16 +4696,30 @@ async function handle(fn) {
3814
4696
  process.stderr.write(`\u2192 ${error.remediation}
3815
4697
  `);
3816
4698
  }
3817
- process.exit(error.exitCode);
4699
+ await exitCli(error.exitCode);
3818
4700
  }
3819
4701
  process.stderr.write(`Error: ${error instanceof Error ? error.message : String(error)}
3820
4702
  `);
3821
- process.exit(ExitCode.INTERNAL);
4703
+ await exitCli(ExitCode.INTERNAL);
3822
4704
  }
3823
4705
  }
3824
4706
  function createProgram() {
3825
4707
  const program2 = new Command().name("wp").description("WoodsCLI \u2014 HubSpot CMS dev kit by WoodsPortal (modules + themes)").version(readVersion()).addHelpText("after", "\nWoodsCLI \xB7 WoodsPortal HubSpot CLI \xB7 npm @woodsportal/hubspot-kit");
3826
4708
  attachGlobals(program2);
4709
+ program2.hook("preAction", async (_thisCommand, actionCommand) => {
4710
+ const global = program2.opts();
4711
+ const name = actionCommand.name();
4712
+ if (name === "version" || name === "help") {
4713
+ return;
4714
+ }
4715
+ await maybeWarnKitUpdate({
4716
+ current: readKitVersion(),
4717
+ projectRoot: resolveProjectRootForVersionCheck(global.project),
4718
+ ci: global.ci,
4719
+ json: global.json,
4720
+ quiet: global.quiet
4721
+ });
4722
+ });
3827
4723
  program2.command("doctor").description("Check prerequisites (Node, HubSpot CLI, .wphs workspace, config)").action(async () => {
3828
4724
  const global = program2.opts();
3829
4725
  await handle(() => runDoctor(buildContext(global)));
@@ -3842,6 +4738,12 @@ function createProgram() {
3842
4738
  await handle(() => runConfigValidate(buildContext(global)));
3843
4739
  });
3844
4740
  program2.command("init").description("Scaffold a new module or theme project").option("-t, --template <type>", "module-hybrid-cdn | module-monolith | theme-starter").option("-n, --name <slug>", "Project name (creates ./<name>/ under cwd)").option("-d, --directory <path>", "Full target directory (skips name \u2192 folder prompt)").option("--router <mode>", "hash (client-frontend default) | browser | none \u2014 modules only").option(
4741
+ "--auth <tier>",
4742
+ "Portal auth: none | starter | sso | full \u2014 modules with router \u2260 none"
4743
+ ).option(
4744
+ "--nav <mode>",
4745
+ "Authenticated nav shell: top | sidebar (requires --auth starter|sso|full)"
4746
+ ).option(
3845
4747
  "--cdn-github <org/repo>",
3846
4748
  "Public GitHub CDN mirror repo (hybrid CDN; use with --ci / -y)"
3847
4749
  ).action(
@@ -3860,12 +4762,16 @@ function createProgram() {
3860
4762
  global
3861
4763
  };
3862
4764
  const routerMode = opts.router === "browser" || opts.router === "none" || opts.router === "hash" ? opts.router : void 0;
4765
+ const authMode = opts.auth === "none" || opts.auth === "starter" || opts.auth === "sso" || opts.auth === "full" ? opts.auth : void 0;
4766
+ const navMode = opts.nav === "top" || opts.nav === "sidebar" ? opts.nav : void 0;
3863
4767
  await handle(
3864
4768
  () => runInit(ctx, {
3865
4769
  template: opts.template,
3866
4770
  name: opts.name,
3867
4771
  directory: opts.directory,
3868
4772
  router: routerMode,
4773
+ auth: authMode,
4774
+ nav: navMode,
3869
4775
  cdnGithub: opts.cdnGithub
3870
4776
  })
3871
4777
  );
@@ -3980,14 +4886,18 @@ function createProgram() {
3980
4886
  });
3981
4887
  program2.command("auth").description("Authenticate HubSpot CLI (hs auth)").action(async () => {
3982
4888
  const global = program2.opts();
3983
- const ctx = buildContext(global);
3984
- const { runCommand: runCommand2 } = await import("./exec-VY6BGWF3.js");
3985
- runCommand2("hs", ["auth"], {
3986
- cwd: ctx.projectRoot,
3987
- logger: ctx.logger,
3988
- dryRun: ctx.global.dryRun
4889
+ await handle(async () => {
4890
+ const ctx = buildContext(global);
4891
+ const { runCommand: runCommand2 } = await import("./exec-NS4GKXZS.js");
4892
+ runCommand2("hs", ["auth"], {
4893
+ cwd: ctx.projectRoot,
4894
+ logger: ctx.logger,
4895
+ dryRun: ctx.global.dryRun,
4896
+ projectRoot: ctx.projectRoot,
4897
+ streamOutput: true
4898
+ });
4899
+ return ExitCode.SUCCESS;
3989
4900
  });
3990
- process.exit(ExitCode.SUCCESS);
3991
4901
  });
3992
4902
  const portal = program2.command("portal").description("HubSpot default portal selection");
3993
4903
  portal.command("list").alias("ls").description("List portals from hubspot.config.yml").action(async () => {
@@ -3998,6 +4908,45 @@ function createProgram() {
3998
4908
  const global = program2.opts();
3999
4909
  await handle(() => runPortalUse(buildContext(global), { name }));
4000
4910
  });
4911
+ const page = program2.command("page").description("Scaffold routed pages");
4912
+ page.command("add [slug]").description("Create a route file + page component").option("--path <path>", "URL path (default: /<slug>)").option("--title <title>", "Page heading").option("--public", "Public route (no sign-in)").option("--protected", "Protected route (requires auth)").option("--nav", "Add link to navigation menu (portal-nav.ts or starter-nav.ts)").option("--no-nav", "Skip navigation menu").action(
4913
+ async (slug, opts) => {
4914
+ const global = program2.opts();
4915
+ await handle(
4916
+ () => runPageAdd(buildContext(global), {
4917
+ slug,
4918
+ path: opts.path,
4919
+ title: opts.title,
4920
+ isPublic: opts.public,
4921
+ isProtected: opts.protected,
4922
+ addToNav: opts.nav,
4923
+ skipNav: opts.noNav
4924
+ })
4925
+ );
4926
+ }
4927
+ );
4928
+ page.command("list").alias("ls").description("List scaffolded pages under src/features/pages/").action(async () => {
4929
+ const global = program2.opts();
4930
+ await handle(() => runPageList(buildContext(global)));
4931
+ });
4932
+ page.command("remove <slug>").description("Remove a scaffolded page (route, component, routes.ts, nav)").option("--no-nav", "Leave navigation menu unchanged").action(async (slug, opts) => {
4933
+ const global = program2.opts();
4934
+ await handle(
4935
+ () => runPageRemove(buildContext(global), { slug, skipNav: opts.noNav })
4936
+ );
4937
+ });
4938
+ const scaffold = program2.command("scaffold").description("Re-apply portal scaffold layers");
4939
+ const scaffoldAuth = scaffold.command("auth").description("Portal auth tier upgrades");
4940
+ scaffoldAuth.command("upgrade").description("Upgrade portal auth tier on an existing module (starter \u2192 sso \u2192 full)").requiredOption("--tier <tier>", "starter | sso | full").action(async (opts) => {
4941
+ const global = program2.opts();
4942
+ const tier = parseScaffoldAuthTierArg(opts.tier);
4943
+ await handle(async () => runScaffoldAuthUpgrade(buildContext(global), { tier }));
4944
+ });
4945
+ scaffold.command("nav").description("Re-apply authenticated navigation shell (top | sidebar)").requiredOption("--mode <mode>", "top | sidebar").action(async (opts) => {
4946
+ const global = program2.opts();
4947
+ const mode = parseScaffoldNavModeArg(opts.mode);
4948
+ await handle(async () => runScaffoldNav(buildContext(global), { mode }));
4949
+ });
4001
4950
  return program2;
4002
4951
  }
4003
4952