@woodsportal/hubspot-kit 1.0.41-dev.4 → 1.0.41-dev.6

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 (580) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +191 -157
  3. package/dist/{chunk-S2YJDWZU.js → chunk-NWBK3XBZ.js} +1 -1
  4. package/dist/chunk-NWBK3XBZ.js.map +1 -0
  5. package/dist/chunk-WHYTQEZX.js +554 -0
  6. package/dist/chunk-WHYTQEZX.js.map +1 -0
  7. package/dist/cli.js +1494 -650
  8. package/dist/cli.js.map +1 -1
  9. package/dist/index.d.ts +12 -0
  10. package/dist/index.js +6 -2
  11. package/dist/index.js.map +1 -1
  12. package/dist/scaffold-page-lifecycle-EHMPWPPE.js +194 -0
  13. package/dist/scaffold-page-lifecycle-EHMPWPPE.js.map +1 -0
  14. package/dist/vite/index.js +1 -1
  15. package/examples/greenfield/module-hybrid-cdn/public/fields.json +73 -28
  16. package/examples/greenfield/module-hybrid-cdn/public/module.html +21 -5
  17. package/examples/greenfield/module-hybrid-cdn/src/assets/css/main.dev.css +15 -0
  18. package/examples/greenfield/module-hybrid-cdn/src/components/layout/starter-nav.tsx +12 -15
  19. package/examples/greenfield/module-hybrid-cdn/src/config/starter-nav.ts +9 -0
  20. package/examples/greenfield/module-hybrid-cdn/src/features/starter/components/starter-home.tsx +25 -29
  21. package/examples/greenfield/module-hybrid-cdn/src/features/starter/hooks/starter-preview-values.ts +60 -0
  22. package/examples/greenfield/module-hybrid-cdn/src/features/starter/hooks/use-starter-preview.dev.ts +18 -0
  23. package/examples/greenfield/module-hybrid-cdn/src/features/starter/hooks/use-starter-preview.ts +20 -37
  24. package/examples/greenfield/module-hybrid-cdn-auth-full/.env.dev.example +3 -0
  25. package/examples/greenfield/module-hybrid-cdn-auth-full/.env.local.example +6 -0
  26. package/examples/greenfield/module-hybrid-cdn-auth-full/.env.prod.example +5 -0
  27. package/examples/greenfield/module-hybrid-cdn-auth-full/.env.stg.example +3 -0
  28. package/examples/greenfield/module-hybrid-cdn-auth-full/.wphs/README.md +17 -0
  29. package/examples/greenfield/module-hybrid-cdn-auth-full/README.md +59 -0
  30. package/examples/greenfield/module-hybrid-cdn-auth-full/hubspot.config.yml.example +10 -0
  31. package/examples/greenfield/module-hybrid-cdn-auth-full/index.html +12 -0
  32. package/examples/greenfield/module-hybrid-cdn-auth-full/package.json +34 -0
  33. package/examples/greenfield/module-hybrid-cdn-auth-full/portal.cdn.json.example +8 -0
  34. package/examples/greenfield/module-hybrid-cdn-auth-full/public/fields.json +100 -0
  35. package/examples/greenfield/module-hybrid-cdn-auth-full/public/meta.json +9 -0
  36. package/examples/greenfield/module-hybrid-cdn-auth-full/public/module.html +38 -0
  37. package/examples/greenfield/module-hybrid-cdn-auth-full/src/app.tsx +172 -0
  38. package/examples/greenfield/module-hybrid-cdn-auth-full/src/assets/css/main.dev.css +23 -0
  39. package/examples/greenfield/module-hybrid-cdn-auth-full/src/assets/css/main.prod.css +12 -0
  40. package/examples/greenfield/module-hybrid-cdn-auth-full/src/assets/css/tailwind.css.config.css +7 -0
  41. package/examples/greenfield/module-hybrid-cdn-auth-full/src/cdn/app-entry.ts +48 -0
  42. package/examples/greenfield/module-hybrid-cdn-auth-full/src/cdn/vendor-entry.ts +28 -0
  43. package/examples/greenfield/module-hybrid-cdn-auth-full/src/components/Layouts/AppLayoutWrapper.tsx +38 -0
  44. package/examples/greenfield/module-hybrid-cdn-auth-full/src/components/Layouts/AuthLayout.tsx +7 -0
  45. package/examples/greenfield/module-hybrid-cdn-auth-full/src/components/Layouts/DefaultLayout.tsx +5 -0
  46. package/examples/greenfield/module-hybrid-cdn-auth-full/src/components/Layouts/MainLayout.tsx +42 -0
  47. package/examples/greenfield/module-hybrid-cdn-auth-full/src/components/Layouts/UnauthorizedLayout.tsx +5 -0
  48. package/examples/greenfield/module-hybrid-cdn-auth-full/src/components/Layouts/index.ts +13 -0
  49. package/examples/greenfield/module-hybrid-cdn-auth-full/src/components/layout/section-card.tsx +20 -0
  50. package/examples/greenfield/module-hybrid-cdn-auth-full/src/components/layout/starter-nav.tsx +28 -0
  51. package/examples/greenfield/module-hybrid-cdn-auth-full/src/components/layout/starter-shell.tsx +18 -0
  52. package/examples/greenfield/module-hybrid-cdn-auth-full/src/components/ui/Button.tsx +22 -0
  53. package/examples/greenfield/module-hybrid-cdn-auth-full/src/components/ui/Form.tsx +22 -0
  54. package/examples/greenfield/module-hybrid-cdn-auth-full/src/components/ui/Input.tsx +12 -0
  55. package/examples/greenfield/module-hybrid-cdn-auth-full/src/components/ui/Toaster.tsx +5 -0
  56. package/examples/greenfield/module-hybrid-cdn-auth-full/src/components/ui/auth/SsoLoginButton.tsx +34 -0
  57. package/examples/greenfield/module-hybrid-cdn-auth-full/src/components/ui/link.tsx +10 -0
  58. package/examples/greenfield/module-hybrid-cdn-auth-full/src/components/ui/loader/loader.tsx +18 -0
  59. package/examples/greenfield/module-hybrid-cdn-auth-full/src/config/auth-tier.ts +4 -0
  60. package/examples/greenfield/module-hybrid-cdn-auth-full/src/config/nav-scaffold.ts +4 -0
  61. package/examples/greenfield/module-hybrid-cdn-auth-full/src/config/portal-nav.ts +9 -0
  62. package/examples/greenfield/module-hybrid-cdn-auth-full/src/config/router.ts +1 -0
  63. package/examples/greenfield/module-hybrid-cdn-auth-full/src/config/routes.ts +10 -0
  64. package/examples/greenfield/module-hybrid-cdn-auth-full/src/data/hubSpotContext.ts +57 -0
  65. package/examples/greenfield/module-hybrid-cdn-auth-full/src/data/sync-sdk-hub-context.ts +44 -0
  66. package/examples/greenfield/module-hybrid-cdn-auth-full/src/defaultData.ts +2 -0
  67. package/examples/greenfield/module-hybrid-cdn-auth-full/src/env.ts +59 -0
  68. package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/auth/app-bootstrap-guards.ts +14 -0
  69. package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/auth/auth-navigation.ts +81 -0
  70. package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/auth/auth-route-paths.ts +29 -0
  71. package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/auth/auth-session-events.ts +2 -0
  72. package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/auth/auth-session.ts +51 -0
  73. package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/auth/clear-client-auth-session.ts +62 -0
  74. package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/auth/components/starter-login.tsx +83 -0
  75. package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/auth/login-email-storage.ts +64 -0
  76. package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/auth/login-session-bridge.ts +74 -0
  77. package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/auth/mfa-otp-utils.ts +24 -0
  78. package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/auth/mfa-pending-storage.ts +108 -0
  79. package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/auth/mfa-portal.ts +45 -0
  80. package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/auth/post-login-path.ts +17 -0
  81. package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/auth/resolve-auth-login-error-message.ts +52 -0
  82. package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/auth/resolve-post-login-path.ts +21 -0
  83. package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/auth/sso-callback.ts +120 -0
  84. package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/auth/use-mfa-login-otp-flow.ts +78 -0
  85. package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/auth/use-portal-session.ts +25 -0
  86. package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/auth/use-signed-in-email.ts +55 -0
  87. package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/auth/use-sso-callback-handler.ts +92 -0
  88. package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/starter/components/starter-guide-page.tsx +210 -0
  89. package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/starter/components/starter-home-page.tsx +21 -0
  90. package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/starter/components/starter-home.tsx +48 -0
  91. package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/starter/components/starter-page-frame.tsx +25 -0
  92. package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/starter/components/starter-protected-page.tsx +54 -0
  93. package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/starter/components/starter-single-page.tsx +17 -0
  94. package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/starter/content/auth-guide-content.ts +85 -0
  95. package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/starter/content/woodscli-guide-content.ts +86 -0
  96. package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/starter/hooks/starter-preview-values.ts +60 -0
  97. package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/starter/hooks/use-starter-preview.dev.ts +18 -0
  98. package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/starter/hooks/use-starter-preview.ts +30 -0
  99. package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/starter/index.ts +6 -0
  100. package/examples/greenfield/module-hybrid-cdn-auth-full/src/hubspot/bootstrap.ts +16 -0
  101. package/examples/greenfield/module-hybrid-cdn-auth-full/src/hubspot/default-data.ts +7 -0
  102. package/examples/greenfield/module-hybrid-cdn-auth-full/src/hubspot/is-cms-editor.ts +78 -0
  103. package/examples/greenfield/module-hybrid-cdn-auth-full/src/hubspot/module-entry.tsx +9 -0
  104. package/examples/greenfield/module-hybrid-cdn-auth-full/src/hubspot/types.ts +17 -0
  105. package/examples/greenfield/module-hybrid-cdn-auth-full/src/integrations/configure-woodsportal-sdk.ts +98 -0
  106. package/examples/greenfield/module-hybrid-cdn-auth-full/src/integrations/tanstack-query/root-provider.tsx +18 -0
  107. package/examples/greenfield/module-hybrid-cdn-auth-full/src/integrations/tanstack-router/router.tsx +39 -0
  108. package/examples/greenfield/module-hybrid-cdn-auth-full/src/integrations/woodsportal-sdk.ts +37 -0
  109. package/examples/greenfield/module-hybrid-cdn-auth-full/src/main.tsx +5 -0
  110. package/examples/greenfield/module-hybrid-cdn-auth-full/src/portal/api-endpoint.ts +11 -0
  111. package/examples/greenfield/module-hybrid-cdn-auth-full/src/portal/ensure-portal-cdn-stylesheet.ts +115 -0
  112. package/examples/greenfield/module-hybrid-cdn-auth-full/src/portal/mount-dev-preview.tsx +12 -0
  113. package/examples/greenfield/module-hybrid-cdn-auth-full/src/portal/mount-portal.tsx +57 -0
  114. package/examples/greenfield/module-hybrid-cdn-auth-full/src/routeTree.gen.ts +182 -0
  115. package/examples/greenfield/module-hybrid-cdn-auth-full/src/routes/__root.tsx +12 -0
  116. package/examples/greenfield/module-hybrid-cdn-auth-full/src/routes/_app/portal.tsx +10 -0
  117. package/examples/greenfield/module-hybrid-cdn-auth-full/src/routes/_auth/login/sso.tsx +27 -0
  118. package/examples/greenfield/module-hybrid-cdn-auth-full/src/routes/_auth/login.tsx +10 -0
  119. package/examples/greenfield/module-hybrid-cdn-auth-full/src/routes/_auth/two-fa.tsx +91 -0
  120. package/examples/greenfield/module-hybrid-cdn-auth-full/src/routes/guide.tsx +9 -0
  121. package/examples/greenfield/module-hybrid-cdn-auth-full/src/routes/index.tsx +8 -0
  122. package/examples/greenfield/module-hybrid-cdn-auth-full/src/routes/unauthorized.tsx +50 -0
  123. package/examples/greenfield/module-hybrid-cdn-auth-full/src/utils/ValidationSchema.ts +1 -0
  124. package/examples/greenfield/module-hybrid-cdn-auth-full/src/utils/cookie.ts +23 -0
  125. package/examples/greenfield/module-hybrid-cdn-auth-full/src/utils/normalize-router-path.ts +10 -0
  126. package/examples/greenfield/module-hybrid-cdn-auth-full/src/utils/private-route.tsx +100 -0
  127. package/examples/greenfield/module-hybrid-cdn-auth-full/src/utils/public-route.tsx +64 -0
  128. package/examples/greenfield/module-hybrid-cdn-auth-full/src/utils/resolve-api-base-url.ts +44 -0
  129. package/examples/greenfield/module-hybrid-cdn-auth-full/src/utils/safe-router-navigation.ts +82 -0
  130. package/examples/greenfield/module-hybrid-cdn-auth-full/tsconfig.json +20 -0
  131. package/examples/greenfield/module-hybrid-cdn-auth-full/vite.config.cdn-app.mjs +1 -0
  132. package/examples/greenfield/module-hybrid-cdn-auth-full/vite.config.cdn-esm.mjs +1 -0
  133. package/examples/greenfield/module-hybrid-cdn-auth-full/vite.config.cdn-vendor.mjs +1 -0
  134. package/examples/greenfield/module-hybrid-cdn-auth-full/vite.config.dev.mjs +1 -0
  135. package/examples/greenfield/module-hybrid-cdn-auth-full/vite.config.hubspot.mjs +1 -0
  136. package/examples/greenfield/module-hybrid-cdn-auth-full/vite.config.monolith.mjs +8 -0
  137. package/examples/greenfield/module-hybrid-cdn-auth-full/woodscli.json +38 -0
  138. package/examples/greenfield/module-hybrid-cdn-auth-full/wphs.adapter.ts +7 -0
  139. package/examples/greenfield/module-hybrid-cdn-auth-sso/.env.dev.example +3 -0
  140. package/examples/greenfield/module-hybrid-cdn-auth-sso/.env.local.example +6 -0
  141. package/examples/greenfield/module-hybrid-cdn-auth-sso/.env.prod.example +5 -0
  142. package/examples/greenfield/module-hybrid-cdn-auth-sso/.env.stg.example +3 -0
  143. package/examples/greenfield/module-hybrid-cdn-auth-sso/.wphs/README.md +17 -0
  144. package/examples/greenfield/module-hybrid-cdn-auth-sso/README.md +59 -0
  145. package/examples/greenfield/module-hybrid-cdn-auth-sso/hubspot.config.yml.example +10 -0
  146. package/examples/greenfield/module-hybrid-cdn-auth-sso/index.html +12 -0
  147. package/examples/greenfield/module-hybrid-cdn-auth-sso/package.json +34 -0
  148. package/examples/greenfield/module-hybrid-cdn-auth-sso/portal.cdn.json.example +8 -0
  149. package/examples/greenfield/module-hybrid-cdn-auth-sso/public/fields.json +100 -0
  150. package/examples/greenfield/module-hybrid-cdn-auth-sso/public/meta.json +9 -0
  151. package/examples/greenfield/module-hybrid-cdn-auth-sso/public/module.html +38 -0
  152. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/app.tsx +180 -0
  153. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/assets/css/main.dev.css +23 -0
  154. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/assets/css/main.prod.css +12 -0
  155. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/assets/css/tailwind.css.config.css +7 -0
  156. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/cdn/app-entry.ts +48 -0
  157. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/cdn/vendor-entry.ts +28 -0
  158. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/components/Layouts/AppLayoutWrapper.tsx +38 -0
  159. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/components/Layouts/AuthLayout.tsx +7 -0
  160. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/components/Layouts/DefaultLayout.tsx +5 -0
  161. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/components/Layouts/MainLayout.tsx +42 -0
  162. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/components/Layouts/UnauthorizedLayout.tsx +5 -0
  163. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/components/Layouts/index.ts +13 -0
  164. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/components/layout/section-card.tsx +20 -0
  165. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/components/layout/starter-nav.tsx +28 -0
  166. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/components/layout/starter-shell.tsx +18 -0
  167. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/components/ui/Button.tsx +22 -0
  168. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/components/ui/Form.tsx +22 -0
  169. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/components/ui/Input.tsx +12 -0
  170. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/components/ui/Toaster.tsx +5 -0
  171. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/components/ui/auth/SsoLoginButton.tsx +34 -0
  172. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/components/ui/link.tsx +10 -0
  173. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/components/ui/loader/loader.tsx +18 -0
  174. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/config/auth-tier.ts +4 -0
  175. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/config/nav-scaffold.ts +4 -0
  176. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/config/portal-nav.ts +9 -0
  177. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/config/router.ts +1 -0
  178. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/config/routes.ts +10 -0
  179. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/data/hubSpotContext.ts +57 -0
  180. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/data/sync-sdk-hub-context.ts +44 -0
  181. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/defaultData.ts +2 -0
  182. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/env.ts +59 -0
  183. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/auth/app-bootstrap-guards.ts +14 -0
  184. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/auth/auth-navigation.ts +81 -0
  185. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/auth/auth-route-paths.ts +29 -0
  186. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/auth/auth-session-events.ts +2 -0
  187. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/auth/auth-session.ts +26 -0
  188. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/auth/clear-client-auth-session.ts +62 -0
  189. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/auth/components/starter-login.tsx +83 -0
  190. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/auth/login-email-storage.ts +64 -0
  191. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/auth/login-session-bridge.ts +72 -0
  192. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/auth/post-login-path.ts +17 -0
  193. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/auth/resolve-auth-login-error-message.ts +52 -0
  194. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/auth/resolve-post-login-path.ts +21 -0
  195. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/auth/sso-callback.ts +120 -0
  196. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/auth/use-portal-session.ts +25 -0
  197. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/auth/use-signed-in-email.ts +55 -0
  198. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/auth/use-sso-callback-handler.ts +92 -0
  199. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/starter/components/starter-guide-page.tsx +210 -0
  200. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/starter/components/starter-home-page.tsx +21 -0
  201. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/starter/components/starter-home.tsx +48 -0
  202. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/starter/components/starter-page-frame.tsx +25 -0
  203. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/starter/components/starter-protected-page.tsx +54 -0
  204. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/starter/components/starter-single-page.tsx +17 -0
  205. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/starter/content/auth-guide-content.ts +85 -0
  206. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/starter/content/woodscli-guide-content.ts +86 -0
  207. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/starter/hooks/starter-preview-values.ts +60 -0
  208. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/starter/hooks/use-starter-preview.dev.ts +18 -0
  209. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/starter/hooks/use-starter-preview.ts +30 -0
  210. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/starter/index.ts +6 -0
  211. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/hubspot/bootstrap.ts +16 -0
  212. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/hubspot/default-data.ts +7 -0
  213. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/hubspot/is-cms-editor.ts +78 -0
  214. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/hubspot/module-entry.tsx +9 -0
  215. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/hubspot/types.ts +17 -0
  216. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/integrations/configure-woodsportal-sdk.ts +94 -0
  217. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/integrations/tanstack-query/root-provider.tsx +18 -0
  218. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/integrations/tanstack-router/router.tsx +39 -0
  219. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/integrations/woodsportal-sdk.ts +37 -0
  220. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/main.tsx +5 -0
  221. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/portal/api-endpoint.ts +11 -0
  222. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/portal/ensure-portal-cdn-stylesheet.ts +115 -0
  223. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/portal/mount-dev-preview.tsx +12 -0
  224. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/portal/mount-portal.tsx +57 -0
  225. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/routeTree.gen.ts +155 -0
  226. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/routes/__root.tsx +12 -0
  227. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/routes/_app/portal.tsx +10 -0
  228. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/routes/_auth/login/sso.tsx +27 -0
  229. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/routes/_auth/login.tsx +10 -0
  230. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/routes/guide.tsx +9 -0
  231. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/routes/index.tsx +8 -0
  232. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/routes/unauthorized.tsx +50 -0
  233. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/utils/ValidationSchema.ts +1 -0
  234. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/utils/cookie.ts +23 -0
  235. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/utils/normalize-router-path.ts +10 -0
  236. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/utils/private-route.tsx +100 -0
  237. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/utils/public-route.tsx +64 -0
  238. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/utils/resolve-api-base-url.ts +44 -0
  239. package/examples/greenfield/module-hybrid-cdn-auth-sso/src/utils/safe-router-navigation.ts +82 -0
  240. package/examples/greenfield/module-hybrid-cdn-auth-sso/tsconfig.json +20 -0
  241. package/examples/greenfield/module-hybrid-cdn-auth-sso/vite.config.cdn-app.mjs +1 -0
  242. package/examples/greenfield/module-hybrid-cdn-auth-sso/vite.config.cdn-esm.mjs +1 -0
  243. package/examples/greenfield/module-hybrid-cdn-auth-sso/vite.config.cdn-vendor.mjs +1 -0
  244. package/examples/greenfield/module-hybrid-cdn-auth-sso/vite.config.dev.mjs +1 -0
  245. package/examples/greenfield/module-hybrid-cdn-auth-sso/vite.config.hubspot.mjs +1 -0
  246. package/examples/greenfield/module-hybrid-cdn-auth-sso/vite.config.monolith.mjs +8 -0
  247. package/examples/greenfield/module-hybrid-cdn-auth-sso/woodscli.json +38 -0
  248. package/examples/greenfield/module-hybrid-cdn-auth-sso/wphs.adapter.ts +7 -0
  249. package/examples/greenfield/module-hybrid-cdn-auth-starter/.env.dev.example +3 -0
  250. package/examples/greenfield/module-hybrid-cdn-auth-starter/.env.local.example +6 -0
  251. package/examples/greenfield/module-hybrid-cdn-auth-starter/.env.prod.example +5 -0
  252. package/examples/greenfield/module-hybrid-cdn-auth-starter/.env.stg.example +3 -0
  253. package/examples/greenfield/module-hybrid-cdn-auth-starter/.wphs/README.md +17 -0
  254. package/examples/greenfield/module-hybrid-cdn-auth-starter/README.md +59 -0
  255. package/examples/greenfield/module-hybrid-cdn-auth-starter/hubspot.config.yml.example +10 -0
  256. package/examples/greenfield/module-hybrid-cdn-auth-starter/index.html +12 -0
  257. package/examples/greenfield/module-hybrid-cdn-auth-starter/package.json +34 -0
  258. package/examples/greenfield/module-hybrid-cdn-auth-starter/portal.cdn.json.example +8 -0
  259. package/examples/greenfield/module-hybrid-cdn-auth-starter/public/fields.json +100 -0
  260. package/examples/greenfield/module-hybrid-cdn-auth-starter/public/meta.json +9 -0
  261. package/examples/greenfield/module-hybrid-cdn-auth-starter/public/module.html +38 -0
  262. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/app.tsx +173 -0
  263. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/assets/css/main.dev.css +23 -0
  264. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/assets/css/main.prod.css +12 -0
  265. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/assets/css/tailwind.css.config.css +7 -0
  266. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/cdn/app-entry.ts +48 -0
  267. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/cdn/vendor-entry.ts +28 -0
  268. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/components/Layouts/AppLayoutWrapper.tsx +38 -0
  269. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/components/Layouts/AuthLayout.tsx +7 -0
  270. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/components/Layouts/DefaultLayout.tsx +5 -0
  271. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/components/Layouts/MainLayout.tsx +42 -0
  272. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/components/Layouts/UnauthorizedLayout.tsx +5 -0
  273. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/components/Layouts/index.ts +13 -0
  274. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/components/layout/section-card.tsx +20 -0
  275. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/components/layout/starter-nav.tsx +28 -0
  276. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/components/layout/starter-shell.tsx +18 -0
  277. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/components/ui/Button.tsx +22 -0
  278. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/components/ui/Form.tsx +22 -0
  279. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/components/ui/Input.tsx +12 -0
  280. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/components/ui/Toaster.tsx +5 -0
  281. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/components/ui/link.tsx +10 -0
  282. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/components/ui/loader/loader.tsx +18 -0
  283. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/config/auth-tier.ts +4 -0
  284. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/config/nav-scaffold.ts +4 -0
  285. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/config/portal-nav.ts +9 -0
  286. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/config/router.ts +1 -0
  287. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/config/routes.ts +10 -0
  288. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/data/hubSpotContext.ts +57 -0
  289. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/data/sync-sdk-hub-context.ts +44 -0
  290. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/defaultData.ts +2 -0
  291. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/env.ts +59 -0
  292. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/auth/app-bootstrap-guards.ts +14 -0
  293. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/auth/auth-navigation.ts +81 -0
  294. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/auth/auth-route-paths.ts +29 -0
  295. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/auth/auth-session-events.ts +2 -0
  296. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/auth/auth-session.ts +26 -0
  297. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/auth/clear-client-auth-session.ts +62 -0
  298. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/auth/components/starter-login.tsx +76 -0
  299. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/auth/login-email-storage.ts +64 -0
  300. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/auth/login-session-bridge.ts +72 -0
  301. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/auth/post-login-path.ts +17 -0
  302. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/auth/resolve-auth-login-error-message.ts +52 -0
  303. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/auth/resolve-post-login-path.ts +21 -0
  304. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/auth/use-portal-session.ts +25 -0
  305. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/auth/use-signed-in-email.ts +55 -0
  306. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/starter/components/starter-guide-page.tsx +218 -0
  307. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/starter/components/starter-home-page.tsx +21 -0
  308. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/starter/components/starter-home.tsx +48 -0
  309. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/starter/components/starter-page-frame.tsx +25 -0
  310. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/starter/components/starter-protected-page.tsx +54 -0
  311. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/starter/components/starter-single-page.tsx +17 -0
  312. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/starter/content/auth-guide-content.ts +91 -0
  313. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/starter/content/woodscli-guide-content.ts +86 -0
  314. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/starter/hooks/starter-preview-values.ts +60 -0
  315. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/starter/hooks/use-starter-preview.dev.ts +18 -0
  316. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/starter/hooks/use-starter-preview.ts +30 -0
  317. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/starter/index.ts +6 -0
  318. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/hubspot/bootstrap.ts +16 -0
  319. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/hubspot/default-data.ts +7 -0
  320. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/hubspot/is-cms-editor.ts +78 -0
  321. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/hubspot/module-entry.tsx +9 -0
  322. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/hubspot/types.ts +17 -0
  323. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/integrations/configure-woodsportal-sdk.ts +94 -0
  324. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/integrations/tanstack-query/root-provider.tsx +18 -0
  325. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/integrations/tanstack-router/router.tsx +39 -0
  326. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/integrations/woodsportal-sdk.ts +37 -0
  327. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/main.tsx +5 -0
  328. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/portal/api-endpoint.ts +11 -0
  329. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/portal/ensure-portal-cdn-stylesheet.ts +115 -0
  330. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/portal/mount-dev-preview.tsx +12 -0
  331. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/portal/mount-portal.tsx +52 -0
  332. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/routeTree.gen.ts +134 -0
  333. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/routes/__root.tsx +12 -0
  334. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/routes/_app/portal.tsx +10 -0
  335. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/routes/_auth/login.tsx +10 -0
  336. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/routes/guide.tsx +9 -0
  337. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/routes/index.tsx +8 -0
  338. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/routes/unauthorized.tsx +50 -0
  339. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/utils/ValidationSchema.ts +1 -0
  340. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/utils/cookie.ts +23 -0
  341. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/utils/normalize-router-path.ts +10 -0
  342. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/utils/private-route.tsx +100 -0
  343. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/utils/public-route.tsx +64 -0
  344. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/utils/resolve-api-base-url.ts +44 -0
  345. package/examples/greenfield/module-hybrid-cdn-auth-starter/src/utils/safe-router-navigation.ts +82 -0
  346. package/examples/greenfield/module-hybrid-cdn-auth-starter/tsconfig.json +20 -0
  347. package/examples/greenfield/module-hybrid-cdn-auth-starter/vite.config.cdn-app.mjs +1 -0
  348. package/examples/greenfield/module-hybrid-cdn-auth-starter/vite.config.cdn-esm.mjs +1 -0
  349. package/examples/greenfield/module-hybrid-cdn-auth-starter/vite.config.cdn-vendor.mjs +1 -0
  350. package/examples/greenfield/module-hybrid-cdn-auth-starter/vite.config.dev.mjs +1 -0
  351. package/examples/greenfield/module-hybrid-cdn-auth-starter/vite.config.hubspot.mjs +1 -0
  352. package/examples/greenfield/module-hybrid-cdn-auth-starter/vite.config.monolith.mjs +8 -0
  353. package/examples/greenfield/module-hybrid-cdn-auth-starter/woodscli.json +38 -0
  354. package/examples/greenfield/module-hybrid-cdn-auth-starter/wphs.adapter.ts +7 -0
  355. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/.env.dev.example +3 -0
  356. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/.env.local.example +6 -0
  357. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/.env.prod.example +5 -0
  358. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/.env.stg.example +3 -0
  359. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/.wphs/README.md +17 -0
  360. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/README.md +59 -0
  361. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/hubspot.config.yml.example +10 -0
  362. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/index.html +12 -0
  363. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/package.json +34 -0
  364. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/portal.cdn.json.example +8 -0
  365. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/public/fields.json +100 -0
  366. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/public/meta.json +9 -0
  367. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/public/module.html +38 -0
  368. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/app.tsx +173 -0
  369. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/assets/css/main.dev.css +23 -0
  370. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/assets/css/main.prod.css +12 -0
  371. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/assets/css/tailwind.css.config.css +7 -0
  372. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/cdn/app-entry.ts +48 -0
  373. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/cdn/vendor-entry.ts +28 -0
  374. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/components/Layouts/AppLayoutWrapper.tsx +38 -0
  375. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/components/Layouts/AuthLayout.tsx +7 -0
  376. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/components/Layouts/DefaultLayout.tsx +5 -0
  377. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/components/Layouts/MainLayout.tsx +7 -0
  378. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/components/Layouts/UnauthorizedLayout.tsx +5 -0
  379. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/components/Layouts/index.ts +13 -0
  380. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/components/layout/portal-sidebar-nav.tsx +33 -0
  381. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/components/layout/portal-sidebar.tsx +139 -0
  382. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/components/layout/section-card.tsx +20 -0
  383. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/components/layout/starter-nav.tsx +28 -0
  384. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/components/layout/starter-shell.tsx +18 -0
  385. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/components/ui/Button.tsx +22 -0
  386. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/components/ui/Form.tsx +22 -0
  387. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/components/ui/Input.tsx +12 -0
  388. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/components/ui/Toaster.tsx +5 -0
  389. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/components/ui/link.tsx +10 -0
  390. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/components/ui/loader/loader.tsx +18 -0
  391. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/config/auth-tier.ts +4 -0
  392. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/config/nav-scaffold.ts +4 -0
  393. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/config/portal-nav.ts +9 -0
  394. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/config/router.ts +1 -0
  395. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/config/routes.ts +10 -0
  396. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/data/hubSpotContext.ts +57 -0
  397. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/data/sync-sdk-hub-context.ts +44 -0
  398. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/defaultData.ts +2 -0
  399. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/env.ts +59 -0
  400. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/auth/app-bootstrap-guards.ts +14 -0
  401. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/auth/auth-navigation.ts +81 -0
  402. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/auth/auth-route-paths.ts +29 -0
  403. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/auth/auth-session-events.ts +2 -0
  404. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/auth/auth-session.ts +26 -0
  405. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/auth/clear-client-auth-session.ts +62 -0
  406. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/auth/components/starter-login.tsx +76 -0
  407. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/auth/login-email-storage.ts +64 -0
  408. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/auth/login-session-bridge.ts +72 -0
  409. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/auth/post-login-path.ts +17 -0
  410. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/auth/resolve-auth-login-error-message.ts +52 -0
  411. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/auth/resolve-post-login-path.ts +21 -0
  412. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/auth/use-portal-session.ts +25 -0
  413. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/auth/use-signed-in-email.ts +55 -0
  414. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/starter/components/starter-guide-page.tsx +210 -0
  415. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/starter/components/starter-home-page.tsx +21 -0
  416. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/starter/components/starter-home.tsx +48 -0
  417. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/starter/components/starter-page-frame.tsx +25 -0
  418. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/starter/components/starter-protected-page.tsx +54 -0
  419. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/starter/components/starter-single-page.tsx +17 -0
  420. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/starter/content/auth-guide-content.ts +85 -0
  421. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/starter/content/woodscli-guide-content.ts +86 -0
  422. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/starter/hooks/starter-preview-values.ts +60 -0
  423. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/starter/hooks/use-starter-preview.dev.ts +18 -0
  424. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/starter/hooks/use-starter-preview.ts +30 -0
  425. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/starter/index.ts +6 -0
  426. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/hubspot/bootstrap.ts +16 -0
  427. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/hubspot/default-data.ts +7 -0
  428. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/hubspot/is-cms-editor.ts +78 -0
  429. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/hubspot/module-entry.tsx +9 -0
  430. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/hubspot/types.ts +17 -0
  431. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/integrations/configure-woodsportal-sdk.ts +94 -0
  432. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/integrations/tanstack-query/root-provider.tsx +18 -0
  433. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/integrations/tanstack-router/router.tsx +39 -0
  434. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/integrations/woodsportal-sdk.ts +37 -0
  435. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/main.tsx +5 -0
  436. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/portal/api-endpoint.ts +11 -0
  437. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/portal/ensure-portal-cdn-stylesheet.ts +115 -0
  438. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/portal/mount-dev-preview.tsx +12 -0
  439. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/portal/mount-portal.tsx +52 -0
  440. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/routeTree.gen.ts +134 -0
  441. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/routes/__root.tsx +12 -0
  442. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/routes/_app/portal.tsx +10 -0
  443. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/routes/_auth/login.tsx +10 -0
  444. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/routes/guide.tsx +9 -0
  445. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/routes/index.tsx +8 -0
  446. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/routes/unauthorized.tsx +50 -0
  447. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/utils/ValidationSchema.ts +1 -0
  448. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/utils/cookie.ts +23 -0
  449. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/utils/normalize-router-path.ts +10 -0
  450. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/utils/private-route.tsx +100 -0
  451. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/utils/public-route.tsx +64 -0
  452. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/utils/resolve-api-base-url.ts +44 -0
  453. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/utils/safe-router-navigation.ts +82 -0
  454. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/tsconfig.json +20 -0
  455. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/vite.config.cdn-app.mjs +1 -0
  456. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/vite.config.cdn-esm.mjs +1 -0
  457. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/vite.config.cdn-vendor.mjs +1 -0
  458. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/vite.config.dev.mjs +1 -0
  459. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/vite.config.hubspot.mjs +1 -0
  460. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/vite.config.monolith.mjs +8 -0
  461. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/woodscli.json +38 -0
  462. package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/wphs.adapter.ts +7 -0
  463. package/examples/greenfield/module-monolith/public/fields.json +66 -16
  464. package/examples/greenfield/module-monolith/src/assets/css/main.dev.css +7 -0
  465. package/examples/greenfield/module-monolith/src/components/layout/starter-nav.tsx +12 -15
  466. package/examples/greenfield/module-monolith/src/config/starter-nav.ts +9 -0
  467. package/examples/greenfield/module-monolith/src/features/starter/components/starter-home.tsx +25 -29
  468. package/examples/greenfield/module-monolith/src/features/starter/hooks/starter-preview-values.ts +60 -0
  469. package/examples/greenfield/module-monolith/src/features/starter/hooks/use-starter-preview.dev.ts +18 -0
  470. package/examples/greenfield/module-monolith/src/features/starter/hooks/use-starter-preview.ts +20 -37
  471. package/examples/manifest.json +5 -1
  472. package/examples/smoke/run-matrix.mjs +37 -0
  473. package/package.json +7 -5
  474. package/scripts/cli-smoke-helpers.mjs +150 -0
  475. package/scripts/regenerate-route-tree.mjs +71 -0
  476. package/scripts/sync-auth-scaffold-from-client.mjs +41 -0
  477. package/scripts/wp-cli-smoke.test.mjs +263 -0
  478. package/src/dev/bootstrap-greenfield.tsx +6 -0
  479. package/templates/module-hybrid-cdn/public/fields.json +47 -7
  480. package/templates/module-hybrid-cdn/public/module.html +7 -6
  481. package/templates/module-hybrid-cdn/src/assets/css/main.dev.css +7 -0
  482. package/templates/module-hybrid-cdn/src/components/layout/starter-nav.tsx +12 -15
  483. package/templates/module-hybrid-cdn/src/config/starter-nav.ts +9 -0
  484. package/templates/module-hybrid-cdn/src/features/starter/components/starter-home.tsx +25 -27
  485. package/templates/module-hybrid-cdn/src/features/starter/hooks/starter-preview-values.ts +5 -5
  486. package/templates/module-monolith/public/fields.json +49 -6
  487. package/templates/module-monolith/src/assets/css/main.dev.css +7 -0
  488. package/templates/module-monolith/src/components/layout/starter-nav.tsx +12 -15
  489. package/templates/module-monolith/src/config/starter-nav.ts +9 -0
  490. package/templates/module-monolith/src/features/starter/components/starter-home.tsx +25 -27
  491. package/templates/module-monolith/src/features/starter/hooks/starter-preview-values.ts +5 -5
  492. package/templates/scaffold/auth/_full/src/app.tsx +172 -0
  493. package/templates/scaffold/auth/_full/src/config/auth-tier.ts +4 -0
  494. package/templates/scaffold/auth/_full/src/features/auth/auth-session.ts +51 -0
  495. package/templates/scaffold/auth/_full/src/features/auth/login-session-bridge.ts +74 -0
  496. package/templates/scaffold/auth/_full/src/features/auth/mfa-otp-utils.ts +24 -0
  497. package/templates/scaffold/auth/_full/src/features/auth/mfa-pending-storage.ts +108 -0
  498. package/templates/scaffold/auth/_full/src/features/auth/mfa-portal.ts +45 -0
  499. package/templates/scaffold/auth/_full/src/features/auth/use-mfa-login-otp-flow.ts +78 -0
  500. package/templates/scaffold/auth/_full/src/routeTree.gen.ts +182 -0
  501. package/templates/scaffold/auth/_full/src/routes/_auth/two-fa.tsx +91 -0
  502. package/templates/scaffold/auth/_nav-sidebar/src/components/Layouts/MainLayout.tsx +7 -0
  503. package/templates/scaffold/auth/_nav-sidebar/src/components/layout/portal-sidebar-nav.tsx +33 -0
  504. package/templates/scaffold/auth/_nav-sidebar/src/components/layout/portal-sidebar.tsx +139 -0
  505. package/templates/scaffold/auth/_nav-top/src/components/Layouts/MainLayout.tsx +42 -0
  506. package/templates/scaffold/auth/_shared/src/app.tsx +178 -0
  507. package/templates/scaffold/auth/_shared/src/components/Layouts/AppLayoutWrapper.tsx +38 -0
  508. package/templates/scaffold/auth/_shared/src/components/Layouts/AuthLayout.tsx +7 -0
  509. package/templates/scaffold/auth/_shared/src/components/Layouts/DefaultLayout.tsx +5 -0
  510. package/templates/scaffold/auth/_shared/src/components/Layouts/UnauthorizedLayout.tsx +5 -0
  511. package/templates/scaffold/auth/_shared/src/components/Layouts/index.ts +13 -0
  512. package/templates/scaffold/auth/_shared/src/components/ui/Button.tsx +22 -0
  513. package/templates/scaffold/auth/_shared/src/components/ui/Form.tsx +22 -0
  514. package/templates/scaffold/auth/_shared/src/components/ui/Input.tsx +12 -0
  515. package/templates/scaffold/auth/_shared/src/components/ui/Toaster.tsx +5 -0
  516. package/templates/scaffold/auth/_shared/src/components/ui/link.tsx +10 -0
  517. package/templates/scaffold/auth/_shared/src/components/ui/loader/loader.tsx +18 -0
  518. package/templates/scaffold/auth/_shared/src/config/auth-tier.ts +4 -0
  519. package/templates/scaffold/auth/_shared/src/config/nav-scaffold.ts +4 -0
  520. package/templates/scaffold/auth/_shared/src/config/portal-nav.ts +9 -0
  521. package/templates/scaffold/auth/_shared/src/config/router.ts +1 -0
  522. package/templates/scaffold/auth/_shared/src/config/routes.ts +10 -0
  523. package/templates/scaffold/auth/_shared/src/data/hubSpotContext.ts +57 -0
  524. package/templates/scaffold/auth/_shared/src/data/sync-sdk-hub-context.ts +44 -0
  525. package/templates/scaffold/auth/_shared/src/env.ts +59 -0
  526. package/templates/scaffold/auth/_shared/src/features/auth/app-bootstrap-guards.ts +14 -0
  527. package/templates/scaffold/auth/_shared/src/features/auth/auth-navigation.ts +81 -0
  528. package/templates/scaffold/auth/_shared/src/features/auth/auth-route-paths.ts +29 -0
  529. package/templates/scaffold/auth/_shared/src/features/auth/auth-session-events.ts +2 -0
  530. package/templates/scaffold/auth/_shared/src/features/auth/auth-session.ts +26 -0
  531. package/templates/scaffold/auth/_shared/src/features/auth/clear-client-auth-session.ts +62 -0
  532. package/templates/scaffold/auth/_shared/src/features/auth/components/starter-login.tsx +76 -0
  533. package/templates/scaffold/auth/_shared/src/features/auth/login-email-storage.ts +64 -0
  534. package/templates/scaffold/auth/_shared/src/features/auth/login-session-bridge.ts +72 -0
  535. package/templates/scaffold/auth/_shared/src/features/auth/post-login-path.ts +17 -0
  536. package/templates/scaffold/auth/_shared/src/features/auth/resolve-auth-login-error-message.ts +52 -0
  537. package/templates/scaffold/auth/_shared/src/features/auth/resolve-post-login-path.ts +21 -0
  538. package/templates/scaffold/auth/_shared/src/features/auth/use-portal-session.ts +25 -0
  539. package/templates/scaffold/auth/_shared/src/features/auth/use-signed-in-email.ts +55 -0
  540. package/templates/scaffold/auth/_shared/src/features/starter/components/starter-guide-page.tsx +218 -0
  541. package/templates/scaffold/auth/_shared/src/features/starter/components/starter-page-frame.tsx +25 -0
  542. package/templates/scaffold/auth/_shared/src/features/starter/components/starter-protected-page.tsx +54 -0
  543. package/templates/scaffold/auth/_shared/src/features/starter/content/auth-guide-content.ts +91 -0
  544. package/templates/scaffold/auth/_shared/src/hubspot/is-cms-editor.ts +78 -0
  545. package/templates/scaffold/auth/_shared/src/integrations/configure-woodsportal-sdk.ts +100 -0
  546. package/templates/scaffold/auth/_shared/src/integrations/tanstack-query/root-provider.tsx +18 -0
  547. package/templates/scaffold/auth/_shared/src/integrations/tanstack-router/router.tsx +39 -0
  548. package/templates/scaffold/auth/_shared/src/integrations/woodsportal-sdk.ts +37 -0
  549. package/templates/scaffold/auth/_shared/src/portal/mount-portal.tsx +59 -0
  550. package/templates/scaffold/auth/_shared/src/routeTree.gen.ts +134 -0
  551. package/templates/scaffold/auth/_shared/src/routes/__root.tsx +12 -0
  552. package/templates/scaffold/auth/_shared/src/routes/_app/portal.tsx +10 -0
  553. package/templates/scaffold/auth/_shared/src/routes/_auth/login.tsx +10 -0
  554. package/templates/scaffold/auth/_shared/src/routes/guide.tsx +9 -0
  555. package/templates/scaffold/auth/_shared/src/routes/index.tsx +8 -0
  556. package/templates/scaffold/auth/_shared/src/routes/unauthorized.tsx +50 -0
  557. package/templates/scaffold/auth/_shared/src/utils/ValidationSchema.ts +1 -0
  558. package/templates/scaffold/auth/_shared/src/utils/cookie.ts +23 -0
  559. package/templates/scaffold/auth/_shared/src/utils/normalize-router-path.ts +10 -0
  560. package/templates/scaffold/auth/_shared/src/utils/private-route.tsx +100 -0
  561. package/templates/scaffold/auth/_shared/src/utils/public-route.tsx +64 -0
  562. package/templates/scaffold/auth/_shared/src/utils/resolve-api-base-url.ts +44 -0
  563. package/templates/scaffold/auth/_shared/src/utils/safe-router-navigation.ts +82 -0
  564. package/templates/scaffold/auth/_sso/src/app.tsx +180 -0
  565. package/templates/scaffold/auth/_sso/src/components/ui/auth/SsoLoginButton.tsx +34 -0
  566. package/templates/scaffold/auth/_sso/src/config/auth-tier.ts +4 -0
  567. package/templates/scaffold/auth/_sso/src/features/auth/components/starter-login.tsx +83 -0
  568. package/templates/scaffold/auth/_sso/src/features/auth/sso-callback.ts +120 -0
  569. package/templates/scaffold/auth/_sso/src/features/auth/use-sso-callback-handler.ts +92 -0
  570. package/templates/scaffold/auth/_sso/src/routeTree.gen.ts +155 -0
  571. package/templates/scaffold/auth/_sso/src/routes/_auth/login/sso.tsx +27 -0
  572. package/templates/scaffold/auth/nav-manifest.json +8 -0
  573. package/templates/scaffold/auth/sync-manifest.json +16 -0
  574. package/templates/scaffold/auth/tier-manifest.json +18 -0
  575. package/templates/scaffold/page/page-component-auth.tsx.tpl +10 -0
  576. package/templates/scaffold/page/page-component-plain.tsx.tpl +11 -0
  577. package/templates/scaffold/page/route-protected-auth.tsx.tpl +10 -0
  578. package/templates/scaffold/page/route-public-auth.tsx.tpl +9 -0
  579. package/templates/scaffold/page/route-public-plain.tsx.tpl +6 -0
  580. package/dist/chunk-S2YJDWZU.js.map +0 -1
package/dist/cli.js CHANGED
@@ -1,4 +1,25 @@
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-WHYTQEZX.js";
2
23
  import {
3
24
  ExitCode,
4
25
  WpHsError,
@@ -11,198 +32,12 @@ import {
11
32
  } from "./chunk-4GOJPOW3.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,160 @@ 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");
302
277
  }
303
278
 
304
279
  // src/cli/commands/doctor.ts
305
- import { existsSync as existsSync7, readFileSync as readFileSync5 } from "fs";
306
- import { join, resolve as resolve8 } from "path";
280
+ import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
281
+ import { join as join2, resolve as resolve7 } from "path";
307
282
 
308
283
  // src/lib/cli-spinner.ts
309
284
  import * as p from "@clack/prompts";
310
- import pc2 from "picocolors";
285
+ import pc3 from "picocolors";
311
286
  function isInteractiveCli(ctx) {
312
287
  const { json, quiet, ci } = ctx.global;
313
288
  return !json && !quiet && !ci && process.stdout.isTTY === true;
@@ -321,11 +296,11 @@ function isVerboseStepLog(ctx) {
321
296
  return ctx.global.verbose === true && !ctx.global.quiet && !ctx.global.json;
322
297
  }
323
298
  async function yieldForSpinnerPaint() {
324
- await new Promise((resolve24) => {
325
- setImmediate(resolve24);
299
+ await new Promise((resolve23) => {
300
+ setImmediate(resolve23);
326
301
  });
327
- await new Promise((resolve24) => {
328
- setTimeout(resolve24, 48);
302
+ await new Promise((resolve23) => {
303
+ setTimeout(resolve23, 48);
329
304
  });
330
305
  }
331
306
  async function runCliTasks(ctx, tasks, options = {}) {
@@ -372,7 +347,7 @@ async function runCliTasks(ctx, tasks, options = {}) {
372
347
  await step.task();
373
348
  spinner2.stop(step.title);
374
349
  } catch (error) {
375
- spinner2.stop(pc2.red(step.title));
350
+ spinner2.stop(pc3.red(step.title));
376
351
  throw error;
377
352
  }
378
353
  }
@@ -384,7 +359,7 @@ function shouldQuietSubprocess(ctx, options = {}) {
384
359
  }
385
360
  function printCommandDone(ctx, message) {
386
361
  if (isInteractiveCli(ctx)) {
387
- process.stdout.write(`${pc2.green("\u25C6")} ${message}
362
+ process.stdout.write(`${pc3.green("\u25C6")} ${message}
388
363
  `);
389
364
  return;
390
365
  }
@@ -392,8 +367,8 @@ function printCommandDone(ctx, message) {
392
367
  }
393
368
 
394
369
  // src/lib/hubspot-cli-setup.ts
395
- import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
396
- import { resolve as resolve4 } from "path";
370
+ import { existsSync as existsSync2, readFileSync as readFileSync4 } from "fs";
371
+ import { resolve as resolve3 } from "path";
397
372
  import { spawnSync } from "child_process";
398
373
  var HUBSPOT_CONFIG_FILE = "hubspot.config.yml";
399
374
  var HUBSPOT_CONFIG_EXAMPLE = "hubspot.config.yml.example";
@@ -419,13 +394,13 @@ function hsTimedOut(result) {
419
394
  return err?.code === "ETIMEDOUT";
420
395
  }
421
396
  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);
397
+ const configPath = resolve3(projectRoot, HUBSPOT_CONFIG_FILE);
398
+ const examplePath = resolve3(projectRoot, HUBSPOT_CONFIG_EXAMPLE);
399
+ const configPresent = existsSync2(configPath);
400
+ const examplePresent = existsSync2(examplePath);
426
401
  let looksLikePlaceholder = false;
427
402
  if (configPresent) {
428
- const raw = readFileSync3(configPath, "utf8");
403
+ const raw = readFileSync4(configPath, "utf8");
429
404
  looksLikePlaceholder = PLACEHOLDER_PATTERNS.some((pattern) => pattern.test(raw));
430
405
  } else {
431
406
  looksLikePlaceholder = true;
@@ -692,14 +667,14 @@ function validateHubSpotFieldsJson(fields) {
692
667
  }
693
668
 
694
669
  // src/lib/developer-slug.ts
695
- import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync4, writeFileSync } from "fs";
696
- import { resolve as resolve5 } from "path";
670
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
671
+ import { resolve as resolve4 } from "path";
697
672
  import * as p2 from "@clack/prompts";
698
673
  var SLUG_MAX_LEN = 12;
699
674
  var SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9-]{0,10}[a-z0-9])?$/;
700
675
  var DEVELOPER_CONFIG_FILE = "developer.json";
701
676
  function developerConfigPath(projectRoot) {
702
- return resolve5(projectRoot, ".wphs", "config", DEVELOPER_CONFIG_FILE);
677
+ return resolve4(projectRoot, ".wphs", "config", DEVELOPER_CONFIG_FILE);
703
678
  }
704
679
  function sanitizeDevSlug(input) {
705
680
  if (!input?.trim()) return void 0;
@@ -711,9 +686,9 @@ function readDeveloperSlug(projectRoot) {
711
686
  const fromEnv = sanitizeDevSlug(process.env.PORTAL_CDN_DEV_SLUG) ?? sanitizeDevSlug(process.env.MODULE_CDN_DEV_SLUG);
712
687
  if (fromEnv) return fromEnv;
713
688
  const path = developerConfigPath(projectRoot);
714
- if (!existsSync4(path)) return void 0;
689
+ if (!existsSync3(path)) return void 0;
715
690
  try {
716
- const raw = JSON.parse(readFileSync4(path, "utf8"));
691
+ const raw = JSON.parse(readFileSync5(path, "utf8"));
717
692
  return sanitizeDevSlug(raw.slug);
718
693
  } catch {
719
694
  return void 0;
@@ -725,8 +700,8 @@ function writeDeveloperSlug(projectRoot, slug) {
725
700
  throw new Error("Invalid CDN dev slug \u2014 use lowercase letters, numbers, and hyphens (max 12 chars)");
726
701
  }
727
702
  const path = developerConfigPath(projectRoot);
728
- mkdirSync(resolve5(path, ".."), { recursive: true });
729
- writeFileSync(path, `${JSON.stringify({ slug: normalized }, null, 2)}
703
+ mkdirSync2(resolve4(path, ".."), { recursive: true });
704
+ writeFileSync2(path, `${JSON.stringify({ slug: normalized }, null, 2)}
730
705
  `);
731
706
  }
732
707
  function needsDeveloperSlugSetup(projectRoot) {
@@ -788,37 +763,37 @@ function resolveModuleBuildIdMetaForCli(projectRoot, env = process.env) {
788
763
  }
789
764
 
790
765
  // src/lib/upload-only-module.ts
791
- import { existsSync as existsSync5 } from "fs";
792
- import { resolve as resolve6 } from "path";
766
+ import { existsSync as existsSync4 } from "fs";
767
+ import { resolve as resolve5 } from "path";
793
768
  function isUploadOnlyPreexistingProject(projectRoot, moduleArtifactDirs) {
794
769
  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")));
770
+ if (existsSync4(resolve5(projectRoot, "package.json"))) return false;
771
+ if (existsSync4(resolve5(projectRoot, "src"))) return false;
772
+ return moduleArtifactDirs.some((dir) => existsSync4(resolve5(dir, "fields.json")));
798
773
  }
799
774
 
800
775
  // src/vite/resolve-dev-config.ts
801
- import { existsSync as existsSync6 } from "fs";
776
+ import { existsSync as existsSync5 } from "fs";
802
777
  import { platform } from "os";
803
- import { resolve as resolve7 } from "path";
778
+ import { resolve as resolve6 } from "path";
804
779
  var VITE_JS_SEGMENTS = ["node_modules", "vite", "bin", "vite.js"];
805
780
  function isProjectViteInstalled(projectRoot) {
806
- return existsSync6(resolve7(projectRoot, ...VITE_JS_SEGMENTS));
781
+ return existsSync5(resolve6(projectRoot, ...VITE_JS_SEGMENTS));
807
782
  }
808
783
  function resolveProjectViteSpawn(projectRoot) {
809
- const viteJs = resolve7(projectRoot, ...VITE_JS_SEGMENTS);
810
- if (existsSync6(viteJs)) {
784
+ const viteJs = resolve6(projectRoot, ...VITE_JS_SEGMENTS);
785
+ if (existsSync5(viteJs)) {
811
786
  return { command: process.execPath, prefixArgs: [viteJs] };
812
787
  }
813
- const binDir = resolve7(projectRoot, "node_modules", ".bin");
788
+ const binDir = resolve6(projectRoot, "node_modules", ".bin");
814
789
  if (platform() === "win32") {
815
- const winCmd = resolve7(binDir, "vite.cmd");
816
- if (existsSync6(winCmd)) {
790
+ const winCmd = resolve6(binDir, "vite.cmd");
791
+ if (existsSync5(winCmd)) {
817
792
  return { command: winCmd, prefixArgs: [] };
818
793
  }
819
794
  }
820
- const posixShim = resolve7(binDir, "vite");
821
- if (existsSync6(posixShim)) {
795
+ const posixShim = resolve6(binDir, "vite");
796
+ if (existsSync5(posixShim)) {
822
797
  return { command: posixShim, prefixArgs: [] };
823
798
  }
824
799
  return null;
@@ -905,10 +880,10 @@ function envFileForTier(tier) {
905
880
  return ".env.prod";
906
881
  }
907
882
  function readEnvFileValue(projectRoot, file, key) {
908
- const path = resolve8(projectRoot, file);
909
- if (!existsSync7(path)) return void 0;
883
+ const path = resolve7(projectRoot, file);
884
+ if (!existsSync6(path)) return void 0;
910
885
  try {
911
- const content = readFileSync5(path, "utf8");
886
+ const content = readFileSync6(path, "utf8");
912
887
  const match = content.match(new RegExp(`^${key}=(.*)$`, "m"));
913
888
  return match?.[1]?.trim();
914
889
  } catch {
@@ -917,8 +892,8 @@ function readEnvFileValue(projectRoot, file, key) {
917
892
  }
918
893
  function checkEnvFiles(ctx) {
919
894
  const checks = [];
920
- const localPath = resolve8(ctx.projectRoot, ".env.local");
921
- const localOk = existsSync7(localPath);
895
+ const localPath = resolve7(ctx.projectRoot, ".env.local");
896
+ const localOk = existsSync6(localPath);
922
897
  checks.push({
923
898
  name: "env-local",
924
899
  ok: localOk,
@@ -926,8 +901,8 @@ function checkEnvFiles(ctx) {
926
901
  remediation: localOk ? void 0 : "wp setup or copy .env.local.example \u2192 .env.local",
927
902
  severity: "warn"
928
903
  });
929
- const legacyLocalDev = resolve8(ctx.projectRoot, ".env.dev");
930
- if (!localOk && existsSync7(legacyLocalDev)) {
904
+ const legacyLocalDev = resolve7(ctx.projectRoot, ".env.dev");
905
+ if (!localOk && existsSync6(legacyLocalDev)) {
931
906
  const nodeEnv = readEnvFileValue(ctx.projectRoot, ".env.dev", "VITE_NODE_ENV");
932
907
  const hasDevMode = readEnvFileValue(ctx.projectRoot, ".env.dev", "VITE_DEV_MODE") != null;
933
908
  const looksLikeLocal = hasDevMode || nodeEnv === "local" || nodeEnv == null || nodeEnv === "development";
@@ -943,7 +918,7 @@ function checkEnvFiles(ctx) {
943
918
  }
944
919
  for (const tier of ctx.config.env.tiers) {
945
920
  const file = envFileForTier(tier);
946
- const ok = existsSync7(resolve8(ctx.projectRoot, file));
921
+ const ok = existsSync6(resolve7(ctx.projectRoot, file));
947
922
  if (tier === "stg") {
948
923
  checks.push({
949
924
  name: "env-stg",
@@ -966,8 +941,8 @@ function checkEnvFiles(ctx) {
966
941
  }
967
942
  function checkLegacyBuildIdFile(ctx) {
968
943
  if (isThemeProject(ctx.config) || !usesCdnPipeline(ctx.config)) return null;
969
- const legacy = resolve8(ctx.projectRoot, ".portal-build-id");
970
- if (!existsSync7(legacy)) return null;
944
+ const legacy = resolve7(ctx.projectRoot, ".portal-build-id");
945
+ if (!existsSync6(legacy)) return null;
971
946
  return {
972
947
  name: "legacy-build-id",
973
948
  ok: false,
@@ -978,8 +953,8 @@ function checkLegacyBuildIdFile(ctx) {
978
953
  }
979
954
  function checkBuildId(ctx) {
980
955
  if (isThemeProject(ctx.config) || !usesCdnPipeline(ctx.config)) return null;
981
- const file = resolve8(ctx.projectRoot, ctx.config.cdn.buildIdFile);
982
- const ok = existsSync7(file);
956
+ const file = resolve7(ctx.projectRoot, ctx.config.cdn.buildIdFile);
957
+ const ok = existsSync6(file);
983
958
  return {
984
959
  name: "build-id",
985
960
  ok,
@@ -1033,7 +1008,7 @@ function checkDevSlug(ctx) {
1033
1008
  };
1034
1009
  }
1035
1010
  function isUploadOnlyProject(ctx) {
1036
- const moduleDirs = [resolve8(ctx.projectRoot, ctx.config.module.uploadPath)];
1011
+ const moduleDirs = [resolve7(ctx.projectRoot, ctx.config.module.uploadPath)];
1037
1012
  return isUploadOnlyPreexistingProject(ctx.projectRoot, moduleDirs);
1038
1013
  }
1039
1014
  function checkUploadOnlyNote(ctx) {
@@ -1047,8 +1022,8 @@ function checkUploadOnlyNote(ctx) {
1047
1022
  };
1048
1023
  }
1049
1024
  function checkDevViteConfig(ctx) {
1050
- const path = resolve8(ctx.projectRoot, "vite.config.dev.mjs");
1051
- const ok = existsSync7(path);
1025
+ const path = resolve7(ctx.projectRoot, "vite.config.dev.mjs");
1026
+ const ok = existsSync6(path);
1052
1027
  return {
1053
1028
  name: "vite-dev-config",
1054
1029
  ok,
@@ -1059,8 +1034,8 @@ function checkDevViteConfig(ctx) {
1059
1034
  }
1060
1035
  function checkMetaJson(ctx) {
1061
1036
  if (isThemeProject(ctx.config)) return null;
1062
- const path = resolve8(ctx.projectRoot, "public/meta.json");
1063
- if (!existsSync7(path)) {
1037
+ const path = resolve7(ctx.projectRoot, "public/meta.json");
1038
+ if (!existsSync6(path)) {
1064
1039
  return {
1065
1040
  name: "meta-json",
1066
1041
  ok: false,
@@ -1070,7 +1045,7 @@ function checkMetaJson(ctx) {
1070
1045
  };
1071
1046
  }
1072
1047
  try {
1073
- const meta = JSON.parse(readFileSync5(path, "utf8"));
1048
+ const meta = JSON.parse(readFileSync6(path, "utf8"));
1074
1049
  const hasContentTypes = Array.isArray(meta.content_types) && meta.content_types.length > 0;
1075
1050
  return {
1076
1051
  name: "meta-json",
@@ -1092,8 +1067,8 @@ function checkMetaJson(ctx) {
1092
1067
  }
1093
1068
  async function checkCdnMirrorPublic(ctx) {
1094
1069
  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)) {
1070
+ const mirrorPath = resolve7(ctx.projectRoot, ctx.config.cdn?.mirror ?? "portal.cdn.json");
1071
+ if (!existsSync6(mirrorPath)) {
1097
1072
  return {
1098
1073
  name: "cdn-mirror-public",
1099
1074
  ok: false,
@@ -1104,7 +1079,7 @@ async function checkCdnMirrorPublic(ctx) {
1104
1079
  }
1105
1080
  let github;
1106
1081
  try {
1107
- const mirror = JSON.parse(readFileSync5(mirrorPath, "utf8"));
1082
+ const mirror = JSON.parse(readFileSync6(mirrorPath, "utf8"));
1108
1083
  github = mirror.github?.trim();
1109
1084
  } catch {
1110
1085
  return {
@@ -1156,8 +1131,8 @@ async function checkCdnMirrorPublic(ctx) {
1156
1131
  severity: "warn"
1157
1132
  };
1158
1133
  }
1159
- const manifestPath = resolve8(ctx.projectRoot, "dist/cdn/manifest.json");
1160
- if (!existsSync7(manifestPath)) {
1134
+ const manifestPath = resolve7(ctx.projectRoot, "dist/cdn/manifest.json");
1135
+ if (!existsSync6(manifestPath)) {
1161
1136
  return {
1162
1137
  name: "cdn-mirror-public",
1163
1138
  ok: true,
@@ -1166,7 +1141,7 @@ async function checkCdnMirrorPublic(ctx) {
1166
1141
  };
1167
1142
  }
1168
1143
  try {
1169
- const manifest = JSON.parse(readFileSync5(manifestPath, "utf8"));
1144
+ const manifest = JSON.parse(readFileSync6(manifestPath, "utf8"));
1170
1145
  const appUrl = manifest.scripts?.find((s) => s.name === "app")?.url;
1171
1146
  if (!appUrl) {
1172
1147
  return {
@@ -1219,8 +1194,8 @@ async function checkCdnMirrorPublic(ctx) {
1219
1194
  }
1220
1195
  function checkFieldsJson(ctx) {
1221
1196
  if (isThemeProject(ctx.config)) return null;
1222
- const path = resolve8(ctx.projectRoot, "public/fields.json");
1223
- if (!existsSync7(path)) {
1197
+ const path = resolve7(ctx.projectRoot, "public/fields.json");
1198
+ if (!existsSync6(path)) {
1224
1199
  return {
1225
1200
  name: "fields-json",
1226
1201
  ok: false,
@@ -1230,7 +1205,7 @@ function checkFieldsJson(ctx) {
1230
1205
  };
1231
1206
  }
1232
1207
  try {
1233
- const fields = JSON.parse(readFileSync5(path, "utf8"));
1208
+ const fields = JSON.parse(readFileSync6(path, "utf8"));
1234
1209
  const issues = validateHubSpotFieldsJson(fields);
1235
1210
  if (issues.length === 0) {
1236
1211
  return {
@@ -1260,11 +1235,11 @@ function checkFieldsJson(ctx) {
1260
1235
  }
1261
1236
  }
1262
1237
  function readProjectFile(projectRoot, relativePath) {
1263
- const path = resolve8(projectRoot, relativePath);
1264
- if (!existsSync7(path)) {
1238
+ const path = resolve7(projectRoot, relativePath);
1239
+ if (!existsSync6(path)) {
1265
1240
  return null;
1266
1241
  }
1267
- return readFileSync5(path, "utf8");
1242
+ return readFileSync6(path, "utf8");
1268
1243
  }
1269
1244
  function checkCssPipeline(ctx) {
1270
1245
  if (isThemeProject(ctx.config) || isUploadOnlyProject(ctx)) {
@@ -1331,9 +1306,9 @@ function checkCssPipeline(ctx) {
1331
1306
  }
1332
1307
  const moduleOutDir = ctx.config.module?.outDir;
1333
1308
  if (moduleOutDir && usesCdnPipeline(ctx.config)) {
1334
- const moduleCssPath = resolve8(ctx.projectRoot, moduleOutDir, "module.css");
1335
- if (existsSync7(moduleCssPath)) {
1336
- const css = readFileSync5(moduleCssPath, "utf8");
1309
+ const moduleCssPath = resolve7(ctx.projectRoot, moduleOutDir, "module.css");
1310
+ if (existsSync6(moduleCssPath)) {
1311
+ const css = readFileSync6(moduleCssPath, "utf8");
1337
1312
  const hasDwUtilities = /dw\\:/.test(css) || /\.dw:/.test(css);
1338
1313
  checks.push({
1339
1314
  name: "css-module-dist-prefixed",
@@ -1348,8 +1323,8 @@ function checkCssPipeline(ctx) {
1348
1323
  }
1349
1324
  function checkHybridModuleHtml(ctx) {
1350
1325
  if (isThemeProject(ctx.config) || !usesCdnPipeline(ctx.config)) return null;
1351
- const path = resolve8(ctx.projectRoot, "public/module.html");
1352
- if (!existsSync7(path)) {
1326
+ const path = resolve7(ctx.projectRoot, "public/module.html");
1327
+ if (!existsSync6(path)) {
1353
1328
  return {
1354
1329
  name: "module-html",
1355
1330
  ok: false,
@@ -1358,7 +1333,7 @@ function checkHybridModuleHtml(ctx) {
1358
1333
  severity: "error"
1359
1334
  };
1360
1335
  }
1361
- const html = readFileSync5(path, "utf8");
1336
+ const html = readFileSync6(path, "utf8");
1362
1337
  const needs = ["__PORTAL_CDN_BASE__", "require_js", "hubSpotData", 'id="app"'];
1363
1338
  const missing = needs.filter((token) => !html.includes(token));
1364
1339
  const usesUndeclaredCdnFields = /module\.portal_(cdn_base_url|vendor_script|app_script|app_css)/.test(
@@ -1376,8 +1351,8 @@ function checkHybridModuleHtml(ctx) {
1376
1351
  function checkCdnViteConfigs(ctx) {
1377
1352
  if (isThemeProject(ctx.config) || !usesCdnPipeline(ctx.config)) return [];
1378
1353
  return ["vite.config.cdn-vendor.mjs", "vite.config.cdn-app.mjs"].map((file) => {
1379
- const path = resolve8(ctx.projectRoot, file);
1380
- const ok = existsSync7(path);
1354
+ const path = resolve7(ctx.projectRoot, file);
1355
+ const ok = existsSync6(path);
1381
1356
  return {
1382
1357
  name: file.replace(/\./g, "-"),
1383
1358
  ok,
@@ -1389,8 +1364,8 @@ function checkCdnViteConfigs(ctx) {
1389
1364
  }
1390
1365
  function checkDevEntry(ctx) {
1391
1366
  const checks = [];
1392
- const indexHtml = resolve8(ctx.projectRoot, "index.html");
1393
- const hasIndex = existsSync7(indexHtml);
1367
+ const indexHtml = resolve7(ctx.projectRoot, "index.html");
1368
+ const hasIndex = existsSync6(indexHtml);
1394
1369
  checks.push({
1395
1370
  name: "index-html",
1396
1371
  ok: hasIndex,
@@ -1398,8 +1373,8 @@ function checkDevEntry(ctx) {
1398
1373
  remediation: hasIndex ? void 0 : "Re-run wp init or copy index.html from kit templates",
1399
1374
  severity: "error"
1400
1375
  });
1401
- const localDev = resolve8(ctx.projectRoot, ctx.config.entries.localDev);
1402
- const hasEntry = existsSync7(localDev);
1376
+ const localDev = resolve7(ctx.projectRoot, ctx.config.entries.localDev);
1377
+ const hasEntry = existsSync6(localDev);
1403
1378
  checks.push({
1404
1379
  name: "local-dev-entry",
1405
1380
  ok: hasEntry,
@@ -1409,7 +1384,7 @@ function checkDevEntry(ctx) {
1409
1384
  });
1410
1385
  if (hasEntry) {
1411
1386
  try {
1412
- const source = readFileSync5(localDev, "utf8");
1387
+ const source = readFileSync6(localDev, "utf8");
1413
1388
  const usesKitNodeBootstrap = source.includes("@woodsportal/hubspot-kit/dev/bootstrap-greenfield") || source.includes("@woodsportal/hubspot-kit/dev/bootstrap");
1414
1389
  const usesWphsShim = source.includes(".wphs/.kit-bootstrap-greenfield") || source.includes(".wphs/.kit-bootstrap") || source.includes("@wphs/bootstrap-greenfield") || source.includes("@wphs/bootstrap");
1415
1390
  checks.push({
@@ -1426,7 +1401,7 @@ function checkDevEntry(ctx) {
1426
1401
  }
1427
1402
  function checkKitScripts() {
1428
1403
  const script = kitPath("scripts", "build-cdn.mjs");
1429
- const ok = existsSync7(script);
1404
+ const ok = existsSync6(script);
1430
1405
  return {
1431
1406
  name: "kit-scripts",
1432
1407
  ok,
@@ -1439,8 +1414,8 @@ function checkWphsWorkspace(ctx) {
1439
1414
  const workspace = ctx.config.dev.moduleConfigDir;
1440
1415
  const checks = [];
1441
1416
  for (const legacy of [".dev", ".woodsportal"]) {
1442
- const legacyPath = resolve8(ctx.projectRoot, legacy);
1443
- if (existsSync7(legacyPath)) {
1417
+ const legacyPath = resolve7(ctx.projectRoot, legacy);
1418
+ if (existsSync6(legacyPath)) {
1444
1419
  checks.push({
1445
1420
  name: `legacy-${legacy}`,
1446
1421
  ok: false,
@@ -1450,7 +1425,7 @@ function checkWphsWorkspace(ctx) {
1450
1425
  });
1451
1426
  }
1452
1427
  }
1453
- if (existsSync7(resolve8(ctx.projectRoot, ".wp-hs-baseline"))) {
1428
+ if (existsSync6(resolve7(ctx.projectRoot, ".wp-hs-baseline"))) {
1454
1429
  checks.push({
1455
1430
  name: "legacy-baseline",
1456
1431
  ok: false,
@@ -1469,10 +1444,10 @@ function checkWphsWorkspace(ctx) {
1469
1444
  });
1470
1445
  return checks;
1471
1446
  }
1472
- const wphsRoot = resolve8(ctx.projectRoot, workspace);
1447
+ const wphsRoot = resolve7(ctx.projectRoot, workspace);
1473
1448
  const required = ["config", "assets/branding", "fixtures", "cache", "audit/baseline", "state"];
1474
1449
  for (const sub of required) {
1475
- const ok = existsSync7(join(wphsRoot, sub));
1450
+ const ok = existsSync6(join2(wphsRoot, sub));
1476
1451
  checks.push({
1477
1452
  name: `wphs-${sub.replace(/\//g, "-")}`,
1478
1453
  ok,
@@ -1485,8 +1460,8 @@ function checkWphsWorkspace(ctx) {
1485
1460
  }
1486
1461
  function checkAdapter(ctx) {
1487
1462
  if (isThemeProject(ctx.config)) return null;
1488
- const adapterPath = resolve8(ctx.projectRoot, ctx.config.dev.adapter);
1489
- const ok = existsSync7(adapterPath);
1463
+ const adapterPath = resolve7(ctx.projectRoot, ctx.config.dev.adapter);
1464
+ const ok = existsSync6(adapterPath);
1490
1465
  return {
1491
1466
  name: "wphs-adapter",
1492
1467
  ok,
@@ -1497,11 +1472,11 @@ function checkAdapter(ctx) {
1497
1472
  }
1498
1473
  function checkModuleConfigUiDeps(ctx) {
1499
1474
  if (isThemeProject(ctx.config)) return [];
1500
- const pkgPath = resolve8(ctx.projectRoot, "package.json");
1501
- if (!existsSync7(pkgPath)) return [];
1475
+ const pkgPath = resolve7(ctx.projectRoot, "package.json");
1476
+ if (!existsSync6(pkgPath)) return [];
1502
1477
  let deps = {};
1503
1478
  try {
1504
- const pkg = JSON.parse(readFileSync5(pkgPath, "utf8"));
1479
+ const pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
1505
1480
  deps = { ...pkg.dependencies, ...pkg.devDependencies };
1506
1481
  } catch {
1507
1482
  return [];
@@ -1528,7 +1503,7 @@ function checkNodeLoaderInterop() {
1528
1503
  };
1529
1504
  }
1530
1505
  function checkViteInstalled(ctx) {
1531
- if (isThemeProject(ctx.config) && !existsSync7(resolve8(ctx.projectRoot, "vite.config.dev.mjs"))) {
1506
+ if (isThemeProject(ctx.config) && !existsSync6(resolve7(ctx.projectRoot, "vite.config.dev.mjs"))) {
1532
1507
  return null;
1533
1508
  }
1534
1509
  const ok = isProjectViteInstalled(ctx.projectRoot);
@@ -1685,29 +1660,10 @@ async function runConfigValidate(ctx) {
1685
1660
  }
1686
1661
 
1687
1662
  // src/cli/commands/build.ts
1688
- import { resolve as resolve10 } from "path";
1689
-
1690
- // src/lib/cli-banner.ts
1691
- import pc3 from "picocolors";
1692
-
1693
- // src/lib/kit-version.ts
1694
- import { readFileSync as readFileSync6 } from "fs";
1695
- import { resolve as resolve9 } from "path";
1696
- var cachedVersion = null;
1697
- function readKitVersion() {
1698
- if (cachedVersion) {
1699
- return cachedVersion;
1700
- }
1701
- try {
1702
- const pkg = JSON.parse(readFileSync6(resolve9(getKitRoot(), "package.json"), "utf8"));
1703
- cachedVersion = pkg.version ?? "0.0.0";
1704
- } catch {
1705
- cachedVersion = "0.0.0";
1706
- }
1707
- return cachedVersion;
1708
- }
1663
+ import { resolve as resolve8 } from "path";
1709
1664
 
1710
1665
  // src/lib/cli-banner.ts
1666
+ import pc4 from "picocolors";
1711
1667
  var WOODSCLI_ASCII_BANNER = `
1712
1668
  __ __ _ _____ _ _____
1713
1669
  \\ \\ / / | | / ____| | |_ _|
@@ -1737,7 +1693,7 @@ function formatProjectLabel(config) {
1737
1693
  return `${config.module.name} \xB7 ${config.projectType}${routerNote}`;
1738
1694
  }
1739
1695
  function section(color, title, body) {
1740
- const bold = (text4) => color ? pc3.bold(text4) : text4;
1696
+ const bold = (text5) => color ? pc4.bold(text5) : text5;
1741
1697
  return ` ${bold(title)}
1742
1698
  ${body}`;
1743
1699
  }
@@ -1746,10 +1702,10 @@ function printWoodsCliDevBanner(options) {
1746
1702
  return;
1747
1703
  }
1748
1704
  const color = useCliColor();
1749
- const cyan = (text4) => color ? pc3.cyan(text4) : text4;
1750
- const dim = (text4) => color ? pc3.dim(text4) : text4;
1751
- const bold = (text4) => color ? pc3.bold(text4) : text4;
1752
- const yellow = (text4) => color ? pc3.yellow(text4) : text4;
1705
+ const cyan = (text5) => color ? pc4.cyan(text5) : text5;
1706
+ const dim = (text5) => color ? pc4.dim(text5) : text5;
1707
+ const bold = (text5) => color ? pc4.bold(text5) : text5;
1708
+ const yellow = (text5) => color ? pc4.yellow(text5) : text5;
1753
1709
  const version = options.version ?? readKitVersion();
1754
1710
  const project = formatProjectLabel(options.config);
1755
1711
  const isTheme = isThemeProject(options.config);
@@ -1833,7 +1789,7 @@ function buildTasks(ctx, options, run) {
1833
1789
  title: `Building monolith module (${tier})`,
1834
1790
  task: async () => {
1835
1791
  await run("run-vite-build.mjs", [
1836
- resolve10(ctx.projectRoot, "vite.config.monolith.mjs"),
1792
+ resolve8(ctx.projectRoot, "vite.config.monolith.mjs"),
1837
1793
  ...tierToViteModeFlag(tier)
1838
1794
  ]);
1839
1795
  await run("run-hubspot-module-postbuild.mjs");
@@ -1847,7 +1803,7 @@ function buildTasks(ctx, options, run) {
1847
1803
  task: async () => {
1848
1804
  if (options.cdnEsm) {
1849
1805
  await run("run-vite-build.mjs", [
1850
- resolve10(ctx.projectRoot, "vite.config.cdn-esm.mjs"),
1806
+ resolve8(ctx.projectRoot, "vite.config.cdn-esm.mjs"),
1851
1807
  ...tierToViteModeFlag(tier)
1852
1808
  ]);
1853
1809
  } else {
@@ -1862,7 +1818,7 @@ function buildTasks(ctx, options, run) {
1862
1818
  title: `Building HubSpot module (${tier})`,
1863
1819
  task: async () => {
1864
1820
  await run("run-vite-build.mjs", [
1865
- resolve10(ctx.projectRoot, "vite.config.hubspot.mjs"),
1821
+ resolve8(ctx.projectRoot, "vite.config.hubspot.mjs"),
1866
1822
  ...tierToViteModeFlag(tier)
1867
1823
  ]);
1868
1824
  await run("run-hubspot-module-postbuild.mjs");
@@ -1959,7 +1915,7 @@ async function runBuild(ctx, options) {
1959
1915
  command: "build",
1960
1916
  tier,
1961
1917
  ok: true,
1962
- outDir: resolve10(ctx.projectRoot, ctx.config.module.outDir)
1918
+ outDir: resolve8(ctx.projectRoot, ctx.config.module.outDir)
1963
1919
  });
1964
1920
  } else if (!options.quiet) {
1965
1921
  printCommandDone(ctx, "Build complete");
@@ -2010,11 +1966,11 @@ async function runDev(ctx, options) {
2010
1966
  }
2011
1967
 
2012
1968
  // src/cli/commands/deploy.ts
2013
- import { resolve as resolve13 } from "path";
1969
+ import { resolve as resolve11 } from "path";
2014
1970
 
2015
1971
  // src/lib/project-env-files.ts
2016
- import { existsSync as existsSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync2 } from "fs";
2017
- import { resolve as resolve11 } from "path";
1972
+ import { existsSync as existsSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync3 } from "fs";
1973
+ import { resolve as resolve9 } from "path";
2018
1974
  var ENV_LOCAL_FILE = ".env.local";
2019
1975
  var ENV_GITIGNORE_LINES = [".env", ".env.*", "!.env*.example", ENV_LOCAL_FILE];
2020
1976
  function parseEnvFile(content) {
@@ -2034,8 +1990,8 @@ function parseEnvFile(content) {
2034
1990
  return result;
2035
1991
  }
2036
1992
  function readEnvLocal(projectRoot) {
2037
- const path = resolve11(projectRoot, ENV_LOCAL_FILE);
2038
- if (!existsSync8(path)) return {};
1993
+ const path = resolve9(projectRoot, ENV_LOCAL_FILE);
1994
+ if (!existsSync7(path)) return {};
2039
1995
  return parseEnvFile(readFileSync7(path, "utf8"));
2040
1996
  }
2041
1997
  function formatEnvLocal(entries) {
@@ -2055,9 +2011,9 @@ function formatEnvLocal(entries) {
2055
2011
  `;
2056
2012
  }
2057
2013
  function writeEnvLocal(projectRoot, entries) {
2058
- const path = resolve11(projectRoot, ENV_LOCAL_FILE);
2059
- const existing = existsSync8(path) ? parseEnvFile(readFileSync7(path, "utf8")) : {};
2060
- writeFileSync2(path, formatEnvLocal({ ...existing, ...entries }));
2014
+ const path = resolve9(projectRoot, ENV_LOCAL_FILE);
2015
+ const existing = existsSync7(path) ? parseEnvFile(readFileSync7(path, "utf8")) : {};
2016
+ writeFileSync3(path, formatEnvLocal({ ...existing, ...entries }));
2061
2017
  }
2062
2018
  function mergeProjectEnv(projectRoot, base = process.env) {
2063
2019
  const local = readEnvLocal(projectRoot);
@@ -2070,11 +2026,11 @@ function mergeProjectEnv(projectRoot, base = process.env) {
2070
2026
  return merged;
2071
2027
  }
2072
2028
  function ensureEnvFilesGitignore(projectRoot) {
2073
- const gitignorePath = resolve11(projectRoot, ".gitignore");
2074
- const existing = existsSync8(gitignorePath) ? readFileSync7(gitignorePath, "utf8") : "";
2029
+ const gitignorePath = resolve9(projectRoot, ".gitignore");
2030
+ const existing = existsSync7(gitignorePath) ? readFileSync7(gitignorePath, "utf8") : "";
2075
2031
  const toAppend = ENV_GITIGNORE_LINES.filter((line) => !existing.includes(line)).join("\n");
2076
2032
  if (!toAppend) return;
2077
- writeFileSync2(gitignorePath, `${existing.trimEnd()}
2033
+ writeFileSync3(gitignorePath, `${existing.trimEnd()}
2078
2034
 
2079
2035
  # Env files (secrets)
2080
2036
  ${toAppend}
@@ -2082,14 +2038,14 @@ ${toAppend}
2082
2038
  }
2083
2039
 
2084
2040
  // src/cli/commands/upload.ts
2085
- import { resolve as resolve12 } from "path";
2041
+ import { resolve as resolve10 } from "path";
2086
2042
  async function uploadModuleOrTheme(ctx) {
2087
2043
  if (isThemeProject(ctx.config)) {
2088
2044
  const theme = ctx.config.theme;
2089
2045
  if (!theme) {
2090
2046
  throw new WpHsError("theme block missing in woodscli.json", { exitCode: ExitCode.USER_ERROR });
2091
2047
  }
2092
- const srcDir = resolve12(ctx.projectRoot, theme.srcDir);
2048
+ const srcDir = resolve10(ctx.projectRoot, theme.srcDir);
2093
2049
  await runCommandAsync("hs", ["cms", "upload", srcDir, theme.uploadPath], {
2094
2050
  cwd: ctx.projectRoot,
2095
2051
  projectRoot: ctx.projectRoot,
@@ -2099,7 +2055,7 @@ async function uploadModuleOrTheme(ctx) {
2099
2055
  });
2100
2056
  return `Uploaded theme ${srcDir} \u2192 ${theme.uploadPath}`;
2101
2057
  }
2102
- const outDir = resolve12(ctx.projectRoot, ctx.config.module.outDir);
2058
+ const outDir = resolve10(ctx.projectRoot, ctx.config.module.outDir);
2103
2059
  const uploadPath = ctx.config.module.uploadPath;
2104
2060
  await runCommandAsync("hs", ["cms", "upload", outDir, uploadPath], {
2105
2061
  cwd: ctx.projectRoot,
@@ -2276,7 +2232,7 @@ async function runDeploy(ctx, options) {
2276
2232
  if (!ctx.global.json) {
2277
2233
  printCommandDone(
2278
2234
  ctx,
2279
- `Deploy complete (${resolve13(ctx.projectRoot, ctx.config.module.outDir)} \u2192 ${ctx.config.module.uploadPath})`
2235
+ `Deploy complete (${resolve11(ctx.projectRoot, ctx.config.module.outDir)} \u2192 ${ctx.config.module.uploadPath})`
2280
2236
  );
2281
2237
  }
2282
2238
  return ExitCode.SUCCESS;
@@ -2349,8 +2305,8 @@ async function runPublish(ctx, options) {
2349
2305
 
2350
2306
  // src/cli/commands/audit.ts
2351
2307
  import { createHash } from "crypto";
2352
- import { cpSync, existsSync as existsSync9, mkdirSync as mkdirSync2, readFileSync as readFileSync8, readdirSync, rmSync, statSync } from "fs";
2353
- import { resolve as resolve14, join as join2 } from "path";
2308
+ import { cpSync, existsSync as existsSync8, mkdirSync as mkdirSync3, readFileSync as readFileSync8, readdirSync, rmSync, statSync } from "fs";
2309
+ import { resolve as resolve12, join as join3 } from "path";
2354
2310
  var FORBIDDEN_SHIP_TOKENS = [
2355
2311
  "ModuleConfigOverlay",
2356
2312
  "module-config-store",
@@ -2364,11 +2320,11 @@ function hashFile(path) {
2364
2320
  return createHash("sha256").update(readFileSync8(path)).digest("hex");
2365
2321
  }
2366
2322
  function walkFiles(dir) {
2367
- if (!existsSync9(dir)) return [];
2323
+ if (!existsSync8(dir)) return [];
2368
2324
  const entries = readdirSync(dir);
2369
2325
  const files = [];
2370
2326
  for (const entry of entries) {
2371
- const full = join2(dir, entry);
2327
+ const full = join3(dir, entry);
2372
2328
  if (statSync(full).isDirectory()) {
2373
2329
  files.push(...walkFiles(full));
2374
2330
  } else {
@@ -2380,8 +2336,8 @@ function walkFiles(dir) {
2380
2336
  async function runAuditShipBundle(ctx) {
2381
2337
  const root = ctx.projectRoot;
2382
2338
  const targets = [
2383
- resolve14(root, "dist/cdn"),
2384
- resolve14(root, ctx.config.module.outDir)
2339
+ resolve12(root, "dist/cdn"),
2340
+ resolve12(root, ctx.config.module.outDir)
2385
2341
  ];
2386
2342
  const violations = [];
2387
2343
  for (const dir of targets) {
@@ -2427,23 +2383,23 @@ async function runAuditBaselineCapture(ctx, options) {
2427
2383
  const root = ctx.projectRoot;
2428
2384
  const baseline = options.baselineDir ?? defaultBaselineDir(root, ctx.config);
2429
2385
  const sources = [
2430
- { label: "cdn", path: resolve14(root, "dist/cdn") },
2431
- { label: "hubspot", path: resolve14(root, ctx.config.module.outDir) }
2386
+ { label: "cdn", path: resolve12(root, "dist/cdn") },
2387
+ { label: "hubspot", path: resolve12(root, ctx.config.module.outDir) }
2432
2388
  ];
2433
2389
  if (ctx.global.dryRun) {
2434
2390
  ctx.logger.info(`[dry-run] Would capture baseline to ${baseline}`);
2435
2391
  return ExitCode.SUCCESS;
2436
2392
  }
2437
2393
  for (const { label, path } of sources) {
2438
- if (!existsSync9(path)) {
2394
+ if (!existsSync8(path)) {
2439
2395
  throw new WpHsError(`Cannot capture baseline \u2014 missing ${path}`, {
2440
2396
  exitCode: ExitCode.USER_ERROR,
2441
2397
  remediation: "Run wp build before wp audit baseline --capture."
2442
2398
  });
2443
2399
  }
2444
- const target = resolve14(baseline, label);
2400
+ const target = resolve12(baseline, label);
2445
2401
  rmSync(target, { recursive: true, force: true });
2446
- mkdirSync2(baseline, { recursive: true });
2402
+ mkdirSync3(baseline, { recursive: true });
2447
2403
  cpSync(path, target, { recursive: true });
2448
2404
  }
2449
2405
  if (ctx.global.json) {
@@ -2457,13 +2413,13 @@ async function runAuditParity(ctx, options) {
2457
2413
  const root = ctx.projectRoot;
2458
2414
  const baseline = options.baselineDir ?? defaultBaselineDir(root, ctx.config);
2459
2415
  const currentDirs = [
2460
- { label: "cdn", path: resolve14(root, "dist/cdn") },
2461
- { label: "hubspot", path: resolve14(root, ctx.config.module.outDir) }
2416
+ { label: "cdn", path: resolve12(root, "dist/cdn") },
2417
+ { label: "hubspot", path: resolve12(root, ctx.config.module.outDir) }
2462
2418
  ];
2463
2419
  const mismatches = [];
2464
2420
  for (const { label, path } of currentDirs) {
2465
- const baselinePath = resolve14(baseline, label);
2466
- if (!existsSync9(baselinePath)) {
2421
+ const baselinePath = resolve12(baseline, label);
2422
+ if (!existsSync8(baselinePath)) {
2467
2423
  ctx.logger.warn(`No baseline at ${baselinePath} \u2014 skipping ${label} parity`);
2468
2424
  continue;
2469
2425
  }
@@ -2478,8 +2434,8 @@ async function runAuditParity(ctx, options) {
2478
2434
  }
2479
2435
  for (const file of baselineFiles) {
2480
2436
  const rel = file.replace(baselinePath, "");
2481
- const current = resolve14(path, rel.slice(1));
2482
- if (!existsSync9(current)) continue;
2437
+ const current = resolve12(path, rel.slice(1));
2438
+ if (!existsSync8(current)) continue;
2483
2439
  const bh = hashFile(file);
2484
2440
  const ch = hashFile(current);
2485
2441
  if (bh !== ch) {
@@ -2506,7 +2462,7 @@ var SECRET_PATTERNS = [
2506
2462
  ];
2507
2463
  async function runAuditSecrets(ctx) {
2508
2464
  const root = ctx.projectRoot;
2509
- const scanRoots = ["src", "public", ".wphs/config"].map((d) => resolve14(root, d));
2465
+ const scanRoots = ["src", "public", ".wphs/config"].map((d) => resolve12(root, d));
2510
2466
  const findings = [];
2511
2467
  for (const dir of scanRoots) {
2512
2468
  for (const file of walkFiles(dir)) {
@@ -2538,13 +2494,13 @@ async function runAuditSecrets(ctx) {
2538
2494
  }
2539
2495
 
2540
2496
  // src/cli/commands/init.ts
2541
- import { cpSync as cpSync2, existsSync as existsSync13, mkdirSync as mkdirSync4, readdirSync as readdirSync2, readFileSync as readFileSync11, writeFileSync as writeFileSync6 } from "fs";
2542
- import { basename as basename2, resolve as resolve18 } from "path";
2543
- import * as p4 from "@clack/prompts";
2497
+ import { cpSync as cpSync3, existsSync as existsSync13, mkdirSync as mkdirSync5, readdirSync as readdirSync2, readFileSync as readFileSync12, writeFileSync as writeFileSync8 } from "fs";
2498
+ import { basename as basename2, resolve as resolve17 } from "path";
2499
+ import * as p5 from "@clack/prompts";
2544
2500
 
2545
2501
  // src/lib/init-router-scaffold.ts
2546
- import { existsSync as existsSync10, mkdirSync as mkdirSync3, readFileSync as readFileSync9, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
2547
- import { resolve as resolve15 } from "path";
2502
+ import { existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync9, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
2503
+ import { resolve as resolve13 } from "path";
2548
2504
  var ROUTER_FILES = [
2549
2505
  "src/routes",
2550
2506
  "src/routeTree.gen.ts",
@@ -2558,10 +2514,10 @@ function isModuleTemplate(template) {
2558
2514
  return template === "module-hybrid-cdn" || template === "module-monolith";
2559
2515
  }
2560
2516
  function writeRouterConfig(targetDir, mode) {
2561
- const configDir = resolve15(targetDir, "src/config");
2562
- mkdirSync3(configDir, { recursive: true });
2563
- writeFileSync3(
2564
- resolve15(configDir, "router.ts"),
2517
+ const configDir = resolve13(targetDir, "src/config");
2518
+ mkdirSync4(configDir, { recursive: true });
2519
+ writeFileSync4(
2520
+ resolve13(configDir, "router.ts"),
2565
2521
  `/** Router history \u2014 set by \`wp init\`. Hash matches woodsportal-client-frontend (HubSpot CMS embeds). */
2566
2522
  export type RouterHistoryMode = 'hash' | 'browser'
2567
2523
 
@@ -2570,8 +2526,8 @@ export const ROUTER_HISTORY: RouterHistoryMode = '${mode}'
2570
2526
  );
2571
2527
  }
2572
2528
  function writeMountDevPreviewRouter(targetDir) {
2573
- writeFileSync3(
2574
- resolve15(targetDir, "src/portal/mount-dev-preview.tsx"),
2529
+ writeFileSync4(
2530
+ resolve13(targetDir, "src/portal/mount-dev-preview.tsx"),
2575
2531
  `import { mountGreenfieldDev } from '../../.wphs/.kit-bootstrap-greenfield.tsx'
2576
2532
 
2577
2533
  import { App } from '@/app'
@@ -2584,8 +2540,8 @@ export async function mountDevPreview() {
2584
2540
  );
2585
2541
  }
2586
2542
  function writeMountDevPreviewSinglePage(targetDir) {
2587
- writeFileSync3(
2588
- resolve15(targetDir, "src/portal/mount-dev-preview.tsx"),
2543
+ writeFileSync4(
2544
+ resolve13(targetDir, "src/portal/mount-dev-preview.tsx"),
2589
2545
  `import { mountGreenfieldDev } from '../../.wphs/.kit-bootstrap-greenfield.tsx'
2590
2546
 
2591
2547
  import { StarterSinglePage } from '@/features/starter'
@@ -2598,8 +2554,8 @@ export async function mountDevPreview() {
2598
2554
  );
2599
2555
  }
2600
2556
  function writeMountPortalRouter(targetDir) {
2601
- writeFileSync3(
2602
- resolve15(targetDir, "src/portal/mount-portal.tsx"),
2557
+ writeFileSync4(
2558
+ resolve13(targetDir, "src/portal/mount-portal.tsx"),
2603
2559
  `import ReactDOM from 'react-dom/client'
2604
2560
 
2605
2561
  import { App } from '@/app'
@@ -2616,8 +2572,8 @@ export async function mountPortal() {
2616
2572
  );
2617
2573
  }
2618
2574
  function writeMountPortalSinglePage(targetDir) {
2619
- writeFileSync3(
2620
- resolve15(targetDir, "src/portal/mount-portal.tsx"),
2575
+ writeFileSync4(
2576
+ resolve13(targetDir, "src/portal/mount-portal.tsx"),
2621
2577
  `import ReactDOM from 'react-dom/client'
2622
2578
 
2623
2579
  import { StarterSinglePage } from '@/features/starter'
@@ -2634,11 +2590,11 @@ export async function mountPortal() {
2634
2590
  );
2635
2591
  }
2636
2592
  function writeStarterSinglePage(targetDir) {
2637
- const file = resolve15(targetDir, "src/features/starter/components/starter-single-page.tsx");
2638
- if (existsSync10(file)) {
2593
+ const file = resolve13(targetDir, "src/features/starter/components/starter-single-page.tsx");
2594
+ if (existsSync9(file)) {
2639
2595
  return;
2640
2596
  }
2641
- writeFileSync3(
2597
+ writeFileSync4(
2642
2598
  file,
2643
2599
  `import { StarterShell } from '@/components/layout/starter-shell'
2644
2600
 
@@ -2661,15 +2617,15 @@ export function StarterSinglePage() {
2661
2617
  );
2662
2618
  }
2663
2619
  function removePath(targetDir, relativePath) {
2664
- const full = resolve15(targetDir, relativePath);
2665
- if (!existsSync10(full)) {
2620
+ const full = resolve13(targetDir, relativePath);
2621
+ if (!existsSync9(full)) {
2666
2622
  return;
2667
2623
  }
2668
2624
  rmSync2(full, { recursive: true, force: true });
2669
2625
  }
2670
2626
  function stripTanStackFromPackage(targetDir) {
2671
- const pkgPath = resolve15(targetDir, "package.json");
2672
- if (!existsSync10(pkgPath)) {
2627
+ const pkgPath = resolve13(targetDir, "package.json");
2628
+ if (!existsSync9(pkgPath)) {
2673
2629
  return;
2674
2630
  }
2675
2631
  const pkg = JSON.parse(readFileSync9(pkgPath, "utf8"));
@@ -2679,7 +2635,7 @@ function stripTanStackFromPackage(targetDir) {
2679
2635
  for (const dep of TANSTACK_DEPS) {
2680
2636
  delete pkg.devDependencies[dep];
2681
2637
  }
2682
- writeFileSync3(pkgPath, `${JSON.stringify(pkg, null, 2)}
2638
+ writeFileSync4(pkgPath, `${JSON.stringify(pkg, null, 2)}
2683
2639
  `);
2684
2640
  }
2685
2641
  function applyRouterScaffold(targetDir, mode, template) {
@@ -2699,126 +2655,510 @@ function applyRouterScaffold(targetDir, mode, template) {
2699
2655
  writeRouterConfig(targetDir, mode);
2700
2656
  writeMountDevPreviewRouter(targetDir);
2701
2657
  writeMountPortalRouter(targetDir);
2702
- const featuresIndex = resolve15(targetDir, "src/features/starter/index.ts");
2703
- if (existsSync10(featuresIndex)) {
2658
+ const featuresIndex = resolve13(targetDir, "src/features/starter/index.ts");
2659
+ if (existsSync9(featuresIndex)) {
2704
2660
  let content = readFileSync9(featuresIndex, "utf8");
2705
2661
  if (!content.includes("StarterSinglePage")) {
2706
2662
  content = content.replace(
2707
2663
  "export type { StarterPreviewValues } from './hooks/use-starter-preview'\n",
2708
2664
  "export type { StarterPreviewValues } from './hooks/use-starter-preview'\nexport { StarterSinglePage } from './components/starter-single-page'\n"
2709
2665
  );
2710
- writeFileSync3(featuresIndex, content);
2666
+ writeFileSync4(featuresIndex, content);
2711
2667
  }
2712
2668
  }
2713
2669
  }
2714
2670
 
2715
- // src/lib/cdn-github-setup.ts
2716
- import { existsSync as existsSync11, readFileSync as readFileSync10, writeFileSync as writeFileSync4 } from "fs";
2717
- import { resolve as resolve16 } from "path";
2718
- import * as p3 from "@clack/prompts";
2719
- var PORTAL_CDN_PUBLISH_TOKEN_ENV = "PORTAL_CDN_PUBLISH_TOKEN";
2720
- var PORTAL_CDN_JSON = "portal.cdn.json";
2721
- var PLACEHOLDER_GITHUB = /your-org|your-portal-cdn-mirror/i;
2722
- function defaultPortalCdnJson() {
2723
- const examplePath = kitPath("templates", "shared", "portal.cdn.json.example");
2724
- if (existsSync11(examplePath)) {
2725
- return JSON.parse(readFileSync10(examplePath, "utf8"));
2726
- }
2671
+ // src/lib/init-auth-scaffold.ts
2672
+ import { cpSync as cpSync2, existsSync as existsSync10, readFileSync as readFileSync10, rmSync as rmSync3, writeFileSync as writeFileSync5 } from "fs";
2673
+ import { resolve as resolve14 } from "path";
2674
+
2675
+ // src/lib/template-auth-deps.ts
2676
+ var AUTH_SCAFFOLD_DEPS = {
2677
+ "woodsportal-client-sdk": "^4.0.7-dev.11",
2678
+ "@tanstack/react-query": "^5.66.9",
2679
+ "@t3-oss/env-core": "^0.12.0",
2680
+ zod: "^3.24.2",
2681
+ sonner: "^2.0.1"
2682
+ };
2683
+ function mergeAuthDevDependencies(existing) {
2727
2684
  return {
2728
- publishMode: "jsdelivr",
2729
- github: "your-org/your-portal-cdn-mirror",
2730
- gitRemote: "https://github.com/your-org/your-portal-cdn-mirror.git",
2731
- cdnPathInRepo: "dist/cdn",
2732
- defaultBranch: "main"
2685
+ ...existing,
2686
+ ...AUTH_SCAFFOLD_DEPS
2733
2687
  };
2734
2688
  }
2735
- function readPortalCdnJson(projectRoot) {
2736
- const path = resolve16(projectRoot, PORTAL_CDN_JSON);
2737
- if (!existsSync11(path)) return null;
2738
- try {
2739
- return JSON.parse(readFileSync10(path, "utf8"));
2740
- } catch {
2741
- return null;
2742
- }
2689
+
2690
+ // src/lib/init-auth-scaffold.ts
2691
+ var AUTH_SCAFFOLD_ROOT = kitPath("templates", "scaffold", "auth");
2692
+ function readTierManifest() {
2693
+ const path = resolve14(AUTH_SCAFFOLD_ROOT, "tier-manifest.json");
2694
+ return JSON.parse(readFileSync10(path, "utf8"));
2743
2695
  }
2744
- function normalizeGithubSlug(input) {
2745
- const trimmed = input.trim().replace(/^https:\/\/github\.com\//, "").replace(/\.git$/, "");
2746
- const parts = trimmed.split("/").filter(Boolean);
2747
- if (parts.length !== 2) return null;
2748
- return `${parts[0]}/${parts[1]}`;
2696
+ function readNavManifest() {
2697
+ const path = resolve14(AUTH_SCAFFOLD_ROOT, "nav-manifest.json");
2698
+ return JSON.parse(readFileSync10(path, "utf8"));
2749
2699
  }
2750
- function portalCdnJsonFromGithub(github, base) {
2751
- const slug = normalizeGithubSlug(github);
2752
- if (!slug) {
2753
- throw new Error(`Invalid GitHub repo \u2014 use org/repo (got "${github}")`);
2700
+ function layersForMode(mode) {
2701
+ const manifest = readTierManifest();
2702
+ const entry = manifest.tiers[mode];
2703
+ if (!entry?.layers?.length) {
2704
+ throw new Error(`Unknown auth scaffold tier in tier-manifest.json: ${mode}`);
2754
2705
  }
2755
- return {
2756
- publishMode: base?.publishMode ?? "jsdelivr",
2757
- github: slug,
2758
- gitRemote: `https://github.com/${slug}.git`,
2759
- cdnPathInRepo: base?.cdnPathInRepo ?? "dist/cdn",
2760
- defaultBranch: base?.defaultBranch ?? "main"
2761
- };
2762
- }
2763
- function hasCdnPublishToken(projectRoot) {
2764
- if (process.env[PORTAL_CDN_PUBLISH_TOKEN_ENV]?.trim()) return true;
2765
- if (process.env.GITHUB_TOKEN?.trim()) return true;
2766
- return Boolean(readEnvLocal(projectRoot)[PORTAL_CDN_PUBLISH_TOKEN_ENV]?.trim());
2706
+ return entry.layers;
2767
2707
  }
2768
- function hasConfiguredCdnGithubMirror(projectRoot) {
2769
- const config = readPortalCdnJson(projectRoot);
2770
- const github = config?.github ?? "";
2771
- return Boolean(github) && !PLACEHOLDER_GITHUB.test(github);
2708
+ function copyLayer(targetDir, layerName) {
2709
+ const layerRoot = resolve14(AUTH_SCAFFOLD_ROOT, layerName);
2710
+ if (!existsSync10(layerRoot)) {
2711
+ throw new Error(`Auth scaffold layer not found: ${layerName}`);
2712
+ }
2713
+ cpSync2(layerRoot, targetDir, { recursive: true, force: true });
2772
2714
  }
2773
- function githubDefaultForPrompt(projectRoot) {
2774
- const existing = readPortalCdnJson(projectRoot);
2775
- if (existing?.github && !PLACEHOLDER_GITHUB.test(existing.github)) {
2776
- return existing.github;
2715
+ function layerForNavMode(nav) {
2716
+ const manifest = readNavManifest();
2717
+ const entry = manifest.modes[nav];
2718
+ if (!entry?.layer) {
2719
+ throw new Error(`Unknown nav scaffold mode in nav-manifest.json: ${nav}`);
2777
2720
  }
2778
- return defaultPortalCdnJson().github;
2721
+ return entry.layer;
2779
2722
  }
2780
- function needsCdnGitHubSetup(projectRoot) {
2781
- return !hasConfiguredCdnGithubMirror(projectRoot) || !hasCdnPublishToken(projectRoot);
2723
+ function copyNavLayer(targetDir, nav) {
2724
+ copyLayer(targetDir, layerForNavMode(nav));
2782
2725
  }
2783
- function applyCdnGitHubSetup(projectRoot, input) {
2784
- const existing = readPortalCdnJson(projectRoot) ?? defaultPortalCdnJson();
2785
- const updated = portalCdnJsonFromGithub(input.github, existing);
2786
- writeFileSync4(resolve16(projectRoot, PORTAL_CDN_JSON), `${JSON.stringify(updated, null, 2)}
2787
- `);
2788
- if (input.token?.trim()) {
2789
- writeEnvLocal(projectRoot, { [PORTAL_CDN_PUBLISH_TOKEN_ENV]: input.token.trim() });
2726
+ function patchNavScaffoldFile(targetDir, nav) {
2727
+ const navScaffoldPath = resolve14(targetDir, "src/config/nav-scaffold.ts");
2728
+ if (!existsSync10(navScaffoldPath)) {
2729
+ return;
2790
2730
  }
2791
- ensureEnvFilesGitignore(projectRoot);
2731
+ const content = `/** Set by wp init --nav ${nav}. */
2732
+ export const NAV_SCAFFOLD_MODE = '${nav}' as const
2733
+
2734
+ export type NavScaffoldMode = typeof NAV_SCAFFOLD_MODE
2735
+ `;
2736
+ writeFileSync5(navScaffoldPath, content);
2792
2737
  }
2793
- async function promptCdnGitHubSetup(options = {}) {
2794
- if (options.skip) return null;
2795
- const projectRoot = options.projectRoot ?? process.cwd();
2796
- if (!needsCdnGitHubSetup(projectRoot)) {
2797
- return null;
2738
+ function patchAuthTierFile(targetDir, mode) {
2739
+ const authTierPath = resolve14(targetDir, "src/config/auth-tier.ts");
2740
+ if (!existsSync10(authTierPath)) {
2741
+ return;
2798
2742
  }
2799
- let github = options.github?.trim();
2800
- if (!github && hasConfiguredCdnGithubMirror(projectRoot)) {
2801
- github = readPortalCdnJson(projectRoot)?.github;
2743
+ const content = `/** Set by wp init --auth ${mode}. */
2744
+ export const AUTH_SCAFFOLD_TIER = '${mode}' as const
2745
+
2746
+ export type AuthScaffoldTier = typeof AUTH_SCAFFOLD_TIER
2747
+ `;
2748
+ writeFileSync5(authTierPath, content);
2749
+ }
2750
+ function patchStarterOnlyBlocks(targetDir, mode) {
2751
+ const hasSso = mode === "sso" || mode === "full";
2752
+ const hasFull = mode === "full";
2753
+ const filesToPatch = [
2754
+ "src/integrations/configure-woodsportal-sdk.ts",
2755
+ "src/features/auth/login-session-bridge.ts",
2756
+ "src/app.tsx",
2757
+ "src/features/auth/components/starter-login.tsx",
2758
+ "src/portal/mount-portal.tsx",
2759
+ "src/features/auth/auth-route-paths.ts"
2760
+ ];
2761
+ for (const relative of filesToPatch) {
2762
+ const filePath = resolve14(targetDir, relative);
2763
+ if (!existsSync10(filePath)) {
2764
+ continue;
2765
+ }
2766
+ let content = readFileSync10(filePath, "utf8");
2767
+ content = stripMarkedBlock(content, "WPHS_AUTH_SSO", hasSso);
2768
+ content = stripMarkedBlock(content, "WPHS_AUTH_FULL", hasFull);
2769
+ if (!hasFull && relative === "src/integrations/configure-woodsportal-sdk.ts") {
2770
+ content = content.replace(
2771
+ /^import \{ isMfaPendingSession \} from '@\/features\/auth\/auth-session'\n/m,
2772
+ ""
2773
+ );
2774
+ }
2775
+ writeFileSync5(filePath, content);
2802
2776
  }
2803
- if (!github) {
2804
- const answer = await p3.text({
2805
- message: "Public GitHub CDN mirror (org/repo)",
2806
- placeholder: options.githubDefault ?? "your-org/your-portal-cdn-mirror",
2807
- initialValue: options.githubDefault,
2808
- validate: (value) => {
2809
- const slug = normalizeGithubSlug(value ?? "");
2810
- return slug ? void 0 : "Enter a public repo as org/repo (jsDelivr serves from it)";
2777
+ }
2778
+ function stripMarkedBlock(content, marker, keep) {
2779
+ const start = `// ${marker}`;
2780
+ const end = `// /${marker}`;
2781
+ const pattern = new RegExp(
2782
+ `${escapeRegExp(start)}[\\s\\S]*?${escapeRegExp(end)}\\n?`,
2783
+ "g"
2784
+ );
2785
+ if (keep) {
2786
+ return content.replace(new RegExp(`${escapeRegExp(start)}\\n?`, "g"), "").replace(new RegExp(`${escapeRegExp(end)}\\n?`, "g"), "");
2787
+ }
2788
+ return content.replace(pattern, "");
2789
+ }
2790
+ function escapeRegExp(value) {
2791
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2792
+ }
2793
+ var LOWER_TIER_AUTH_GUARD_FILES = [
2794
+ "src/features/auth/login-session-bridge.ts",
2795
+ "src/integrations/configure-woodsportal-sdk.ts",
2796
+ "src/app.tsx",
2797
+ "src/portal/mount-portal.tsx",
2798
+ "src/features/auth/components/starter-login.tsx"
2799
+ ];
2800
+ function assertLowerTierAuthScaffold(targetDir, mode) {
2801
+ const violations = [];
2802
+ for (const relative of LOWER_TIER_AUTH_GUARD_FILES) {
2803
+ const filePath = resolve14(targetDir, relative);
2804
+ if (!existsSync10(filePath)) {
2805
+ continue;
2806
+ }
2807
+ const content = readFileSync10(filePath, "utf8");
2808
+ if (content.includes("WPHS_AUTH_")) {
2809
+ violations.push(`${relative}: leftover WPHS_AUTH_* strip markers`);
2810
+ }
2811
+ if (relative === "src/features/auth/login-session-bridge.ts") {
2812
+ if (content.includes("mfa-pending-storage")) {
2813
+ violations.push(`${relative}: must not import mfa-pending-storage on --auth ${mode}`);
2814
+ }
2815
+ if (content.includes("navigateToMfaGate")) {
2816
+ violations.push(`${relative}: must not reference navigateToMfaGate on --auth ${mode}`);
2811
2817
  }
2818
+ }
2819
+ }
2820
+ if (existsSync10(resolve14(targetDir, "src/routes/_auth/two-fa.tsx"))) {
2821
+ violations.push("src/routes/_auth/two-fa.tsx: MFA route must not exist on --auth " + mode);
2822
+ }
2823
+ if (violations.length > 0) {
2824
+ throw new WpHsError(`Auth scaffold tier "${mode}" still contains full-tier artifacts:
2825
+ - ${violations.join("\n- ")}`, {
2826
+ exitCode: ExitCode.INTERNAL,
2827
+ remediation: "Fix strip markers in templates/scaffold/auth/_shared or report a hubspot-kit bug."
2812
2828
  });
2813
- if (p3.isCancel(answer)) return null;
2814
- github = normalizeGithubSlug(answer) ?? void 0;
2815
2829
  }
2816
- if (!github) return null;
2817
- if (hasCdnPublishToken(projectRoot)) {
2818
- return { github };
2830
+ }
2831
+ function assertFullTierAuthScaffold(targetDir) {
2832
+ const required = [
2833
+ "src/features/auth/mfa-pending-storage.ts",
2834
+ "src/routes/_auth/two-fa.tsx"
2835
+ ];
2836
+ const missing = required.filter((relative) => !existsSync10(resolve14(targetDir, relative)));
2837
+ if (missing.length > 0) {
2838
+ throw new WpHsError(`Auth scaffold tier "full" is missing required files:
2839
+ - ${missing.join("\n- ")}`, {
2840
+ exitCode: ExitCode.INTERNAL
2841
+ });
2819
2842
  }
2820
- const tokenAnswer = await p3.password({
2821
- message: "GitHub token (repo push on CDN mirror)",
2843
+ const bridge = readFileSync10(resolve14(targetDir, "src/features/auth/login-session-bridge.ts"), "utf8");
2844
+ if (bridge.includes("WPHS_AUTH_")) {
2845
+ throw new WpHsError('Auth scaffold tier "full" left WPHS_AUTH_* markers in login-session-bridge.ts', {
2846
+ exitCode: ExitCode.INTERNAL
2847
+ });
2848
+ }
2849
+ if (!bridge.includes("navigateToMfaGate") || !bridge.includes("mfa-pending-storage")) {
2850
+ throw new WpHsError('Auth scaffold tier "full" login-session-bridge.ts is missing MFA wiring', {
2851
+ exitCode: ExitCode.INTERNAL
2852
+ });
2853
+ }
2854
+ }
2855
+ function patchPackageJson(targetDir) {
2856
+ const pkgPath = resolve14(targetDir, "package.json");
2857
+ if (!existsSync10(pkgPath)) {
2858
+ return;
2859
+ }
2860
+ const pkg = JSON.parse(readFileSync10(pkgPath, "utf8"));
2861
+ pkg.devDependencies = mergeAuthDevDependencies(pkg.devDependencies);
2862
+ writeFileSync5(pkgPath, `${JSON.stringify(pkg, null, 2)}
2863
+ `);
2864
+ }
2865
+ function writeAuthMountDevPreview(targetDir) {
2866
+ writeFileSync5(
2867
+ resolve14(targetDir, "src/portal/mount-dev-preview.tsx"),
2868
+ `import { mountGreenfieldDev } from '../../.wphs/.kit-bootstrap-greenfield.tsx'
2869
+
2870
+ import { App } from '@/app'
2871
+ import { getPortalRouter, mountPortal } from '@/portal/mount-portal'
2872
+
2873
+ /** Local dev: module-config overlay + auth-aware App shell. */
2874
+ export async function mountDevPreview() {
2875
+ await mountGreenfieldDev({
2876
+ beforePreview: () => mountPortal({ skipAppRender: true }),
2877
+ preview: <App router={getPortalRouter()} />,
2878
+ })
2879
+ }
2880
+ `
2881
+ );
2882
+ }
2883
+ function validateAuthRouterCombo(auth, router) {
2884
+ if (auth === "none") {
2885
+ return;
2886
+ }
2887
+ if (router === "none") {
2888
+ throw new Error(
2889
+ "Portal auth scaffold requires client-side routing. Use --router hash or --router browser."
2890
+ );
2891
+ }
2892
+ }
2893
+ function validateNavAuthCombo(auth, nav) {
2894
+ if (!nav || nav === "top") {
2895
+ return;
2896
+ }
2897
+ if (auth === "none") {
2898
+ throw new Error(
2899
+ "Portal navigation shell requires auth scaffold. Use --auth starter|sso|full or omit --nav."
2900
+ );
2901
+ }
2902
+ }
2903
+ function assertNavScaffold(targetDir, nav) {
2904
+ const mainLayoutPath = resolve14(targetDir, "src/components/Layouts/MainLayout.tsx");
2905
+ if (!existsSync10(mainLayoutPath)) {
2906
+ throw new WpHsError("Auth scaffold is missing MainLayout.tsx after nav layer copy", {
2907
+ exitCode: ExitCode.INTERNAL
2908
+ });
2909
+ }
2910
+ const sidebarPath = resolve14(targetDir, "src/components/layout/portal-sidebar.tsx");
2911
+ if (nav === "sidebar") {
2912
+ if (!existsSync10(sidebarPath)) {
2913
+ throw new WpHsError('Nav scaffold mode "sidebar" is missing portal-sidebar.tsx', {
2914
+ exitCode: ExitCode.INTERNAL
2915
+ });
2916
+ }
2917
+ } else if (existsSync10(sidebarPath)) {
2918
+ throw new WpHsError('Nav scaffold mode "top" must not include portal-sidebar.tsx', {
2919
+ exitCode: ExitCode.INTERNAL
2920
+ });
2921
+ }
2922
+ const navScaffold = readFileSync10(resolve14(targetDir, "src/config/nav-scaffold.ts"), "utf8");
2923
+ if (!navScaffold.includes(`'${nav}'`)) {
2924
+ throw new WpHsError(`nav-scaffold.ts was not patched for mode "${nav}"`, {
2925
+ exitCode: ExitCode.INTERNAL
2926
+ });
2927
+ }
2928
+ }
2929
+ function applyAuthScaffold(targetDir, mode, routerMode, template, nav = "top") {
2930
+ if (mode === "none" || !isModuleTemplate(template)) {
2931
+ return;
2932
+ }
2933
+ validateAuthRouterCombo(mode, routerMode);
2934
+ validateNavAuthCombo(mode, nav);
2935
+ for (const layer of layersForMode(mode)) {
2936
+ copyLayer(targetDir, layer);
2937
+ }
2938
+ patchAuthTierFile(targetDir, mode);
2939
+ patchStarterOnlyBlocks(targetDir, mode);
2940
+ patchNavScaffoldFile(targetDir, nav);
2941
+ copyNavLayer(targetDir, nav);
2942
+ assertNavScaffold(targetDir, nav);
2943
+ if (mode === "starter" || mode === "sso") {
2944
+ assertLowerTierAuthScaffold(targetDir, mode);
2945
+ } else if (mode === "full") {
2946
+ assertFullTierAuthScaffold(targetDir);
2947
+ }
2948
+ patchPackageJson(targetDir);
2949
+ if (routerMode && routerMode !== "none") {
2950
+ writeAuthMountDevPreview(targetDir);
2951
+ }
2952
+ }
2953
+ var AUTH_TIER_ORDER = ["none", "starter", "sso", "full"];
2954
+ function authTierRank(tier) {
2955
+ return AUTH_TIER_ORDER.indexOf(tier);
2956
+ }
2957
+ function readCurrentAuthTier(targetDir) {
2958
+ const authTierPath = resolve14(targetDir, "src/config/auth-tier.ts");
2959
+ if (!existsSync10(authTierPath)) {
2960
+ return "none";
2961
+ }
2962
+ const content = readFileSync10(authTierPath, "utf8");
2963
+ const match = content.match(/AUTH_SCAFFOLD_TIER = '(starter|sso|full)'/);
2964
+ return match?.[1] ?? "none";
2965
+ }
2966
+ function applyAuthTierUpgrade(targetDir, targetTier, routerMode, template, nav = "top") {
2967
+ if (!isModuleTemplate(template)) {
2968
+ throw new WpHsError("Auth upgrade is only supported for module projects.", {
2969
+ exitCode: ExitCode.USER_ERROR
2970
+ });
2971
+ }
2972
+ validateAuthRouterCombo(targetTier, routerMode);
2973
+ const currentTier = readCurrentAuthTier(targetDir);
2974
+ if (currentTier === "none") {
2975
+ applyAuthScaffold(targetDir, targetTier, routerMode, template, nav);
2976
+ return targetTier;
2977
+ }
2978
+ if (authTierRank(targetTier) <= authTierRank(currentTier)) {
2979
+ throw new WpHsError(
2980
+ `Auth upgrade requires a higher tier than "${currentTier}" (requested "${targetTier}").`,
2981
+ {
2982
+ exitCode: ExitCode.USER_ERROR,
2983
+ remediation: "Use starter \u2192 sso \u2192 full. Re-init for downgrades."
2984
+ }
2985
+ );
2986
+ }
2987
+ const targetLayers = layersForMode(targetTier);
2988
+ const currentLayers = layersForMode(currentTier);
2989
+ const layersToCopy = targetLayers.filter((layer) => !currentLayers.includes(layer));
2990
+ for (const layer of layersToCopy) {
2991
+ copyLayer(targetDir, layer);
2992
+ }
2993
+ patchAuthTierFile(targetDir, targetTier);
2994
+ patchStarterOnlyBlocks(targetDir, targetTier);
2995
+ if (targetTier === "starter" || targetTier === "sso") {
2996
+ assertLowerTierAuthScaffold(targetDir, targetTier);
2997
+ } else {
2998
+ assertFullTierAuthScaffold(targetDir);
2999
+ }
3000
+ patchPackageJson(targetDir);
3001
+ if (routerMode && routerMode !== "none") {
3002
+ writeAuthMountDevPreview(targetDir);
3003
+ }
3004
+ return targetTier;
3005
+ }
3006
+ var SIDEBAR_NAV_FILES = [
3007
+ "src/components/layout/portal-sidebar.tsx",
3008
+ "src/components/layout/portal-sidebar-nav.tsx"
3009
+ ];
3010
+ function applyNavScaffoldUpgrade(targetDir, nav) {
3011
+ const authTierPath = resolve14(targetDir, "src/config/auth-tier.ts");
3012
+ if (!existsSync10(authTierPath)) {
3013
+ throw new WpHsError("Nav scaffold requires portal auth (missing src/config/auth-tier.ts).", {
3014
+ exitCode: ExitCode.USER_ERROR,
3015
+ remediation: "Run wp scaffold auth upgrade --tier starter first."
3016
+ });
3017
+ }
3018
+ if (nav === "top") {
3019
+ for (const relative of SIDEBAR_NAV_FILES) {
3020
+ const filePath = resolve14(targetDir, relative);
3021
+ if (existsSync10(filePath)) {
3022
+ rmSync3(filePath);
3023
+ }
3024
+ }
3025
+ }
3026
+ patchNavScaffoldFile(targetDir, nav);
3027
+ copyNavLayer(targetDir, nav);
3028
+ assertNavScaffold(targetDir, nav);
3029
+ }
3030
+ function readProjectAuthTier(targetDir) {
3031
+ return readCurrentAuthTier(targetDir);
3032
+ }
3033
+
3034
+ // src/lib/init-wizard.ts
3035
+ import * as p4 from "@clack/prompts";
3036
+
3037
+ // src/lib/project-name.ts
3038
+ var PROJECT_NAME_PATTERN = /^[a-z][a-z0-9-]*$/;
3039
+ function normalizeProjectName(input) {
3040
+ return input.trim().toLowerCase().replace(/[\s_]+/g, "-").replace(/[^a-z0-9-]/g, "").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
3041
+ }
3042
+ function validateProjectName(name) {
3043
+ if (!name) return "Project name is required";
3044
+ if (!PROJECT_NAME_PATTERN.test(name)) {
3045
+ return "Use lowercase letters, numbers, and hyphens (e.g. my-portal-module)";
3046
+ }
3047
+ if (name.length > 64) return "Project name is too long (max 64 characters)";
3048
+ return void 0;
3049
+ }
3050
+ function moduleOutDirFromSlug(slug) {
3051
+ const pascal = slug.split("-").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
3052
+ return `dist/${pascal || "HubSpot"}.Module`;
3053
+ }
3054
+
3055
+ // src/lib/cdn-github-setup.ts
3056
+ import { existsSync as existsSync11, readFileSync as readFileSync11, writeFileSync as writeFileSync6 } from "fs";
3057
+ import { resolve as resolve15 } from "path";
3058
+ import * as p3 from "@clack/prompts";
3059
+ var PORTAL_CDN_PUBLISH_TOKEN_ENV = "PORTAL_CDN_PUBLISH_TOKEN";
3060
+ var PORTAL_CDN_JSON = "portal.cdn.json";
3061
+ var PLACEHOLDER_GITHUB = /your-org|your-portal-cdn-mirror/i;
3062
+ function defaultPortalCdnJson() {
3063
+ const examplePath = kitPath("templates", "shared", "portal.cdn.json.example");
3064
+ if (existsSync11(examplePath)) {
3065
+ return JSON.parse(readFileSync11(examplePath, "utf8"));
3066
+ }
3067
+ return {
3068
+ publishMode: "jsdelivr",
3069
+ github: "your-org/your-portal-cdn-mirror",
3070
+ gitRemote: "https://github.com/your-org/your-portal-cdn-mirror.git",
3071
+ cdnPathInRepo: "dist/cdn",
3072
+ defaultBranch: "main"
3073
+ };
3074
+ }
3075
+ function readPortalCdnJson(projectRoot) {
3076
+ const path = resolve15(projectRoot, PORTAL_CDN_JSON);
3077
+ if (!existsSync11(path)) return null;
3078
+ try {
3079
+ return JSON.parse(readFileSync11(path, "utf8"));
3080
+ } catch {
3081
+ return null;
3082
+ }
3083
+ }
3084
+ function normalizeGithubSlug(input) {
3085
+ const trimmed = input.trim().replace(/^https:\/\/github\.com\//, "").replace(/\.git$/, "");
3086
+ const parts = trimmed.split("/").filter(Boolean);
3087
+ if (parts.length !== 2) return null;
3088
+ return `${parts[0]}/${parts[1]}`;
3089
+ }
3090
+ function portalCdnJsonFromGithub(github, base) {
3091
+ const slug = normalizeGithubSlug(github);
3092
+ if (!slug) {
3093
+ throw new Error(`Invalid GitHub repo \u2014 use org/repo (got "${github}")`);
3094
+ }
3095
+ return {
3096
+ publishMode: base?.publishMode ?? "jsdelivr",
3097
+ github: slug,
3098
+ gitRemote: `https://github.com/${slug}.git`,
3099
+ cdnPathInRepo: base?.cdnPathInRepo ?? "dist/cdn",
3100
+ defaultBranch: base?.defaultBranch ?? "main"
3101
+ };
3102
+ }
3103
+ function hasCdnPublishToken(projectRoot) {
3104
+ if (process.env[PORTAL_CDN_PUBLISH_TOKEN_ENV]?.trim()) return true;
3105
+ if (process.env.GITHUB_TOKEN?.trim()) return true;
3106
+ return Boolean(readEnvLocal(projectRoot)[PORTAL_CDN_PUBLISH_TOKEN_ENV]?.trim());
3107
+ }
3108
+ function hasConfiguredCdnGithubMirror(projectRoot) {
3109
+ const config = readPortalCdnJson(projectRoot);
3110
+ const github = config?.github ?? "";
3111
+ return Boolean(github) && !PLACEHOLDER_GITHUB.test(github);
3112
+ }
3113
+ function githubDefaultForPrompt(projectRoot) {
3114
+ const existing = readPortalCdnJson(projectRoot);
3115
+ if (existing?.github && !PLACEHOLDER_GITHUB.test(existing.github)) {
3116
+ return existing.github;
3117
+ }
3118
+ return defaultPortalCdnJson().github;
3119
+ }
3120
+ function needsCdnGitHubSetup(projectRoot) {
3121
+ return !hasConfiguredCdnGithubMirror(projectRoot) || !hasCdnPublishToken(projectRoot);
3122
+ }
3123
+ function applyCdnGitHubSetup(projectRoot, input) {
3124
+ const existing = readPortalCdnJson(projectRoot) ?? defaultPortalCdnJson();
3125
+ const updated = portalCdnJsonFromGithub(input.github, existing);
3126
+ writeFileSync6(resolve15(projectRoot, PORTAL_CDN_JSON), `${JSON.stringify(updated, null, 2)}
3127
+ `);
3128
+ if (input.token?.trim()) {
3129
+ writeEnvLocal(projectRoot, { [PORTAL_CDN_PUBLISH_TOKEN_ENV]: input.token.trim() });
3130
+ }
3131
+ ensureEnvFilesGitignore(projectRoot);
3132
+ }
3133
+ async function promptCdnGitHubSetup(options = {}) {
3134
+ if (options.skip) return null;
3135
+ const projectRoot = options.projectRoot ?? process.cwd();
3136
+ if (!needsCdnGitHubSetup(projectRoot)) {
3137
+ return null;
3138
+ }
3139
+ let github = options.github?.trim();
3140
+ if (!github && hasConfiguredCdnGithubMirror(projectRoot)) {
3141
+ github = readPortalCdnJson(projectRoot)?.github;
3142
+ }
3143
+ if (!github) {
3144
+ const answer = await p3.text({
3145
+ message: "Public GitHub CDN mirror (org/repo)",
3146
+ placeholder: options.githubDefault ?? "your-org/your-portal-cdn-mirror",
3147
+ initialValue: options.githubDefault,
3148
+ validate: (value) => {
3149
+ const slug = normalizeGithubSlug(value ?? "");
3150
+ return slug ? void 0 : "Enter a public repo as org/repo (jsDelivr serves from it)";
3151
+ }
3152
+ });
3153
+ if (p3.isCancel(answer)) return null;
3154
+ github = normalizeGithubSlug(answer) ?? void 0;
3155
+ }
3156
+ if (!github) return null;
3157
+ if (hasCdnPublishToken(projectRoot)) {
3158
+ return { github };
3159
+ }
3160
+ const tokenAnswer = await p3.password({
3161
+ message: "GitHub token (repo push on CDN mirror)",
2822
3162
  validate: (value) => {
2823
3163
  if (!value?.trim()) return "Required for wp publish \u2014 create a fine-grained PAT with Contents: Read and write";
2824
3164
  return void 0;
@@ -2831,9 +3171,185 @@ function formatCdnSetupNote(_projectRoot) {
2831
3171
  return `CDN config written \u2014 edit ${PORTAL_CDN_JSON} if needed; token saved to ${ENV_LOCAL_FILE} (gitignored)`;
2832
3172
  }
2833
3173
 
3174
+ // src/lib/init-wizard.ts
3175
+ function cancelIfNeeded(value) {
3176
+ if (p4.isCancel(value)) {
3177
+ throw new WpHsError("Init cancelled", { exitCode: ExitCode.USER_ERROR });
3178
+ }
3179
+ return value;
3180
+ }
3181
+ async function runInitWizard(options) {
3182
+ let template = options.template;
3183
+ if (!template) {
3184
+ const type = cancelIfNeeded(
3185
+ await p4.select({
3186
+ message: "Project type",
3187
+ options: [
3188
+ {
3189
+ value: "module-hybrid-cdn",
3190
+ label: "Module (hybrid CDN)",
3191
+ hint: "jsDelivr + thin module.js"
3192
+ },
3193
+ {
3194
+ value: "module-monolith",
3195
+ label: "Module (monolith)",
3196
+ hint: "Single module.js IIFE"
3197
+ },
3198
+ {
3199
+ value: "theme-starter",
3200
+ label: "Theme starter",
3201
+ hint: "CMS theme templates"
3202
+ }
3203
+ ]
3204
+ })
3205
+ );
3206
+ template = type;
3207
+ }
3208
+ let projectName = options.name ? normalizeProjectName(options.name) : void 0;
3209
+ if (!options.directory && !projectName) {
3210
+ const nameAnswer = cancelIfNeeded(
3211
+ await p4.text({
3212
+ message: "Project name (folder name)",
3213
+ placeholder: "my-portal-module",
3214
+ validate: (value) => validateProjectName(normalizeProjectName(value ?? ""))
3215
+ })
3216
+ );
3217
+ projectName = normalizeProjectName(nameAnswer);
3218
+ }
3219
+ if (!projectName && options.directory) {
3220
+ projectName = normalizeProjectName(options.directory.split("/").pop() ?? "my-portal");
3221
+ }
3222
+ if (!projectName) {
3223
+ throw new WpHsError("Project name is required", {
3224
+ exitCode: ExitCode.USER_ERROR,
3225
+ remediation: "Pass --name or --directory."
3226
+ });
3227
+ }
3228
+ let router = options.router;
3229
+ if (!router && isModuleTemplate(template)) {
3230
+ const routerAnswer = cancelIfNeeded(
3231
+ await p4.select({
3232
+ message: "Client-side routing",
3233
+ initialValue: "hash",
3234
+ options: [
3235
+ {
3236
+ value: "hash",
3237
+ label: "Hash history (recommended)",
3238
+ hint: "Works in HubSpot CMS embeds"
3239
+ },
3240
+ {
3241
+ value: "browser",
3242
+ label: "Browser history",
3243
+ hint: "Clean URLs for local dev only"
3244
+ },
3245
+ {
3246
+ value: "none",
3247
+ label: "No router",
3248
+ hint: "Single-page starter"
3249
+ }
3250
+ ]
3251
+ })
3252
+ );
3253
+ router = routerAnswer;
3254
+ }
3255
+ let auth = options.auth ?? "none";
3256
+ if (!options.auth && isModuleTemplate(template) && router && router !== "none") {
3257
+ const authAnswer = cancelIfNeeded(
3258
+ await p4.select({
3259
+ message: "Portal auth (woodsportal-api + SDK)",
3260
+ initialValue: "none",
3261
+ options: [
3262
+ { value: "none", label: "No portal login", hint: "Routes only" },
3263
+ { value: "starter", label: "Email + password", hint: "Login + sample protected page" },
3264
+ { value: "sso", label: "Email + password + SSO", hint: "OAuth callback route" },
3265
+ {
3266
+ value: "full",
3267
+ label: "Email + password + SSO + MFA",
3268
+ hint: "Recommended for production portals"
3269
+ }
3270
+ ]
3271
+ })
3272
+ );
3273
+ auth = authAnswer;
3274
+ } else if (router === "none" && isModuleTemplate(template)) {
3275
+ p4.log.info("Portal auth requires routing \u2014 auth tier set to none.");
3276
+ auth = "none";
3277
+ }
3278
+ let nav = options.nav ?? "top";
3279
+ if (!options.nav && auth !== "none" && isModuleTemplate(template)) {
3280
+ const navAnswer = cancelIfNeeded(
3281
+ await p4.select({
3282
+ message: "Portal navigation shell",
3283
+ initialValue: "top",
3284
+ options: [
3285
+ {
3286
+ value: "top",
3287
+ label: "Top navigation (recommended)",
3288
+ hint: "Simple header links"
3289
+ },
3290
+ {
3291
+ value: "sidebar",
3292
+ label: "Sidebar navigation",
3293
+ hint: "Collapsible left menu (production-shaped)"
3294
+ }
3295
+ ]
3296
+ })
3297
+ );
3298
+ nav = navAnswer;
3299
+ } else if (auth === "none") {
3300
+ nav = "top";
3301
+ }
3302
+ let cdnSetup = null;
3303
+ if (template === "module-hybrid-cdn") {
3304
+ if (options.cdnGithub) {
3305
+ cdnSetup = { github: options.cdnGithub };
3306
+ } else {
3307
+ p4.log.step("Hybrid CDN \u2014 GitHub mirror (public repo for jsDelivr)");
3308
+ const prompted = await promptCdnGitHubSetup({
3309
+ projectRoot: options.cwd,
3310
+ githubDefault: githubDefaultForPrompt(options.cwd),
3311
+ github: options.cdnGithub
3312
+ });
3313
+ if (prompted) {
3314
+ cdnSetup = prompted;
3315
+ }
3316
+ }
3317
+ }
3318
+ const folder = options.directory ?? `${options.cwd}/${projectName}`;
3319
+ const summary = [
3320
+ `Template: ${template}`,
3321
+ `Folder: ${folder}`,
3322
+ ...isModuleTemplate(template) ? [
3323
+ `Router: ${router ?? "hash"}`,
3324
+ `Auth: ${auth}`,
3325
+ ...auth !== "none" ? [`Nav: ${nav}`] : []
3326
+ ] : [],
3327
+ ...template === "module-hybrid-cdn" ? [`CDN repo: ${cdnSetup?.github ?? "(skipped \u2014 configure later)"}`] : []
3328
+ ].join("\n");
3329
+ p4.note(summary, "Scaffold summary");
3330
+ const confirmed = cancelIfNeeded(
3331
+ await p4.confirm({
3332
+ message: "Proceed with scaffold?",
3333
+ initialValue: true
3334
+ })
3335
+ );
3336
+ if (!confirmed) {
3337
+ throw new WpHsError("Init cancelled", { exitCode: ExitCode.USER_ERROR });
3338
+ }
3339
+ return {
3340
+ template,
3341
+ projectName,
3342
+ router,
3343
+ auth,
3344
+ nav,
3345
+ cdnSetup,
3346
+ confirmed: true
3347
+ };
3348
+ }
3349
+
2834
3350
  // src/lib/project-setup.ts
2835
- import { copyFileSync, existsSync as existsSync12, writeFileSync as writeFileSync5 } from "fs";
2836
- import { basename, resolve as resolve17 } from "path";
3351
+ import { copyFileSync, existsSync as existsSync12, writeFileSync as writeFileSync7 } from "fs";
3352
+ import { basename, resolve as resolve16 } from "path";
2837
3353
  function envFileForTier2(tier) {
2838
3354
  return tier === "dev" ? ".env.dev" : tier === "stg" ? ".env.stg" : ".env.prod";
2839
3355
  }
@@ -2848,7 +3364,7 @@ function firstExisting(paths) {
2848
3364
  return null;
2849
3365
  }
2850
3366
  function copyMissingFile(projectRoot, targetName, sourceCandidates, dryRun) {
2851
- const target = resolve17(projectRoot, targetName);
3367
+ const target = resolve16(projectRoot, targetName);
2852
3368
  if (existsSync12(target)) {
2853
3369
  return { action: "skipped", path: targetName, detail: "already present" };
2854
3370
  }
@@ -2871,12 +3387,12 @@ function copyMissingFile(projectRoot, targetName, sourceCandidates, dryRun) {
2871
3387
  };
2872
3388
  }
2873
3389
  function writeMissingFile(projectRoot, targetName, content, dryRun) {
2874
- const target = resolve17(projectRoot, targetName);
3390
+ const target = resolve16(projectRoot, targetName);
2875
3391
  if (existsSync12(target)) {
2876
3392
  return { action: "skipped", path: targetName, detail: "already present" };
2877
3393
  }
2878
3394
  if (!dryRun) {
2879
- writeFileSync5(target, content);
3395
+ writeFileSync7(target, content);
2880
3396
  }
2881
3397
  return {
2882
3398
  action: "copied",
@@ -2902,15 +3418,15 @@ function applyProjectSetup(ctx, options = {}) {
2902
3418
  const templateDir = kitTemplateDir(config);
2903
3419
  results.push(
2904
3420
  copyMissingFile(projectRoot, HUBSPOT_CONFIG_FILE, [
2905
- resolve17(projectRoot, HUBSPOT_CONFIG_EXAMPLE),
2906
- resolve17(templateDir, HUBSPOT_CONFIG_EXAMPLE),
3421
+ resolve16(projectRoot, HUBSPOT_CONFIG_EXAMPLE),
3422
+ resolve16(templateDir, HUBSPOT_CONFIG_EXAMPLE),
2907
3423
  kitPath("templates", HUBSPOT_CONFIG_EXAMPLE)
2908
3424
  ], dryRun)
2909
3425
  );
2910
3426
  results.push(
2911
3427
  copyMissingFile(projectRoot, ".env.local", [
2912
- resolve17(projectRoot, ".env.local.example"),
2913
- resolve17(sharedDir, ".env.local.example")
3428
+ resolve16(projectRoot, ".env.local.example"),
3429
+ resolve16(sharedDir, ".env.local.example")
2914
3430
  ], dryRun)
2915
3431
  );
2916
3432
  for (const tier of tiersToSetup(config, options.tier)) {
@@ -2918,8 +3434,8 @@ function applyProjectSetup(ctx, options = {}) {
2918
3434
  const envExample = `${envFile}.example`;
2919
3435
  results.push(
2920
3436
  copyMissingFile(projectRoot, envFile, [
2921
- resolve17(projectRoot, envExample),
2922
- resolve17(sharedDir, envExample)
3437
+ resolve16(projectRoot, envExample),
3438
+ resolve16(sharedDir, envExample)
2923
3439
  ], dryRun)
2924
3440
  );
2925
3441
  }
@@ -2927,8 +3443,8 @@ function applyProjectSetup(ctx, options = {}) {
2927
3443
  const mirror = config.cdn.mirror;
2928
3444
  results.push(
2929
3445
  copyMissingFile(projectRoot, mirror, [
2930
- resolve17(projectRoot, `${mirror}.example`),
2931
- resolve17(sharedDir, "portal.cdn.json.example")
3446
+ resolve16(projectRoot, `${mirror}.example`),
3447
+ resolve16(sharedDir, "portal.cdn.json.example")
2932
3448
  ], dryRun)
2933
3449
  );
2934
3450
  const buildIdFile = config.cdn.buildIdFile;
@@ -2943,14 +3459,14 @@ function applyProjectSetup(ctx, options = {}) {
2943
3459
  }
2944
3460
  for (const file of templateViteFiles(config)) {
2945
3461
  results.push(
2946
- copyMissingFile(projectRoot, file, [resolve17(templateDir, file)], dryRun)
3462
+ copyMissingFile(projectRoot, file, [resolve16(templateDir, file)], dryRun)
2947
3463
  );
2948
3464
  }
2949
3465
  if (isModuleProject(config) && !isThemeProject(config)) {
2950
3466
  const adapter = config.dev.adapter;
2951
- const adapterPath = resolve17(projectRoot, adapter);
3467
+ const adapterPath = resolve16(projectRoot, adapter);
2952
3468
  if (!existsSync12(adapterPath)) {
2953
- const templateAdapter = resolve17(templateDir, "wphs.adapter.ts");
3469
+ const templateAdapter = resolve16(templateDir, "wphs.adapter.ts");
2954
3470
  if (existsSync12(templateAdapter)) {
2955
3471
  results.push(copyMissingFile(projectRoot, adapter, [templateAdapter], dryRun));
2956
3472
  } else {
@@ -2976,19 +3492,19 @@ function copySharedSetupExamples(targetDir) {
2976
3492
  ".env.prod.example",
2977
3493
  "portal.cdn.json.example"
2978
3494
  ]) {
2979
- const src = resolve17(sharedDir, name);
2980
- const dest = resolve17(targetDir, name);
3495
+ const src = resolve16(sharedDir, name);
3496
+ const dest = resolve16(targetDir, name);
2981
3497
  if (existsSync12(src) && !existsSync12(dest)) {
2982
3498
  copyFileSync(src, dest);
2983
3499
  }
2984
3500
  }
2985
3501
  const hubspotExample = firstExisting([
2986
- resolve17(sharedDir, HUBSPOT_CONFIG_EXAMPLE),
3502
+ resolve16(sharedDir, HUBSPOT_CONFIG_EXAMPLE),
2987
3503
  kitPath("templates", HUBSPOT_CONFIG_EXAMPLE)
2988
3504
  ]);
2989
3505
  if (!hubspotExample) return;
2990
- const destHubspot = resolve17(targetDir, HUBSPOT_CONFIG_EXAMPLE);
2991
- if (!existsSync12(destHubspot) && !existsSync12(resolve17(targetDir, HUBSPOT_CONFIG_FILE))) {
3506
+ const destHubspot = resolve16(targetDir, HUBSPOT_CONFIG_EXAMPLE);
3507
+ if (!existsSync12(destHubspot) && !existsSync12(resolve16(targetDir, HUBSPOT_CONFIG_FILE))) {
2992
3508
  copyFileSync(hubspotExample, destHubspot);
2993
3509
  }
2994
3510
  }
@@ -3036,26 +3552,8 @@ var TEMPLATE_SCRIPTS = {
3036
3552
  }
3037
3553
  };
3038
3554
 
3039
- // src/lib/project-name.ts
3040
- var PROJECT_NAME_PATTERN = /^[a-z][a-z0-9-]*$/;
3041
- function normalizeProjectName(input) {
3042
- return input.trim().toLowerCase().replace(/[\s_]+/g, "-").replace(/[^a-z0-9-]/g, "").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
3043
- }
3044
- function validateProjectName(name) {
3045
- if (!name) return "Project name is required";
3046
- if (!PROJECT_NAME_PATTERN.test(name)) {
3047
- return "Use lowercase letters, numbers, and hyphens (e.g. my-portal-module)";
3048
- }
3049
- if (name.length > 64) return "Project name is too long (max 64 characters)";
3050
- return void 0;
3051
- }
3052
- function moduleOutDirFromSlug(slug) {
3053
- const pascal = slug.split("-").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
3054
- return `dist/${pascal || "HubSpot"}.Module`;
3055
- }
3056
-
3057
3555
  // src/cli/commands/init.ts
3058
- function buildConfigForProject(template, projectName, router) {
3556
+ function buildConfigForProject(template, projectName, router, auth = "none", nav = "top") {
3059
3557
  const base = { ...DEFAULT_WOODSPORTAL_HS, projectType: template };
3060
3558
  if (template === "theme-starter") {
3061
3559
  return {
@@ -3080,8 +3578,12 @@ function buildConfigForProject(template, projectName, router) {
3080
3578
  mode: template === "module-monolith" ? "none" : base.cdn.mode
3081
3579
  }
3082
3580
  };
3083
- if (isModuleTemplate(template) && router) {
3084
- config.scaffold = { router };
3581
+ if (isModuleTemplate(template)) {
3582
+ config.scaffold = {
3583
+ router: router ?? "hash",
3584
+ auth: auth ?? "none",
3585
+ ...auth !== "none" ? { nav: nav ?? "top" } : {}
3586
+ };
3085
3587
  }
3086
3588
  return config;
3087
3589
  }
@@ -3097,7 +3599,7 @@ function assertTargetDirIsScaffoldable(targetDir) {
3097
3599
  }
3098
3600
  function resolveInitTargetDir(options, interactiveName) {
3099
3601
  if (options.directory) {
3100
- return resolve18(options.directory);
3602
+ return resolve17(options.directory);
3101
3603
  }
3102
3604
  const parentDir = process.cwd();
3103
3605
  const slug = options.name ?? interactiveName;
@@ -3111,67 +3613,31 @@ function resolveInitTargetDir(options, interactiveName) {
3111
3613
  if (error) {
3112
3614
  throw new WpHsError(error, { exitCode: ExitCode.USER_ERROR });
3113
3615
  }
3114
- return resolve18(parentDir, slug);
3616
+ return resolve17(parentDir, slug);
3115
3617
  }
3116
3618
  async function runInit(ctx, options) {
3117
3619
  const nonInteractive = ctx.global.ci || ctx.global.yes || ctx.global.json;
3118
3620
  let template = options.template;
3119
3621
  let projectName = options.name ? normalizeProjectName(options.name) : void 0;
3120
3622
  let router = options.router;
3623
+ let auth = options.auth ?? "none";
3624
+ let nav = options.nav ?? "top";
3625
+ let cdnGithub = options.cdnGithub;
3626
+ let wizardCdnSetup = null;
3121
3627
  if (!nonInteractive) {
3122
- if (!template) {
3123
- const type = await p4.select({
3124
- message: "Project type",
3125
- options: [
3126
- {
3127
- value: "module-hybrid-cdn",
3128
- label: "Module (hybrid CDN)",
3129
- hint: "jsDelivr + thin module.js"
3130
- },
3131
- { value: "module-monolith", label: "Module (monolith)", hint: "Single module.js IIFE" },
3132
- { value: "theme-starter", label: "Theme starter", hint: "CMS theme templates" }
3133
- ]
3134
- });
3135
- if (p4.isCancel(type)) throw new WpHsError("Init cancelled", { exitCode: ExitCode.USER_ERROR });
3136
- template = type;
3137
- }
3138
- if (!options.directory && !projectName) {
3139
- const nameAnswer = await p4.text({
3140
- message: "Project name (folder name)",
3141
- placeholder: "my-portal-module",
3142
- validate: (value) => validateProjectName(normalizeProjectName(value ?? ""))
3143
- });
3144
- if (p4.isCancel(nameAnswer)) {
3145
- throw new WpHsError("Init cancelled", { exitCode: ExitCode.USER_ERROR });
3146
- }
3147
- projectName = normalizeProjectName(nameAnswer);
3148
- }
3149
- if (!router && template && isModuleTemplate(template)) {
3150
- const routerAnswer = await p4.select({
3151
- message: "Client-side routing",
3152
- initialValue: "hash",
3153
- options: [
3154
- {
3155
- value: "hash",
3156
- label: "Hash history (recommended)",
3157
- hint: "Same as woodsportal-client-frontend \u2014 works in HubSpot CMS embeds"
3158
- },
3159
- {
3160
- value: "browser",
3161
- label: "Browser history",
3162
- hint: "Clean /guide URLs for local dev (not for HubSpot iframe embeds)"
3163
- },
3164
- {
3165
- value: "none",
3166
- label: "No router",
3167
- hint: "Single-page starter \u2014 add TanStack Router later"
3168
- }
3169
- ]
3170
- });
3171
- if (p4.isCancel(routerAnswer)) {
3172
- throw new WpHsError("Init cancelled", { exitCode: ExitCode.USER_ERROR });
3173
- }
3174
- router = routerAnswer;
3628
+ const wizard = await runInitWizard({
3629
+ ...options,
3630
+ cwd: process.cwd(),
3631
+ auth: options.auth
3632
+ });
3633
+ template = wizard.template;
3634
+ projectName = wizard.projectName;
3635
+ router = wizard.router;
3636
+ auth = wizard.auth;
3637
+ nav = wizard.nav ?? "top";
3638
+ wizardCdnSetup = wizard.cdnSetup;
3639
+ if (wizard.cdnSetup?.github) {
3640
+ cdnGithub = wizard.cdnSetup.github;
3175
3641
  }
3176
3642
  }
3177
3643
  if (!template) {
@@ -3180,6 +3646,19 @@ async function runInit(ctx, options) {
3180
3646
  if (!router && isModuleTemplate(template)) {
3181
3647
  router = "hash";
3182
3648
  }
3649
+ if (!options.auth && nonInteractive && isModuleTemplate(template)) {
3650
+ auth = "none";
3651
+ }
3652
+ try {
3653
+ validateAuthRouterCombo(auth, router);
3654
+ validateNavAuthCombo(auth, nav);
3655
+ } catch (error) {
3656
+ const message = error instanceof Error ? error.message : String(error);
3657
+ throw new WpHsError(message, {
3658
+ exitCode: ExitCode.USER_ERROR,
3659
+ remediation: "Use --router hash or --router browser with --auth starter|sso|full. Nav shell (--nav sidebar) requires auth."
3660
+ });
3661
+ }
3183
3662
  const targetDir = resolveInitTargetDir(options, projectName);
3184
3663
  const slug = projectName ?? basename2(targetDir);
3185
3664
  assertTargetDirIsScaffoldable(targetDir);
@@ -3188,36 +3667,41 @@ async function runInit(ctx, options) {
3188
3667
  throw new WpHsError(`Template not found: ${template}`, { exitCode: ExitCode.USER_ERROR });
3189
3668
  }
3190
3669
  if (ctx.global.dryRun) {
3191
- ctx.logger.info(`[dry-run] Would scaffold ${template} in ${targetDir}`);
3670
+ ctx.logger.info(
3671
+ `[dry-run] Would scaffold ${template} in ${targetDir} (router=${router ?? "n/a"}, auth=${auth}, nav=${auth !== "none" ? nav : "n/a"})`
3672
+ );
3192
3673
  return ExitCode.SUCCESS;
3193
3674
  }
3194
- mkdirSync4(targetDir, { recursive: true });
3195
- cpSync2(templateDir, targetDir, { recursive: true });
3675
+ mkdirSync5(targetDir, { recursive: true });
3676
+ cpSync3(templateDir, targetDir, { recursive: true });
3196
3677
  copySharedSetupExamples(targetDir);
3197
- const config = buildConfigForProject(template, slug, router);
3198
- writeFileSync6(resolve18(targetDir, WOODSCLI_CONFIG_FILE), `${JSON.stringify(config, null, 2)}
3678
+ const config = buildConfigForProject(template, slug, router, auth, nav);
3679
+ writeFileSync8(resolve17(targetDir, WOODSCLI_CONFIG_FILE), `${JSON.stringify(config, null, 2)}
3199
3680
  `);
3200
- const pkgPath = resolve18(targetDir, "package.json");
3681
+ const pkgPath = resolve17(targetDir, "package.json");
3201
3682
  if (existsSync13(pkgPath)) {
3202
- const pkg = JSON.parse(readFileSync11(pkgPath, "utf8"));
3683
+ const pkg = JSON.parse(readFileSync12(pkgPath, "utf8"));
3203
3684
  pkg.name = slug;
3204
3685
  pkg.scripts = { ...pkg.scripts, ...TEMPLATE_SCRIPTS[template] };
3205
3686
  pkg.devDependencies = {
3206
3687
  ...pkg.devDependencies,
3207
3688
  ...templateDevDependencies()
3208
3689
  };
3209
- writeFileSync6(pkgPath, `${JSON.stringify(pkg, null, 2)}
3690
+ writeFileSync8(pkgPath, `${JSON.stringify(pkg, null, 2)}
3210
3691
  `);
3211
3692
  }
3212
3693
  if (router && isModuleTemplate(template)) {
3213
3694
  applyRouterScaffold(targetDir, router, template);
3214
3695
  }
3696
+ if (isModuleTemplate(template)) {
3697
+ applyAuthScaffold(targetDir, auth, router, template, nav);
3698
+ }
3215
3699
  runNodeScript(kitPath("scripts/ensure-wphs-workspace.mjs"), [], {
3216
3700
  cwd: targetDir,
3217
3701
  logger: ctx.logger,
3218
3702
  dryRun: false
3219
3703
  });
3220
- const gitignorePath = resolve18(targetDir, ".gitignore");
3704
+ const gitignorePath = resolve17(targetDir, ".gitignore");
3221
3705
  const gitignoreExtras = [
3222
3706
  "hubspot.config.yml",
3223
3707
  ".wphs/config/module-config.local.json",
@@ -3230,40 +3714,27 @@ async function runInit(ctx, options) {
3230
3714
  ".wphs/.kit-bootstrap.tsx",
3231
3715
  ".wphs/.kit-bootstrap-greenfield.tsx"
3232
3716
  ];
3233
- const existing = existsSync13(gitignorePath) ? readFileSync11(gitignorePath, "utf8") : "";
3717
+ const existing = existsSync13(gitignorePath) ? readFileSync12(gitignorePath, "utf8") : "";
3234
3718
  const toAppend = gitignoreExtras.filter((line) => !existing.includes(line)).join("\n");
3235
3719
  if (toAppend) {
3236
- writeFileSync6(gitignorePath, `${existing.trimEnd()}
3720
+ writeFileSync8(gitignorePath, `${existing.trimEnd()}
3237
3721
  ${toAppend}
3238
3722
  `);
3239
3723
  }
3240
- if (template === "module-hybrid-cdn" && !ctx.global.dryRun && needsCdnGitHubSetup(targetDir)) {
3241
- const githubDefault = githubDefaultForPrompt(targetDir);
3242
- let cdnSetup = null;
3243
- if (nonInteractive) {
3244
- if (options.cdnGithub) {
3245
- cdnSetup = { github: options.cdnGithub };
3246
- }
3247
- } else {
3248
- p4.log.step("Hybrid CDN \u2014 GitHub mirror (public repo for jsDelivr)");
3249
- cdnSetup = await promptCdnGitHubSetup({
3250
- projectRoot: targetDir,
3251
- githubDefault,
3252
- github: options.cdnGithub
3253
- });
3254
- }
3255
- if (cdnSetup) {
3256
- applyCdnGitHubSetup(targetDir, cdnSetup);
3724
+ if (template === "module-hybrid-cdn" && !ctx.global.dryRun) {
3725
+ const setup = wizardCdnSetup ?? (cdnGithub ? { github: cdnGithub } : null);
3726
+ if (setup) {
3727
+ applyCdnGitHubSetup(targetDir, setup);
3257
3728
  if (!nonInteractive) {
3258
- p4.log.success(formatCdnSetupNote(targetDir));
3729
+ p5.log.success(formatCdnSetupNote(targetDir));
3259
3730
  } else {
3260
3731
  ctx.logger.success(formatCdnSetupNote(targetDir));
3261
3732
  }
3262
3733
  }
3263
3734
  }
3264
3735
  if (!nonInteractive) {
3265
- p4.log.success(`Scaffolded ${template} in ${targetDir}`);
3266
- p4.note(`cd ${slug} && yarn install && wp setup && wp doctor && wp dev`, "Getting started");
3736
+ p5.log.success(`Scaffolded ${template} in ${targetDir}`);
3737
+ p5.note(`cd ${slug} && yarn install && wp setup && wp doctor && wp dev`, "Getting started");
3267
3738
  } else {
3268
3739
  ctx.logger.success(`Scaffolded ${template} in ${targetDir}`);
3269
3740
  }
@@ -3271,15 +3742,15 @@ ${toAppend}
3271
3742
  }
3272
3743
 
3273
3744
  // src/cli/commands/migrate.ts
3274
- 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";
3275
- import { join as join3, resolve as resolve19 } from "path";
3745
+ 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";
3746
+ import { join as join4, resolve as resolve18 } from "path";
3276
3747
  var LEGACY_DIRS = [".dev", ".woodsportal"];
3277
3748
  var LEGACY_BASELINE = ".wp-hs-baseline";
3278
3749
  var LEGACY_BUILD_ID_FILE = ".portal-build-id";
3279
3750
  var MODULE_BUILD_ID_FILE = ".module-build-id";
3280
3751
  function readFirstNonCommentLine(filePath) {
3281
3752
  if (!existsSync14(filePath)) return void 0;
3282
- const line = readFileSync12(filePath, "utf8").split("\n").map((l) => l.trim()).find((l) => l.length > 0 && !l.startsWith("#"));
3753
+ const line = readFileSync13(filePath, "utf8").split("\n").map((l) => l.trim()).find((l) => l.length > 0 && !l.startsWith("#"));
3283
3754
  return line || void 0;
3284
3755
  }
3285
3756
  function stripToBaseline(raw) {
@@ -3293,71 +3764,71 @@ function stripToBaseline(raw) {
3293
3764
  }
3294
3765
  async function runMigrate(ctx, _options = {}) {
3295
3766
  const root = ctx.projectRoot;
3296
- const wphsRoot = resolve19(root, WPHS_WORKSPACE_DIR);
3767
+ const wphsRoot = resolve18(root, WPHS_WORKSPACE_DIR);
3297
3768
  const actions = [];
3298
3769
  for (const legacy of LEGACY_DIRS) {
3299
- const legacyPath = resolve19(root, legacy);
3770
+ const legacyPath = resolve18(root, legacy);
3300
3771
  if (!existsSync14(legacyPath)) continue;
3301
3772
  actions.push(`Move ${legacy}/ \u2192 .wphs/`);
3302
3773
  if (ctx.global.dryRun) continue;
3303
- mkdirSync5(join3(wphsRoot, "config"), { recursive: true });
3304
- mkdirSync5(join3(wphsRoot, "assets", "branding"), { recursive: true });
3305
- const localConfig = join3(legacyPath, "module-config.local.json");
3306
- const exampleConfig = join3(legacyPath, "module-config.example.json");
3774
+ mkdirSync6(join4(wphsRoot, "config"), { recursive: true });
3775
+ mkdirSync6(join4(wphsRoot, "assets", "branding"), { recursive: true });
3776
+ const localConfig = join4(legacyPath, "module-config.local.json");
3777
+ const exampleConfig = join4(legacyPath, "module-config.example.json");
3307
3778
  if (existsSync14(localConfig)) {
3308
- cpSync3(localConfig, join3(wphsRoot, "config", "module-config.local.json"));
3779
+ cpSync4(localConfig, join4(wphsRoot, "config", "module-config.local.json"));
3309
3780
  }
3310
3781
  if (existsSync14(exampleConfig)) {
3311
- cpSync3(exampleConfig, join3(wphsRoot, "config", "module-config.example.json"));
3782
+ cpSync4(exampleConfig, join4(wphsRoot, "config", "module-config.example.json"));
3312
3783
  }
3313
- const branding = join3(legacyPath, "branding-assets");
3784
+ const branding = join4(legacyPath, "branding-assets");
3314
3785
  if (existsSync14(branding)) {
3315
3786
  for (const file of readdirSync3(branding)) {
3316
- cpSync3(join3(branding, file), join3(wphsRoot, "assets", "branding", file));
3787
+ cpSync4(join4(branding, file), join4(wphsRoot, "assets", "branding", file));
3317
3788
  }
3318
3789
  }
3319
- rmSync3(legacyPath, { recursive: true, force: true });
3790
+ rmSync4(legacyPath, { recursive: true, force: true });
3320
3791
  }
3321
- const legacyBaseline = resolve19(root, LEGACY_BASELINE);
3322
- const targetBaseline = join3(wphsRoot, "audit", "baseline");
3792
+ const legacyBaseline = resolve18(root, LEGACY_BASELINE);
3793
+ const targetBaseline = join4(wphsRoot, "audit", "baseline");
3323
3794
  if (existsSync14(legacyBaseline)) {
3324
3795
  actions.push(`Move ${LEGACY_BASELINE}/ \u2192 .wphs/audit/baseline/`);
3325
3796
  if (!ctx.global.dryRun) {
3326
- mkdirSync5(targetBaseline, { recursive: true });
3327
- cpSync3(legacyBaseline, targetBaseline, { recursive: true });
3328
- rmSync3(legacyBaseline, { recursive: true, force: true });
3797
+ mkdirSync6(targetBaseline, { recursive: true });
3798
+ cpSync4(legacyBaseline, targetBaseline, { recursive: true });
3799
+ rmSync4(legacyBaseline, { recursive: true, force: true });
3329
3800
  }
3330
3801
  }
3331
- const legacyConfigPath2 = resolve19(root, LEGACY_WOODSPORTAL_HS_FILE);
3332
- const woodscliPath = resolve19(root, WOODSCLI_CONFIG_FILE);
3333
- if (existsSync14(legacyConfigPath2) && !existsSync14(woodscliPath)) {
3802
+ const legacyConfigPath = resolve18(root, LEGACY_WOODSPORTAL_HS_FILE);
3803
+ const woodscliPath = resolve18(root, WOODSCLI_CONFIG_FILE);
3804
+ if (existsSync14(legacyConfigPath) && !existsSync14(woodscliPath)) {
3334
3805
  actions.push(`Rename ${LEGACY_WOODSPORTAL_HS_FILE} \u2192 ${WOODSCLI_CONFIG_FILE}`);
3335
3806
  if (!ctx.global.dryRun) {
3336
- renameSync(legacyConfigPath2, woodscliPath);
3807
+ renameSync(legacyConfigPath, woodscliPath);
3337
3808
  }
3338
- } else if (existsSync14(legacyConfigPath2) && existsSync14(woodscliPath)) {
3809
+ } else if (existsSync14(legacyConfigPath) && existsSync14(woodscliPath)) {
3339
3810
  actions.push(`Remove duplicate ${LEGACY_WOODSPORTAL_HS_FILE}`);
3340
3811
  if (!ctx.global.dryRun) {
3341
- rmSync3(legacyConfigPath2, { force: true });
3812
+ rmSync4(legacyConfigPath, { force: true });
3342
3813
  }
3343
3814
  }
3344
- const legacyBuildIdPath = resolve19(root, LEGACY_BUILD_ID_FILE);
3345
- const moduleBuildIdPath = resolve19(root, MODULE_BUILD_ID_FILE);
3815
+ const legacyBuildIdPath = resolve18(root, LEGACY_BUILD_ID_FILE);
3816
+ const moduleBuildIdPath = resolve18(root, MODULE_BUILD_ID_FILE);
3346
3817
  if (existsSync14(legacyBuildIdPath) && !existsSync14(moduleBuildIdPath)) {
3347
3818
  const baseline = stripToBaseline(readFirstNonCommentLine(legacyBuildIdPath)) ?? "v0.0.1-dev";
3348
3819
  actions.push(`Migrate ${LEGACY_BUILD_ID_FILE} \u2192 ${MODULE_BUILD_ID_FILE} (baseline ${baseline})`);
3349
3820
  if (!ctx.global.dryRun) {
3350
- writeFileSync7(
3821
+ writeFileSync9(
3351
3822
  moduleBuildIdPath,
3352
3823
  `# Channel baseline for local CDN deploys (wp deploy auto-bumps per developer).
3353
3824
  ${baseline}
3354
3825
  `
3355
3826
  );
3356
- rmSync3(legacyBuildIdPath, { force: true });
3827
+ rmSync4(legacyBuildIdPath, { force: true });
3357
3828
  }
3358
3829
  }
3359
3830
  if (existsSync14(woodscliPath)) {
3360
- const raw = JSON.parse(readFileSync12(woodscliPath, "utf8"));
3831
+ const raw = JSON.parse(readFileSync13(woodscliPath, "utf8"));
3361
3832
  let configChanged = false;
3362
3833
  if (raw?.dev?.moduleConfigDir && raw.dev.moduleConfigDir !== WPHS_WORKSPACE_DIR) {
3363
3834
  actions.push(`Update dev.moduleConfigDir \u2192 .wphs in project config`);
@@ -3374,7 +3845,7 @@ ${baseline}
3374
3845
  }
3375
3846
  }
3376
3847
  if (configChanged && !ctx.global.dryRun) {
3377
- writeFileSync7(woodscliPath, `${JSON.stringify(raw, null, 2)}
3848
+ writeFileSync9(woodscliPath, `${JSON.stringify(raw, null, 2)}
3378
3849
  `);
3379
3850
  }
3380
3851
  }
@@ -3402,10 +3873,10 @@ ${baseline}
3402
3873
  }
3403
3874
 
3404
3875
  // src/cli/commands/examples.ts
3405
- import { readFileSync as readFileSync13 } from "fs";
3876
+ import { readFileSync as readFileSync14 } from "fs";
3406
3877
  function loadManifest() {
3407
3878
  const manifestPath = kitPath("examples", "manifest.json");
3408
- const raw = JSON.parse(readFileSync13(manifestPath, "utf8"));
3879
+ const raw = JSON.parse(readFileSync14(manifestPath, "utf8"));
3409
3880
  return raw.scenarios;
3410
3881
  }
3411
3882
  async function runExamplesList(ctx, options = {}) {
@@ -3438,13 +3909,13 @@ async function runExamplesSmoke(ctx, options = {}) {
3438
3909
  }
3439
3910
 
3440
3911
  // src/cli/commands/fields.ts
3441
- import { cpSync as cpSync4, existsSync as existsSync15, readFileSync as readFileSync14, writeFileSync as writeFileSync8 } from "fs";
3442
- import { resolve as resolve20 } from "path";
3912
+ import { cpSync as cpSync5, existsSync as existsSync15, readFileSync as readFileSync15, writeFileSync as writeFileSync10 } from "fs";
3913
+ import { resolve as resolve19 } from "path";
3443
3914
  async function runFieldsSync(ctx, options = {}) {
3444
3915
  const direction = options.direction ?? "pull";
3445
- const fieldsPath = resolve20(ctx.projectRoot, ctx.config.dev.fieldsJson);
3446
- const cacheDir = resolve20(ctx.projectRoot, ctx.config.dev.moduleConfigDir, "cache", "fields-sync");
3447
- const cachePath = resolve20(cacheDir, "fields.json");
3916
+ const fieldsPath = resolve19(ctx.projectRoot, ctx.config.dev.fieldsJson);
3917
+ const cacheDir = resolve19(ctx.projectRoot, ctx.config.dev.moduleConfigDir, "cache", "fields-sync");
3918
+ const cachePath = resolve19(cacheDir, "fields.json");
3448
3919
  if (direction === "pull") {
3449
3920
  if (!existsSync15(fieldsPath)) {
3450
3921
  throw new WpHsError(`Missing ${ctx.config.dev.fieldsJson}`, {
@@ -3456,9 +3927,9 @@ async function runFieldsSync(ctx, options = {}) {
3456
3927
  ctx.logger.info(`[dry-run] Would cache ${fieldsPath} \u2192 ${cachePath}`);
3457
3928
  return ExitCode.SUCCESS;
3458
3929
  }
3459
- const { mkdirSync: mkdirSync6 } = await import("fs");
3460
- mkdirSync6(cacheDir, { recursive: true });
3461
- cpSync4(fieldsPath, cachePath);
3930
+ const { mkdirSync: mkdirSync7 } = await import("fs");
3931
+ mkdirSync7(cacheDir, { recursive: true });
3932
+ cpSync5(fieldsPath, cachePath);
3462
3933
  ctx.logger.success(`Cached fields \u2192 ${cachePath}`);
3463
3934
  return ExitCode.SUCCESS;
3464
3935
  }
@@ -3471,14 +3942,14 @@ async function runFieldsSync(ctx, options = {}) {
3471
3942
  ctx.logger.info(`[dry-run] Would write ${cachePath} \u2192 ${fieldsPath}`);
3472
3943
  return ExitCode.SUCCESS;
3473
3944
  }
3474
- writeFileSync8(fieldsPath, readFileSync14(cachePath, "utf8"));
3945
+ writeFileSync10(fieldsPath, readFileSync15(cachePath, "utf8"));
3475
3946
  ctx.logger.success(`Restored fields from cache \u2192 ${fieldsPath}`);
3476
3947
  return ExitCode.SUCCESS;
3477
3948
  }
3478
3949
 
3479
3950
  // src/cli/commands/regression.ts
3480
3951
  import { existsSync as existsSync16 } from "fs";
3481
- import { resolve as resolve21 } from "path";
3952
+ import { resolve as resolve20 } from "path";
3482
3953
  var LEGACY_SHIM_SCRIPTS = [
3483
3954
  "build-cdn.mjs",
3484
3955
  "run-vite-build.mjs",
@@ -3501,7 +3972,7 @@ async function runRegressionSmoke(ctx, options = {}) {
3501
3972
  if (!validateOk) failures.push("wp config validate failed");
3502
3973
  if (options.checkLegacyShims !== false) {
3503
3974
  for (const script of LEGACY_SHIM_SCRIPTS) {
3504
- const shim = resolve21(ctx.projectRoot, "scripts", script);
3975
+ const shim = resolve20(ctx.projectRoot, "scripts", script);
3505
3976
  const kitScript = kitPath("scripts", script);
3506
3977
  const shimOk = existsSync16(shim) && existsSync16(kitScript);
3507
3978
  checks.push({ name: `shim:${script}`, ok: shimOk });
@@ -3509,12 +3980,12 @@ async function runRegressionSmoke(ctx, options = {}) {
3509
3980
  }
3510
3981
  }
3511
3982
  for (const rel of FORBIDDEN_CONSUMER_PATHS) {
3512
- const forbidden = resolve21(ctx.projectRoot, rel);
3983
+ const forbidden = resolve20(ctx.projectRoot, rel);
3513
3984
  const absent = !existsSync16(forbidden);
3514
3985
  checks.push({ name: `no-${rel}`, ok: absent });
3515
3986
  if (!absent) failures.push(`Forbidden dev path still present: ${rel}`);
3516
3987
  }
3517
- const woodscliConfig = resolve21(ctx.projectRoot, WOODSCLI_CONFIG_FILE);
3988
+ const woodscliConfig = resolve20(ctx.projectRoot, WOODSCLI_CONFIG_FILE);
3518
3989
  const configOk = existsSync16(woodscliConfig);
3519
3990
  checks.push({ name: WOODSCLI_CONFIG_FILE, ok: configOk });
3520
3991
  if (!configOk) failures.push(`${WOODSCLI_CONFIG_FILE} missing`);
@@ -3625,11 +4096,11 @@ async function runSetup(ctx, options = {}) {
3625
4096
  }
3626
4097
 
3627
4098
  // src/cli/commands/portal.ts
3628
- import * as p5 from "@clack/prompts";
4099
+ import * as p6 from "@clack/prompts";
3629
4100
 
3630
4101
  // src/lib/hubspot-portals.ts
3631
- import { existsSync as existsSync17, readFileSync as readFileSync15 } from "fs";
3632
- import { resolve as resolve22 } from "path";
4102
+ import { existsSync as existsSync17, readFileSync as readFileSync16 } from "fs";
4103
+ import { resolve as resolve21 } from "path";
3633
4104
  function stripYamlScalar(value) {
3634
4105
  const trimmed = value.trim();
3635
4106
  if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
@@ -3667,20 +4138,20 @@ function parseHubSpotConfigPortals(raw) {
3667
4138
  return { defaultPortal, portals };
3668
4139
  }
3669
4140
  function readHubSpotPortals(projectRoot) {
3670
- const configPath = resolve22(projectRoot, HUBSPOT_CONFIG_FILE);
4141
+ const configPath = resolve21(projectRoot, HUBSPOT_CONFIG_FILE);
3671
4142
  if (!existsSync17(configPath)) {
3672
4143
  throw new WpHsError(`${HUBSPOT_CONFIG_FILE} not found in project root.`, {
3673
4144
  exitCode: ExitCode.USER_ERROR,
3674
4145
  remediation: "Run wp setup, then add your portal + PAT."
3675
4146
  });
3676
4147
  }
3677
- const raw = readFileSync15(configPath, "utf8");
4148
+ const raw = readFileSync16(configPath, "utf8");
3678
4149
  const { defaultPortal, portals } = parseHubSpotConfigPortals(raw);
3679
4150
  return { configPath, defaultPortal, portals };
3680
4151
  }
3681
4152
  function resolvePortalName(portals, nameOrId) {
3682
4153
  const needle = nameOrId.trim();
3683
- return portals.find((p6) => p6.name === needle) ?? portals.find((p6) => p6.portalId === needle) ?? null;
4154
+ return portals.find((p9) => p9.name === needle) ?? portals.find((p9) => p9.portalId === needle) ?? null;
3684
4155
  }
3685
4156
 
3686
4157
  // src/cli/commands/portal.ts
@@ -3735,7 +4206,7 @@ async function runPortalUse(ctx, options = {}) {
3735
4206
  remediation: "wp portal use <name> \xB7 wp portal list"
3736
4207
  });
3737
4208
  }
3738
- const answer = await p5.select({
4209
+ const answer = await p6.select({
3739
4210
  message: "Set default HubSpot portal",
3740
4211
  options: data.portals.map((portal2) => ({
3741
4212
  value: portal2.name,
@@ -3743,7 +4214,7 @@ async function runPortalUse(ctx, options = {}) {
3743
4214
  hint: portal2.name === data.defaultPortal ? `current default \xB7 ${portal2.portalId ?? "no id"}` : portal2.portalId ?? void 0
3744
4215
  }))
3745
4216
  });
3746
- if (p5.isCancel(answer)) {
4217
+ if (p6.isCancel(answer)) {
3747
4218
  throw new WpHsError("Portal selection cancelled", { exitCode: ExitCode.USER_ERROR });
3748
4219
  }
3749
4220
  targetName = answer;
@@ -3777,6 +4248,311 @@ async function runPortalUse(ctx, options = {}) {
3777
4248
  return ExitCode.SUCCESS;
3778
4249
  }
3779
4250
 
4251
+ // src/cli/commands/page.ts
4252
+ import * as p7 from "@clack/prompts";
4253
+ function cancelIfNeeded2(value) {
4254
+ if (p7.isCancel(value)) {
4255
+ throw new WpHsError("Page scaffold cancelled", { exitCode: ExitCode.USER_ERROR });
4256
+ }
4257
+ return value;
4258
+ }
4259
+ function resolveAccessFlags(options) {
4260
+ if (options.isPublic && options.isProtected) {
4261
+ throw new WpHsError("Use only one of --public or --protected.", {
4262
+ exitCode: ExitCode.USER_ERROR
4263
+ });
4264
+ }
4265
+ if (options.isPublic) {
4266
+ return "public";
4267
+ }
4268
+ if (options.isProtected) {
4269
+ return "protected";
4270
+ }
4271
+ return void 0;
4272
+ }
4273
+ async function promptPageAddOptions(ctx, options) {
4274
+ const nonInteractive = ctx.global.ci || ctx.global.yes || ctx.global.json;
4275
+ const hasNavConfig = navConfigFileExists(ctx.projectRoot);
4276
+ let slug = options.slug ? normalizePageSlug(options.slug) : void 0;
4277
+ if (!slug && nonInteractive) {
4278
+ throw new WpHsError("Page slug is required in non-interactive mode.", {
4279
+ exitCode: ExitCode.USER_ERROR,
4280
+ remediation: "Pass a slug: wp page add settings"
4281
+ });
4282
+ }
4283
+ if (!slug) {
4284
+ const slugAnswer = cancelIfNeeded2(
4285
+ await p7.text({
4286
+ message: "Page slug (URL segment)",
4287
+ placeholder: "settings",
4288
+ validate: (value) => {
4289
+ const normalized = normalizePageSlug(value ?? "");
4290
+ if (!normalized || !/^[a-z][a-z0-9-]*$/.test(normalized)) {
4291
+ return "Use lowercase letters, numbers, and hyphens";
4292
+ }
4293
+ return void 0;
4294
+ }
4295
+ })
4296
+ );
4297
+ slug = normalizePageSlug(slugAnswer);
4298
+ }
4299
+ let path = options.path ? normalizePagePath(options.path, slug) : void 0;
4300
+ if (!path && !nonInteractive) {
4301
+ const pathAnswer = cancelIfNeeded2(
4302
+ await p7.text({
4303
+ message: "Route path",
4304
+ initialValue: `/${slug}`,
4305
+ validate: (value) => {
4306
+ const normalized = normalizePagePath(value ?? "", slug);
4307
+ if (!normalized.startsWith("/")) {
4308
+ return "Path must start with /";
4309
+ }
4310
+ return void 0;
4311
+ }
4312
+ })
4313
+ );
4314
+ path = normalizePagePath(pathAnswer, slug);
4315
+ }
4316
+ path ??= normalizePagePath(`/${slug}`, slug);
4317
+ let title = options.title?.trim() || titleFromSlug(slug);
4318
+ if (!options.title && !nonInteractive) {
4319
+ const titleAnswer = cancelIfNeeded2(
4320
+ await p7.text({
4321
+ message: "Page title",
4322
+ initialValue: title,
4323
+ validate: (value) => value?.trim() ? void 0 : "Title is required"
4324
+ })
4325
+ );
4326
+ title = titleAnswer.trim();
4327
+ }
4328
+ let access = resolveAccessFlags(options);
4329
+ if (!access && nonInteractive) {
4330
+ throw new WpHsError("Access mode is required in non-interactive mode.", {
4331
+ exitCode: ExitCode.USER_ERROR,
4332
+ remediation: "Pass --public or --protected."
4333
+ });
4334
+ }
4335
+ if (!access) {
4336
+ const accessAnswer = cancelIfNeeded2(
4337
+ await p7.select({
4338
+ message: "Who can access this page?",
4339
+ initialValue: "public",
4340
+ options: [
4341
+ {
4342
+ value: "public",
4343
+ label: "Public",
4344
+ hint: "Visible without signing in"
4345
+ },
4346
+ {
4347
+ value: "protected",
4348
+ label: "Protected",
4349
+ hint: "Requires portal sign-in"
4350
+ }
4351
+ ]
4352
+ })
4353
+ );
4354
+ access = accessAnswer;
4355
+ }
4356
+ let addToNav;
4357
+ if (options.addToNav) {
4358
+ addToNav = true;
4359
+ } else if (options.skipNav) {
4360
+ addToNav = false;
4361
+ } else if (!hasNavConfig) {
4362
+ addToNav = false;
4363
+ } else if (nonInteractive) {
4364
+ throw new WpHsError("Navigation choice is required in non-interactive mode.", {
4365
+ exitCode: ExitCode.USER_ERROR,
4366
+ remediation: "Pass --nav or --no-nav when portal-nav.ts or starter-nav.ts exists."
4367
+ });
4368
+ } else {
4369
+ const navAnswer = cancelIfNeeded2(
4370
+ await p7.confirm({
4371
+ message: "Add to navigation menu?",
4372
+ initialValue: true
4373
+ })
4374
+ );
4375
+ addToNav = navAnswer;
4376
+ }
4377
+ return { slug, path, title, access, addToNav };
4378
+ }
4379
+ async function runPageAdd(ctx, options) {
4380
+ const resolved = await promptPageAddOptions(ctx, options);
4381
+ const result = await scaffoldPage({
4382
+ projectRoot: ctx.projectRoot,
4383
+ config: ctx.config,
4384
+ slug: resolved.slug,
4385
+ path: resolved.path,
4386
+ title: resolved.title,
4387
+ access: resolved.access,
4388
+ addToNav: resolved.addToNav,
4389
+ dryRun: ctx.global.dryRun,
4390
+ logger: ctx.logger
4391
+ });
4392
+ if (ctx.global.json) {
4393
+ ctx.logger.json({
4394
+ ok: true,
4395
+ slug: resolved.slug,
4396
+ path: resolved.path,
4397
+ access: resolved.access,
4398
+ addToNav: resolved.addToNav,
4399
+ ...result
4400
+ });
4401
+ return ExitCode.SUCCESS;
4402
+ }
4403
+ if (ctx.global.dryRun) {
4404
+ ctx.logger.info("[dry-run] Page scaffold planned.");
4405
+ return ExitCode.SUCCESS;
4406
+ }
4407
+ const lines = [
4408
+ `Created ${result.routeFile}`,
4409
+ `Created ${result.pageFile}`,
4410
+ result.routeTreeRegenerated ? "Regenerated src/routeTree.gen.ts" : result.routeTreeWarning ?? "Restart wp dev if navigation types look stale."
4411
+ ];
4412
+ if (!ctx.global.quiet) {
4413
+ p7.log.success(`Page "${resolved.title}" scaffolded at ${resolved.path}`);
4414
+ p7.note(lines.join("\n"), "Next steps");
4415
+ } else {
4416
+ ctx.logger.success(lines.join("\n"));
4417
+ }
4418
+ return ExitCode.SUCCESS;
4419
+ }
4420
+ async function runPageList(ctx) {
4421
+ const { listScaffoldedPages } = await import("./scaffold-page-lifecycle-EHMPWPPE.js");
4422
+ const pages = listScaffoldedPages(ctx.projectRoot, ctx.config);
4423
+ if (ctx.global.json) {
4424
+ ctx.logger.json({ pages });
4425
+ return ExitCode.SUCCESS;
4426
+ }
4427
+ if (pages.length === 0) {
4428
+ ctx.logger.info("No scaffolded pages in src/features/pages/ (use wp page add).");
4429
+ return ExitCode.SUCCESS;
4430
+ }
4431
+ if (!ctx.global.quiet) {
4432
+ p7.log.info(`Scaffolded pages (${pages.length})`);
4433
+ for (const page of pages) {
4434
+ const navFlag = page.inNav ? "nav" : "no-nav";
4435
+ p7.log.message(
4436
+ ` ${page.slug} ${page.path} ${page.access} ${navFlag} ${page.routeFile}`
4437
+ );
4438
+ }
4439
+ } else {
4440
+ for (const page of pages) {
4441
+ ctx.logger.info(`${page.slug} ${page.path} ${page.access} ${page.inNav}`);
4442
+ }
4443
+ }
4444
+ return ExitCode.SUCCESS;
4445
+ }
4446
+ async function runPageRemove(ctx, options) {
4447
+ const { removeScaffoldedPage } = await import("./scaffold-page-lifecycle-EHMPWPPE.js");
4448
+ const result = removeScaffoldedPage({
4449
+ projectRoot: ctx.projectRoot,
4450
+ config: ctx.config,
4451
+ slug: options.slug,
4452
+ removeFromNav: !options.skipNav,
4453
+ dryRun: ctx.global.dryRun,
4454
+ logger: ctx.logger
4455
+ });
4456
+ if (ctx.global.json) {
4457
+ ctx.logger.json({ ok: true, ...result });
4458
+ return ExitCode.SUCCESS;
4459
+ }
4460
+ if (ctx.global.dryRun) {
4461
+ ctx.logger.info("[dry-run] Page removal planned.");
4462
+ return ExitCode.SUCCESS;
4463
+ }
4464
+ p7.log.success(`Removed page "${result.slug}"`);
4465
+ return ExitCode.SUCCESS;
4466
+ }
4467
+
4468
+ // src/cli/commands/scaffold.ts
4469
+ import * as p8 from "@clack/prompts";
4470
+ import { writeFileSync as writeFileSync11 } from "fs";
4471
+ import { resolve as resolve22 } from "path";
4472
+ function persistScaffoldConfig(projectRoot, config, patch) {
4473
+ const auth = patch.auth ?? config.scaffold?.auth ?? "none";
4474
+ const router = patch.router ?? config.scaffold?.router ?? "hash";
4475
+ const scaffold = { router, auth };
4476
+ if (auth !== "none") {
4477
+ scaffold.nav = patch.nav ?? config.scaffold?.nav ?? "top";
4478
+ }
4479
+ const next = { ...config, scaffold };
4480
+ writeFileSync11(resolve22(projectRoot, WOODSCLI_CONFIG_FILE), `${JSON.stringify(next, null, 2)}
4481
+ `);
4482
+ return next;
4483
+ }
4484
+ function parseAuthTier(value) {
4485
+ if (value === "starter" || value === "sso" || value === "full") {
4486
+ return value;
4487
+ }
4488
+ return void 0;
4489
+ }
4490
+ function runScaffoldAuthUpgrade(ctx, options) {
4491
+ const routerMode = ctx.config.scaffold?.router ?? "hash";
4492
+ const nav = ctx.config.scaffold?.nav ?? "top";
4493
+ const current = readProjectAuthTier(ctx.projectRoot);
4494
+ if (ctx.global.dryRun) {
4495
+ ctx.logger.info(
4496
+ `[dry-run] Would upgrade auth from "${current}" to "${options.tier}" in ${ctx.projectRoot}`
4497
+ );
4498
+ return ExitCode.SUCCESS;
4499
+ }
4500
+ const applied = applyAuthTierUpgrade(
4501
+ ctx.projectRoot,
4502
+ options.tier,
4503
+ routerMode,
4504
+ ctx.config.projectType,
4505
+ nav
4506
+ );
4507
+ persistScaffoldConfig(ctx.projectRoot, ctx.config, {
4508
+ auth: applied,
4509
+ nav: applied !== "none" ? nav : void 0
4510
+ });
4511
+ if (ctx.global.json) {
4512
+ ctx.logger.json({ ok: true, previousTier: current, tier: applied });
4513
+ return ExitCode.SUCCESS;
4514
+ }
4515
+ p8.log.success(`Portal auth upgraded to "${applied}" (was "${current}")`);
4516
+ p8.note("Review new routes under src/routes/_auth/ and run wp dev.", "Next steps");
4517
+ return ExitCode.SUCCESS;
4518
+ }
4519
+ function runScaffoldNav(ctx, options) {
4520
+ const currentNav = ctx.config.scaffold?.nav ?? "top";
4521
+ if (ctx.config.scaffold?.auth === "none" || readProjectAuthTier(ctx.projectRoot) === "none") {
4522
+ throw new WpHsError("Nav scaffold requires portal auth.", {
4523
+ exitCode: ExitCode.USER_ERROR,
4524
+ remediation: "Run wp scaffold auth upgrade --tier starter first."
4525
+ });
4526
+ }
4527
+ if (ctx.global.dryRun) {
4528
+ ctx.logger.info(`[dry-run] Would apply nav shell "${options.mode}" (was "${currentNav}")`);
4529
+ return ExitCode.SUCCESS;
4530
+ }
4531
+ applyNavScaffoldUpgrade(ctx.projectRoot, options.mode);
4532
+ persistScaffoldConfig(ctx.projectRoot, ctx.config, { nav: options.mode });
4533
+ if (ctx.global.json) {
4534
+ ctx.logger.json({ ok: true, previousMode: currentNav, mode: options.mode });
4535
+ return ExitCode.SUCCESS;
4536
+ }
4537
+ p8.log.success(`Navigation shell set to "${options.mode}" (was "${currentNav}")`);
4538
+ return ExitCode.SUCCESS;
4539
+ }
4540
+ function parseScaffoldAuthTierArg(value) {
4541
+ const tier = value ? parseAuthTier(value) : void 0;
4542
+ if (!tier) {
4543
+ throw new WpHsError("Auth tier is required: starter | sso | full", {
4544
+ exitCode: ExitCode.USER_ERROR
4545
+ });
4546
+ }
4547
+ return tier;
4548
+ }
4549
+ function parseScaffoldNavModeArg(value) {
4550
+ if (value === "top" || value === "sidebar") {
4551
+ return value;
4552
+ }
4553
+ throw new WpHsError("Nav mode is required: top | sidebar", { exitCode: ExitCode.USER_ERROR });
4554
+ }
4555
+
3780
4556
  // src/cli/commands/watch.ts
3781
4557
  async function runWatch(ctx, options) {
3782
4558
  ensureHubSpotConfigForCms(ctx);
@@ -3802,14 +4578,19 @@ async function runWatch(ctx, options) {
3802
4578
  }
3803
4579
 
3804
4580
  // src/cli/program.ts
3805
- function readVersion() {
4581
+ function resolveProjectRootForVersionCheck(project) {
4582
+ if (project) {
4583
+ return project;
4584
+ }
3806
4585
  try {
3807
- const pkg = JSON.parse(readFileSync16(resolve23(getKitRoot(), "package.json"), "utf8"));
3808
- return pkg.version ?? "0.0.0";
4586
+ return findProjectRoot();
3809
4587
  } catch {
3810
- return "0.0.0";
4588
+ return process.cwd();
3811
4589
  }
3812
4590
  }
4591
+ function readVersion() {
4592
+ return readKitVersion();
4593
+ }
3813
4594
  function buildContext(global) {
3814
4595
  const logger = createLogger({
3815
4596
  verbose: global.verbose,
@@ -3817,7 +4598,7 @@ function buildContext(global) {
3817
4598
  json: global.json,
3818
4599
  ci: global.ci
3819
4600
  });
3820
- const projectRoot = global.project ?? findProjectRoot2();
4601
+ const projectRoot = global.project ?? findProjectRoot();
3821
4602
  const { config } = loadConfig(projectRoot);
3822
4603
  return { projectRoot, config, logger, global };
3823
4604
  }
@@ -3846,6 +4627,20 @@ async function handle(fn) {
3846
4627
  function createProgram() {
3847
4628
  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");
3848
4629
  attachGlobals(program2);
4630
+ program2.hook("preAction", async (_thisCommand, actionCommand) => {
4631
+ const global = program2.opts();
4632
+ const name = actionCommand.name();
4633
+ if (name === "version" || name === "help") {
4634
+ return;
4635
+ }
4636
+ await maybeWarnKitUpdate({
4637
+ current: readKitVersion(),
4638
+ projectRoot: resolveProjectRootForVersionCheck(global.project),
4639
+ ci: global.ci,
4640
+ json: global.json,
4641
+ quiet: global.quiet
4642
+ });
4643
+ });
3849
4644
  program2.command("doctor").description("Check prerequisites (Node, HubSpot CLI, .wphs workspace, config)").action(async () => {
3850
4645
  const global = program2.opts();
3851
4646
  await handle(() => runDoctor(buildContext(global)));
@@ -3864,6 +4659,12 @@ function createProgram() {
3864
4659
  await handle(() => runConfigValidate(buildContext(global)));
3865
4660
  });
3866
4661
  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(
4662
+ "--auth <tier>",
4663
+ "Portal auth: none | starter | sso | full \u2014 modules with router \u2260 none"
4664
+ ).option(
4665
+ "--nav <mode>",
4666
+ "Authenticated nav shell: top | sidebar (requires --auth starter|sso|full)"
4667
+ ).option(
3867
4668
  "--cdn-github <org/repo>",
3868
4669
  "Public GitHub CDN mirror repo (hybrid CDN; use with --ci / -y)"
3869
4670
  ).action(
@@ -3882,12 +4683,16 @@ function createProgram() {
3882
4683
  global
3883
4684
  };
3884
4685
  const routerMode = opts.router === "browser" || opts.router === "none" || opts.router === "hash" ? opts.router : void 0;
4686
+ const authMode = opts.auth === "none" || opts.auth === "starter" || opts.auth === "sso" || opts.auth === "full" ? opts.auth : void 0;
4687
+ const navMode = opts.nav === "top" || opts.nav === "sidebar" ? opts.nav : void 0;
3885
4688
  await handle(
3886
4689
  () => runInit(ctx, {
3887
4690
  template: opts.template,
3888
4691
  name: opts.name,
3889
4692
  directory: opts.directory,
3890
4693
  router: routerMode,
4694
+ auth: authMode,
4695
+ nav: navMode,
3891
4696
  cdnGithub: opts.cdnGithub
3892
4697
  })
3893
4698
  );
@@ -4020,6 +4825,45 @@ function createProgram() {
4020
4825
  const global = program2.opts();
4021
4826
  await handle(() => runPortalUse(buildContext(global), { name }));
4022
4827
  });
4828
+ const page = program2.command("page").description("Scaffold routed pages");
4829
+ 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(
4830
+ async (slug, opts) => {
4831
+ const global = program2.opts();
4832
+ await handle(
4833
+ () => runPageAdd(buildContext(global), {
4834
+ slug,
4835
+ path: opts.path,
4836
+ title: opts.title,
4837
+ isPublic: opts.public,
4838
+ isProtected: opts.protected,
4839
+ addToNav: opts.nav,
4840
+ skipNav: opts.noNav
4841
+ })
4842
+ );
4843
+ }
4844
+ );
4845
+ page.command("list").alias("ls").description("List scaffolded pages under src/features/pages/").action(async () => {
4846
+ const global = program2.opts();
4847
+ await handle(() => runPageList(buildContext(global)));
4848
+ });
4849
+ 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) => {
4850
+ const global = program2.opts();
4851
+ await handle(
4852
+ () => runPageRemove(buildContext(global), { slug, skipNav: opts.noNav })
4853
+ );
4854
+ });
4855
+ const scaffold = program2.command("scaffold").description("Re-apply portal scaffold layers");
4856
+ const scaffoldAuth = scaffold.command("auth").description("Portal auth tier upgrades");
4857
+ 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) => {
4858
+ const global = program2.opts();
4859
+ const tier = parseScaffoldAuthTierArg(opts.tier);
4860
+ await handle(async () => runScaffoldAuthUpgrade(buildContext(global), { tier }));
4861
+ });
4862
+ scaffold.command("nav").description("Re-apply authenticated navigation shell (top | sidebar)").requiredOption("--mode <mode>", "top | sidebar").action(async (opts) => {
4863
+ const global = program2.opts();
4864
+ const mode = parseScaffoldNavModeArg(opts.mode);
4865
+ await handle(async () => runScaffoldNav(buildContext(global), { mode }));
4866
+ });
4023
4867
  return program2;
4024
4868
  }
4025
4869