@woodsportal/hubspot-kit 1.0.41 → 1.0.42-dev.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -1
- package/README.md +191 -157
- package/dist/{chunk-S2YJDWZU.js → chunk-NWBK3XBZ.js} +1 -1
- package/dist/chunk-NWBK3XBZ.js.map +1 -0
- package/dist/chunk-WHYTQEZX.js +554 -0
- package/dist/chunk-WHYTQEZX.js.map +1 -0
- package/dist/cli.js +1778 -864
- package/dist/cli.js.map +1 -1
- package/dist/ensure-wphs-workspace-5S3GTO6K.js +234 -0
- package/dist/ensure-wphs-workspace-5S3GTO6K.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/dist/scaffold-page-lifecycle-EHMPWPPE.js +194 -0
- package/dist/scaffold-page-lifecycle-EHMPWPPE.js.map +1 -0
- package/dist/vite/index.js +1 -1
- package/examples/greenfield/module-hybrid-cdn/public/fields.json +73 -28
- package/examples/greenfield/module-hybrid-cdn/public/module.html +21 -5
- package/examples/greenfield/module-hybrid-cdn/src/assets/css/main.dev.css +15 -0
- package/examples/greenfield/module-hybrid-cdn/src/components/layout/starter-nav.tsx +12 -15
- package/examples/greenfield/module-hybrid-cdn/src/config/starter-nav.ts +9 -0
- package/examples/greenfield/module-hybrid-cdn/src/features/starter/components/starter-home.tsx +25 -29
- package/examples/greenfield/module-hybrid-cdn/src/features/starter/hooks/starter-preview-values.ts +60 -0
- package/examples/greenfield/module-hybrid-cdn/src/features/starter/hooks/use-starter-preview.dev.ts +18 -0
- package/examples/greenfield/module-hybrid-cdn/src/features/starter/hooks/use-starter-preview.ts +20 -37
- package/examples/greenfield/module-hybrid-cdn-auth-full/.env.dev.example +3 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/.env.local.example +6 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/.env.prod.example +5 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/.env.stg.example +3 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/.wphs/README.md +17 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/README.md +59 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/hubspot.config.yml.example +10 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/index.html +12 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/package.json +34 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/portal.cdn.json.example +8 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/public/fields.json +100 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/public/meta.json +9 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/public/module.html +38 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/app.tsx +172 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/assets/css/main.dev.css +23 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/assets/css/main.prod.css +12 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/assets/css/tailwind.css.config.css +7 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/cdn/app-entry.ts +48 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/cdn/vendor-entry.ts +28 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/components/Layouts/AppLayoutWrapper.tsx +38 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/components/Layouts/AuthLayout.tsx +7 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/components/Layouts/DefaultLayout.tsx +5 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/components/Layouts/MainLayout.tsx +42 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/components/Layouts/UnauthorizedLayout.tsx +5 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/components/Layouts/index.ts +13 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/components/layout/section-card.tsx +20 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/components/layout/starter-nav.tsx +28 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/components/layout/starter-shell.tsx +18 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/components/ui/Button.tsx +22 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/components/ui/Form.tsx +22 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/components/ui/Input.tsx +12 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/components/ui/Toaster.tsx +5 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/components/ui/auth/SsoLoginButton.tsx +34 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/components/ui/link.tsx +10 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/components/ui/loader/loader.tsx +18 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/config/auth-tier.ts +4 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/config/nav-scaffold.ts +4 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/config/portal-nav.ts +9 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/config/router.ts +1 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/config/routes.ts +10 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/data/hubSpotContext.ts +57 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/data/sync-sdk-hub-context.ts +44 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/defaultData.ts +2 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/env.ts +59 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/auth/app-bootstrap-guards.ts +14 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/auth/auth-navigation.ts +81 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/auth/auth-route-paths.ts +29 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/auth/auth-session-events.ts +2 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/auth/auth-session.ts +51 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/auth/clear-client-auth-session.ts +62 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/auth/components/starter-login.tsx +83 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/auth/login-email-storage.ts +64 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/auth/login-session-bridge.ts +74 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/auth/mfa-otp-utils.ts +24 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/auth/mfa-pending-storage.ts +108 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/auth/mfa-portal.ts +45 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/auth/post-login-path.ts +17 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/auth/resolve-auth-login-error-message.ts +52 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/auth/resolve-post-login-path.ts +21 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/auth/sso-callback.ts +120 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/auth/use-mfa-login-otp-flow.ts +78 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/auth/use-portal-session.ts +25 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/auth/use-signed-in-email.ts +55 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/auth/use-sso-callback-handler.ts +92 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/starter/components/starter-guide-page.tsx +210 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/starter/components/starter-home-page.tsx +21 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/starter/components/starter-home.tsx +48 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/starter/components/starter-page-frame.tsx +25 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/starter/components/starter-protected-page.tsx +54 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/starter/components/starter-single-page.tsx +17 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/starter/content/auth-guide-content.ts +85 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/starter/content/woodscli-guide-content.ts +86 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/starter/hooks/starter-preview-values.ts +60 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/starter/hooks/use-starter-preview.dev.ts +18 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/starter/hooks/use-starter-preview.ts +30 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/starter/index.ts +6 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/hubspot/bootstrap.ts +16 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/hubspot/default-data.ts +7 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/hubspot/is-cms-editor.ts +78 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/hubspot/module-entry.tsx +9 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/hubspot/types.ts +17 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/integrations/configure-woodsportal-sdk.ts +98 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/integrations/tanstack-query/root-provider.tsx +18 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/integrations/tanstack-router/router.tsx +39 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/integrations/woodsportal-sdk.ts +37 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/main.tsx +5 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/portal/api-endpoint.ts +11 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/portal/ensure-portal-cdn-stylesheet.ts +115 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/portal/mount-dev-preview.tsx +12 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/portal/mount-portal.tsx +57 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/routeTree.gen.ts +182 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/routes/__root.tsx +12 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/routes/_app/portal.tsx +10 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/routes/_auth/login/sso.tsx +27 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/routes/_auth/login.tsx +10 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/routes/_auth/two-fa.tsx +91 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/routes/guide.tsx +9 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/routes/index.tsx +8 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/routes/unauthorized.tsx +50 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/utils/ValidationSchema.ts +1 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/utils/cookie.ts +23 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/utils/normalize-router-path.ts +10 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/utils/private-route.tsx +100 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/utils/public-route.tsx +64 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/utils/resolve-api-base-url.ts +44 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/utils/safe-router-navigation.ts +82 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/tsconfig.json +20 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/vite.config.cdn-app.mjs +1 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/vite.config.cdn-esm.mjs +1 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/vite.config.cdn-vendor.mjs +1 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/vite.config.dev.mjs +1 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/vite.config.hubspot.mjs +1 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/vite.config.monolith.mjs +8 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/woodscli.json +38 -0
- package/examples/greenfield/module-hybrid-cdn-auth-full/wphs.adapter.ts +7 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/.env.dev.example +3 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/.env.local.example +6 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/.env.prod.example +5 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/.env.stg.example +3 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/.wphs/README.md +17 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/README.md +59 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/hubspot.config.yml.example +10 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/index.html +12 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/package.json +34 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/portal.cdn.json.example +8 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/public/fields.json +100 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/public/meta.json +9 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/public/module.html +38 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/app.tsx +180 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/assets/css/main.dev.css +23 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/assets/css/main.prod.css +12 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/assets/css/tailwind.css.config.css +7 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/cdn/app-entry.ts +48 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/cdn/vendor-entry.ts +28 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/components/Layouts/AppLayoutWrapper.tsx +38 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/components/Layouts/AuthLayout.tsx +7 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/components/Layouts/DefaultLayout.tsx +5 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/components/Layouts/MainLayout.tsx +42 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/components/Layouts/UnauthorizedLayout.tsx +5 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/components/Layouts/index.ts +13 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/components/layout/section-card.tsx +20 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/components/layout/starter-nav.tsx +28 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/components/layout/starter-shell.tsx +18 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/components/ui/Button.tsx +22 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/components/ui/Form.tsx +22 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/components/ui/Input.tsx +12 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/components/ui/Toaster.tsx +5 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/components/ui/auth/SsoLoginButton.tsx +34 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/components/ui/link.tsx +10 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/components/ui/loader/loader.tsx +18 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/config/auth-tier.ts +4 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/config/nav-scaffold.ts +4 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/config/portal-nav.ts +9 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/config/router.ts +1 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/config/routes.ts +10 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/data/hubSpotContext.ts +57 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/data/sync-sdk-hub-context.ts +44 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/defaultData.ts +2 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/env.ts +59 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/auth/app-bootstrap-guards.ts +14 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/auth/auth-navigation.ts +81 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/auth/auth-route-paths.ts +29 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/auth/auth-session-events.ts +2 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/auth/auth-session.ts +26 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/auth/clear-client-auth-session.ts +62 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/auth/components/starter-login.tsx +83 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/auth/login-email-storage.ts +64 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/auth/login-session-bridge.ts +72 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/auth/post-login-path.ts +17 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/auth/resolve-auth-login-error-message.ts +52 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/auth/resolve-post-login-path.ts +21 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/auth/sso-callback.ts +120 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/auth/use-portal-session.ts +25 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/auth/use-signed-in-email.ts +55 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/auth/use-sso-callback-handler.ts +92 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/starter/components/starter-guide-page.tsx +210 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/starter/components/starter-home-page.tsx +21 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/starter/components/starter-home.tsx +48 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/starter/components/starter-page-frame.tsx +25 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/starter/components/starter-protected-page.tsx +54 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/starter/components/starter-single-page.tsx +17 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/starter/content/auth-guide-content.ts +85 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/starter/content/woodscli-guide-content.ts +86 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/starter/hooks/starter-preview-values.ts +60 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/starter/hooks/use-starter-preview.dev.ts +18 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/starter/hooks/use-starter-preview.ts +30 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/starter/index.ts +6 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/hubspot/bootstrap.ts +16 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/hubspot/default-data.ts +7 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/hubspot/is-cms-editor.ts +78 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/hubspot/module-entry.tsx +9 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/hubspot/types.ts +17 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/integrations/configure-woodsportal-sdk.ts +94 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/integrations/tanstack-query/root-provider.tsx +18 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/integrations/tanstack-router/router.tsx +39 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/integrations/woodsportal-sdk.ts +37 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/main.tsx +5 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/portal/api-endpoint.ts +11 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/portal/ensure-portal-cdn-stylesheet.ts +115 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/portal/mount-dev-preview.tsx +12 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/portal/mount-portal.tsx +57 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/routeTree.gen.ts +155 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/routes/__root.tsx +12 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/routes/_app/portal.tsx +10 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/routes/_auth/login/sso.tsx +27 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/routes/_auth/login.tsx +10 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/routes/guide.tsx +9 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/routes/index.tsx +8 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/routes/unauthorized.tsx +50 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/utils/ValidationSchema.ts +1 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/utils/cookie.ts +23 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/utils/normalize-router-path.ts +10 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/utils/private-route.tsx +100 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/utils/public-route.tsx +64 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/utils/resolve-api-base-url.ts +44 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/utils/safe-router-navigation.ts +82 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/tsconfig.json +20 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/vite.config.cdn-app.mjs +1 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/vite.config.cdn-esm.mjs +1 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/vite.config.cdn-vendor.mjs +1 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/vite.config.dev.mjs +1 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/vite.config.hubspot.mjs +1 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/vite.config.monolith.mjs +8 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/woodscli.json +38 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/wphs.adapter.ts +7 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/.env.dev.example +3 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/.env.local.example +6 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/.env.prod.example +5 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/.env.stg.example +3 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/.wphs/README.md +17 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/README.md +59 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/hubspot.config.yml.example +10 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/index.html +12 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/package.json +34 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/portal.cdn.json.example +8 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/public/fields.json +100 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/public/meta.json +9 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/public/module.html +38 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/app.tsx +173 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/assets/css/main.dev.css +23 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/assets/css/main.prod.css +12 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/assets/css/tailwind.css.config.css +7 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/cdn/app-entry.ts +48 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/cdn/vendor-entry.ts +28 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/components/Layouts/AppLayoutWrapper.tsx +38 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/components/Layouts/AuthLayout.tsx +7 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/components/Layouts/DefaultLayout.tsx +5 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/components/Layouts/MainLayout.tsx +42 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/components/Layouts/UnauthorizedLayout.tsx +5 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/components/Layouts/index.ts +13 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/components/layout/section-card.tsx +20 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/components/layout/starter-nav.tsx +28 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/components/layout/starter-shell.tsx +18 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/components/ui/Button.tsx +22 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/components/ui/Form.tsx +22 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/components/ui/Input.tsx +12 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/components/ui/Toaster.tsx +5 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/components/ui/link.tsx +10 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/components/ui/loader/loader.tsx +18 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/config/auth-tier.ts +4 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/config/nav-scaffold.ts +4 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/config/portal-nav.ts +9 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/config/router.ts +1 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/config/routes.ts +10 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/data/hubSpotContext.ts +57 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/data/sync-sdk-hub-context.ts +44 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/defaultData.ts +2 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/env.ts +59 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/auth/app-bootstrap-guards.ts +14 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/auth/auth-navigation.ts +81 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/auth/auth-route-paths.ts +29 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/auth/auth-session-events.ts +2 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/auth/auth-session.ts +26 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/auth/clear-client-auth-session.ts +62 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/auth/components/starter-login.tsx +76 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/auth/login-email-storage.ts +64 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/auth/login-session-bridge.ts +72 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/auth/post-login-path.ts +17 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/auth/resolve-auth-login-error-message.ts +52 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/auth/resolve-post-login-path.ts +21 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/auth/use-portal-session.ts +25 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/auth/use-signed-in-email.ts +55 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/starter/components/starter-guide-page.tsx +218 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/starter/components/starter-home-page.tsx +21 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/starter/components/starter-home.tsx +48 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/starter/components/starter-page-frame.tsx +25 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/starter/components/starter-protected-page.tsx +54 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/starter/components/starter-single-page.tsx +17 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/starter/content/auth-guide-content.ts +91 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/starter/content/woodscli-guide-content.ts +86 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/starter/hooks/starter-preview-values.ts +60 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/starter/hooks/use-starter-preview.dev.ts +18 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/starter/hooks/use-starter-preview.ts +30 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/features/starter/index.ts +6 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/hubspot/bootstrap.ts +16 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/hubspot/default-data.ts +7 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/hubspot/is-cms-editor.ts +78 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/hubspot/module-entry.tsx +9 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/hubspot/types.ts +17 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/integrations/configure-woodsportal-sdk.ts +94 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/integrations/tanstack-query/root-provider.tsx +18 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/integrations/tanstack-router/router.tsx +39 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/integrations/woodsportal-sdk.ts +37 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/main.tsx +5 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/portal/api-endpoint.ts +11 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/portal/ensure-portal-cdn-stylesheet.ts +115 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/portal/mount-dev-preview.tsx +12 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/portal/mount-portal.tsx +52 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/routeTree.gen.ts +134 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/routes/__root.tsx +12 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/routes/_app/portal.tsx +10 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/routes/_auth/login.tsx +10 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/routes/guide.tsx +9 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/routes/index.tsx +8 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/routes/unauthorized.tsx +50 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/utils/ValidationSchema.ts +1 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/utils/cookie.ts +23 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/utils/normalize-router-path.ts +10 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/utils/private-route.tsx +100 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/utils/public-route.tsx +64 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/utils/resolve-api-base-url.ts +44 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/src/utils/safe-router-navigation.ts +82 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/tsconfig.json +20 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/vite.config.cdn-app.mjs +1 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/vite.config.cdn-esm.mjs +1 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/vite.config.cdn-vendor.mjs +1 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/vite.config.dev.mjs +1 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/vite.config.hubspot.mjs +1 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/vite.config.monolith.mjs +8 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/woodscli.json +38 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/wphs.adapter.ts +7 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/.env.dev.example +3 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/.env.local.example +6 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/.env.prod.example +5 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/.env.stg.example +3 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/.wphs/README.md +17 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/README.md +59 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/hubspot.config.yml.example +10 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/index.html +12 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/package.json +34 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/portal.cdn.json.example +8 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/public/fields.json +100 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/public/meta.json +9 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/public/module.html +38 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/app.tsx +173 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/assets/css/main.dev.css +23 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/assets/css/main.prod.css +12 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/assets/css/tailwind.css.config.css +7 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/cdn/app-entry.ts +48 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/cdn/vendor-entry.ts +28 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/components/Layouts/AppLayoutWrapper.tsx +38 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/components/Layouts/AuthLayout.tsx +7 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/components/Layouts/DefaultLayout.tsx +5 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/components/Layouts/MainLayout.tsx +7 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/components/Layouts/UnauthorizedLayout.tsx +5 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/components/Layouts/index.ts +13 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/components/layout/portal-sidebar-nav.tsx +33 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/components/layout/portal-sidebar.tsx +139 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/components/layout/section-card.tsx +20 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/components/layout/starter-nav.tsx +28 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/components/layout/starter-shell.tsx +18 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/components/ui/Button.tsx +22 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/components/ui/Form.tsx +22 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/components/ui/Input.tsx +12 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/components/ui/Toaster.tsx +5 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/components/ui/link.tsx +10 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/components/ui/loader/loader.tsx +18 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/config/auth-tier.ts +4 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/config/nav-scaffold.ts +4 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/config/portal-nav.ts +9 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/config/router.ts +1 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/config/routes.ts +10 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/data/hubSpotContext.ts +57 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/data/sync-sdk-hub-context.ts +44 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/defaultData.ts +2 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/env.ts +59 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/auth/app-bootstrap-guards.ts +14 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/auth/auth-navigation.ts +81 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/auth/auth-route-paths.ts +29 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/auth/auth-session-events.ts +2 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/auth/auth-session.ts +26 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/auth/clear-client-auth-session.ts +62 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/auth/components/starter-login.tsx +76 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/auth/login-email-storage.ts +64 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/auth/login-session-bridge.ts +72 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/auth/post-login-path.ts +17 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/auth/resolve-auth-login-error-message.ts +52 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/auth/resolve-post-login-path.ts +21 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/auth/use-portal-session.ts +25 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/auth/use-signed-in-email.ts +55 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/starter/components/starter-guide-page.tsx +210 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/starter/components/starter-home-page.tsx +21 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/starter/components/starter-home.tsx +48 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/starter/components/starter-page-frame.tsx +25 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/starter/components/starter-protected-page.tsx +54 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/starter/components/starter-single-page.tsx +17 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/starter/content/auth-guide-content.ts +85 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/starter/content/woodscli-guide-content.ts +86 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/starter/hooks/starter-preview-values.ts +60 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/starter/hooks/use-starter-preview.dev.ts +18 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/starter/hooks/use-starter-preview.ts +30 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/features/starter/index.ts +6 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/hubspot/bootstrap.ts +16 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/hubspot/default-data.ts +7 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/hubspot/is-cms-editor.ts +78 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/hubspot/module-entry.tsx +9 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/hubspot/types.ts +17 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/integrations/configure-woodsportal-sdk.ts +94 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/integrations/tanstack-query/root-provider.tsx +18 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/integrations/tanstack-router/router.tsx +39 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/integrations/woodsportal-sdk.ts +37 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/main.tsx +5 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/portal/api-endpoint.ts +11 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/portal/ensure-portal-cdn-stylesheet.ts +115 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/portal/mount-dev-preview.tsx +12 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/portal/mount-portal.tsx +52 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/routeTree.gen.ts +134 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/routes/__root.tsx +12 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/routes/_app/portal.tsx +10 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/routes/_auth/login.tsx +10 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/routes/guide.tsx +9 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/routes/index.tsx +8 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/routes/unauthorized.tsx +50 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/utils/ValidationSchema.ts +1 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/utils/cookie.ts +23 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/utils/normalize-router-path.ts +10 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/utils/private-route.tsx +100 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/utils/public-route.tsx +64 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/utils/resolve-api-base-url.ts +44 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/src/utils/safe-router-navigation.ts +82 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/tsconfig.json +20 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/vite.config.cdn-app.mjs +1 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/vite.config.cdn-esm.mjs +1 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/vite.config.cdn-vendor.mjs +1 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/vite.config.dev.mjs +1 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/vite.config.hubspot.mjs +1 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/vite.config.monolith.mjs +8 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/woodscli.json +38 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/wphs.adapter.ts +7 -0
- package/examples/greenfield/module-monolith/public/fields.json +66 -16
- package/examples/greenfield/module-monolith/src/assets/css/main.dev.css +7 -0
- package/examples/greenfield/module-monolith/src/components/layout/starter-nav.tsx +12 -15
- package/examples/greenfield/module-monolith/src/config/starter-nav.ts +9 -0
- package/examples/greenfield/module-monolith/src/features/starter/components/starter-home.tsx +25 -29
- package/examples/greenfield/module-monolith/src/features/starter/hooks/starter-preview-values.ts +60 -0
- package/examples/greenfield/module-monolith/src/features/starter/hooks/use-starter-preview.dev.ts +18 -0
- package/examples/greenfield/module-monolith/src/features/starter/hooks/use-starter-preview.ts +20 -37
- package/examples/manifest.json +5 -1
- package/examples/smoke/run-matrix.mjs +37 -0
- package/package.json +28 -12
- package/scripts/audit-tdz-split.mjs +11 -5
- package/scripts/check-cdn-size-regression.mjs +49 -0
- package/scripts/check-hubspot-bundle-size.mjs +18 -4
- package/scripts/cli-smoke-helpers.mjs +150 -0
- package/scripts/generate-cdn-bundle-breakdown.mjs +61 -0
- package/scripts/generate-cdn-manifest.mjs +23 -8
- package/scripts/portal-vite-env.mjs +76 -2
- package/scripts/publish-cdn-github.mjs +79 -14
- package/scripts/refresh-artifact-sizes.mjs +77 -0
- package/scripts/regenerate-route-tree.mjs +71 -0
- package/scripts/sync-auth-scaffold-from-client.mjs +41 -0
- package/scripts/verify-cdn-api-endpoint.mjs +17 -10
- package/scripts/vite/create-dev-server-config.mjs +7 -1
- package/scripts/vite/portal-build-shared.js +62 -2
- package/scripts/wp-cli-smoke.test.mjs +263 -0
- package/src/dev/bootstrap-greenfield.tsx +6 -0
- package/src/dev-module-config/ui/module-config-overlay-side.ts +13 -3
- package/src/dev-module-config/ui/module-config-toolbar-theme.ts +12 -3
- package/templates/module-hybrid-cdn/public/fields.json +47 -7
- package/templates/module-hybrid-cdn/public/module.html +7 -6
- package/templates/module-hybrid-cdn/src/assets/css/main.dev.css +7 -0
- package/templates/module-hybrid-cdn/src/components/layout/starter-nav.tsx +12 -15
- package/templates/module-hybrid-cdn/src/config/starter-nav.ts +9 -0
- package/templates/module-hybrid-cdn/src/features/starter/components/starter-home.tsx +25 -27
- package/templates/module-hybrid-cdn/src/features/starter/hooks/starter-preview-values.ts +5 -5
- package/templates/module-monolith/public/fields.json +49 -6
- package/templates/module-monolith/src/assets/css/main.dev.css +7 -0
- package/templates/module-monolith/src/components/layout/starter-nav.tsx +12 -15
- package/templates/module-monolith/src/config/starter-nav.ts +9 -0
- package/templates/module-monolith/src/features/starter/components/starter-home.tsx +25 -27
- package/templates/module-monolith/src/features/starter/hooks/starter-preview-values.ts +5 -5
- package/templates/scaffold/auth/_full/src/app.tsx +172 -0
- package/templates/scaffold/auth/_full/src/config/auth-tier.ts +4 -0
- package/templates/scaffold/auth/_full/src/features/auth/auth-session.ts +51 -0
- package/templates/scaffold/auth/_full/src/features/auth/login-session-bridge.ts +74 -0
- package/templates/scaffold/auth/_full/src/features/auth/mfa-otp-utils.ts +24 -0
- package/templates/scaffold/auth/_full/src/features/auth/mfa-pending-storage.ts +108 -0
- package/templates/scaffold/auth/_full/src/features/auth/mfa-portal.ts +45 -0
- package/templates/scaffold/auth/_full/src/features/auth/use-mfa-login-otp-flow.ts +78 -0
- package/templates/scaffold/auth/_full/src/routeTree.gen.ts +182 -0
- package/templates/scaffold/auth/_full/src/routes/_auth/two-fa.tsx +91 -0
- package/templates/scaffold/auth/_nav-sidebar/src/components/Layouts/MainLayout.tsx +7 -0
- package/templates/scaffold/auth/_nav-sidebar/src/components/layout/portal-sidebar-nav.tsx +33 -0
- package/templates/scaffold/auth/_nav-sidebar/src/components/layout/portal-sidebar.tsx +139 -0
- package/templates/scaffold/auth/_nav-top/src/components/Layouts/MainLayout.tsx +42 -0
- package/templates/scaffold/auth/_shared/src/app.tsx +178 -0
- package/templates/scaffold/auth/_shared/src/components/Layouts/AppLayoutWrapper.tsx +38 -0
- package/templates/scaffold/auth/_shared/src/components/Layouts/AuthLayout.tsx +7 -0
- package/templates/scaffold/auth/_shared/src/components/Layouts/DefaultLayout.tsx +5 -0
- package/templates/scaffold/auth/_shared/src/components/Layouts/UnauthorizedLayout.tsx +5 -0
- package/templates/scaffold/auth/_shared/src/components/Layouts/index.ts +13 -0
- package/templates/scaffold/auth/_shared/src/components/ui/Button.tsx +22 -0
- package/templates/scaffold/auth/_shared/src/components/ui/Form.tsx +22 -0
- package/templates/scaffold/auth/_shared/src/components/ui/Input.tsx +12 -0
- package/templates/scaffold/auth/_shared/src/components/ui/Toaster.tsx +5 -0
- package/templates/scaffold/auth/_shared/src/components/ui/link.tsx +10 -0
- package/templates/scaffold/auth/_shared/src/components/ui/loader/loader.tsx +18 -0
- package/templates/scaffold/auth/_shared/src/config/auth-tier.ts +4 -0
- package/templates/scaffold/auth/_shared/src/config/nav-scaffold.ts +4 -0
- package/templates/scaffold/auth/_shared/src/config/portal-nav.ts +9 -0
- package/templates/scaffold/auth/_shared/src/config/router.ts +1 -0
- package/templates/scaffold/auth/_shared/src/config/routes.ts +10 -0
- package/templates/scaffold/auth/_shared/src/data/hubSpotContext.ts +57 -0
- package/templates/scaffold/auth/_shared/src/data/sync-sdk-hub-context.ts +44 -0
- package/templates/scaffold/auth/_shared/src/env.ts +59 -0
- package/templates/scaffold/auth/_shared/src/features/auth/app-bootstrap-guards.ts +14 -0
- package/templates/scaffold/auth/_shared/src/features/auth/auth-navigation.ts +81 -0
- package/templates/scaffold/auth/_shared/src/features/auth/auth-route-paths.ts +29 -0
- package/templates/scaffold/auth/_shared/src/features/auth/auth-session-events.ts +2 -0
- package/templates/scaffold/auth/_shared/src/features/auth/auth-session.ts +26 -0
- package/templates/scaffold/auth/_shared/src/features/auth/clear-client-auth-session.ts +62 -0
- package/templates/scaffold/auth/_shared/src/features/auth/components/starter-login.tsx +76 -0
- package/templates/scaffold/auth/_shared/src/features/auth/login-email-storage.ts +64 -0
- package/templates/scaffold/auth/_shared/src/features/auth/login-session-bridge.ts +72 -0
- package/templates/scaffold/auth/_shared/src/features/auth/post-login-path.ts +17 -0
- package/templates/scaffold/auth/_shared/src/features/auth/resolve-auth-login-error-message.ts +52 -0
- package/templates/scaffold/auth/_shared/src/features/auth/resolve-post-login-path.ts +21 -0
- package/templates/scaffold/auth/_shared/src/features/auth/use-portal-session.ts +25 -0
- package/templates/scaffold/auth/_shared/src/features/auth/use-signed-in-email.ts +55 -0
- package/templates/scaffold/auth/_shared/src/features/starter/components/starter-guide-page.tsx +218 -0
- package/templates/scaffold/auth/_shared/src/features/starter/components/starter-page-frame.tsx +25 -0
- package/templates/scaffold/auth/_shared/src/features/starter/components/starter-protected-page.tsx +54 -0
- package/templates/scaffold/auth/_shared/src/features/starter/content/auth-guide-content.ts +91 -0
- package/templates/scaffold/auth/_shared/src/hubspot/is-cms-editor.ts +78 -0
- package/templates/scaffold/auth/_shared/src/integrations/configure-woodsportal-sdk.ts +100 -0
- package/templates/scaffold/auth/_shared/src/integrations/tanstack-query/root-provider.tsx +18 -0
- package/templates/scaffold/auth/_shared/src/integrations/tanstack-router/router.tsx +39 -0
- package/templates/scaffold/auth/_shared/src/integrations/woodsportal-sdk.ts +37 -0
- package/templates/scaffold/auth/_shared/src/portal/mount-portal.tsx +59 -0
- package/templates/scaffold/auth/_shared/src/routeTree.gen.ts +134 -0
- package/templates/scaffold/auth/_shared/src/routes/__root.tsx +12 -0
- package/templates/scaffold/auth/_shared/src/routes/_app/portal.tsx +10 -0
- package/templates/scaffold/auth/_shared/src/routes/_auth/login.tsx +10 -0
- package/templates/scaffold/auth/_shared/src/routes/guide.tsx +9 -0
- package/templates/scaffold/auth/_shared/src/routes/index.tsx +8 -0
- package/templates/scaffold/auth/_shared/src/routes/unauthorized.tsx +50 -0
- package/templates/scaffold/auth/_shared/src/utils/ValidationSchema.ts +1 -0
- package/templates/scaffold/auth/_shared/src/utils/cookie.ts +23 -0
- package/templates/scaffold/auth/_shared/src/utils/normalize-router-path.ts +10 -0
- package/templates/scaffold/auth/_shared/src/utils/private-route.tsx +100 -0
- package/templates/scaffold/auth/_shared/src/utils/public-route.tsx +64 -0
- package/templates/scaffold/auth/_shared/src/utils/resolve-api-base-url.ts +44 -0
- package/templates/scaffold/auth/_shared/src/utils/safe-router-navigation.ts +82 -0
- package/templates/scaffold/auth/_sso/src/app.tsx +180 -0
- package/templates/scaffold/auth/_sso/src/components/ui/auth/SsoLoginButton.tsx +34 -0
- package/templates/scaffold/auth/_sso/src/config/auth-tier.ts +4 -0
- package/templates/scaffold/auth/_sso/src/features/auth/components/starter-login.tsx +83 -0
- package/templates/scaffold/auth/_sso/src/features/auth/sso-callback.ts +120 -0
- package/templates/scaffold/auth/_sso/src/features/auth/use-sso-callback-handler.ts +92 -0
- package/templates/scaffold/auth/_sso/src/routeTree.gen.ts +155 -0
- package/templates/scaffold/auth/_sso/src/routes/_auth/login/sso.tsx +27 -0
- package/templates/scaffold/auth/nav-manifest.json +8 -0
- package/templates/scaffold/auth/sync-manifest.json +16 -0
- package/templates/scaffold/auth/tier-manifest.json +18 -0
- package/templates/scaffold/page/page-component-auth.tsx.tpl +10 -0
- package/templates/scaffold/page/page-component-plain.tsx.tpl +11 -0
- package/templates/scaffold/page/route-protected-auth.tsx.tpl +10 -0
- package/templates/scaffold/page/route-public-auth.tsx.tpl +9 -0
- package/templates/scaffold/page/route-public-plain.tsx.tpl +6 -0
- package/vite/vite.config.cdn-app.mjs +43 -30
- package/vite/vite.config.cdn-audit.mjs +34 -0
- package/vite/vite.config.cdn-esm.mjs +1 -1
- package/dist/chunk-S2YJDWZU.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,208 +1,42 @@
|
|
|
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,
|
|
5
26
|
isWpHsError,
|
|
6
27
|
runCommand,
|
|
7
28
|
runCommandAsync,
|
|
8
|
-
runNodeScript,
|
|
9
29
|
runNodeScriptAsync,
|
|
10
30
|
runNodeScriptLongRunning
|
|
11
31
|
} from "./chunk-4GOJPOW3.js";
|
|
12
32
|
|
|
13
33
|
// src/cli/program.ts
|
|
14
|
-
import { readFileSync as readFileSync16 } from "fs";
|
|
15
|
-
import { resolve as resolve23 } from "path";
|
|
16
34
|
import { Command } from "commander";
|
|
17
35
|
|
|
18
36
|
// src/lib/config.ts
|
|
19
|
-
import { readFileSync
|
|
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
|
|
37
|
+
import { readFileSync } from "fs";
|
|
204
38
|
function loadConfig(projectRoot) {
|
|
205
|
-
const root = projectRoot ??
|
|
39
|
+
const root = projectRoot ?? findProjectRoot();
|
|
206
40
|
const resolved = resolveProjectConfig(root);
|
|
207
41
|
if (!resolved) {
|
|
208
42
|
throw new WpHsError(`Missing ${WOODSCLI_CONFIG_FILE}`, {
|
|
@@ -212,7 +46,7 @@ function loadConfig(projectRoot) {
|
|
|
212
46
|
}
|
|
213
47
|
let raw;
|
|
214
48
|
try {
|
|
215
|
-
raw = JSON.parse(
|
|
49
|
+
raw = JSON.parse(readFileSync(resolved.path, "utf8"));
|
|
216
50
|
} catch (cause) {
|
|
217
51
|
throw new WpHsError(`Invalid JSON in ${resolved.fileName}`, {
|
|
218
52
|
exitCode: ExitCode.USER_ERROR,
|
|
@@ -294,20 +128,171 @@ function createLogger(options = {}) {
|
|
|
294
128
|
};
|
|
295
129
|
}
|
|
296
130
|
|
|
131
|
+
// src/lib/kit-version-check.ts
|
|
132
|
+
import { existsSync, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
133
|
+
import { homedir } from "os";
|
|
134
|
+
import { dirname, join } from "path";
|
|
135
|
+
import pc2 from "picocolors";
|
|
136
|
+
import semver from "semver";
|
|
137
|
+
var NPM_REGISTRY_URL = "https://registry.npmjs.org/@woodsportal%2Fhubspot-kit";
|
|
138
|
+
var VERSION_CHECK_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
139
|
+
var VERSION_CHECK_FETCH_TIMEOUT_MS = 2500;
|
|
140
|
+
function getVersionCheckCachePath() {
|
|
141
|
+
if (process.env.WP_VERSION_CHECK_CACHE) {
|
|
142
|
+
return process.env.WP_VERSION_CHECK_CACHE;
|
|
143
|
+
}
|
|
144
|
+
const base = process.env.XDG_CACHE_HOME ? join(process.env.XDG_CACHE_HOME, "woodsportal") : join(homedir(), ".cache", "woodsportal");
|
|
145
|
+
return join(base, "wp-kit-version.json");
|
|
146
|
+
}
|
|
147
|
+
function readVersionCheckCache(now = Date.now()) {
|
|
148
|
+
const cachePath = getVersionCheckCachePath();
|
|
149
|
+
if (!existsSync(cachePath)) {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
try {
|
|
153
|
+
const parsed = JSON.parse(readFileSync2(cachePath, "utf8"));
|
|
154
|
+
if (!parsed.latest || typeof parsed.fetchedAt !== "number") {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
if (now - parsed.fetchedAt > VERSION_CHECK_CACHE_TTL_MS) {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
return parsed;
|
|
161
|
+
} catch {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
function writeVersionCheckCache(cache) {
|
|
166
|
+
const cachePath = getVersionCheckCachePath();
|
|
167
|
+
mkdirSync(dirname(cachePath), { recursive: true });
|
|
168
|
+
writeFileSync(cachePath, `${JSON.stringify(cache)}
|
|
169
|
+
`, "utf8");
|
|
170
|
+
}
|
|
171
|
+
async function fetchLatestKitVersion() {
|
|
172
|
+
try {
|
|
173
|
+
const response = await fetch(NPM_REGISTRY_URL, {
|
|
174
|
+
signal: AbortSignal.timeout(VERSION_CHECK_FETCH_TIMEOUT_MS),
|
|
175
|
+
headers: { accept: "application/json" }
|
|
176
|
+
});
|
|
177
|
+
if (!response.ok) {
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
const body = await response.json();
|
|
181
|
+
const latest = body["dist-tags"]?.latest;
|
|
182
|
+
return typeof latest === "string" && latest.length > 0 ? latest : null;
|
|
183
|
+
} catch {
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
async function resolveLatestKitVersion(now = Date.now()) {
|
|
188
|
+
const cached = readVersionCheckCache(now);
|
|
189
|
+
if (cached) {
|
|
190
|
+
return cached.latest;
|
|
191
|
+
}
|
|
192
|
+
const latest = await fetchLatestKitVersion();
|
|
193
|
+
if (!latest) {
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
writeVersionCheckCache({ latest, fetchedAt: now });
|
|
197
|
+
return latest;
|
|
198
|
+
}
|
|
199
|
+
function isKitUpdateAvailable(current, latest) {
|
|
200
|
+
const currentCoerced = semver.coerce(current);
|
|
201
|
+
const latestCoerced = semver.coerce(latest);
|
|
202
|
+
if (!currentCoerced || !latestCoerced) {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
return semver.lt(currentCoerced, latestCoerced);
|
|
206
|
+
}
|
|
207
|
+
function formatKitUpgradeCommand(projectRoot) {
|
|
208
|
+
const root = projectRoot || process.cwd();
|
|
209
|
+
const spec = `${KIT_PACKAGE_NAME}@latest`;
|
|
210
|
+
if (existsSync(join(root, "yarn.lock"))) {
|
|
211
|
+
return `yarn add -D ${spec}`;
|
|
212
|
+
}
|
|
213
|
+
if (existsSync(join(root, "pnpm-lock.yaml"))) {
|
|
214
|
+
return `pnpm add -D ${spec}`;
|
|
215
|
+
}
|
|
216
|
+
return `npm install -D ${spec}`;
|
|
217
|
+
}
|
|
218
|
+
function shouldUseWarnColor() {
|
|
219
|
+
if (process.env.NO_COLOR !== void 0) return false;
|
|
220
|
+
if (process.env.CI === "true") return false;
|
|
221
|
+
return process.stderr.isTTY === true;
|
|
222
|
+
}
|
|
223
|
+
function shouldSkipVersionCheck(options) {
|
|
224
|
+
if (options.ci || process.env.CI === "true") return true;
|
|
225
|
+
if (options.json || options.quiet) return true;
|
|
226
|
+
if (process.env.WP_SKIP_VERSION_CHECK === "1") return true;
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
function formatKitUpdateWarning(current, latest, projectRoot) {
|
|
230
|
+
const upgrade = formatKitUpgradeCommand(projectRoot);
|
|
231
|
+
const useColor = shouldUseWarnColor();
|
|
232
|
+
const warn = useColor ? pc2.yellow("warn") : "warn";
|
|
233
|
+
const dim = useColor ? pc2.dim : (text5) => text5;
|
|
234
|
+
return [
|
|
235
|
+
`[${warn}] ${KIT_PACKAGE_NAME} v${current} is installed; latest is v${latest}.`,
|
|
236
|
+
`${dim(" ")}Upgrade: ${upgrade}`
|
|
237
|
+
].join("\n");
|
|
238
|
+
}
|
|
239
|
+
async function maybeWarnKitUpdate(options) {
|
|
240
|
+
try {
|
|
241
|
+
if (shouldSkipVersionCheck(options)) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
const latest = await resolveLatestKitVersion();
|
|
245
|
+
if (!latest || !isKitUpdateAvailable(options.current, latest)) {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
process.stderr.write(`${formatKitUpdateWarning(options.current, latest, options.projectRoot)}
|
|
249
|
+
`);
|
|
250
|
+
} catch {
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// src/lib/kit-version.ts
|
|
255
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
256
|
+
import { resolve } from "path";
|
|
257
|
+
var cachedVersion = null;
|
|
258
|
+
function readKitVersion() {
|
|
259
|
+
if (cachedVersion) {
|
|
260
|
+
return cachedVersion;
|
|
261
|
+
}
|
|
262
|
+
try {
|
|
263
|
+
const pkg = JSON.parse(readFileSync3(resolve(getKitRoot(), "package.json"), "utf8"));
|
|
264
|
+
cachedVersion = pkg.version ?? "0.0.0";
|
|
265
|
+
} catch {
|
|
266
|
+
cachedVersion = "0.0.0";
|
|
267
|
+
}
|
|
268
|
+
return cachedVersion;
|
|
269
|
+
}
|
|
270
|
+
|
|
297
271
|
// src/lib/wphs-baseline.ts
|
|
298
|
-
import { resolve as
|
|
272
|
+
import { resolve as resolve2 } from "path";
|
|
299
273
|
function defaultBaselineDir(projectRoot, config) {
|
|
300
274
|
const workspace = config.dev.moduleConfigDir ?? WPHS_WORKSPACE_DIR;
|
|
301
|
-
return
|
|
275
|
+
return resolve2(projectRoot, workspace, "audit", "baseline");
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// src/lib/exit-cli.ts
|
|
279
|
+
async function exitCli(code) {
|
|
280
|
+
await new Promise((resolve23) => {
|
|
281
|
+
setImmediate(resolve23);
|
|
282
|
+
});
|
|
283
|
+
await new Promise((resolve23) => {
|
|
284
|
+
setTimeout(resolve23, 0);
|
|
285
|
+
});
|
|
286
|
+
process.exit(code);
|
|
302
287
|
}
|
|
303
288
|
|
|
304
289
|
// src/cli/commands/doctor.ts
|
|
305
|
-
import { existsSync as
|
|
306
|
-
import { join, resolve as
|
|
290
|
+
import { existsSync as existsSync8, readFileSync as readFileSync8 } from "fs";
|
|
291
|
+
import { join as join2, resolve as resolve9 } from "path";
|
|
307
292
|
|
|
308
293
|
// src/lib/cli-spinner.ts
|
|
309
294
|
import * as p from "@clack/prompts";
|
|
310
|
-
import
|
|
295
|
+
import pc3 from "picocolors";
|
|
311
296
|
function isInteractiveCli(ctx) {
|
|
312
297
|
const { json, quiet, ci } = ctx.global;
|
|
313
298
|
return !json && !quiet && !ci && process.stdout.isTTY === true;
|
|
@@ -321,11 +306,11 @@ function isVerboseStepLog(ctx) {
|
|
|
321
306
|
return ctx.global.verbose === true && !ctx.global.quiet && !ctx.global.json;
|
|
322
307
|
}
|
|
323
308
|
async function yieldForSpinnerPaint() {
|
|
324
|
-
await new Promise((
|
|
325
|
-
setImmediate(
|
|
309
|
+
await new Promise((resolve23) => {
|
|
310
|
+
setImmediate(resolve23);
|
|
326
311
|
});
|
|
327
|
-
await new Promise((
|
|
328
|
-
setTimeout(
|
|
312
|
+
await new Promise((resolve23) => {
|
|
313
|
+
setTimeout(resolve23, 48);
|
|
329
314
|
});
|
|
330
315
|
}
|
|
331
316
|
async function runCliTasks(ctx, tasks, options = {}) {
|
|
@@ -372,7 +357,7 @@ async function runCliTasks(ctx, tasks, options = {}) {
|
|
|
372
357
|
await step.task();
|
|
373
358
|
spinner2.stop(step.title);
|
|
374
359
|
} catch (error) {
|
|
375
|
-
spinner2.stop(
|
|
360
|
+
spinner2.stop(pc3.red(step.title));
|
|
376
361
|
throw error;
|
|
377
362
|
}
|
|
378
363
|
}
|
|
@@ -384,7 +369,7 @@ function shouldQuietSubprocess(ctx, options = {}) {
|
|
|
384
369
|
}
|
|
385
370
|
function printCommandDone(ctx, message) {
|
|
386
371
|
if (isInteractiveCli(ctx)) {
|
|
387
|
-
process.stdout.write(`${
|
|
372
|
+
process.stdout.write(`${pc3.green("\u25C6")} ${message}
|
|
388
373
|
`);
|
|
389
374
|
return;
|
|
390
375
|
}
|
|
@@ -392,8 +377,8 @@ function printCommandDone(ctx, message) {
|
|
|
392
377
|
}
|
|
393
378
|
|
|
394
379
|
// src/lib/hubspot-cli-setup.ts
|
|
395
|
-
import { existsSync as
|
|
396
|
-
import { resolve as
|
|
380
|
+
import { existsSync as existsSync2, readFileSync as readFileSync4 } from "fs";
|
|
381
|
+
import { resolve as resolve3 } from "path";
|
|
397
382
|
import { spawnSync } from "child_process";
|
|
398
383
|
var HUBSPOT_CONFIG_FILE = "hubspot.config.yml";
|
|
399
384
|
var HUBSPOT_CONFIG_EXAMPLE = "hubspot.config.yml.example";
|
|
@@ -419,13 +404,13 @@ function hsTimedOut(result) {
|
|
|
419
404
|
return err?.code === "ETIMEDOUT";
|
|
420
405
|
}
|
|
421
406
|
function assessHubSpotConfigFiles(projectRoot) {
|
|
422
|
-
const configPath =
|
|
423
|
-
const examplePath =
|
|
424
|
-
const configPresent =
|
|
425
|
-
const examplePresent =
|
|
407
|
+
const configPath = resolve3(projectRoot, HUBSPOT_CONFIG_FILE);
|
|
408
|
+
const examplePath = resolve3(projectRoot, HUBSPOT_CONFIG_EXAMPLE);
|
|
409
|
+
const configPresent = existsSync2(configPath);
|
|
410
|
+
const examplePresent = existsSync2(examplePath);
|
|
426
411
|
let looksLikePlaceholder = false;
|
|
427
412
|
if (configPresent) {
|
|
428
|
-
const raw =
|
|
413
|
+
const raw = readFileSync4(configPath, "utf8");
|
|
429
414
|
looksLikePlaceholder = PLACEHOLDER_PATTERNS.some((pattern) => pattern.test(raw));
|
|
430
415
|
} else {
|
|
431
416
|
looksLikePlaceholder = true;
|
|
@@ -551,7 +536,7 @@ function doctorHubSpotChecks(projectRoot) {
|
|
|
551
536
|
ok: assessment.hsCliInstalled,
|
|
552
537
|
message: assessment.hsCliInstalled ? `HubSpot CLI ${assessment.hsCliVersion ?? ""}`.trim() : "HubSpot CLI not installed",
|
|
553
538
|
remediation: assessment.hsCliInstalled ? void 0 : "npm i -g @hubspot/cli",
|
|
554
|
-
severity: "
|
|
539
|
+
severity: "warn"
|
|
555
540
|
},
|
|
556
541
|
{
|
|
557
542
|
name: "hubspot-config",
|
|
@@ -570,136 +555,345 @@ function doctorHubSpotChecks(projectRoot) {
|
|
|
570
555
|
];
|
|
571
556
|
}
|
|
572
557
|
|
|
573
|
-
// src/lib/
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
"babel-plugin-react-compiler": "^1.0.0",
|
|
578
|
-
"@tailwindcss/vite": "^4.0.6",
|
|
579
|
-
"@tanstack/router-plugin": "^1.121.2",
|
|
580
|
-
"@tanstack/react-router": "^1.121.2",
|
|
581
|
-
"@dnd-kit/core": "^6.3.1",
|
|
582
|
-
"@dnd-kit/sortable": "^10.0.0",
|
|
583
|
-
"@dnd-kit/utilities": "^3.2.2",
|
|
584
|
-
react: "^19.0.0",
|
|
585
|
-
"react-dom": "^19.0.0",
|
|
586
|
-
tailwindcss: "^4.0.6"
|
|
587
|
-
};
|
|
588
|
-
var KIT_DEV_DEP = "@woodsportal/hubspot-kit";
|
|
589
|
-
var KIT_DEV_DEP_VERSION = "^1.0.30";
|
|
590
|
-
var MODULE_CONFIG_UI_DEPS = [
|
|
591
|
-
"@dnd-kit/core",
|
|
592
|
-
"@dnd-kit/sortable",
|
|
593
|
-
"@dnd-kit/utilities"
|
|
594
|
-
];
|
|
595
|
-
function templateDevDependencies() {
|
|
596
|
-
return {
|
|
597
|
-
[KIT_DEV_DEP]: KIT_DEV_DEP_VERSION,
|
|
598
|
-
...VITE_DEV_PEER_DEPS
|
|
599
|
-
};
|
|
600
|
-
}
|
|
558
|
+
// src/lib/cdn-github-setup.ts
|
|
559
|
+
import { existsSync as existsSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
|
|
560
|
+
import { resolve as resolve5 } from "path";
|
|
561
|
+
import * as p2 from "@clack/prompts";
|
|
601
562
|
|
|
602
|
-
// src/lib/
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
"
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
"hubdb_row",
|
|
619
|
-
"hubdb_table",
|
|
620
|
-
"icon",
|
|
621
|
-
"image",
|
|
622
|
-
"link",
|
|
623
|
-
"logo",
|
|
624
|
-
"meeting",
|
|
625
|
-
"menu",
|
|
626
|
-
"number",
|
|
627
|
-
"page",
|
|
628
|
-
"payment",
|
|
629
|
-
"richtext",
|
|
630
|
-
"salesforce_campaign",
|
|
631
|
-
"simple_menu",
|
|
632
|
-
"spacing",
|
|
633
|
-
"tag",
|
|
634
|
-
"text",
|
|
635
|
-
"url",
|
|
636
|
-
"video",
|
|
637
|
-
"workflow"
|
|
638
|
-
]);
|
|
639
|
-
function isFieldRecord(value) {
|
|
640
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
641
|
-
}
|
|
642
|
-
function validateFieldNode(node, path, issues) {
|
|
643
|
-
if (!isFieldRecord(node)) {
|
|
644
|
-
issues.push({ path, message: "expected a field object" });
|
|
645
|
-
return;
|
|
646
|
-
}
|
|
647
|
-
const type = node.type;
|
|
648
|
-
if (typeof type !== "string" || type.length === 0) {
|
|
649
|
-
issues.push({ path: `${path}.type`, message: "missing field type" });
|
|
650
|
-
} else if (!HUBSPOT_MODULE_FIELD_TYPES.has(type)) {
|
|
651
|
-
issues.push({
|
|
652
|
-
path: `${path}.type`,
|
|
653
|
-
message: type === "textarea" ? `'textarea' is not a HubSpot field type \u2014 use "type": "text" with "display": "textarea"` : `'${type}' is not a valid HubSpot field type`
|
|
654
|
-
});
|
|
655
|
-
}
|
|
656
|
-
const tab = node.tab;
|
|
657
|
-
if (tab === "STYLE" && type !== "group") {
|
|
658
|
-
issues.push({
|
|
659
|
-
path: `${path}.tab`,
|
|
660
|
-
message: 'STYLE tab fields must live inside a "type": "group" with "tab": "STYLE" (HubSpot page editor Styles tab)'
|
|
661
|
-
});
|
|
662
|
-
}
|
|
663
|
-
if (type !== "group") {
|
|
664
|
-
const name = node.name;
|
|
665
|
-
if (typeof name !== "string" || name.trim().length === 0) {
|
|
666
|
-
issues.push({ path: `${path}.name`, message: "missing field name" });
|
|
667
|
-
}
|
|
668
|
-
const label = node.label;
|
|
669
|
-
if (typeof label !== "string" || label.trim().length === 0) {
|
|
670
|
-
issues.push({ path: `${path}.label`, message: "missing field label" });
|
|
563
|
+
// src/lib/project-env-files.ts
|
|
564
|
+
import { existsSync as existsSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
|
|
565
|
+
import { resolve as resolve4 } from "path";
|
|
566
|
+
var ENV_LOCAL_FILE = ".env.local";
|
|
567
|
+
var ENV_GITIGNORE_LINES = [".env", ".env.*", "!.env*.example", ENV_LOCAL_FILE];
|
|
568
|
+
function parseEnvFile(content) {
|
|
569
|
+
const result = {};
|
|
570
|
+
for (const line of content.split("\n")) {
|
|
571
|
+
const trimmed = line.trim();
|
|
572
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
573
|
+
const eq = trimmed.indexOf("=");
|
|
574
|
+
if (eq <= 0) continue;
|
|
575
|
+
const key = trimmed.slice(0, eq).trim();
|
|
576
|
+
let value = trimmed.slice(eq + 1).trim();
|
|
577
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
578
|
+
value = value.slice(1, -1);
|
|
671
579
|
}
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
580
|
+
result[key] = value;
|
|
581
|
+
}
|
|
582
|
+
return result;
|
|
583
|
+
}
|
|
584
|
+
function readEnvLocal(projectRoot) {
|
|
585
|
+
const path = resolve4(projectRoot, ENV_LOCAL_FILE);
|
|
586
|
+
if (!existsSync3(path)) return {};
|
|
587
|
+
return parseEnvFile(readFileSync5(path, "utf8"));
|
|
588
|
+
}
|
|
589
|
+
function formatEnvLocal(entries) {
|
|
590
|
+
const lines = [
|
|
591
|
+
"# Local secrets \u2014 gitignored. Created by wp init / wp setup (hybrid CDN).",
|
|
592
|
+
"# PORTAL_CDN_PUBLISH_TOKEN needs repo push on your portal.cdn.json mirror.",
|
|
593
|
+
""
|
|
594
|
+
];
|
|
595
|
+
for (const [key, value] of Object.entries(entries)) {
|
|
596
|
+
if (value.includes(" ") || value.includes("#")) {
|
|
597
|
+
lines.push(`${key}="${value.replace(/"/g, '\\"')}"`);
|
|
676
598
|
} else {
|
|
677
|
-
|
|
678
|
-
validateFieldNode(child, `${path}.children[${index}]`, issues);
|
|
679
|
-
});
|
|
599
|
+
lines.push(`${key}=${value}`);
|
|
680
600
|
}
|
|
681
601
|
}
|
|
602
|
+
return `${lines.join("\n").trimEnd()}
|
|
603
|
+
`;
|
|
682
604
|
}
|
|
683
|
-
function
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
|
|
605
|
+
function writeEnvLocal(projectRoot, entries) {
|
|
606
|
+
const path = resolve4(projectRoot, ENV_LOCAL_FILE);
|
|
607
|
+
const existing = existsSync3(path) ? parseEnvFile(readFileSync5(path, "utf8")) : {};
|
|
608
|
+
writeFileSync2(path, formatEnvLocal({ ...existing, ...entries }));
|
|
609
|
+
}
|
|
610
|
+
function mergeProjectEnv(projectRoot, base = process.env) {
|
|
611
|
+
const local = readEnvLocal(projectRoot);
|
|
612
|
+
const merged = { ...base };
|
|
613
|
+
for (const [key, value] of Object.entries(local)) {
|
|
614
|
+
if (merged[key] == null || merged[key] === "") {
|
|
615
|
+
merged[key] = value;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
return merged;
|
|
619
|
+
}
|
|
620
|
+
function ensureEnvFilesGitignore(projectRoot) {
|
|
621
|
+
const gitignorePath = resolve4(projectRoot, ".gitignore");
|
|
622
|
+
const existing = existsSync3(gitignorePath) ? readFileSync5(gitignorePath, "utf8") : "";
|
|
623
|
+
const toAppend = ENV_GITIGNORE_LINES.filter((line) => !existing.includes(line)).join("\n");
|
|
624
|
+
if (!toAppend) return;
|
|
625
|
+
writeFileSync2(gitignorePath, `${existing.trimEnd()}
|
|
626
|
+
|
|
627
|
+
# Env files (secrets)
|
|
628
|
+
${toAppend}
|
|
629
|
+
`);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// src/lib/cdn-github-setup.ts
|
|
633
|
+
var PORTAL_CDN_PUBLISH_TOKEN_ENV = "PORTAL_CDN_PUBLISH_TOKEN";
|
|
634
|
+
var PORTAL_CDN_JSON = "portal.cdn.json";
|
|
635
|
+
var PLACEHOLDER_GITHUB = /your-org|your-portal-cdn-mirror/i;
|
|
636
|
+
function isPlaceholderGithubSlug(github) {
|
|
637
|
+
return PLACEHOLDER_GITHUB.test(github);
|
|
638
|
+
}
|
|
639
|
+
function defaultPortalCdnJson() {
|
|
640
|
+
const examplePath = kitPath("templates", "shared", "portal.cdn.json.example");
|
|
641
|
+
if (existsSync4(examplePath)) {
|
|
642
|
+
return JSON.parse(readFileSync6(examplePath, "utf8"));
|
|
643
|
+
}
|
|
644
|
+
return {
|
|
645
|
+
publishMode: "jsdelivr",
|
|
646
|
+
github: "your-org/your-portal-cdn-mirror",
|
|
647
|
+
gitRemote: "https://github.com/your-org/your-portal-cdn-mirror.git",
|
|
648
|
+
cdnPathInRepo: "dist/cdn",
|
|
649
|
+
defaultBranch: "main"
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
function readPortalCdnJson(projectRoot) {
|
|
653
|
+
const path = resolve5(projectRoot, PORTAL_CDN_JSON);
|
|
654
|
+
if (!existsSync4(path)) return null;
|
|
655
|
+
try {
|
|
656
|
+
return JSON.parse(readFileSync6(path, "utf8"));
|
|
657
|
+
} catch {
|
|
658
|
+
return null;
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
function normalizeGithubSlug(input) {
|
|
662
|
+
const trimmed = input.trim().replace(/^https:\/\/github\.com\//, "").replace(/\.git$/, "");
|
|
663
|
+
const parts = trimmed.split("/").filter(Boolean);
|
|
664
|
+
if (parts.length !== 2) return null;
|
|
665
|
+
return `${parts[0]}/${parts[1]}`;
|
|
666
|
+
}
|
|
667
|
+
function portalCdnJsonFromGithub(github, base) {
|
|
668
|
+
const slug = normalizeGithubSlug(github);
|
|
669
|
+
if (!slug) {
|
|
670
|
+
throw new Error(`Invalid GitHub repo \u2014 use org/repo (got "${github}")`);
|
|
671
|
+
}
|
|
672
|
+
return {
|
|
673
|
+
publishMode: base?.publishMode ?? "jsdelivr",
|
|
674
|
+
github: slug,
|
|
675
|
+
gitRemote: `https://github.com/${slug}.git`,
|
|
676
|
+
cdnPathInRepo: base?.cdnPathInRepo ?? "dist/cdn",
|
|
677
|
+
defaultBranch: base?.defaultBranch ?? "main"
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
function hasCdnPublishToken(projectRoot) {
|
|
681
|
+
if (process.env[PORTAL_CDN_PUBLISH_TOKEN_ENV]?.trim()) return true;
|
|
682
|
+
if (process.env.GITHUB_TOKEN?.trim()) return true;
|
|
683
|
+
return Boolean(readEnvLocal(projectRoot)[PORTAL_CDN_PUBLISH_TOKEN_ENV]?.trim());
|
|
684
|
+
}
|
|
685
|
+
function hasConfiguredCdnGithubMirror(projectRoot) {
|
|
686
|
+
const config = readPortalCdnJson(projectRoot);
|
|
687
|
+
const github = config?.github ?? "";
|
|
688
|
+
return Boolean(github) && !PLACEHOLDER_GITHUB.test(github);
|
|
689
|
+
}
|
|
690
|
+
function githubDefaultForPrompt(projectRoot) {
|
|
691
|
+
const existing = readPortalCdnJson(projectRoot);
|
|
692
|
+
if (existing?.github && !PLACEHOLDER_GITHUB.test(existing.github)) {
|
|
693
|
+
return existing.github;
|
|
694
|
+
}
|
|
695
|
+
return defaultPortalCdnJson().github;
|
|
696
|
+
}
|
|
697
|
+
function needsCdnGitHubSetup(projectRoot) {
|
|
698
|
+
return !hasConfiguredCdnGithubMirror(projectRoot) || !hasCdnPublishToken(projectRoot);
|
|
699
|
+
}
|
|
700
|
+
function applyCdnGitHubSetup(projectRoot, input) {
|
|
701
|
+
const existing = readPortalCdnJson(projectRoot) ?? defaultPortalCdnJson();
|
|
702
|
+
const updated = portalCdnJsonFromGithub(input.github, existing);
|
|
703
|
+
writeFileSync3(resolve5(projectRoot, PORTAL_CDN_JSON), `${JSON.stringify(updated, null, 2)}
|
|
704
|
+
`);
|
|
705
|
+
if (input.token?.trim()) {
|
|
706
|
+
writeEnvLocal(projectRoot, { [PORTAL_CDN_PUBLISH_TOKEN_ENV]: input.token.trim() });
|
|
707
|
+
}
|
|
708
|
+
ensureEnvFilesGitignore(projectRoot);
|
|
709
|
+
}
|
|
710
|
+
async function promptCdnGitHubSetup(options = {}) {
|
|
711
|
+
if (options.skip) return null;
|
|
712
|
+
const projectRoot = options.projectRoot ?? process.cwd();
|
|
713
|
+
if (!needsCdnGitHubSetup(projectRoot)) {
|
|
714
|
+
return null;
|
|
715
|
+
}
|
|
716
|
+
let github = options.github?.trim();
|
|
717
|
+
if (!github && hasConfiguredCdnGithubMirror(projectRoot)) {
|
|
718
|
+
github = readPortalCdnJson(projectRoot)?.github;
|
|
719
|
+
}
|
|
720
|
+
if (!github) {
|
|
721
|
+
const answer = await p2.text({
|
|
722
|
+
message: "Public GitHub CDN mirror (org/repo)",
|
|
723
|
+
placeholder: options.githubDefault ?? "your-org/your-portal-cdn-mirror",
|
|
724
|
+
initialValue: options.githubDefault,
|
|
725
|
+
validate: (value) => {
|
|
726
|
+
const slug = normalizeGithubSlug(value ?? "");
|
|
727
|
+
return slug ? void 0 : "Enter a public repo as org/repo (jsDelivr serves from it)";
|
|
728
|
+
}
|
|
729
|
+
});
|
|
730
|
+
if (p2.isCancel(answer)) return null;
|
|
731
|
+
github = normalizeGithubSlug(answer) ?? void 0;
|
|
732
|
+
}
|
|
733
|
+
if (!github) return null;
|
|
734
|
+
if (hasCdnPublishToken(projectRoot)) {
|
|
735
|
+
return { github };
|
|
736
|
+
}
|
|
737
|
+
const tokenAnswer = await p2.password({
|
|
738
|
+
message: "GitHub token (repo push on CDN mirror)",
|
|
739
|
+
validate: (value) => {
|
|
740
|
+
if (!value?.trim()) return "Required for wp publish \u2014 create a fine-grained PAT with Contents: Read and write";
|
|
741
|
+
return void 0;
|
|
742
|
+
}
|
|
743
|
+
});
|
|
744
|
+
if (p2.isCancel(tokenAnswer)) return null;
|
|
745
|
+
return { github, token: tokenAnswer.trim() };
|
|
746
|
+
}
|
|
747
|
+
function formatCdnSetupNote(_projectRoot) {
|
|
748
|
+
return `CDN config written \u2014 edit ${PORTAL_CDN_JSON} if needed; token saved to ${ENV_LOCAL_FILE} (gitignored)`;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// src/lib/wphs-workspace.ts
|
|
752
|
+
var ensureModulePromise;
|
|
753
|
+
function loadEnsureModule() {
|
|
754
|
+
if (!ensureModulePromise) {
|
|
755
|
+
ensureModulePromise = Promise.resolve().then(async () => {
|
|
756
|
+
const mod = await import("./ensure-wphs-workspace-5S3GTO6K.js");
|
|
757
|
+
return mod;
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
return ensureModulePromise;
|
|
761
|
+
}
|
|
762
|
+
async function ensureProjectWphsWorkspace(projectRoot) {
|
|
763
|
+
const { ensureWphsWorkspace } = await loadEnsureModule();
|
|
764
|
+
return ensureWphsWorkspace(projectRoot, getKitRoot());
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
// src/lib/template-dev-deps.ts
|
|
768
|
+
var VITE_DEV_PEER_DEPS = {
|
|
769
|
+
vite: "^6.1.0",
|
|
770
|
+
"@vitejs/plugin-react": "^4.3.4",
|
|
771
|
+
"babel-plugin-react-compiler": "^1.0.0",
|
|
772
|
+
"@tailwindcss/vite": "^4.0.6",
|
|
773
|
+
"@tanstack/router-plugin": "^1.121.2",
|
|
774
|
+
"@tanstack/react-router": "^1.121.2",
|
|
775
|
+
"@dnd-kit/core": "^6.3.1",
|
|
776
|
+
"@dnd-kit/sortable": "^10.0.0",
|
|
777
|
+
"@dnd-kit/utilities": "^3.2.2",
|
|
778
|
+
react: "^19.0.0",
|
|
779
|
+
"react-dom": "^19.0.0",
|
|
780
|
+
tailwindcss: "^4.0.6"
|
|
781
|
+
};
|
|
782
|
+
var KIT_DEV_DEP = "@woodsportal/hubspot-kit";
|
|
783
|
+
var KIT_DEV_DEP_VERSION = "^1.0.30";
|
|
784
|
+
var MODULE_CONFIG_UI_DEPS = [
|
|
785
|
+
"@dnd-kit/core",
|
|
786
|
+
"@dnd-kit/sortable",
|
|
787
|
+
"@dnd-kit/utilities"
|
|
788
|
+
];
|
|
789
|
+
function templateDevDependencies() {
|
|
790
|
+
return {
|
|
791
|
+
[KIT_DEV_DEP]: KIT_DEV_DEP_VERSION,
|
|
792
|
+
...VITE_DEV_PEER_DEPS
|
|
793
|
+
};
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
// src/lib/hubspot-fields-json.ts
|
|
797
|
+
var HUBSPOT_MODULE_FIELD_TYPES = /* @__PURE__ */ new Set([
|
|
798
|
+
"boolean",
|
|
799
|
+
"choice",
|
|
800
|
+
"color",
|
|
801
|
+
"cta",
|
|
802
|
+
"date",
|
|
803
|
+
"datetime",
|
|
804
|
+
"email",
|
|
805
|
+
"embed",
|
|
806
|
+
"file",
|
|
807
|
+
"followup_email",
|
|
808
|
+
"font",
|
|
809
|
+
"form",
|
|
810
|
+
"group",
|
|
811
|
+
"html",
|
|
812
|
+
"hubdb_row",
|
|
813
|
+
"hubdb_table",
|
|
814
|
+
"icon",
|
|
815
|
+
"image",
|
|
816
|
+
"link",
|
|
817
|
+
"logo",
|
|
818
|
+
"meeting",
|
|
819
|
+
"menu",
|
|
820
|
+
"number",
|
|
821
|
+
"page",
|
|
822
|
+
"payment",
|
|
823
|
+
"richtext",
|
|
824
|
+
"salesforce_campaign",
|
|
825
|
+
"simple_menu",
|
|
826
|
+
"spacing",
|
|
827
|
+
"tag",
|
|
828
|
+
"text",
|
|
829
|
+
"url",
|
|
830
|
+
"video",
|
|
831
|
+
"workflow"
|
|
832
|
+
]);
|
|
833
|
+
function isFieldRecord(value) {
|
|
834
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
835
|
+
}
|
|
836
|
+
function validateFieldNode(node, path, issues) {
|
|
837
|
+
if (!isFieldRecord(node)) {
|
|
838
|
+
issues.push({ path, message: "expected a field object" });
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
841
|
+
const type = node.type;
|
|
842
|
+
if (typeof type !== "string" || type.length === 0) {
|
|
843
|
+
issues.push({ path: `${path}.type`, message: "missing field type" });
|
|
844
|
+
} else if (!HUBSPOT_MODULE_FIELD_TYPES.has(type)) {
|
|
845
|
+
issues.push({
|
|
846
|
+
path: `${path}.type`,
|
|
847
|
+
message: type === "textarea" ? `'textarea' is not a HubSpot field type \u2014 use "type": "text" with "display": "textarea"` : `'${type}' is not a valid HubSpot field type`
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
const tab = node.tab;
|
|
851
|
+
if (tab === "STYLE" && type !== "group") {
|
|
852
|
+
issues.push({
|
|
853
|
+
path: `${path}.tab`,
|
|
854
|
+
message: 'STYLE tab fields must live inside a "type": "group" with "tab": "STYLE" (HubSpot page editor Styles tab)'
|
|
855
|
+
});
|
|
856
|
+
}
|
|
857
|
+
if (type !== "group") {
|
|
858
|
+
const name = node.name;
|
|
859
|
+
if (typeof name !== "string" || name.trim().length === 0) {
|
|
860
|
+
issues.push({ path: `${path}.name`, message: "missing field name" });
|
|
861
|
+
}
|
|
862
|
+
const label = node.label;
|
|
863
|
+
if (typeof label !== "string" || label.trim().length === 0) {
|
|
864
|
+
issues.push({ path: `${path}.label`, message: "missing field label" });
|
|
865
|
+
}
|
|
866
|
+
} else {
|
|
867
|
+
const children = node.children;
|
|
868
|
+
if (!Array.isArray(children)) {
|
|
869
|
+
issues.push({ path: `${path}.children`, message: "group fields require a children array" });
|
|
870
|
+
} else {
|
|
871
|
+
children.forEach((child, index) => {
|
|
872
|
+
validateFieldNode(child, `${path}.children[${index}]`, issues);
|
|
873
|
+
});
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
function validateHubSpotFieldsJson(fields) {
|
|
878
|
+
if (!Array.isArray(fields)) {
|
|
879
|
+
return [{ path: "$", message: "fields.json must be a top-level array" }];
|
|
880
|
+
}
|
|
881
|
+
const issues = [];
|
|
882
|
+
fields.forEach((field, index) => {
|
|
689
883
|
validateFieldNode(field, `[${index}]`, issues);
|
|
690
884
|
});
|
|
691
885
|
return issues;
|
|
692
886
|
}
|
|
693
887
|
|
|
694
888
|
// src/lib/developer-slug.ts
|
|
695
|
-
import { existsSync as
|
|
696
|
-
import { resolve as
|
|
697
|
-
import * as
|
|
889
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
|
|
890
|
+
import { resolve as resolve6 } from "path";
|
|
891
|
+
import * as p3 from "@clack/prompts";
|
|
698
892
|
var SLUG_MAX_LEN = 12;
|
|
699
893
|
var SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9-]{0,10}[a-z0-9])?$/;
|
|
700
894
|
var DEVELOPER_CONFIG_FILE = "developer.json";
|
|
701
895
|
function developerConfigPath(projectRoot) {
|
|
702
|
-
return
|
|
896
|
+
return resolve6(projectRoot, ".wphs", "config", DEVELOPER_CONFIG_FILE);
|
|
703
897
|
}
|
|
704
898
|
function sanitizeDevSlug(input) {
|
|
705
899
|
if (!input?.trim()) return void 0;
|
|
@@ -711,9 +905,9 @@ function readDeveloperSlug(projectRoot) {
|
|
|
711
905
|
const fromEnv = sanitizeDevSlug(process.env.PORTAL_CDN_DEV_SLUG) ?? sanitizeDevSlug(process.env.MODULE_CDN_DEV_SLUG);
|
|
712
906
|
if (fromEnv) return fromEnv;
|
|
713
907
|
const path = developerConfigPath(projectRoot);
|
|
714
|
-
if (!
|
|
908
|
+
if (!existsSync5(path)) return void 0;
|
|
715
909
|
try {
|
|
716
|
-
const raw = JSON.parse(
|
|
910
|
+
const raw = JSON.parse(readFileSync7(path, "utf8"));
|
|
717
911
|
return sanitizeDevSlug(raw.slug);
|
|
718
912
|
} catch {
|
|
719
913
|
return void 0;
|
|
@@ -725,8 +919,8 @@ function writeDeveloperSlug(projectRoot, slug) {
|
|
|
725
919
|
throw new Error("Invalid CDN dev slug \u2014 use lowercase letters, numbers, and hyphens (max 12 chars)");
|
|
726
920
|
}
|
|
727
921
|
const path = developerConfigPath(projectRoot);
|
|
728
|
-
|
|
729
|
-
|
|
922
|
+
mkdirSync2(resolve6(path, ".."), { recursive: true });
|
|
923
|
+
writeFileSync4(path, `${JSON.stringify({ slug: normalized }, null, 2)}
|
|
730
924
|
`);
|
|
731
925
|
}
|
|
732
926
|
function needsDeveloperSlugSetup(projectRoot) {
|
|
@@ -736,7 +930,7 @@ async function promptDeveloperSlug(projectRoot) {
|
|
|
736
930
|
if (!needsDeveloperSlugSetup(projectRoot)) {
|
|
737
931
|
return readDeveloperSlug(projectRoot);
|
|
738
932
|
}
|
|
739
|
-
const slug = await
|
|
933
|
+
const slug = await p3.text({
|
|
740
934
|
message: "CDN dev slug (unique per developer for shared CDN mirror \u2014 e.g. your first name)",
|
|
741
935
|
placeholder: "manab",
|
|
742
936
|
validate: (value) => {
|
|
@@ -746,7 +940,7 @@ async function promptDeveloperSlug(projectRoot) {
|
|
|
746
940
|
return void 0;
|
|
747
941
|
}
|
|
748
942
|
});
|
|
749
|
-
if (
|
|
943
|
+
if (p3.isCancel(slug) || typeof slug !== "string") {
|
|
750
944
|
return void 0;
|
|
751
945
|
}
|
|
752
946
|
writeDeveloperSlug(projectRoot, slug);
|
|
@@ -788,37 +982,37 @@ function resolveModuleBuildIdMetaForCli(projectRoot, env = process.env) {
|
|
|
788
982
|
}
|
|
789
983
|
|
|
790
984
|
// src/lib/upload-only-module.ts
|
|
791
|
-
import { existsSync as
|
|
792
|
-
import { resolve as
|
|
985
|
+
import { existsSync as existsSync6 } from "fs";
|
|
986
|
+
import { resolve as resolve7 } from "path";
|
|
793
987
|
function isUploadOnlyPreexistingProject(projectRoot, moduleArtifactDirs) {
|
|
794
988
|
if (moduleArtifactDirs.length === 0) return false;
|
|
795
|
-
if (
|
|
796
|
-
if (
|
|
797
|
-
return moduleArtifactDirs.some((dir) =>
|
|
989
|
+
if (existsSync6(resolve7(projectRoot, "package.json"))) return false;
|
|
990
|
+
if (existsSync6(resolve7(projectRoot, "src"))) return false;
|
|
991
|
+
return moduleArtifactDirs.some((dir) => existsSync6(resolve7(dir, "fields.json")));
|
|
798
992
|
}
|
|
799
993
|
|
|
800
994
|
// src/vite/resolve-dev-config.ts
|
|
801
|
-
import { existsSync as
|
|
995
|
+
import { existsSync as existsSync7 } from "fs";
|
|
802
996
|
import { platform } from "os";
|
|
803
|
-
import { resolve as
|
|
997
|
+
import { resolve as resolve8 } from "path";
|
|
804
998
|
var VITE_JS_SEGMENTS = ["node_modules", "vite", "bin", "vite.js"];
|
|
805
999
|
function isProjectViteInstalled(projectRoot) {
|
|
806
|
-
return
|
|
1000
|
+
return existsSync7(resolve8(projectRoot, ...VITE_JS_SEGMENTS));
|
|
807
1001
|
}
|
|
808
1002
|
function resolveProjectViteSpawn(projectRoot) {
|
|
809
|
-
const viteJs =
|
|
810
|
-
if (
|
|
1003
|
+
const viteJs = resolve8(projectRoot, ...VITE_JS_SEGMENTS);
|
|
1004
|
+
if (existsSync7(viteJs)) {
|
|
811
1005
|
return { command: process.execPath, prefixArgs: [viteJs] };
|
|
812
1006
|
}
|
|
813
|
-
const binDir =
|
|
1007
|
+
const binDir = resolve8(projectRoot, "node_modules", ".bin");
|
|
814
1008
|
if (platform() === "win32") {
|
|
815
|
-
const winCmd =
|
|
816
|
-
if (
|
|
1009
|
+
const winCmd = resolve8(binDir, "vite.cmd");
|
|
1010
|
+
if (existsSync7(winCmd)) {
|
|
817
1011
|
return { command: winCmd, prefixArgs: [] };
|
|
818
1012
|
}
|
|
819
1013
|
}
|
|
820
|
-
const posixShim =
|
|
821
|
-
if (
|
|
1014
|
+
const posixShim = resolve8(binDir, "vite");
|
|
1015
|
+
if (existsSync7(posixShim)) {
|
|
822
1016
|
return { command: posixShim, prefixArgs: [] };
|
|
823
1017
|
}
|
|
824
1018
|
return null;
|
|
@@ -905,10 +1099,10 @@ function envFileForTier(tier) {
|
|
|
905
1099
|
return ".env.prod";
|
|
906
1100
|
}
|
|
907
1101
|
function readEnvFileValue(projectRoot, file, key) {
|
|
908
|
-
const path =
|
|
909
|
-
if (!
|
|
1102
|
+
const path = resolve9(projectRoot, file);
|
|
1103
|
+
if (!existsSync8(path)) return void 0;
|
|
910
1104
|
try {
|
|
911
|
-
const content =
|
|
1105
|
+
const content = readFileSync8(path, "utf8");
|
|
912
1106
|
const match = content.match(new RegExp(`^${key}=(.*)$`, "m"));
|
|
913
1107
|
return match?.[1]?.trim();
|
|
914
1108
|
} catch {
|
|
@@ -917,8 +1111,8 @@ function readEnvFileValue(projectRoot, file, key) {
|
|
|
917
1111
|
}
|
|
918
1112
|
function checkEnvFiles(ctx) {
|
|
919
1113
|
const checks = [];
|
|
920
|
-
const localPath =
|
|
921
|
-
const localOk =
|
|
1114
|
+
const localPath = resolve9(ctx.projectRoot, ".env.local");
|
|
1115
|
+
const localOk = existsSync8(localPath);
|
|
922
1116
|
checks.push({
|
|
923
1117
|
name: "env-local",
|
|
924
1118
|
ok: localOk,
|
|
@@ -926,8 +1120,8 @@ function checkEnvFiles(ctx) {
|
|
|
926
1120
|
remediation: localOk ? void 0 : "wp setup or copy .env.local.example \u2192 .env.local",
|
|
927
1121
|
severity: "warn"
|
|
928
1122
|
});
|
|
929
|
-
const legacyLocalDev =
|
|
930
|
-
if (!localOk &&
|
|
1123
|
+
const legacyLocalDev = resolve9(ctx.projectRoot, ".env.dev");
|
|
1124
|
+
if (!localOk && existsSync8(legacyLocalDev)) {
|
|
931
1125
|
const nodeEnv = readEnvFileValue(ctx.projectRoot, ".env.dev", "VITE_NODE_ENV");
|
|
932
1126
|
const hasDevMode = readEnvFileValue(ctx.projectRoot, ".env.dev", "VITE_DEV_MODE") != null;
|
|
933
1127
|
const looksLikeLocal = hasDevMode || nodeEnv === "local" || nodeEnv == null || nodeEnv === "development";
|
|
@@ -943,7 +1137,7 @@ function checkEnvFiles(ctx) {
|
|
|
943
1137
|
}
|
|
944
1138
|
for (const tier of ctx.config.env.tiers) {
|
|
945
1139
|
const file = envFileForTier(tier);
|
|
946
|
-
const ok =
|
|
1140
|
+
const ok = existsSync8(resolve9(ctx.projectRoot, file));
|
|
947
1141
|
if (tier === "stg") {
|
|
948
1142
|
checks.push({
|
|
949
1143
|
name: "env-stg",
|
|
@@ -966,8 +1160,8 @@ function checkEnvFiles(ctx) {
|
|
|
966
1160
|
}
|
|
967
1161
|
function checkLegacyBuildIdFile(ctx) {
|
|
968
1162
|
if (isThemeProject(ctx.config) || !usesCdnPipeline(ctx.config)) return null;
|
|
969
|
-
const legacy =
|
|
970
|
-
if (!
|
|
1163
|
+
const legacy = resolve9(ctx.projectRoot, ".portal-build-id");
|
|
1164
|
+
if (!existsSync8(legacy)) return null;
|
|
971
1165
|
return {
|
|
972
1166
|
name: "legacy-build-id",
|
|
973
1167
|
ok: false,
|
|
@@ -978,8 +1172,8 @@ function checkLegacyBuildIdFile(ctx) {
|
|
|
978
1172
|
}
|
|
979
1173
|
function checkBuildId(ctx) {
|
|
980
1174
|
if (isThemeProject(ctx.config) || !usesCdnPipeline(ctx.config)) return null;
|
|
981
|
-
const file =
|
|
982
|
-
const ok =
|
|
1175
|
+
const file = resolve9(ctx.projectRoot, ctx.config.cdn.buildIdFile);
|
|
1176
|
+
const ok = existsSync8(file);
|
|
983
1177
|
return {
|
|
984
1178
|
name: "build-id",
|
|
985
1179
|
ok,
|
|
@@ -1033,7 +1227,7 @@ function checkDevSlug(ctx) {
|
|
|
1033
1227
|
};
|
|
1034
1228
|
}
|
|
1035
1229
|
function isUploadOnlyProject(ctx) {
|
|
1036
|
-
const moduleDirs = [
|
|
1230
|
+
const moduleDirs = [resolve9(ctx.projectRoot, ctx.config.module.uploadPath)];
|
|
1037
1231
|
return isUploadOnlyPreexistingProject(ctx.projectRoot, moduleDirs);
|
|
1038
1232
|
}
|
|
1039
1233
|
function checkUploadOnlyNote(ctx) {
|
|
@@ -1047,8 +1241,8 @@ function checkUploadOnlyNote(ctx) {
|
|
|
1047
1241
|
};
|
|
1048
1242
|
}
|
|
1049
1243
|
function checkDevViteConfig(ctx) {
|
|
1050
|
-
const path =
|
|
1051
|
-
const ok =
|
|
1244
|
+
const path = resolve9(ctx.projectRoot, "vite.config.dev.mjs");
|
|
1245
|
+
const ok = existsSync8(path);
|
|
1052
1246
|
return {
|
|
1053
1247
|
name: "vite-dev-config",
|
|
1054
1248
|
ok,
|
|
@@ -1059,8 +1253,8 @@ function checkDevViteConfig(ctx) {
|
|
|
1059
1253
|
}
|
|
1060
1254
|
function checkMetaJson(ctx) {
|
|
1061
1255
|
if (isThemeProject(ctx.config)) return null;
|
|
1062
|
-
const path =
|
|
1063
|
-
if (!
|
|
1256
|
+
const path = resolve9(ctx.projectRoot, "public/meta.json");
|
|
1257
|
+
if (!existsSync8(path)) {
|
|
1064
1258
|
return {
|
|
1065
1259
|
name: "meta-json",
|
|
1066
1260
|
ok: false,
|
|
@@ -1070,7 +1264,7 @@ function checkMetaJson(ctx) {
|
|
|
1070
1264
|
};
|
|
1071
1265
|
}
|
|
1072
1266
|
try {
|
|
1073
|
-
const meta = JSON.parse(
|
|
1267
|
+
const meta = JSON.parse(readFileSync8(path, "utf8"));
|
|
1074
1268
|
const hasContentTypes = Array.isArray(meta.content_types) && meta.content_types.length > 0;
|
|
1075
1269
|
return {
|
|
1076
1270
|
name: "meta-json",
|
|
@@ -1092,8 +1286,8 @@ function checkMetaJson(ctx) {
|
|
|
1092
1286
|
}
|
|
1093
1287
|
async function checkCdnMirrorPublic(ctx) {
|
|
1094
1288
|
if (isThemeProject(ctx.config) || !usesCdnPipeline(ctx.config)) return null;
|
|
1095
|
-
const mirrorPath =
|
|
1096
|
-
if (!
|
|
1289
|
+
const mirrorPath = resolve9(ctx.projectRoot, ctx.config.cdn?.mirror ?? "portal.cdn.json");
|
|
1290
|
+
if (!existsSync8(mirrorPath)) {
|
|
1097
1291
|
return {
|
|
1098
1292
|
name: "cdn-mirror-public",
|
|
1099
1293
|
ok: false,
|
|
@@ -1104,7 +1298,7 @@ async function checkCdnMirrorPublic(ctx) {
|
|
|
1104
1298
|
}
|
|
1105
1299
|
let github;
|
|
1106
1300
|
try {
|
|
1107
|
-
const mirror = JSON.parse(
|
|
1301
|
+
const mirror = JSON.parse(readFileSync8(mirrorPath, "utf8"));
|
|
1108
1302
|
github = mirror.github?.trim();
|
|
1109
1303
|
} catch {
|
|
1110
1304
|
return {
|
|
@@ -1124,6 +1318,15 @@ async function checkCdnMirrorPublic(ctx) {
|
|
|
1124
1318
|
severity: "error"
|
|
1125
1319
|
};
|
|
1126
1320
|
}
|
|
1321
|
+
if (isPlaceholderGithubSlug(github)) {
|
|
1322
|
+
return {
|
|
1323
|
+
name: "cdn-mirror-public",
|
|
1324
|
+
ok: false,
|
|
1325
|
+
message: `portal.cdn.json still has placeholder github slug (${github})`,
|
|
1326
|
+
remediation: "Run wp setup and set a real public org/repo for jsDelivr",
|
|
1327
|
+
severity: "warn"
|
|
1328
|
+
};
|
|
1329
|
+
}
|
|
1127
1330
|
try {
|
|
1128
1331
|
const res = await fetch(`https://api.github.com/repos/${github}`, {
|
|
1129
1332
|
headers: { Accept: "application/vnd.github+json" }
|
|
@@ -1156,8 +1359,8 @@ async function checkCdnMirrorPublic(ctx) {
|
|
|
1156
1359
|
severity: "warn"
|
|
1157
1360
|
};
|
|
1158
1361
|
}
|
|
1159
|
-
const manifestPath =
|
|
1160
|
-
if (!
|
|
1362
|
+
const manifestPath = resolve9(ctx.projectRoot, "dist/cdn/manifest.json");
|
|
1363
|
+
if (!existsSync8(manifestPath)) {
|
|
1161
1364
|
return {
|
|
1162
1365
|
name: "cdn-mirror-public",
|
|
1163
1366
|
ok: true,
|
|
@@ -1166,7 +1369,7 @@ async function checkCdnMirrorPublic(ctx) {
|
|
|
1166
1369
|
};
|
|
1167
1370
|
}
|
|
1168
1371
|
try {
|
|
1169
|
-
const manifest = JSON.parse(
|
|
1372
|
+
const manifest = JSON.parse(readFileSync8(manifestPath, "utf8"));
|
|
1170
1373
|
const appUrl = manifest.scripts?.find((s) => s.name === "app")?.url;
|
|
1171
1374
|
if (!appUrl) {
|
|
1172
1375
|
return {
|
|
@@ -1176,20 +1379,36 @@ async function checkCdnMirrorPublic(ctx) {
|
|
|
1176
1379
|
severity: "error"
|
|
1177
1380
|
};
|
|
1178
1381
|
}
|
|
1179
|
-
const
|
|
1180
|
-
if (!
|
|
1382
|
+
const appHead = await fetch(appUrl, { method: "HEAD" });
|
|
1383
|
+
if (!appHead.ok) {
|
|
1181
1384
|
return {
|
|
1182
1385
|
name: "cdn-mirror-public",
|
|
1183
1386
|
ok: false,
|
|
1184
|
-
message: `jsDelivr app chunk not reachable (HTTP ${
|
|
1387
|
+
message: `jsDelivr app chunk not reachable (HTTP ${appHead.status}) \u2014 HubSpot preview will be blank`,
|
|
1185
1388
|
remediation: "Run wp deploy (publish CDN + upload module) after making the mirror repo public",
|
|
1186
1389
|
severity: "error"
|
|
1187
1390
|
};
|
|
1188
1391
|
}
|
|
1392
|
+
const lazyChunks = (manifest.scripts ?? []).filter(
|
|
1393
|
+
(entry) => typeof entry.url === "string" && (entry.file?.startsWith("chunks/") || entry.url.includes("/chunks/"))
|
|
1394
|
+
);
|
|
1395
|
+
for (const chunk of lazyChunks) {
|
|
1396
|
+
const chunkHead = await fetch(chunk.url, { method: "HEAD" });
|
|
1397
|
+
if (!chunkHead.ok) {
|
|
1398
|
+
return {
|
|
1399
|
+
name: "cdn-mirror-public",
|
|
1400
|
+
ok: false,
|
|
1401
|
+
message: `jsDelivr lazy chunk not reachable: ${chunk.file ?? chunk.url} (HTTP ${chunkHead.status})`,
|
|
1402
|
+
remediation: "Run wp deploy to republish dist/cdn (including chunks/) and wait 5\u201315 min for jsDelivr propagation",
|
|
1403
|
+
severity: "error"
|
|
1404
|
+
};
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
const lazyChunkNote = lazyChunks.length > 0 ? ` and ${lazyChunks.length} lazy chunk(s) are reachable` : "";
|
|
1189
1408
|
return {
|
|
1190
1409
|
name: "cdn-mirror-public",
|
|
1191
1410
|
ok: true,
|
|
1192
|
-
message: `CDN mirror ${github} is public and jsDelivr app chunk
|
|
1411
|
+
message: `CDN mirror ${github} is public and jsDelivr app chunk${lazyChunkNote}`,
|
|
1193
1412
|
severity: "error"
|
|
1194
1413
|
};
|
|
1195
1414
|
} catch {
|
|
@@ -1203,8 +1422,8 @@ async function checkCdnMirrorPublic(ctx) {
|
|
|
1203
1422
|
}
|
|
1204
1423
|
function checkFieldsJson(ctx) {
|
|
1205
1424
|
if (isThemeProject(ctx.config)) return null;
|
|
1206
|
-
const path =
|
|
1207
|
-
if (!
|
|
1425
|
+
const path = resolve9(ctx.projectRoot, "public/fields.json");
|
|
1426
|
+
if (!existsSync8(path)) {
|
|
1208
1427
|
return {
|
|
1209
1428
|
name: "fields-json",
|
|
1210
1429
|
ok: false,
|
|
@@ -1214,7 +1433,7 @@ function checkFieldsJson(ctx) {
|
|
|
1214
1433
|
};
|
|
1215
1434
|
}
|
|
1216
1435
|
try {
|
|
1217
|
-
const fields = JSON.parse(
|
|
1436
|
+
const fields = JSON.parse(readFileSync8(path, "utf8"));
|
|
1218
1437
|
const issues = validateHubSpotFieldsJson(fields);
|
|
1219
1438
|
if (issues.length === 0) {
|
|
1220
1439
|
return {
|
|
@@ -1244,11 +1463,11 @@ function checkFieldsJson(ctx) {
|
|
|
1244
1463
|
}
|
|
1245
1464
|
}
|
|
1246
1465
|
function readProjectFile(projectRoot, relativePath) {
|
|
1247
|
-
const path =
|
|
1248
|
-
if (!
|
|
1466
|
+
const path = resolve9(projectRoot, relativePath);
|
|
1467
|
+
if (!existsSync8(path)) {
|
|
1249
1468
|
return null;
|
|
1250
1469
|
}
|
|
1251
|
-
return
|
|
1470
|
+
return readFileSync8(path, "utf8");
|
|
1252
1471
|
}
|
|
1253
1472
|
function checkCssPipeline(ctx) {
|
|
1254
1473
|
if (isThemeProject(ctx.config) || isUploadOnlyProject(ctx)) {
|
|
@@ -1315,9 +1534,9 @@ function checkCssPipeline(ctx) {
|
|
|
1315
1534
|
}
|
|
1316
1535
|
const moduleOutDir = ctx.config.module?.outDir;
|
|
1317
1536
|
if (moduleOutDir && usesCdnPipeline(ctx.config)) {
|
|
1318
|
-
const moduleCssPath =
|
|
1319
|
-
if (
|
|
1320
|
-
const css =
|
|
1537
|
+
const moduleCssPath = resolve9(ctx.projectRoot, moduleOutDir, "module.css");
|
|
1538
|
+
if (existsSync8(moduleCssPath)) {
|
|
1539
|
+
const css = readFileSync8(moduleCssPath, "utf8");
|
|
1321
1540
|
const hasDwUtilities = /dw\\:/.test(css) || /\.dw:/.test(css);
|
|
1322
1541
|
checks.push({
|
|
1323
1542
|
name: "css-module-dist-prefixed",
|
|
@@ -1332,8 +1551,8 @@ function checkCssPipeline(ctx) {
|
|
|
1332
1551
|
}
|
|
1333
1552
|
function checkHybridModuleHtml(ctx) {
|
|
1334
1553
|
if (isThemeProject(ctx.config) || !usesCdnPipeline(ctx.config)) return null;
|
|
1335
|
-
const path =
|
|
1336
|
-
if (!
|
|
1554
|
+
const path = resolve9(ctx.projectRoot, "public/module.html");
|
|
1555
|
+
if (!existsSync8(path)) {
|
|
1337
1556
|
return {
|
|
1338
1557
|
name: "module-html",
|
|
1339
1558
|
ok: false,
|
|
@@ -1342,7 +1561,7 @@ function checkHybridModuleHtml(ctx) {
|
|
|
1342
1561
|
severity: "error"
|
|
1343
1562
|
};
|
|
1344
1563
|
}
|
|
1345
|
-
const html =
|
|
1564
|
+
const html = readFileSync8(path, "utf8");
|
|
1346
1565
|
const needs = ["__PORTAL_CDN_BASE__", "require_js", "hubSpotData", 'id="app"'];
|
|
1347
1566
|
const missing = needs.filter((token) => !html.includes(token));
|
|
1348
1567
|
const usesUndeclaredCdnFields = /module\.portal_(cdn_base_url|vendor_script|app_script|app_css)/.test(
|
|
@@ -1360,8 +1579,8 @@ function checkHybridModuleHtml(ctx) {
|
|
|
1360
1579
|
function checkCdnViteConfigs(ctx) {
|
|
1361
1580
|
if (isThemeProject(ctx.config) || !usesCdnPipeline(ctx.config)) return [];
|
|
1362
1581
|
return ["vite.config.cdn-vendor.mjs", "vite.config.cdn-app.mjs"].map((file) => {
|
|
1363
|
-
const path =
|
|
1364
|
-
const ok =
|
|
1582
|
+
const path = resolve9(ctx.projectRoot, file);
|
|
1583
|
+
const ok = existsSync8(path);
|
|
1365
1584
|
return {
|
|
1366
1585
|
name: file.replace(/\./g, "-"),
|
|
1367
1586
|
ok,
|
|
@@ -1373,8 +1592,8 @@ function checkCdnViteConfigs(ctx) {
|
|
|
1373
1592
|
}
|
|
1374
1593
|
function checkDevEntry(ctx) {
|
|
1375
1594
|
const checks = [];
|
|
1376
|
-
const indexHtml =
|
|
1377
|
-
const hasIndex =
|
|
1595
|
+
const indexHtml = resolve9(ctx.projectRoot, "index.html");
|
|
1596
|
+
const hasIndex = existsSync8(indexHtml);
|
|
1378
1597
|
checks.push({
|
|
1379
1598
|
name: "index-html",
|
|
1380
1599
|
ok: hasIndex,
|
|
@@ -1382,8 +1601,8 @@ function checkDevEntry(ctx) {
|
|
|
1382
1601
|
remediation: hasIndex ? void 0 : "Re-run wp init or copy index.html from kit templates",
|
|
1383
1602
|
severity: "error"
|
|
1384
1603
|
});
|
|
1385
|
-
const localDev =
|
|
1386
|
-
const hasEntry =
|
|
1604
|
+
const localDev = resolve9(ctx.projectRoot, ctx.config.entries.localDev);
|
|
1605
|
+
const hasEntry = existsSync8(localDev);
|
|
1387
1606
|
checks.push({
|
|
1388
1607
|
name: "local-dev-entry",
|
|
1389
1608
|
ok: hasEntry,
|
|
@@ -1393,7 +1612,7 @@ function checkDevEntry(ctx) {
|
|
|
1393
1612
|
});
|
|
1394
1613
|
if (hasEntry) {
|
|
1395
1614
|
try {
|
|
1396
|
-
const source =
|
|
1615
|
+
const source = readFileSync8(localDev, "utf8");
|
|
1397
1616
|
const usesKitNodeBootstrap = source.includes("@woodsportal/hubspot-kit/dev/bootstrap-greenfield") || source.includes("@woodsportal/hubspot-kit/dev/bootstrap");
|
|
1398
1617
|
const usesWphsShim = source.includes(".wphs/.kit-bootstrap-greenfield") || source.includes(".wphs/.kit-bootstrap") || source.includes("@wphs/bootstrap-greenfield") || source.includes("@wphs/bootstrap");
|
|
1399
1618
|
checks.push({
|
|
@@ -1410,7 +1629,7 @@ function checkDevEntry(ctx) {
|
|
|
1410
1629
|
}
|
|
1411
1630
|
function checkKitScripts() {
|
|
1412
1631
|
const script = kitPath("scripts", "build-cdn.mjs");
|
|
1413
|
-
const ok =
|
|
1632
|
+
const ok = existsSync8(script);
|
|
1414
1633
|
return {
|
|
1415
1634
|
name: "kit-scripts",
|
|
1416
1635
|
ok,
|
|
@@ -1419,12 +1638,38 @@ function checkKitScripts() {
|
|
|
1419
1638
|
severity: "error"
|
|
1420
1639
|
};
|
|
1421
1640
|
}
|
|
1641
|
+
function checkWphsScaffold(scaffoldError) {
|
|
1642
|
+
if (!scaffoldError) return null;
|
|
1643
|
+
return {
|
|
1644
|
+
name: "wphs-scaffold",
|
|
1645
|
+
ok: false,
|
|
1646
|
+
message: `.wphs workspace scaffold failed \u2014 ${scaffoldError}`,
|
|
1647
|
+
remediation: "Run wp doctor --verbose or wp setup; check write permissions on the project root (Windows: enable Developer Mode if junction creation fails)",
|
|
1648
|
+
severity: "error"
|
|
1649
|
+
};
|
|
1650
|
+
}
|
|
1651
|
+
async function runWphsScaffold(ctx) {
|
|
1652
|
+
if (ctx.global.dryRun) return void 0;
|
|
1653
|
+
try {
|
|
1654
|
+
const paths = await ensureProjectWphsWorkspace(ctx.projectRoot);
|
|
1655
|
+
if (ctx.global.verbose) {
|
|
1656
|
+
ctx.logger.info(`.wphs workspace ready at ${paths.workspaceRoot}`);
|
|
1657
|
+
}
|
|
1658
|
+
return void 0;
|
|
1659
|
+
} catch (error) {
|
|
1660
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1661
|
+
if (ctx.global.verbose) {
|
|
1662
|
+
ctx.logger.error(`Could not scaffold .wphs workspace: ${message}`);
|
|
1663
|
+
}
|
|
1664
|
+
return message;
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1422
1667
|
function checkWphsWorkspace(ctx) {
|
|
1423
1668
|
const workspace = ctx.config.dev.moduleConfigDir;
|
|
1424
1669
|
const checks = [];
|
|
1425
1670
|
for (const legacy of [".dev", ".woodsportal"]) {
|
|
1426
|
-
const legacyPath =
|
|
1427
|
-
if (
|
|
1671
|
+
const legacyPath = resolve9(ctx.projectRoot, legacy);
|
|
1672
|
+
if (existsSync8(legacyPath)) {
|
|
1428
1673
|
checks.push({
|
|
1429
1674
|
name: `legacy-${legacy}`,
|
|
1430
1675
|
ok: false,
|
|
@@ -1434,7 +1679,7 @@ function checkWphsWorkspace(ctx) {
|
|
|
1434
1679
|
});
|
|
1435
1680
|
}
|
|
1436
1681
|
}
|
|
1437
|
-
if (
|
|
1682
|
+
if (existsSync8(resolve9(ctx.projectRoot, ".wp-hs-baseline"))) {
|
|
1438
1683
|
checks.push({
|
|
1439
1684
|
name: "legacy-baseline",
|
|
1440
1685
|
ok: false,
|
|
@@ -1453,15 +1698,15 @@ function checkWphsWorkspace(ctx) {
|
|
|
1453
1698
|
});
|
|
1454
1699
|
return checks;
|
|
1455
1700
|
}
|
|
1456
|
-
const wphsRoot =
|
|
1701
|
+
const wphsRoot = resolve9(ctx.projectRoot, workspace);
|
|
1457
1702
|
const required = ["config", "assets/branding", "fixtures", "cache", "audit/baseline", "state"];
|
|
1458
1703
|
for (const sub of required) {
|
|
1459
|
-
const ok =
|
|
1704
|
+
const ok = existsSync8(join2(wphsRoot, sub));
|
|
1460
1705
|
checks.push({
|
|
1461
1706
|
name: `wphs-${sub.replace(/\//g, "-")}`,
|
|
1462
1707
|
ok,
|
|
1463
1708
|
message: ok ? `.wphs/${sub}/ present` : `.wphs/${sub}/ missing`,
|
|
1464
|
-
remediation: ok ? void 0 : "Run wp
|
|
1709
|
+
remediation: ok ? void 0 : "Run wp setup or wp doctor --verbose; check write permissions on the project root",
|
|
1465
1710
|
severity: "error"
|
|
1466
1711
|
});
|
|
1467
1712
|
}
|
|
@@ -1469,8 +1714,8 @@ function checkWphsWorkspace(ctx) {
|
|
|
1469
1714
|
}
|
|
1470
1715
|
function checkAdapter(ctx) {
|
|
1471
1716
|
if (isThemeProject(ctx.config)) return null;
|
|
1472
|
-
const adapterPath =
|
|
1473
|
-
const ok =
|
|
1717
|
+
const adapterPath = resolve9(ctx.projectRoot, ctx.config.dev.adapter);
|
|
1718
|
+
const ok = existsSync8(adapterPath);
|
|
1474
1719
|
return {
|
|
1475
1720
|
name: "wphs-adapter",
|
|
1476
1721
|
ok,
|
|
@@ -1481,11 +1726,11 @@ function checkAdapter(ctx) {
|
|
|
1481
1726
|
}
|
|
1482
1727
|
function checkModuleConfigUiDeps(ctx) {
|
|
1483
1728
|
if (isThemeProject(ctx.config)) return [];
|
|
1484
|
-
const pkgPath =
|
|
1485
|
-
if (!
|
|
1729
|
+
const pkgPath = resolve9(ctx.projectRoot, "package.json");
|
|
1730
|
+
if (!existsSync8(pkgPath)) return [];
|
|
1486
1731
|
let deps = {};
|
|
1487
1732
|
try {
|
|
1488
|
-
const pkg = JSON.parse(
|
|
1733
|
+
const pkg = JSON.parse(readFileSync8(pkgPath, "utf8"));
|
|
1489
1734
|
deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
1490
1735
|
} catch {
|
|
1491
1736
|
return [];
|
|
@@ -1512,7 +1757,7 @@ function checkNodeLoaderInterop() {
|
|
|
1512
1757
|
};
|
|
1513
1758
|
}
|
|
1514
1759
|
function checkViteInstalled(ctx) {
|
|
1515
|
-
if (isThemeProject(ctx.config) && !
|
|
1760
|
+
if (isThemeProject(ctx.config) && !existsSync8(resolve9(ctx.projectRoot, "vite.config.dev.mjs"))) {
|
|
1516
1761
|
return null;
|
|
1517
1762
|
}
|
|
1518
1763
|
const ok = isProjectViteInstalled(ctx.projectRoot);
|
|
@@ -1526,19 +1771,12 @@ function checkViteInstalled(ctx) {
|
|
|
1526
1771
|
}
|
|
1527
1772
|
async function gatherDoctorChecks(ctx) {
|
|
1528
1773
|
const checks = [];
|
|
1774
|
+
let wphsScaffoldError;
|
|
1529
1775
|
await runCliTasks(ctx, [
|
|
1530
1776
|
{
|
|
1531
1777
|
title: "Preparing .wphs workspace",
|
|
1532
1778
|
task: async () => {
|
|
1533
|
-
|
|
1534
|
-
try {
|
|
1535
|
-
runNodeScript(kitPath("scripts", "ensure-wphs-workspace.mjs"), [], {
|
|
1536
|
-
cwd: ctx.projectRoot,
|
|
1537
|
-
logger: ctx.logger,
|
|
1538
|
-
dryRun: false
|
|
1539
|
-
});
|
|
1540
|
-
} catch {
|
|
1541
|
-
}
|
|
1779
|
+
wphsScaffoldError = await runWphsScaffold(ctx);
|
|
1542
1780
|
}
|
|
1543
1781
|
},
|
|
1544
1782
|
{
|
|
@@ -1562,7 +1800,11 @@ async function gatherDoctorChecks(ctx) {
|
|
|
1562
1800
|
{
|
|
1563
1801
|
title: "Checking woodscli.json and project layout",
|
|
1564
1802
|
task: async () => {
|
|
1565
|
-
|
|
1803
|
+
wphsScaffoldError = await runWphsScaffold(ctx) ?? wphsScaffoldError;
|
|
1804
|
+
checks.push(...checkConfig(ctx));
|
|
1805
|
+
const scaffoldCheck = checkWphsScaffold(wphsScaffoldError);
|
|
1806
|
+
if (scaffoldCheck) checks.push(scaffoldCheck);
|
|
1807
|
+
checks.push(...checkWphsWorkspace(ctx));
|
|
1566
1808
|
const fieldsCheck = checkFieldsJson(ctx);
|
|
1567
1809
|
if (fieldsCheck) checks.push(fieldsCheck);
|
|
1568
1810
|
const metaCheck = checkMetaJson(ctx);
|
|
@@ -1672,26 +1914,7 @@ async function runConfigValidate(ctx) {
|
|
|
1672
1914
|
import { resolve as resolve10 } from "path";
|
|
1673
1915
|
|
|
1674
1916
|
// src/lib/cli-banner.ts
|
|
1675
|
-
import
|
|
1676
|
-
|
|
1677
|
-
// src/lib/kit-version.ts
|
|
1678
|
-
import { readFileSync as readFileSync6 } from "fs";
|
|
1679
|
-
import { resolve as resolve9 } from "path";
|
|
1680
|
-
var cachedVersion = null;
|
|
1681
|
-
function readKitVersion() {
|
|
1682
|
-
if (cachedVersion) {
|
|
1683
|
-
return cachedVersion;
|
|
1684
|
-
}
|
|
1685
|
-
try {
|
|
1686
|
-
const pkg = JSON.parse(readFileSync6(resolve9(getKitRoot(), "package.json"), "utf8"));
|
|
1687
|
-
cachedVersion = pkg.version ?? "0.0.0";
|
|
1688
|
-
} catch {
|
|
1689
|
-
cachedVersion = "0.0.0";
|
|
1690
|
-
}
|
|
1691
|
-
return cachedVersion;
|
|
1692
|
-
}
|
|
1693
|
-
|
|
1694
|
-
// src/lib/cli-banner.ts
|
|
1917
|
+
import pc4 from "picocolors";
|
|
1695
1918
|
var WOODSCLI_ASCII_BANNER = `
|
|
1696
1919
|
__ __ _ _____ _ _____
|
|
1697
1920
|
\\ \\ / / | | / ____| | |_ _|
|
|
@@ -1721,7 +1944,7 @@ function formatProjectLabel(config) {
|
|
|
1721
1944
|
return `${config.module.name} \xB7 ${config.projectType}${routerNote}`;
|
|
1722
1945
|
}
|
|
1723
1946
|
function section(color, title, body) {
|
|
1724
|
-
const bold = (
|
|
1947
|
+
const bold = (text5) => color ? pc4.bold(text5) : text5;
|
|
1725
1948
|
return ` ${bold(title)}
|
|
1726
1949
|
${body}`;
|
|
1727
1950
|
}
|
|
@@ -1730,10 +1953,10 @@ function printWoodsCliDevBanner(options) {
|
|
|
1730
1953
|
return;
|
|
1731
1954
|
}
|
|
1732
1955
|
const color = useCliColor();
|
|
1733
|
-
const cyan = (
|
|
1734
|
-
const dim = (
|
|
1735
|
-
const bold = (
|
|
1736
|
-
const yellow = (
|
|
1956
|
+
const cyan = (text5) => color ? pc4.cyan(text5) : text5;
|
|
1957
|
+
const dim = (text5) => color ? pc4.dim(text5) : text5;
|
|
1958
|
+
const bold = (text5) => color ? pc4.bold(text5) : text5;
|
|
1959
|
+
const yellow = (text5) => color ? pc4.yellow(text5) : text5;
|
|
1737
1960
|
const version = options.version ?? readKitVersion();
|
|
1738
1961
|
const project = formatProjectLabel(options.config);
|
|
1739
1962
|
const isTheme = isThemeProject(options.config);
|
|
@@ -1994,86 +2217,17 @@ async function runDev(ctx, options) {
|
|
|
1994
2217
|
}
|
|
1995
2218
|
|
|
1996
2219
|
// src/cli/commands/deploy.ts
|
|
1997
|
-
import { resolve as
|
|
1998
|
-
|
|
1999
|
-
// src/lib/project-env-files.ts
|
|
2000
|
-
import { existsSync as existsSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync2 } from "fs";
|
|
2001
|
-
import { resolve as resolve11 } from "path";
|
|
2002
|
-
var ENV_LOCAL_FILE = ".env.local";
|
|
2003
|
-
var ENV_GITIGNORE_LINES = [".env", ".env.*", "!.env*.example", ENV_LOCAL_FILE];
|
|
2004
|
-
function parseEnvFile(content) {
|
|
2005
|
-
const result = {};
|
|
2006
|
-
for (const line of content.split("\n")) {
|
|
2007
|
-
const trimmed = line.trim();
|
|
2008
|
-
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
2009
|
-
const eq = trimmed.indexOf("=");
|
|
2010
|
-
if (eq <= 0) continue;
|
|
2011
|
-
const key = trimmed.slice(0, eq).trim();
|
|
2012
|
-
let value = trimmed.slice(eq + 1).trim();
|
|
2013
|
-
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
2014
|
-
value = value.slice(1, -1);
|
|
2015
|
-
}
|
|
2016
|
-
result[key] = value;
|
|
2017
|
-
}
|
|
2018
|
-
return result;
|
|
2019
|
-
}
|
|
2020
|
-
function readEnvLocal(projectRoot) {
|
|
2021
|
-
const path = resolve11(projectRoot, ENV_LOCAL_FILE);
|
|
2022
|
-
if (!existsSync8(path)) return {};
|
|
2023
|
-
return parseEnvFile(readFileSync7(path, "utf8"));
|
|
2024
|
-
}
|
|
2025
|
-
function formatEnvLocal(entries) {
|
|
2026
|
-
const lines = [
|
|
2027
|
-
"# Local secrets \u2014 gitignored. Created by wp init / wp setup (hybrid CDN).",
|
|
2028
|
-
"# PORTAL_CDN_PUBLISH_TOKEN needs repo push on your portal.cdn.json mirror.",
|
|
2029
|
-
""
|
|
2030
|
-
];
|
|
2031
|
-
for (const [key, value] of Object.entries(entries)) {
|
|
2032
|
-
if (value.includes(" ") || value.includes("#")) {
|
|
2033
|
-
lines.push(`${key}="${value.replace(/"/g, '\\"')}"`);
|
|
2034
|
-
} else {
|
|
2035
|
-
lines.push(`${key}=${value}`);
|
|
2036
|
-
}
|
|
2037
|
-
}
|
|
2038
|
-
return `${lines.join("\n").trimEnd()}
|
|
2039
|
-
`;
|
|
2040
|
-
}
|
|
2041
|
-
function writeEnvLocal(projectRoot, entries) {
|
|
2042
|
-
const path = resolve11(projectRoot, ENV_LOCAL_FILE);
|
|
2043
|
-
const existing = existsSync8(path) ? parseEnvFile(readFileSync7(path, "utf8")) : {};
|
|
2044
|
-
writeFileSync2(path, formatEnvLocal({ ...existing, ...entries }));
|
|
2045
|
-
}
|
|
2046
|
-
function mergeProjectEnv(projectRoot, base = process.env) {
|
|
2047
|
-
const local = readEnvLocal(projectRoot);
|
|
2048
|
-
const merged = { ...base };
|
|
2049
|
-
for (const [key, value] of Object.entries(local)) {
|
|
2050
|
-
if (merged[key] == null || merged[key] === "") {
|
|
2051
|
-
merged[key] = value;
|
|
2052
|
-
}
|
|
2053
|
-
}
|
|
2054
|
-
return merged;
|
|
2055
|
-
}
|
|
2056
|
-
function ensureEnvFilesGitignore(projectRoot) {
|
|
2057
|
-
const gitignorePath = resolve11(projectRoot, ".gitignore");
|
|
2058
|
-
const existing = existsSync8(gitignorePath) ? readFileSync7(gitignorePath, "utf8") : "";
|
|
2059
|
-
const toAppend = ENV_GITIGNORE_LINES.filter((line) => !existing.includes(line)).join("\n");
|
|
2060
|
-
if (!toAppend) return;
|
|
2061
|
-
writeFileSync2(gitignorePath, `${existing.trimEnd()}
|
|
2062
|
-
|
|
2063
|
-
# Env files (secrets)
|
|
2064
|
-
${toAppend}
|
|
2065
|
-
`);
|
|
2066
|
-
}
|
|
2220
|
+
import { resolve as resolve12 } from "path";
|
|
2067
2221
|
|
|
2068
2222
|
// src/cli/commands/upload.ts
|
|
2069
|
-
import { resolve as
|
|
2223
|
+
import { resolve as resolve11 } from "path";
|
|
2070
2224
|
async function uploadModuleOrTheme(ctx) {
|
|
2071
2225
|
if (isThemeProject(ctx.config)) {
|
|
2072
2226
|
const theme = ctx.config.theme;
|
|
2073
2227
|
if (!theme) {
|
|
2074
2228
|
throw new WpHsError("theme block missing in woodscli.json", { exitCode: ExitCode.USER_ERROR });
|
|
2075
2229
|
}
|
|
2076
|
-
const srcDir =
|
|
2230
|
+
const srcDir = resolve11(ctx.projectRoot, theme.srcDir);
|
|
2077
2231
|
await runCommandAsync("hs", ["cms", "upload", srcDir, theme.uploadPath], {
|
|
2078
2232
|
cwd: ctx.projectRoot,
|
|
2079
2233
|
projectRoot: ctx.projectRoot,
|
|
@@ -2083,7 +2237,7 @@ async function uploadModuleOrTheme(ctx) {
|
|
|
2083
2237
|
});
|
|
2084
2238
|
return `Uploaded theme ${srcDir} \u2192 ${theme.uploadPath}`;
|
|
2085
2239
|
}
|
|
2086
|
-
const outDir =
|
|
2240
|
+
const outDir = resolve11(ctx.projectRoot, ctx.config.module.outDir);
|
|
2087
2241
|
const uploadPath = ctx.config.module.uploadPath;
|
|
2088
2242
|
await runCommandAsync("hs", ["cms", "upload", outDir, uploadPath], {
|
|
2089
2243
|
cwd: ctx.projectRoot,
|
|
@@ -2260,7 +2414,7 @@ async function runDeploy(ctx, options) {
|
|
|
2260
2414
|
if (!ctx.global.json) {
|
|
2261
2415
|
printCommandDone(
|
|
2262
2416
|
ctx,
|
|
2263
|
-
`Deploy complete (${
|
|
2417
|
+
`Deploy complete (${resolve12(ctx.projectRoot, ctx.config.module.outDir)} \u2192 ${ctx.config.module.uploadPath})`
|
|
2264
2418
|
);
|
|
2265
2419
|
}
|
|
2266
2420
|
return ExitCode.SUCCESS;
|
|
@@ -2333,8 +2487,8 @@ async function runPublish(ctx, options) {
|
|
|
2333
2487
|
|
|
2334
2488
|
// src/cli/commands/audit.ts
|
|
2335
2489
|
import { createHash } from "crypto";
|
|
2336
|
-
import { cpSync, existsSync as existsSync9, mkdirSync as
|
|
2337
|
-
import { resolve as
|
|
2490
|
+
import { cpSync, existsSync as existsSync9, mkdirSync as mkdirSync3, readFileSync as readFileSync9, readdirSync, rmSync, statSync } from "fs";
|
|
2491
|
+
import { resolve as resolve13, join as join3 } from "path";
|
|
2338
2492
|
var FORBIDDEN_SHIP_TOKENS = [
|
|
2339
2493
|
"ModuleConfigOverlay",
|
|
2340
2494
|
"module-config-store",
|
|
@@ -2343,15 +2497,16 @@ var FORBIDDEN_SHIP_TOKENS = [
|
|
|
2343
2497
|
"dev.module-config"
|
|
2344
2498
|
];
|
|
2345
2499
|
var FORBIDDEN_FIXTURE_MENU_TOKENS = ["User Home", "Primary Company Deals"];
|
|
2500
|
+
var FORBIDDEN_PREVIEW_FIXTURE_TOKENS = ["preview.editor@example.com", "CMS_EDITOR_PREVIEW"];
|
|
2346
2501
|
function hashFile(path) {
|
|
2347
|
-
return createHash("sha256").update(
|
|
2502
|
+
return createHash("sha256").update(readFileSync9(path)).digest("hex");
|
|
2348
2503
|
}
|
|
2349
2504
|
function walkFiles(dir) {
|
|
2350
2505
|
if (!existsSync9(dir)) return [];
|
|
2351
2506
|
const entries = readdirSync(dir);
|
|
2352
2507
|
const files = [];
|
|
2353
2508
|
for (const entry of entries) {
|
|
2354
|
-
const full =
|
|
2509
|
+
const full = join3(dir, entry);
|
|
2355
2510
|
if (statSync(full).isDirectory()) {
|
|
2356
2511
|
files.push(...walkFiles(full));
|
|
2357
2512
|
} else {
|
|
@@ -2363,26 +2518,31 @@ function walkFiles(dir) {
|
|
|
2363
2518
|
async function runAuditShipBundle(ctx) {
|
|
2364
2519
|
const root = ctx.projectRoot;
|
|
2365
2520
|
const targets = [
|
|
2366
|
-
|
|
2367
|
-
|
|
2521
|
+
resolve13(root, "dist/cdn"),
|
|
2522
|
+
resolve13(root, ctx.config.module.outDir)
|
|
2368
2523
|
];
|
|
2369
2524
|
const violations = [];
|
|
2370
2525
|
for (const dir of targets) {
|
|
2371
2526
|
for (const file of walkFiles(dir)) {
|
|
2372
2527
|
if (!/\.(js|css|html)$/i.test(file)) continue;
|
|
2373
|
-
const content =
|
|
2528
|
+
const content = readFileSync9(file, "utf8");
|
|
2374
2529
|
for (const token of FORBIDDEN_SHIP_TOKENS) {
|
|
2375
2530
|
if (content.includes(token)) {
|
|
2376
2531
|
violations.push(`${file}: contains forbidden token "${token}"`);
|
|
2377
2532
|
}
|
|
2378
2533
|
}
|
|
2379
|
-
const
|
|
2380
|
-
if (
|
|
2534
|
+
const isCdnAppEntry = /[/\\]dist[/\\]cdn[/\\]app\..*\.js$/i.test(file);
|
|
2535
|
+
if (isCdnAppEntry) {
|
|
2381
2536
|
for (const token of FORBIDDEN_FIXTURE_MENU_TOKENS) {
|
|
2382
2537
|
if (content.includes(token)) {
|
|
2383
2538
|
violations.push(`${file}: contains fixture menu marker "${token}"`);
|
|
2384
2539
|
}
|
|
2385
2540
|
}
|
|
2541
|
+
for (const token of FORBIDDEN_PREVIEW_FIXTURE_TOKENS) {
|
|
2542
|
+
if (content.includes(token)) {
|
|
2543
|
+
violations.push(`${file}: contains preview fixture marker "${token}"`);
|
|
2544
|
+
}
|
|
2545
|
+
}
|
|
2386
2546
|
}
|
|
2387
2547
|
}
|
|
2388
2548
|
}
|
|
@@ -2405,8 +2565,8 @@ async function runAuditBaselineCapture(ctx, options) {
|
|
|
2405
2565
|
const root = ctx.projectRoot;
|
|
2406
2566
|
const baseline = options.baselineDir ?? defaultBaselineDir(root, ctx.config);
|
|
2407
2567
|
const sources = [
|
|
2408
|
-
{ label: "cdn", path:
|
|
2409
|
-
{ label: "hubspot", path:
|
|
2568
|
+
{ label: "cdn", path: resolve13(root, "dist/cdn") },
|
|
2569
|
+
{ label: "hubspot", path: resolve13(root, ctx.config.module.outDir) }
|
|
2410
2570
|
];
|
|
2411
2571
|
if (ctx.global.dryRun) {
|
|
2412
2572
|
ctx.logger.info(`[dry-run] Would capture baseline to ${baseline}`);
|
|
@@ -2419,9 +2579,9 @@ async function runAuditBaselineCapture(ctx, options) {
|
|
|
2419
2579
|
remediation: "Run wp build before wp audit baseline --capture."
|
|
2420
2580
|
});
|
|
2421
2581
|
}
|
|
2422
|
-
const target =
|
|
2582
|
+
const target = resolve13(baseline, label);
|
|
2423
2583
|
rmSync(target, { recursive: true, force: true });
|
|
2424
|
-
|
|
2584
|
+
mkdirSync3(baseline, { recursive: true });
|
|
2425
2585
|
cpSync(path, target, { recursive: true });
|
|
2426
2586
|
}
|
|
2427
2587
|
if (ctx.global.json) {
|
|
@@ -2435,12 +2595,12 @@ async function runAuditParity(ctx, options) {
|
|
|
2435
2595
|
const root = ctx.projectRoot;
|
|
2436
2596
|
const baseline = options.baselineDir ?? defaultBaselineDir(root, ctx.config);
|
|
2437
2597
|
const currentDirs = [
|
|
2438
|
-
{ label: "cdn", path:
|
|
2439
|
-
{ label: "hubspot", path:
|
|
2598
|
+
{ label: "cdn", path: resolve13(root, "dist/cdn") },
|
|
2599
|
+
{ label: "hubspot", path: resolve13(root, ctx.config.module.outDir) }
|
|
2440
2600
|
];
|
|
2441
2601
|
const mismatches = [];
|
|
2442
2602
|
for (const { label, path } of currentDirs) {
|
|
2443
|
-
const baselinePath =
|
|
2603
|
+
const baselinePath = resolve13(baseline, label);
|
|
2444
2604
|
if (!existsSync9(baselinePath)) {
|
|
2445
2605
|
ctx.logger.warn(`No baseline at ${baselinePath} \u2014 skipping ${label} parity`);
|
|
2446
2606
|
continue;
|
|
@@ -2456,7 +2616,7 @@ async function runAuditParity(ctx, options) {
|
|
|
2456
2616
|
}
|
|
2457
2617
|
for (const file of baselineFiles) {
|
|
2458
2618
|
const rel = file.replace(baselinePath, "");
|
|
2459
|
-
const current =
|
|
2619
|
+
const current = resolve13(path, rel.slice(1));
|
|
2460
2620
|
if (!existsSync9(current)) continue;
|
|
2461
2621
|
const bh = hashFile(file);
|
|
2462
2622
|
const ch = hashFile(current);
|
|
@@ -2484,13 +2644,13 @@ var SECRET_PATTERNS = [
|
|
|
2484
2644
|
];
|
|
2485
2645
|
async function runAuditSecrets(ctx) {
|
|
2486
2646
|
const root = ctx.projectRoot;
|
|
2487
|
-
const scanRoots = ["src", "public", ".wphs/config"].map((d) =>
|
|
2647
|
+
const scanRoots = ["src", "public", ".wphs/config"].map((d) => resolve13(root, d));
|
|
2488
2648
|
const findings = [];
|
|
2489
2649
|
for (const dir of scanRoots) {
|
|
2490
2650
|
for (const file of walkFiles(dir)) {
|
|
2491
2651
|
if (!/\.(ts|tsx|js|jsx|json|env|yml|yaml|html)$/i.test(file)) continue;
|
|
2492
2652
|
if (file.includes("node_modules")) continue;
|
|
2493
|
-
const content =
|
|
2653
|
+
const content = readFileSync9(file, "utf8");
|
|
2494
2654
|
for (const pattern of SECRET_PATTERNS) {
|
|
2495
2655
|
pattern.lastIndex = 0;
|
|
2496
2656
|
if (pattern.test(content)) {
|
|
@@ -2516,13 +2676,13 @@ async function runAuditSecrets(ctx) {
|
|
|
2516
2676
|
}
|
|
2517
2677
|
|
|
2518
2678
|
// src/cli/commands/init.ts
|
|
2519
|
-
import { cpSync as
|
|
2520
|
-
import { basename as basename2, resolve as
|
|
2521
|
-
import * as
|
|
2679
|
+
import { cpSync as cpSync3, existsSync as existsSync13, mkdirSync as mkdirSync5, readdirSync as readdirSync2, readFileSync as readFileSync12, writeFileSync as writeFileSync8 } from "fs";
|
|
2680
|
+
import { basename as basename2, resolve as resolve17 } from "path";
|
|
2681
|
+
import * as p5 from "@clack/prompts";
|
|
2522
2682
|
|
|
2523
2683
|
// src/lib/init-router-scaffold.ts
|
|
2524
|
-
import { existsSync as existsSync10, mkdirSync as
|
|
2525
|
-
import { resolve as
|
|
2684
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync4, readFileSync as readFileSync10, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "fs";
|
|
2685
|
+
import { resolve as resolve14 } from "path";
|
|
2526
2686
|
var ROUTER_FILES = [
|
|
2527
2687
|
"src/routes",
|
|
2528
2688
|
"src/routeTree.gen.ts",
|
|
@@ -2536,10 +2696,10 @@ function isModuleTemplate(template) {
|
|
|
2536
2696
|
return template === "module-hybrid-cdn" || template === "module-monolith";
|
|
2537
2697
|
}
|
|
2538
2698
|
function writeRouterConfig(targetDir, mode) {
|
|
2539
|
-
const configDir =
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2699
|
+
const configDir = resolve14(targetDir, "src/config");
|
|
2700
|
+
mkdirSync4(configDir, { recursive: true });
|
|
2701
|
+
writeFileSync5(
|
|
2702
|
+
resolve14(configDir, "router.ts"),
|
|
2543
2703
|
`/** Router history \u2014 set by \`wp init\`. Hash matches woodsportal-client-frontend (HubSpot CMS embeds). */
|
|
2544
2704
|
export type RouterHistoryMode = 'hash' | 'browser'
|
|
2545
2705
|
|
|
@@ -2548,8 +2708,8 @@ export const ROUTER_HISTORY: RouterHistoryMode = '${mode}'
|
|
|
2548
2708
|
);
|
|
2549
2709
|
}
|
|
2550
2710
|
function writeMountDevPreviewRouter(targetDir) {
|
|
2551
|
-
|
|
2552
|
-
|
|
2711
|
+
writeFileSync5(
|
|
2712
|
+
resolve14(targetDir, "src/portal/mount-dev-preview.tsx"),
|
|
2553
2713
|
`import { mountGreenfieldDev } from '../../.wphs/.kit-bootstrap-greenfield.tsx'
|
|
2554
2714
|
|
|
2555
2715
|
import { App } from '@/app'
|
|
@@ -2562,8 +2722,8 @@ export async function mountDevPreview() {
|
|
|
2562
2722
|
);
|
|
2563
2723
|
}
|
|
2564
2724
|
function writeMountDevPreviewSinglePage(targetDir) {
|
|
2565
|
-
|
|
2566
|
-
|
|
2725
|
+
writeFileSync5(
|
|
2726
|
+
resolve14(targetDir, "src/portal/mount-dev-preview.tsx"),
|
|
2567
2727
|
`import { mountGreenfieldDev } from '../../.wphs/.kit-bootstrap-greenfield.tsx'
|
|
2568
2728
|
|
|
2569
2729
|
import { StarterSinglePage } from '@/features/starter'
|
|
@@ -2576,8 +2736,8 @@ export async function mountDevPreview() {
|
|
|
2576
2736
|
);
|
|
2577
2737
|
}
|
|
2578
2738
|
function writeMountPortalRouter(targetDir) {
|
|
2579
|
-
|
|
2580
|
-
|
|
2739
|
+
writeFileSync5(
|
|
2740
|
+
resolve14(targetDir, "src/portal/mount-portal.tsx"),
|
|
2581
2741
|
`import ReactDOM from 'react-dom/client'
|
|
2582
2742
|
|
|
2583
2743
|
import { App } from '@/app'
|
|
@@ -2594,8 +2754,8 @@ export async function mountPortal() {
|
|
|
2594
2754
|
);
|
|
2595
2755
|
}
|
|
2596
2756
|
function writeMountPortalSinglePage(targetDir) {
|
|
2597
|
-
|
|
2598
|
-
|
|
2757
|
+
writeFileSync5(
|
|
2758
|
+
resolve14(targetDir, "src/portal/mount-portal.tsx"),
|
|
2599
2759
|
`import ReactDOM from 'react-dom/client'
|
|
2600
2760
|
|
|
2601
2761
|
import { StarterSinglePage } from '@/features/starter'
|
|
@@ -2612,11 +2772,11 @@ export async function mountPortal() {
|
|
|
2612
2772
|
);
|
|
2613
2773
|
}
|
|
2614
2774
|
function writeStarterSinglePage(targetDir) {
|
|
2615
|
-
const file =
|
|
2775
|
+
const file = resolve14(targetDir, "src/features/starter/components/starter-single-page.tsx");
|
|
2616
2776
|
if (existsSync10(file)) {
|
|
2617
2777
|
return;
|
|
2618
2778
|
}
|
|
2619
|
-
|
|
2779
|
+
writeFileSync5(
|
|
2620
2780
|
file,
|
|
2621
2781
|
`import { StarterShell } from '@/components/layout/starter-shell'
|
|
2622
2782
|
|
|
@@ -2639,25 +2799,25 @@ export function StarterSinglePage() {
|
|
|
2639
2799
|
);
|
|
2640
2800
|
}
|
|
2641
2801
|
function removePath(targetDir, relativePath) {
|
|
2642
|
-
const full =
|
|
2802
|
+
const full = resolve14(targetDir, relativePath);
|
|
2643
2803
|
if (!existsSync10(full)) {
|
|
2644
2804
|
return;
|
|
2645
2805
|
}
|
|
2646
2806
|
rmSync2(full, { recursive: true, force: true });
|
|
2647
2807
|
}
|
|
2648
2808
|
function stripTanStackFromPackage(targetDir) {
|
|
2649
|
-
const pkgPath =
|
|
2809
|
+
const pkgPath = resolve14(targetDir, "package.json");
|
|
2650
2810
|
if (!existsSync10(pkgPath)) {
|
|
2651
2811
|
return;
|
|
2652
2812
|
}
|
|
2653
|
-
const pkg = JSON.parse(
|
|
2813
|
+
const pkg = JSON.parse(readFileSync10(pkgPath, "utf8"));
|
|
2654
2814
|
if (!pkg.devDependencies) {
|
|
2655
2815
|
return;
|
|
2656
2816
|
}
|
|
2657
2817
|
for (const dep of TANSTACK_DEPS) {
|
|
2658
2818
|
delete pkg.devDependencies[dep];
|
|
2659
2819
|
}
|
|
2660
|
-
|
|
2820
|
+
writeFileSync5(pkgPath, `${JSON.stringify(pkg, null, 2)}
|
|
2661
2821
|
`);
|
|
2662
2822
|
}
|
|
2663
2823
|
function applyRouterScaffold(targetDir, mode, template) {
|
|
@@ -2677,141 +2837,582 @@ function applyRouterScaffold(targetDir, mode, template) {
|
|
|
2677
2837
|
writeRouterConfig(targetDir, mode);
|
|
2678
2838
|
writeMountDevPreviewRouter(targetDir);
|
|
2679
2839
|
writeMountPortalRouter(targetDir);
|
|
2680
|
-
const featuresIndex =
|
|
2840
|
+
const featuresIndex = resolve14(targetDir, "src/features/starter/index.ts");
|
|
2681
2841
|
if (existsSync10(featuresIndex)) {
|
|
2682
|
-
let content =
|
|
2842
|
+
let content = readFileSync10(featuresIndex, "utf8");
|
|
2683
2843
|
if (!content.includes("StarterSinglePage")) {
|
|
2684
2844
|
content = content.replace(
|
|
2685
2845
|
"export type { StarterPreviewValues } from './hooks/use-starter-preview'\n",
|
|
2686
2846
|
"export type { StarterPreviewValues } from './hooks/use-starter-preview'\nexport { StarterSinglePage } from './components/starter-single-page'\n"
|
|
2687
2847
|
);
|
|
2688
|
-
|
|
2848
|
+
writeFileSync5(featuresIndex, content);
|
|
2689
2849
|
}
|
|
2690
2850
|
}
|
|
2691
2851
|
}
|
|
2692
2852
|
|
|
2693
|
-
// src/lib/
|
|
2694
|
-
import { existsSync as existsSync11, readFileSync as
|
|
2695
|
-
import { resolve as
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
var
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2853
|
+
// src/lib/init-auth-scaffold.ts
|
|
2854
|
+
import { cpSync as cpSync2, existsSync as existsSync11, readFileSync as readFileSync11, rmSync as rmSync3, writeFileSync as writeFileSync6 } from "fs";
|
|
2855
|
+
import { resolve as resolve15 } from "path";
|
|
2856
|
+
|
|
2857
|
+
// src/lib/template-auth-deps.ts
|
|
2858
|
+
var AUTH_SCAFFOLD_DEPS = {
|
|
2859
|
+
"woodsportal-client-sdk": "^4.0.7-dev.11",
|
|
2860
|
+
"@tanstack/react-query": "^5.66.9",
|
|
2861
|
+
"@t3-oss/env-core": "^0.12.0",
|
|
2862
|
+
zod: "^3.24.2",
|
|
2863
|
+
sonner: "^2.0.1"
|
|
2864
|
+
};
|
|
2865
|
+
function mergeAuthDevDependencies(existing) {
|
|
2705
2866
|
return {
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
gitRemote: "https://github.com/your-org/your-portal-cdn-mirror.git",
|
|
2709
|
-
cdnPathInRepo: "dist/cdn",
|
|
2710
|
-
defaultBranch: "main"
|
|
2867
|
+
...existing,
|
|
2868
|
+
...AUTH_SCAFFOLD_DEPS
|
|
2711
2869
|
};
|
|
2712
2870
|
}
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2871
|
+
|
|
2872
|
+
// src/lib/init-auth-scaffold.ts
|
|
2873
|
+
var AUTH_SCAFFOLD_ROOT = kitPath("templates", "scaffold", "auth");
|
|
2874
|
+
function readTierManifest() {
|
|
2875
|
+
const path = resolve15(AUTH_SCAFFOLD_ROOT, "tier-manifest.json");
|
|
2876
|
+
return JSON.parse(readFileSync11(path, "utf8"));
|
|
2877
|
+
}
|
|
2878
|
+
function readNavManifest() {
|
|
2879
|
+
const path = resolve15(AUTH_SCAFFOLD_ROOT, "nav-manifest.json");
|
|
2880
|
+
return JSON.parse(readFileSync11(path, "utf8"));
|
|
2881
|
+
}
|
|
2882
|
+
function layersForMode(mode) {
|
|
2883
|
+
const manifest = readTierManifest();
|
|
2884
|
+
const entry = manifest.tiers[mode];
|
|
2885
|
+
if (!entry?.layers?.length) {
|
|
2886
|
+
throw new Error(`Unknown auth scaffold tier in tier-manifest.json: ${mode}`);
|
|
2720
2887
|
}
|
|
2888
|
+
return entry.layers;
|
|
2721
2889
|
}
|
|
2722
|
-
function
|
|
2723
|
-
const
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2890
|
+
function copyLayer(targetDir, layerName) {
|
|
2891
|
+
const layerRoot = resolve15(AUTH_SCAFFOLD_ROOT, layerName);
|
|
2892
|
+
if (!existsSync11(layerRoot)) {
|
|
2893
|
+
throw new Error(`Auth scaffold layer not found: ${layerName}`);
|
|
2894
|
+
}
|
|
2895
|
+
cpSync2(layerRoot, targetDir, { recursive: true, force: true });
|
|
2727
2896
|
}
|
|
2728
|
-
function
|
|
2729
|
-
const
|
|
2730
|
-
|
|
2731
|
-
|
|
2897
|
+
function layerForNavMode(nav) {
|
|
2898
|
+
const manifest = readNavManifest();
|
|
2899
|
+
const entry = manifest.modes[nav];
|
|
2900
|
+
if (!entry?.layer) {
|
|
2901
|
+
throw new Error(`Unknown nav scaffold mode in nav-manifest.json: ${nav}`);
|
|
2732
2902
|
}
|
|
2733
|
-
return
|
|
2734
|
-
publishMode: base?.publishMode ?? "jsdelivr",
|
|
2735
|
-
github: slug,
|
|
2736
|
-
gitRemote: `https://github.com/${slug}.git`,
|
|
2737
|
-
cdnPathInRepo: base?.cdnPathInRepo ?? "dist/cdn",
|
|
2738
|
-
defaultBranch: base?.defaultBranch ?? "main"
|
|
2739
|
-
};
|
|
2903
|
+
return entry.layer;
|
|
2740
2904
|
}
|
|
2741
|
-
function
|
|
2742
|
-
|
|
2743
|
-
if (process.env.GITHUB_TOKEN?.trim()) return true;
|
|
2744
|
-
return Boolean(readEnvLocal(projectRoot)[PORTAL_CDN_PUBLISH_TOKEN_ENV]?.trim());
|
|
2905
|
+
function copyNavLayer(targetDir, nav) {
|
|
2906
|
+
copyLayer(targetDir, layerForNavMode(nav));
|
|
2745
2907
|
}
|
|
2746
|
-
function
|
|
2747
|
-
const
|
|
2748
|
-
|
|
2749
|
-
|
|
2908
|
+
function patchNavScaffoldFile(targetDir, nav) {
|
|
2909
|
+
const navScaffoldPath = resolve15(targetDir, "src/config/nav-scaffold.ts");
|
|
2910
|
+
if (!existsSync11(navScaffoldPath)) {
|
|
2911
|
+
return;
|
|
2912
|
+
}
|
|
2913
|
+
const content = `/** Set by wp init --nav ${nav}. */
|
|
2914
|
+
export const NAV_SCAFFOLD_MODE = '${nav}' as const
|
|
2915
|
+
|
|
2916
|
+
export type NavScaffoldMode = typeof NAV_SCAFFOLD_MODE
|
|
2917
|
+
`;
|
|
2918
|
+
writeFileSync6(navScaffoldPath, content);
|
|
2750
2919
|
}
|
|
2751
|
-
function
|
|
2752
|
-
const
|
|
2753
|
-
if (
|
|
2754
|
-
return
|
|
2920
|
+
function patchAuthTierFile(targetDir, mode) {
|
|
2921
|
+
const authTierPath = resolve15(targetDir, "src/config/auth-tier.ts");
|
|
2922
|
+
if (!existsSync11(authTierPath)) {
|
|
2923
|
+
return;
|
|
2924
|
+
}
|
|
2925
|
+
const content = `/** Set by wp init --auth ${mode}. */
|
|
2926
|
+
export const AUTH_SCAFFOLD_TIER = '${mode}' as const
|
|
2927
|
+
|
|
2928
|
+
export type AuthScaffoldTier = typeof AUTH_SCAFFOLD_TIER
|
|
2929
|
+
`;
|
|
2930
|
+
writeFileSync6(authTierPath, content);
|
|
2931
|
+
}
|
|
2932
|
+
function patchStarterOnlyBlocks(targetDir, mode) {
|
|
2933
|
+
const hasSso = mode === "sso" || mode === "full";
|
|
2934
|
+
const hasFull = mode === "full";
|
|
2935
|
+
const filesToPatch = [
|
|
2936
|
+
"src/integrations/configure-woodsportal-sdk.ts",
|
|
2937
|
+
"src/features/auth/login-session-bridge.ts",
|
|
2938
|
+
"src/app.tsx",
|
|
2939
|
+
"src/features/auth/components/starter-login.tsx",
|
|
2940
|
+
"src/portal/mount-portal.tsx",
|
|
2941
|
+
"src/features/auth/auth-route-paths.ts"
|
|
2942
|
+
];
|
|
2943
|
+
for (const relative of filesToPatch) {
|
|
2944
|
+
const filePath = resolve15(targetDir, relative);
|
|
2945
|
+
if (!existsSync11(filePath)) {
|
|
2946
|
+
continue;
|
|
2947
|
+
}
|
|
2948
|
+
let content = readFileSync11(filePath, "utf8");
|
|
2949
|
+
content = stripMarkedBlock(content, "WPHS_AUTH_SSO", hasSso);
|
|
2950
|
+
content = stripMarkedBlock(content, "WPHS_AUTH_FULL", hasFull);
|
|
2951
|
+
if (!hasFull && relative === "src/integrations/configure-woodsportal-sdk.ts") {
|
|
2952
|
+
content = content.replace(
|
|
2953
|
+
/^import \{ isMfaPendingSession \} from '@\/features\/auth\/auth-session'\n/m,
|
|
2954
|
+
""
|
|
2955
|
+
);
|
|
2956
|
+
}
|
|
2957
|
+
writeFileSync6(filePath, content);
|
|
2755
2958
|
}
|
|
2756
|
-
return defaultPortalCdnJson().github;
|
|
2757
2959
|
}
|
|
2758
|
-
function
|
|
2759
|
-
|
|
2960
|
+
function stripMarkedBlock(content, marker, keep) {
|
|
2961
|
+
const start = `// ${marker}`;
|
|
2962
|
+
const end = `// /${marker}`;
|
|
2963
|
+
const pattern = new RegExp(
|
|
2964
|
+
`${escapeRegExp(start)}[\\s\\S]*?${escapeRegExp(end)}\\n?`,
|
|
2965
|
+
"g"
|
|
2966
|
+
);
|
|
2967
|
+
if (keep) {
|
|
2968
|
+
return content.replace(new RegExp(`${escapeRegExp(start)}\\n?`, "g"), "").replace(new RegExp(`${escapeRegExp(end)}\\n?`, "g"), "");
|
|
2969
|
+
}
|
|
2970
|
+
return content.replace(pattern, "");
|
|
2760
2971
|
}
|
|
2761
|
-
function
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2972
|
+
function escapeRegExp(value) {
|
|
2973
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2974
|
+
}
|
|
2975
|
+
var LOWER_TIER_AUTH_GUARD_FILES = [
|
|
2976
|
+
"src/features/auth/login-session-bridge.ts",
|
|
2977
|
+
"src/integrations/configure-woodsportal-sdk.ts",
|
|
2978
|
+
"src/app.tsx",
|
|
2979
|
+
"src/portal/mount-portal.tsx",
|
|
2980
|
+
"src/features/auth/components/starter-login.tsx"
|
|
2981
|
+
];
|
|
2982
|
+
function assertLowerTierAuthScaffold(targetDir, mode) {
|
|
2983
|
+
const violations = [];
|
|
2984
|
+
for (const relative of LOWER_TIER_AUTH_GUARD_FILES) {
|
|
2985
|
+
const filePath = resolve15(targetDir, relative);
|
|
2986
|
+
if (!existsSync11(filePath)) {
|
|
2987
|
+
continue;
|
|
2988
|
+
}
|
|
2989
|
+
const content = readFileSync11(filePath, "utf8");
|
|
2990
|
+
if (content.includes("WPHS_AUTH_")) {
|
|
2991
|
+
violations.push(`${relative}: leftover WPHS_AUTH_* strip markers`);
|
|
2992
|
+
}
|
|
2993
|
+
if (relative === "src/features/auth/login-session-bridge.ts") {
|
|
2994
|
+
if (content.includes("mfa-pending-storage")) {
|
|
2995
|
+
violations.push(`${relative}: must not import mfa-pending-storage on --auth ${mode}`);
|
|
2996
|
+
}
|
|
2997
|
+
if (content.includes("navigateToMfaGate")) {
|
|
2998
|
+
violations.push(`${relative}: must not reference navigateToMfaGate on --auth ${mode}`);
|
|
2999
|
+
}
|
|
3000
|
+
}
|
|
3001
|
+
}
|
|
3002
|
+
if (existsSync11(resolve15(targetDir, "src/routes/_auth/two-fa.tsx"))) {
|
|
3003
|
+
violations.push("src/routes/_auth/two-fa.tsx: MFA route must not exist on --auth " + mode);
|
|
3004
|
+
}
|
|
3005
|
+
if (violations.length > 0) {
|
|
3006
|
+
throw new WpHsError(`Auth scaffold tier "${mode}" still contains full-tier artifacts:
|
|
3007
|
+
- ${violations.join("\n- ")}`, {
|
|
3008
|
+
exitCode: ExitCode.INTERNAL,
|
|
3009
|
+
remediation: "Fix strip markers in templates/scaffold/auth/_shared or report a hubspot-kit bug."
|
|
3010
|
+
});
|
|
3011
|
+
}
|
|
3012
|
+
}
|
|
3013
|
+
function assertFullTierAuthScaffold(targetDir) {
|
|
3014
|
+
const required = [
|
|
3015
|
+
"src/features/auth/mfa-pending-storage.ts",
|
|
3016
|
+
"src/routes/_auth/two-fa.tsx"
|
|
3017
|
+
];
|
|
3018
|
+
const missing = required.filter((relative) => !existsSync11(resolve15(targetDir, relative)));
|
|
3019
|
+
if (missing.length > 0) {
|
|
3020
|
+
throw new WpHsError(`Auth scaffold tier "full" is missing required files:
|
|
3021
|
+
- ${missing.join("\n- ")}`, {
|
|
3022
|
+
exitCode: ExitCode.INTERNAL
|
|
3023
|
+
});
|
|
3024
|
+
}
|
|
3025
|
+
const bridge = readFileSync11(resolve15(targetDir, "src/features/auth/login-session-bridge.ts"), "utf8");
|
|
3026
|
+
if (bridge.includes("WPHS_AUTH_")) {
|
|
3027
|
+
throw new WpHsError('Auth scaffold tier "full" left WPHS_AUTH_* markers in login-session-bridge.ts', {
|
|
3028
|
+
exitCode: ExitCode.INTERNAL
|
|
3029
|
+
});
|
|
3030
|
+
}
|
|
3031
|
+
if (!bridge.includes("navigateToMfaGate") || !bridge.includes("mfa-pending-storage")) {
|
|
3032
|
+
throw new WpHsError('Auth scaffold tier "full" login-session-bridge.ts is missing MFA wiring', {
|
|
3033
|
+
exitCode: ExitCode.INTERNAL
|
|
3034
|
+
});
|
|
3035
|
+
}
|
|
3036
|
+
}
|
|
3037
|
+
function patchPackageJson(targetDir) {
|
|
3038
|
+
const pkgPath = resolve15(targetDir, "package.json");
|
|
3039
|
+
if (!existsSync11(pkgPath)) {
|
|
3040
|
+
return;
|
|
3041
|
+
}
|
|
3042
|
+
const pkg = JSON.parse(readFileSync11(pkgPath, "utf8"));
|
|
3043
|
+
pkg.devDependencies = mergeAuthDevDependencies(pkg.devDependencies);
|
|
3044
|
+
writeFileSync6(pkgPath, `${JSON.stringify(pkg, null, 2)}
|
|
2765
3045
|
`);
|
|
2766
|
-
|
|
2767
|
-
|
|
3046
|
+
}
|
|
3047
|
+
function writeAuthMountDevPreview(targetDir) {
|
|
3048
|
+
writeFileSync6(
|
|
3049
|
+
resolve15(targetDir, "src/portal/mount-dev-preview.tsx"),
|
|
3050
|
+
`import { mountGreenfieldDev } from '../../.wphs/.kit-bootstrap-greenfield.tsx'
|
|
3051
|
+
|
|
3052
|
+
import { App } from '@/app'
|
|
3053
|
+
import { getPortalRouter, mountPortal } from '@/portal/mount-portal'
|
|
3054
|
+
|
|
3055
|
+
/** Local dev: module-config overlay + auth-aware App shell. */
|
|
3056
|
+
export async function mountDevPreview() {
|
|
3057
|
+
await mountGreenfieldDev({
|
|
3058
|
+
beforePreview: () => mountPortal({ skipAppRender: true }),
|
|
3059
|
+
preview: <App router={getPortalRouter()} />,
|
|
3060
|
+
})
|
|
3061
|
+
}
|
|
3062
|
+
`
|
|
3063
|
+
);
|
|
3064
|
+
}
|
|
3065
|
+
function validateAuthRouterCombo(auth, router) {
|
|
3066
|
+
if (auth === "none") {
|
|
3067
|
+
return;
|
|
3068
|
+
}
|
|
3069
|
+
if (router === "none") {
|
|
3070
|
+
throw new Error(
|
|
3071
|
+
"Portal auth scaffold requires client-side routing. Use --router hash or --router browser."
|
|
3072
|
+
);
|
|
2768
3073
|
}
|
|
2769
|
-
ensureEnvFilesGitignore(projectRoot);
|
|
2770
3074
|
}
|
|
2771
|
-
|
|
2772
|
-
if (
|
|
2773
|
-
|
|
2774
|
-
if (!needsCdnGitHubSetup(projectRoot)) {
|
|
2775
|
-
return null;
|
|
3075
|
+
function validateNavAuthCombo(auth, nav) {
|
|
3076
|
+
if (!nav || nav === "top") {
|
|
3077
|
+
return;
|
|
2776
3078
|
}
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
3079
|
+
if (auth === "none") {
|
|
3080
|
+
throw new Error(
|
|
3081
|
+
"Portal navigation shell requires auth scaffold. Use --auth starter|sso|full or omit --nav."
|
|
3082
|
+
);
|
|
2780
3083
|
}
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
3084
|
+
}
|
|
3085
|
+
function assertNavScaffold(targetDir, nav) {
|
|
3086
|
+
const mainLayoutPath = resolve15(targetDir, "src/components/Layouts/MainLayout.tsx");
|
|
3087
|
+
if (!existsSync11(mainLayoutPath)) {
|
|
3088
|
+
throw new WpHsError("Auth scaffold is missing MainLayout.tsx after nav layer copy", {
|
|
3089
|
+
exitCode: ExitCode.INTERNAL
|
|
3090
|
+
});
|
|
3091
|
+
}
|
|
3092
|
+
const sidebarPath = resolve15(targetDir, "src/components/layout/portal-sidebar.tsx");
|
|
3093
|
+
if (nav === "sidebar") {
|
|
3094
|
+
if (!existsSync11(sidebarPath)) {
|
|
3095
|
+
throw new WpHsError('Nav scaffold mode "sidebar" is missing portal-sidebar.tsx', {
|
|
3096
|
+
exitCode: ExitCode.INTERNAL
|
|
3097
|
+
});
|
|
3098
|
+
}
|
|
3099
|
+
} else if (existsSync11(sidebarPath)) {
|
|
3100
|
+
throw new WpHsError('Nav scaffold mode "top" must not include portal-sidebar.tsx', {
|
|
3101
|
+
exitCode: ExitCode.INTERNAL
|
|
3102
|
+
});
|
|
3103
|
+
}
|
|
3104
|
+
const navScaffold = readFileSync11(resolve15(targetDir, "src/config/nav-scaffold.ts"), "utf8");
|
|
3105
|
+
if (!navScaffold.includes(`'${nav}'`)) {
|
|
3106
|
+
throw new WpHsError(`nav-scaffold.ts was not patched for mode "${nav}"`, {
|
|
3107
|
+
exitCode: ExitCode.INTERNAL
|
|
3108
|
+
});
|
|
3109
|
+
}
|
|
3110
|
+
}
|
|
3111
|
+
function applyAuthScaffold(targetDir, mode, routerMode, template, nav = "top") {
|
|
3112
|
+
if (mode === "none" || !isModuleTemplate(template)) {
|
|
3113
|
+
return;
|
|
3114
|
+
}
|
|
3115
|
+
validateAuthRouterCombo(mode, routerMode);
|
|
3116
|
+
validateNavAuthCombo(mode, nav);
|
|
3117
|
+
for (const layer of layersForMode(mode)) {
|
|
3118
|
+
copyLayer(targetDir, layer);
|
|
3119
|
+
}
|
|
3120
|
+
patchAuthTierFile(targetDir, mode);
|
|
3121
|
+
patchStarterOnlyBlocks(targetDir, mode);
|
|
3122
|
+
patchNavScaffoldFile(targetDir, nav);
|
|
3123
|
+
copyNavLayer(targetDir, nav);
|
|
3124
|
+
assertNavScaffold(targetDir, nav);
|
|
3125
|
+
if (mode === "starter" || mode === "sso") {
|
|
3126
|
+
assertLowerTierAuthScaffold(targetDir, mode);
|
|
3127
|
+
} else if (mode === "full") {
|
|
3128
|
+
assertFullTierAuthScaffold(targetDir);
|
|
3129
|
+
}
|
|
3130
|
+
patchPackageJson(targetDir);
|
|
3131
|
+
if (routerMode && routerMode !== "none") {
|
|
3132
|
+
writeAuthMountDevPreview(targetDir);
|
|
3133
|
+
}
|
|
3134
|
+
}
|
|
3135
|
+
var AUTH_TIER_ORDER = ["none", "starter", "sso", "full"];
|
|
3136
|
+
function authTierRank(tier) {
|
|
3137
|
+
return AUTH_TIER_ORDER.indexOf(tier);
|
|
3138
|
+
}
|
|
3139
|
+
function readCurrentAuthTier(targetDir) {
|
|
3140
|
+
const authTierPath = resolve15(targetDir, "src/config/auth-tier.ts");
|
|
3141
|
+
if (!existsSync11(authTierPath)) {
|
|
3142
|
+
return "none";
|
|
3143
|
+
}
|
|
3144
|
+
const content = readFileSync11(authTierPath, "utf8");
|
|
3145
|
+
const match = content.match(/AUTH_SCAFFOLD_TIER = '(starter|sso|full)'/);
|
|
3146
|
+
return match?.[1] ?? "none";
|
|
3147
|
+
}
|
|
3148
|
+
function applyAuthTierUpgrade(targetDir, targetTier, routerMode, template, nav = "top") {
|
|
3149
|
+
if (!isModuleTemplate(template)) {
|
|
3150
|
+
throw new WpHsError("Auth upgrade is only supported for module projects.", {
|
|
3151
|
+
exitCode: ExitCode.USER_ERROR
|
|
3152
|
+
});
|
|
3153
|
+
}
|
|
3154
|
+
validateAuthRouterCombo(targetTier, routerMode);
|
|
3155
|
+
const currentTier = readCurrentAuthTier(targetDir);
|
|
3156
|
+
if (currentTier === "none") {
|
|
3157
|
+
applyAuthScaffold(targetDir, targetTier, routerMode, template, nav);
|
|
3158
|
+
return targetTier;
|
|
3159
|
+
}
|
|
3160
|
+
if (authTierRank(targetTier) <= authTierRank(currentTier)) {
|
|
3161
|
+
throw new WpHsError(
|
|
3162
|
+
`Auth upgrade requires a higher tier than "${currentTier}" (requested "${targetTier}").`,
|
|
3163
|
+
{
|
|
3164
|
+
exitCode: ExitCode.USER_ERROR,
|
|
3165
|
+
remediation: "Use starter \u2192 sso \u2192 full. Re-init for downgrades."
|
|
3166
|
+
}
|
|
3167
|
+
);
|
|
3168
|
+
}
|
|
3169
|
+
const targetLayers = layersForMode(targetTier);
|
|
3170
|
+
const currentLayers = layersForMode(currentTier);
|
|
3171
|
+
const layersToCopy = targetLayers.filter((layer) => !currentLayers.includes(layer));
|
|
3172
|
+
for (const layer of layersToCopy) {
|
|
3173
|
+
copyLayer(targetDir, layer);
|
|
3174
|
+
}
|
|
3175
|
+
patchAuthTierFile(targetDir, targetTier);
|
|
3176
|
+
patchStarterOnlyBlocks(targetDir, targetTier);
|
|
3177
|
+
if (targetTier === "starter" || targetTier === "sso") {
|
|
3178
|
+
assertLowerTierAuthScaffold(targetDir, targetTier);
|
|
3179
|
+
} else {
|
|
3180
|
+
assertFullTierAuthScaffold(targetDir);
|
|
3181
|
+
}
|
|
3182
|
+
patchPackageJson(targetDir);
|
|
3183
|
+
if (routerMode && routerMode !== "none") {
|
|
3184
|
+
writeAuthMountDevPreview(targetDir);
|
|
3185
|
+
}
|
|
3186
|
+
return targetTier;
|
|
3187
|
+
}
|
|
3188
|
+
var SIDEBAR_NAV_FILES = [
|
|
3189
|
+
"src/components/layout/portal-sidebar.tsx",
|
|
3190
|
+
"src/components/layout/portal-sidebar-nav.tsx"
|
|
3191
|
+
];
|
|
3192
|
+
function applyNavScaffoldUpgrade(targetDir, nav) {
|
|
3193
|
+
const authTierPath = resolve15(targetDir, "src/config/auth-tier.ts");
|
|
3194
|
+
if (!existsSync11(authTierPath)) {
|
|
3195
|
+
throw new WpHsError("Nav scaffold requires portal auth (missing src/config/auth-tier.ts).", {
|
|
3196
|
+
exitCode: ExitCode.USER_ERROR,
|
|
3197
|
+
remediation: "Run wp scaffold auth upgrade --tier starter first."
|
|
3198
|
+
});
|
|
3199
|
+
}
|
|
3200
|
+
if (nav === "top") {
|
|
3201
|
+
for (const relative of SIDEBAR_NAV_FILES) {
|
|
3202
|
+
const filePath = resolve15(targetDir, relative);
|
|
3203
|
+
if (existsSync11(filePath)) {
|
|
3204
|
+
rmSync3(filePath);
|
|
3205
|
+
}
|
|
3206
|
+
}
|
|
3207
|
+
}
|
|
3208
|
+
patchNavScaffoldFile(targetDir, nav);
|
|
3209
|
+
copyNavLayer(targetDir, nav);
|
|
3210
|
+
assertNavScaffold(targetDir, nav);
|
|
3211
|
+
}
|
|
3212
|
+
function readProjectAuthTier(targetDir) {
|
|
3213
|
+
return readCurrentAuthTier(targetDir);
|
|
3214
|
+
}
|
|
3215
|
+
|
|
3216
|
+
// src/lib/init-wizard.ts
|
|
3217
|
+
import * as p4 from "@clack/prompts";
|
|
3218
|
+
|
|
3219
|
+
// src/lib/project-name.ts
|
|
3220
|
+
var PROJECT_NAME_PATTERN = /^[a-z][a-z0-9-]*$/;
|
|
3221
|
+
function normalizeProjectName(input) {
|
|
3222
|
+
return input.trim().toLowerCase().replace(/[\s_]+/g, "-").replace(/[^a-z0-9-]/g, "").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
|
|
3223
|
+
}
|
|
3224
|
+
function validateProjectName(name) {
|
|
3225
|
+
if (!name) return "Project name is required";
|
|
3226
|
+
if (!PROJECT_NAME_PATTERN.test(name)) {
|
|
3227
|
+
return "Use lowercase letters, numbers, and hyphens (e.g. my-portal-module)";
|
|
3228
|
+
}
|
|
3229
|
+
if (name.length > 64) return "Project name is too long (max 64 characters)";
|
|
3230
|
+
return void 0;
|
|
3231
|
+
}
|
|
3232
|
+
function moduleOutDirFromSlug(slug) {
|
|
3233
|
+
const pascal = slug.split("-").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
3234
|
+
return `dist/${pascal || "HubSpot"}.Module`;
|
|
3235
|
+
}
|
|
3236
|
+
|
|
3237
|
+
// src/lib/init-wizard.ts
|
|
3238
|
+
function cancelIfNeeded(value) {
|
|
3239
|
+
if (p4.isCancel(value)) {
|
|
3240
|
+
throw new WpHsError("Init cancelled", { exitCode: ExitCode.USER_ERROR });
|
|
3241
|
+
}
|
|
3242
|
+
return value;
|
|
3243
|
+
}
|
|
3244
|
+
async function runInitWizard(options) {
|
|
3245
|
+
let template = options.template;
|
|
3246
|
+
if (!template) {
|
|
3247
|
+
const type = cancelIfNeeded(
|
|
3248
|
+
await p4.select({
|
|
3249
|
+
message: "Project type",
|
|
3250
|
+
options: [
|
|
3251
|
+
{
|
|
3252
|
+
value: "module-hybrid-cdn",
|
|
3253
|
+
label: "Module (hybrid CDN)",
|
|
3254
|
+
hint: "jsDelivr + thin module.js"
|
|
3255
|
+
},
|
|
3256
|
+
{
|
|
3257
|
+
value: "module-monolith",
|
|
3258
|
+
label: "Module (monolith)",
|
|
3259
|
+
hint: "Single module.js IIFE"
|
|
3260
|
+
},
|
|
3261
|
+
{
|
|
3262
|
+
value: "theme-starter",
|
|
3263
|
+
label: "Theme starter",
|
|
3264
|
+
hint: "CMS theme templates"
|
|
3265
|
+
}
|
|
3266
|
+
]
|
|
3267
|
+
})
|
|
3268
|
+
);
|
|
3269
|
+
template = type;
|
|
3270
|
+
}
|
|
3271
|
+
let projectName = options.name ? normalizeProjectName(options.name) : void 0;
|
|
3272
|
+
if (!options.directory && !projectName) {
|
|
3273
|
+
const nameAnswer = cancelIfNeeded(
|
|
3274
|
+
await p4.text({
|
|
3275
|
+
message: "Project name (folder name)",
|
|
3276
|
+
placeholder: "my-portal-module",
|
|
3277
|
+
validate: (value) => validateProjectName(normalizeProjectName(value ?? ""))
|
|
3278
|
+
})
|
|
3279
|
+
);
|
|
3280
|
+
projectName = normalizeProjectName(nameAnswer);
|
|
3281
|
+
}
|
|
3282
|
+
if (!projectName && options.directory) {
|
|
3283
|
+
projectName = normalizeProjectName(options.directory.split("/").pop() ?? "my-portal");
|
|
3284
|
+
}
|
|
3285
|
+
if (!projectName) {
|
|
3286
|
+
throw new WpHsError("Project name is required", {
|
|
3287
|
+
exitCode: ExitCode.USER_ERROR,
|
|
3288
|
+
remediation: "Pass --name or --directory."
|
|
3289
|
+
});
|
|
3290
|
+
}
|
|
3291
|
+
let router = options.router;
|
|
3292
|
+
if (!router && isModuleTemplate(template)) {
|
|
3293
|
+
const routerAnswer = cancelIfNeeded(
|
|
3294
|
+
await p4.select({
|
|
3295
|
+
message: "Client-side routing",
|
|
3296
|
+
initialValue: "hash",
|
|
3297
|
+
options: [
|
|
3298
|
+
{
|
|
3299
|
+
value: "hash",
|
|
3300
|
+
label: "Hash history (recommended)",
|
|
3301
|
+
hint: "Works in HubSpot CMS embeds"
|
|
3302
|
+
},
|
|
3303
|
+
{
|
|
3304
|
+
value: "browser",
|
|
3305
|
+
label: "Browser history",
|
|
3306
|
+
hint: "Clean URLs for local dev only"
|
|
3307
|
+
},
|
|
3308
|
+
{
|
|
3309
|
+
value: "none",
|
|
3310
|
+
label: "No router",
|
|
3311
|
+
hint: "Single-page starter"
|
|
3312
|
+
}
|
|
3313
|
+
]
|
|
3314
|
+
})
|
|
3315
|
+
);
|
|
3316
|
+
router = routerAnswer;
|
|
3317
|
+
}
|
|
3318
|
+
let auth = options.auth ?? "none";
|
|
3319
|
+
if (!options.auth && isModuleTemplate(template) && router && router !== "none") {
|
|
3320
|
+
const authAnswer = cancelIfNeeded(
|
|
3321
|
+
await p4.select({
|
|
3322
|
+
message: "Portal auth (woodsportal-api + SDK)",
|
|
3323
|
+
initialValue: "none",
|
|
3324
|
+
options: [
|
|
3325
|
+
{ value: "none", label: "No portal login", hint: "Routes only" },
|
|
3326
|
+
{ value: "starter", label: "Email + password", hint: "Login + sample protected page" },
|
|
3327
|
+
{ value: "sso", label: "Email + password + SSO", hint: "OAuth callback route" },
|
|
3328
|
+
{
|
|
3329
|
+
value: "full",
|
|
3330
|
+
label: "Email + password + SSO + MFA",
|
|
3331
|
+
hint: "Recommended for production portals"
|
|
3332
|
+
}
|
|
3333
|
+
]
|
|
3334
|
+
})
|
|
3335
|
+
);
|
|
3336
|
+
auth = authAnswer;
|
|
3337
|
+
} else if (router === "none" && isModuleTemplate(template)) {
|
|
3338
|
+
p4.log.info("Portal auth requires routing \u2014 auth tier set to none.");
|
|
3339
|
+
auth = "none";
|
|
3340
|
+
}
|
|
3341
|
+
let nav = options.nav ?? "top";
|
|
3342
|
+
if (!options.nav && auth !== "none" && isModuleTemplate(template)) {
|
|
3343
|
+
const navAnswer = cancelIfNeeded(
|
|
3344
|
+
await p4.select({
|
|
3345
|
+
message: "Portal navigation shell",
|
|
3346
|
+
initialValue: "top",
|
|
3347
|
+
options: [
|
|
3348
|
+
{
|
|
3349
|
+
value: "top",
|
|
3350
|
+
label: "Top navigation (recommended)",
|
|
3351
|
+
hint: "Simple header links"
|
|
3352
|
+
},
|
|
3353
|
+
{
|
|
3354
|
+
value: "sidebar",
|
|
3355
|
+
label: "Sidebar navigation",
|
|
3356
|
+
hint: "Collapsible left menu (production-shaped)"
|
|
3357
|
+
}
|
|
3358
|
+
]
|
|
3359
|
+
})
|
|
3360
|
+
);
|
|
3361
|
+
nav = navAnswer;
|
|
3362
|
+
} else if (auth === "none") {
|
|
3363
|
+
nav = "top";
|
|
3364
|
+
}
|
|
3365
|
+
let cdnSetup = null;
|
|
3366
|
+
if (template === "module-hybrid-cdn") {
|
|
3367
|
+
if (options.cdnGithub) {
|
|
3368
|
+
cdnSetup = { github: options.cdnGithub };
|
|
3369
|
+
} else {
|
|
3370
|
+
p4.log.step("Hybrid CDN \u2014 GitHub mirror (public repo for jsDelivr)");
|
|
3371
|
+
const prompted = await promptCdnGitHubSetup({
|
|
3372
|
+
projectRoot: options.cwd,
|
|
3373
|
+
githubDefault: githubDefaultForPrompt(options.cwd),
|
|
3374
|
+
github: options.cdnGithub
|
|
3375
|
+
});
|
|
3376
|
+
if (prompted) {
|
|
3377
|
+
cdnSetup = prompted;
|
|
2789
3378
|
}
|
|
2790
|
-
}
|
|
2791
|
-
if (p3.isCancel(answer)) return null;
|
|
2792
|
-
github = normalizeGithubSlug(answer) ?? void 0;
|
|
3379
|
+
}
|
|
2793
3380
|
}
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
3381
|
+
const folder = options.directory ?? `${options.cwd}/${projectName}`;
|
|
3382
|
+
const summary = [
|
|
3383
|
+
`Template: ${template}`,
|
|
3384
|
+
`Folder: ${folder}`,
|
|
3385
|
+
...isModuleTemplate(template) ? [
|
|
3386
|
+
`Router: ${router ?? "hash"}`,
|
|
3387
|
+
`Auth: ${auth}`,
|
|
3388
|
+
...auth !== "none" ? [`Nav: ${nav}`] : []
|
|
3389
|
+
] : [],
|
|
3390
|
+
...template === "module-hybrid-cdn" ? [`CDN repo: ${cdnSetup?.github ?? "(skipped \u2014 configure later)"}`] : []
|
|
3391
|
+
].join("\n");
|
|
3392
|
+
p4.note(summary, "Scaffold summary");
|
|
3393
|
+
const confirmed = cancelIfNeeded(
|
|
3394
|
+
await p4.confirm({
|
|
3395
|
+
message: "Proceed with scaffold?",
|
|
3396
|
+
initialValue: true
|
|
3397
|
+
})
|
|
3398
|
+
);
|
|
3399
|
+
if (!confirmed) {
|
|
3400
|
+
throw new WpHsError("Init cancelled", { exitCode: ExitCode.USER_ERROR });
|
|
2797
3401
|
}
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
}
|
|
2808
|
-
function formatCdnSetupNote(_projectRoot) {
|
|
2809
|
-
return `CDN config written \u2014 edit ${PORTAL_CDN_JSON} if needed; token saved to ${ENV_LOCAL_FILE} (gitignored)`;
|
|
3402
|
+
return {
|
|
3403
|
+
template,
|
|
3404
|
+
projectName,
|
|
3405
|
+
router,
|
|
3406
|
+
auth,
|
|
3407
|
+
nav,
|
|
3408
|
+
cdnSetup,
|
|
3409
|
+
confirmed: true
|
|
3410
|
+
};
|
|
2810
3411
|
}
|
|
2811
3412
|
|
|
2812
3413
|
// src/lib/project-setup.ts
|
|
2813
|
-
import { copyFileSync, existsSync as existsSync12, writeFileSync as
|
|
2814
|
-
import { basename, resolve as
|
|
3414
|
+
import { copyFileSync, existsSync as existsSync12, writeFileSync as writeFileSync7 } from "fs";
|
|
3415
|
+
import { basename, resolve as resolve16 } from "path";
|
|
2815
3416
|
function envFileForTier2(tier) {
|
|
2816
3417
|
return tier === "dev" ? ".env.dev" : tier === "stg" ? ".env.stg" : ".env.prod";
|
|
2817
3418
|
}
|
|
@@ -2826,7 +3427,7 @@ function firstExisting(paths) {
|
|
|
2826
3427
|
return null;
|
|
2827
3428
|
}
|
|
2828
3429
|
function copyMissingFile(projectRoot, targetName, sourceCandidates, dryRun) {
|
|
2829
|
-
const target =
|
|
3430
|
+
const target = resolve16(projectRoot, targetName);
|
|
2830
3431
|
if (existsSync12(target)) {
|
|
2831
3432
|
return { action: "skipped", path: targetName, detail: "already present" };
|
|
2832
3433
|
}
|
|
@@ -2849,12 +3450,12 @@ function copyMissingFile(projectRoot, targetName, sourceCandidates, dryRun) {
|
|
|
2849
3450
|
};
|
|
2850
3451
|
}
|
|
2851
3452
|
function writeMissingFile(projectRoot, targetName, content, dryRun) {
|
|
2852
|
-
const target =
|
|
3453
|
+
const target = resolve16(projectRoot, targetName);
|
|
2853
3454
|
if (existsSync12(target)) {
|
|
2854
3455
|
return { action: "skipped", path: targetName, detail: "already present" };
|
|
2855
3456
|
}
|
|
2856
3457
|
if (!dryRun) {
|
|
2857
|
-
|
|
3458
|
+
writeFileSync7(target, content);
|
|
2858
3459
|
}
|
|
2859
3460
|
return {
|
|
2860
3461
|
action: "copied",
|
|
@@ -2880,15 +3481,15 @@ function applyProjectSetup(ctx, options = {}) {
|
|
|
2880
3481
|
const templateDir = kitTemplateDir(config);
|
|
2881
3482
|
results.push(
|
|
2882
3483
|
copyMissingFile(projectRoot, HUBSPOT_CONFIG_FILE, [
|
|
2883
|
-
|
|
2884
|
-
|
|
3484
|
+
resolve16(projectRoot, HUBSPOT_CONFIG_EXAMPLE),
|
|
3485
|
+
resolve16(templateDir, HUBSPOT_CONFIG_EXAMPLE),
|
|
2885
3486
|
kitPath("templates", HUBSPOT_CONFIG_EXAMPLE)
|
|
2886
3487
|
], dryRun)
|
|
2887
3488
|
);
|
|
2888
3489
|
results.push(
|
|
2889
3490
|
copyMissingFile(projectRoot, ".env.local", [
|
|
2890
|
-
|
|
2891
|
-
|
|
3491
|
+
resolve16(projectRoot, ".env.local.example"),
|
|
3492
|
+
resolve16(sharedDir, ".env.local.example")
|
|
2892
3493
|
], dryRun)
|
|
2893
3494
|
);
|
|
2894
3495
|
for (const tier of tiersToSetup(config, options.tier)) {
|
|
@@ -2896,8 +3497,8 @@ function applyProjectSetup(ctx, options = {}) {
|
|
|
2896
3497
|
const envExample = `${envFile}.example`;
|
|
2897
3498
|
results.push(
|
|
2898
3499
|
copyMissingFile(projectRoot, envFile, [
|
|
2899
|
-
|
|
2900
|
-
|
|
3500
|
+
resolve16(projectRoot, envExample),
|
|
3501
|
+
resolve16(sharedDir, envExample)
|
|
2901
3502
|
], dryRun)
|
|
2902
3503
|
);
|
|
2903
3504
|
}
|
|
@@ -2905,8 +3506,8 @@ function applyProjectSetup(ctx, options = {}) {
|
|
|
2905
3506
|
const mirror = config.cdn.mirror;
|
|
2906
3507
|
results.push(
|
|
2907
3508
|
copyMissingFile(projectRoot, mirror, [
|
|
2908
|
-
|
|
2909
|
-
|
|
3509
|
+
resolve16(projectRoot, `${mirror}.example`),
|
|
3510
|
+
resolve16(sharedDir, "portal.cdn.json.example")
|
|
2910
3511
|
], dryRun)
|
|
2911
3512
|
);
|
|
2912
3513
|
const buildIdFile = config.cdn.buildIdFile;
|
|
@@ -2921,14 +3522,14 @@ function applyProjectSetup(ctx, options = {}) {
|
|
|
2921
3522
|
}
|
|
2922
3523
|
for (const file of templateViteFiles(config)) {
|
|
2923
3524
|
results.push(
|
|
2924
|
-
copyMissingFile(projectRoot, file, [
|
|
3525
|
+
copyMissingFile(projectRoot, file, [resolve16(templateDir, file)], dryRun)
|
|
2925
3526
|
);
|
|
2926
3527
|
}
|
|
2927
3528
|
if (isModuleProject(config) && !isThemeProject(config)) {
|
|
2928
3529
|
const adapter = config.dev.adapter;
|
|
2929
|
-
const adapterPath =
|
|
3530
|
+
const adapterPath = resolve16(projectRoot, adapter);
|
|
2930
3531
|
if (!existsSync12(adapterPath)) {
|
|
2931
|
-
const templateAdapter =
|
|
3532
|
+
const templateAdapter = resolve16(templateDir, "wphs.adapter.ts");
|
|
2932
3533
|
if (existsSync12(templateAdapter)) {
|
|
2933
3534
|
results.push(copyMissingFile(projectRoot, adapter, [templateAdapter], dryRun));
|
|
2934
3535
|
} else {
|
|
@@ -2954,19 +3555,19 @@ function copySharedSetupExamples(targetDir) {
|
|
|
2954
3555
|
".env.prod.example",
|
|
2955
3556
|
"portal.cdn.json.example"
|
|
2956
3557
|
]) {
|
|
2957
|
-
const src =
|
|
2958
|
-
const dest =
|
|
3558
|
+
const src = resolve16(sharedDir, name);
|
|
3559
|
+
const dest = resolve16(targetDir, name);
|
|
2959
3560
|
if (existsSync12(src) && !existsSync12(dest)) {
|
|
2960
3561
|
copyFileSync(src, dest);
|
|
2961
3562
|
}
|
|
2962
3563
|
}
|
|
2963
3564
|
const hubspotExample = firstExisting([
|
|
2964
|
-
|
|
3565
|
+
resolve16(sharedDir, HUBSPOT_CONFIG_EXAMPLE),
|
|
2965
3566
|
kitPath("templates", HUBSPOT_CONFIG_EXAMPLE)
|
|
2966
3567
|
]);
|
|
2967
3568
|
if (!hubspotExample) return;
|
|
2968
|
-
const destHubspot =
|
|
2969
|
-
if (!existsSync12(destHubspot) && !existsSync12(
|
|
3569
|
+
const destHubspot = resolve16(targetDir, HUBSPOT_CONFIG_EXAMPLE);
|
|
3570
|
+
if (!existsSync12(destHubspot) && !existsSync12(resolve16(targetDir, HUBSPOT_CONFIG_FILE))) {
|
|
2970
3571
|
copyFileSync(hubspotExample, destHubspot);
|
|
2971
3572
|
}
|
|
2972
3573
|
}
|
|
@@ -3014,26 +3615,8 @@ var TEMPLATE_SCRIPTS = {
|
|
|
3014
3615
|
}
|
|
3015
3616
|
};
|
|
3016
3617
|
|
|
3017
|
-
// src/lib/project-name.ts
|
|
3018
|
-
var PROJECT_NAME_PATTERN = /^[a-z][a-z0-9-]*$/;
|
|
3019
|
-
function normalizeProjectName(input) {
|
|
3020
|
-
return input.trim().toLowerCase().replace(/[\s_]+/g, "-").replace(/[^a-z0-9-]/g, "").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
|
|
3021
|
-
}
|
|
3022
|
-
function validateProjectName(name) {
|
|
3023
|
-
if (!name) return "Project name is required";
|
|
3024
|
-
if (!PROJECT_NAME_PATTERN.test(name)) {
|
|
3025
|
-
return "Use lowercase letters, numbers, and hyphens (e.g. my-portal-module)";
|
|
3026
|
-
}
|
|
3027
|
-
if (name.length > 64) return "Project name is too long (max 64 characters)";
|
|
3028
|
-
return void 0;
|
|
3029
|
-
}
|
|
3030
|
-
function moduleOutDirFromSlug(slug) {
|
|
3031
|
-
const pascal = slug.split("-").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
3032
|
-
return `dist/${pascal || "HubSpot"}.Module`;
|
|
3033
|
-
}
|
|
3034
|
-
|
|
3035
3618
|
// src/cli/commands/init.ts
|
|
3036
|
-
function buildConfigForProject(template, projectName, router) {
|
|
3619
|
+
function buildConfigForProject(template, projectName, router, auth = "none", nav = "top") {
|
|
3037
3620
|
const base = { ...DEFAULT_WOODSPORTAL_HS, projectType: template };
|
|
3038
3621
|
if (template === "theme-starter") {
|
|
3039
3622
|
return {
|
|
@@ -3058,8 +3641,12 @@ function buildConfigForProject(template, projectName, router) {
|
|
|
3058
3641
|
mode: template === "module-monolith" ? "none" : base.cdn.mode
|
|
3059
3642
|
}
|
|
3060
3643
|
};
|
|
3061
|
-
if (isModuleTemplate(template)
|
|
3062
|
-
config.scaffold = {
|
|
3644
|
+
if (isModuleTemplate(template)) {
|
|
3645
|
+
config.scaffold = {
|
|
3646
|
+
router: router ?? "hash",
|
|
3647
|
+
auth: auth ?? "none",
|
|
3648
|
+
...auth !== "none" ? { nav: nav ?? "top" } : {}
|
|
3649
|
+
};
|
|
3063
3650
|
}
|
|
3064
3651
|
return config;
|
|
3065
3652
|
}
|
|
@@ -3075,7 +3662,7 @@ function assertTargetDirIsScaffoldable(targetDir) {
|
|
|
3075
3662
|
}
|
|
3076
3663
|
function resolveInitTargetDir(options, interactiveName) {
|
|
3077
3664
|
if (options.directory) {
|
|
3078
|
-
return
|
|
3665
|
+
return resolve17(options.directory);
|
|
3079
3666
|
}
|
|
3080
3667
|
const parentDir = process.cwd();
|
|
3081
3668
|
const slug = options.name ?? interactiveName;
|
|
@@ -3089,67 +3676,31 @@ function resolveInitTargetDir(options, interactiveName) {
|
|
|
3089
3676
|
if (error) {
|
|
3090
3677
|
throw new WpHsError(error, { exitCode: ExitCode.USER_ERROR });
|
|
3091
3678
|
}
|
|
3092
|
-
return
|
|
3679
|
+
return resolve17(parentDir, slug);
|
|
3093
3680
|
}
|
|
3094
3681
|
async function runInit(ctx, options) {
|
|
3095
3682
|
const nonInteractive = ctx.global.ci || ctx.global.yes || ctx.global.json;
|
|
3096
3683
|
let template = options.template;
|
|
3097
3684
|
let projectName = options.name ? normalizeProjectName(options.name) : void 0;
|
|
3098
3685
|
let router = options.router;
|
|
3686
|
+
let auth = options.auth ?? "none";
|
|
3687
|
+
let nav = options.nav ?? "top";
|
|
3688
|
+
let cdnGithub = options.cdnGithub;
|
|
3689
|
+
let wizardCdnSetup = null;
|
|
3099
3690
|
if (!nonInteractive) {
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
if (p4.isCancel(type)) throw new WpHsError("Init cancelled", { exitCode: ExitCode.USER_ERROR });
|
|
3114
|
-
template = type;
|
|
3115
|
-
}
|
|
3116
|
-
if (!options.directory && !projectName) {
|
|
3117
|
-
const nameAnswer = await p4.text({
|
|
3118
|
-
message: "Project name (folder name)",
|
|
3119
|
-
placeholder: "my-portal-module",
|
|
3120
|
-
validate: (value) => validateProjectName(normalizeProjectName(value ?? ""))
|
|
3121
|
-
});
|
|
3122
|
-
if (p4.isCancel(nameAnswer)) {
|
|
3123
|
-
throw new WpHsError("Init cancelled", { exitCode: ExitCode.USER_ERROR });
|
|
3124
|
-
}
|
|
3125
|
-
projectName = normalizeProjectName(nameAnswer);
|
|
3126
|
-
}
|
|
3127
|
-
if (!router && template && isModuleTemplate(template)) {
|
|
3128
|
-
const routerAnswer = await p4.select({
|
|
3129
|
-
message: "Client-side routing",
|
|
3130
|
-
initialValue: "hash",
|
|
3131
|
-
options: [
|
|
3132
|
-
{
|
|
3133
|
-
value: "hash",
|
|
3134
|
-
label: "Hash history (recommended)",
|
|
3135
|
-
hint: "Same as woodsportal-client-frontend \u2014 works in HubSpot CMS embeds"
|
|
3136
|
-
},
|
|
3137
|
-
{
|
|
3138
|
-
value: "browser",
|
|
3139
|
-
label: "Browser history",
|
|
3140
|
-
hint: "Clean /guide URLs for local dev (not for HubSpot iframe embeds)"
|
|
3141
|
-
},
|
|
3142
|
-
{
|
|
3143
|
-
value: "none",
|
|
3144
|
-
label: "No router",
|
|
3145
|
-
hint: "Single-page starter \u2014 add TanStack Router later"
|
|
3146
|
-
}
|
|
3147
|
-
]
|
|
3148
|
-
});
|
|
3149
|
-
if (p4.isCancel(routerAnswer)) {
|
|
3150
|
-
throw new WpHsError("Init cancelled", { exitCode: ExitCode.USER_ERROR });
|
|
3151
|
-
}
|
|
3152
|
-
router = routerAnswer;
|
|
3691
|
+
const wizard = await runInitWizard({
|
|
3692
|
+
...options,
|
|
3693
|
+
cwd: process.cwd(),
|
|
3694
|
+
auth: options.auth
|
|
3695
|
+
});
|
|
3696
|
+
template = wizard.template;
|
|
3697
|
+
projectName = wizard.projectName;
|
|
3698
|
+
router = wizard.router;
|
|
3699
|
+
auth = wizard.auth;
|
|
3700
|
+
nav = wizard.nav ?? "top";
|
|
3701
|
+
wizardCdnSetup = wizard.cdnSetup;
|
|
3702
|
+
if (wizard.cdnSetup?.github) {
|
|
3703
|
+
cdnGithub = wizard.cdnSetup.github;
|
|
3153
3704
|
}
|
|
3154
3705
|
}
|
|
3155
3706
|
if (!template) {
|
|
@@ -3158,6 +3709,19 @@ async function runInit(ctx, options) {
|
|
|
3158
3709
|
if (!router && isModuleTemplate(template)) {
|
|
3159
3710
|
router = "hash";
|
|
3160
3711
|
}
|
|
3712
|
+
if (!options.auth && nonInteractive && isModuleTemplate(template)) {
|
|
3713
|
+
auth = "none";
|
|
3714
|
+
}
|
|
3715
|
+
try {
|
|
3716
|
+
validateAuthRouterCombo(auth, router);
|
|
3717
|
+
validateNavAuthCombo(auth, nav);
|
|
3718
|
+
} catch (error) {
|
|
3719
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3720
|
+
throw new WpHsError(message, {
|
|
3721
|
+
exitCode: ExitCode.USER_ERROR,
|
|
3722
|
+
remediation: "Use --router hash or --router browser with --auth starter|sso|full. Nav shell (--nav sidebar) requires auth."
|
|
3723
|
+
});
|
|
3724
|
+
}
|
|
3161
3725
|
const targetDir = resolveInitTargetDir(options, projectName);
|
|
3162
3726
|
const slug = projectName ?? basename2(targetDir);
|
|
3163
3727
|
assertTargetDirIsScaffoldable(targetDir);
|
|
@@ -3166,36 +3730,37 @@ async function runInit(ctx, options) {
|
|
|
3166
3730
|
throw new WpHsError(`Template not found: ${template}`, { exitCode: ExitCode.USER_ERROR });
|
|
3167
3731
|
}
|
|
3168
3732
|
if (ctx.global.dryRun) {
|
|
3169
|
-
ctx.logger.info(
|
|
3733
|
+
ctx.logger.info(
|
|
3734
|
+
`[dry-run] Would scaffold ${template} in ${targetDir} (router=${router ?? "n/a"}, auth=${auth}, nav=${auth !== "none" ? nav : "n/a"})`
|
|
3735
|
+
);
|
|
3170
3736
|
return ExitCode.SUCCESS;
|
|
3171
3737
|
}
|
|
3172
|
-
|
|
3173
|
-
|
|
3738
|
+
mkdirSync5(targetDir, { recursive: true });
|
|
3739
|
+
cpSync3(templateDir, targetDir, { recursive: true });
|
|
3174
3740
|
copySharedSetupExamples(targetDir);
|
|
3175
|
-
const config = buildConfigForProject(template, slug, router);
|
|
3176
|
-
|
|
3741
|
+
const config = buildConfigForProject(template, slug, router, auth, nav);
|
|
3742
|
+
writeFileSync8(resolve17(targetDir, WOODSCLI_CONFIG_FILE), `${JSON.stringify(config, null, 2)}
|
|
3177
3743
|
`);
|
|
3178
|
-
const pkgPath =
|
|
3744
|
+
const pkgPath = resolve17(targetDir, "package.json");
|
|
3179
3745
|
if (existsSync13(pkgPath)) {
|
|
3180
|
-
const pkg = JSON.parse(
|
|
3746
|
+
const pkg = JSON.parse(readFileSync12(pkgPath, "utf8"));
|
|
3181
3747
|
pkg.name = slug;
|
|
3182
3748
|
pkg.scripts = { ...pkg.scripts, ...TEMPLATE_SCRIPTS[template] };
|
|
3183
3749
|
pkg.devDependencies = {
|
|
3184
3750
|
...pkg.devDependencies,
|
|
3185
3751
|
...templateDevDependencies()
|
|
3186
3752
|
};
|
|
3187
|
-
|
|
3753
|
+
writeFileSync8(pkgPath, `${JSON.stringify(pkg, null, 2)}
|
|
3188
3754
|
`);
|
|
3189
3755
|
}
|
|
3190
3756
|
if (router && isModuleTemplate(template)) {
|
|
3191
3757
|
applyRouterScaffold(targetDir, router, template);
|
|
3192
3758
|
}
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
const gitignorePath = resolve18(targetDir, ".gitignore");
|
|
3759
|
+
if (isModuleTemplate(template)) {
|
|
3760
|
+
applyAuthScaffold(targetDir, auth, router, template, nav);
|
|
3761
|
+
}
|
|
3762
|
+
await ensureProjectWphsWorkspace(targetDir);
|
|
3763
|
+
const gitignorePath = resolve17(targetDir, ".gitignore");
|
|
3199
3764
|
const gitignoreExtras = [
|
|
3200
3765
|
"hubspot.config.yml",
|
|
3201
3766
|
".wphs/config/module-config.local.json",
|
|
@@ -3208,40 +3773,27 @@ async function runInit(ctx, options) {
|
|
|
3208
3773
|
".wphs/.kit-bootstrap.tsx",
|
|
3209
3774
|
".wphs/.kit-bootstrap-greenfield.tsx"
|
|
3210
3775
|
];
|
|
3211
|
-
const existing = existsSync13(gitignorePath) ?
|
|
3776
|
+
const existing = existsSync13(gitignorePath) ? readFileSync12(gitignorePath, "utf8") : "";
|
|
3212
3777
|
const toAppend = gitignoreExtras.filter((line) => !existing.includes(line)).join("\n");
|
|
3213
3778
|
if (toAppend) {
|
|
3214
|
-
|
|
3779
|
+
writeFileSync8(gitignorePath, `${existing.trimEnd()}
|
|
3215
3780
|
${toAppend}
|
|
3216
3781
|
`);
|
|
3217
3782
|
}
|
|
3218
|
-
if (template === "module-hybrid-cdn" && !ctx.global.dryRun
|
|
3219
|
-
const
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
if (options.cdnGithub) {
|
|
3223
|
-
cdnSetup = { github: options.cdnGithub };
|
|
3224
|
-
}
|
|
3225
|
-
} else {
|
|
3226
|
-
p4.log.step("Hybrid CDN \u2014 GitHub mirror (public repo for jsDelivr)");
|
|
3227
|
-
cdnSetup = await promptCdnGitHubSetup({
|
|
3228
|
-
projectRoot: targetDir,
|
|
3229
|
-
githubDefault,
|
|
3230
|
-
github: options.cdnGithub
|
|
3231
|
-
});
|
|
3232
|
-
}
|
|
3233
|
-
if (cdnSetup) {
|
|
3234
|
-
applyCdnGitHubSetup(targetDir, cdnSetup);
|
|
3783
|
+
if (template === "module-hybrid-cdn" && !ctx.global.dryRun) {
|
|
3784
|
+
const setup = wizardCdnSetup ?? (cdnGithub ? { github: cdnGithub } : null);
|
|
3785
|
+
if (setup) {
|
|
3786
|
+
applyCdnGitHubSetup(targetDir, setup);
|
|
3235
3787
|
if (!nonInteractive) {
|
|
3236
|
-
|
|
3788
|
+
p5.log.success(formatCdnSetupNote(targetDir));
|
|
3237
3789
|
} else {
|
|
3238
3790
|
ctx.logger.success(formatCdnSetupNote(targetDir));
|
|
3239
3791
|
}
|
|
3240
3792
|
}
|
|
3241
3793
|
}
|
|
3242
3794
|
if (!nonInteractive) {
|
|
3243
|
-
|
|
3244
|
-
|
|
3795
|
+
p5.log.success(`Scaffolded ${template} in ${targetDir}`);
|
|
3796
|
+
p5.note(`cd ${slug} && yarn install && wp setup && wp doctor && wp dev`, "Getting started");
|
|
3245
3797
|
} else {
|
|
3246
3798
|
ctx.logger.success(`Scaffolded ${template} in ${targetDir}`);
|
|
3247
3799
|
}
|
|
@@ -3249,15 +3801,15 @@ ${toAppend}
|
|
|
3249
3801
|
}
|
|
3250
3802
|
|
|
3251
3803
|
// src/cli/commands/migrate.ts
|
|
3252
|
-
import { cpSync as
|
|
3253
|
-
import { join as
|
|
3804
|
+
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";
|
|
3805
|
+
import { join as join4, resolve as resolve18 } from "path";
|
|
3254
3806
|
var LEGACY_DIRS = [".dev", ".woodsportal"];
|
|
3255
3807
|
var LEGACY_BASELINE = ".wp-hs-baseline";
|
|
3256
3808
|
var LEGACY_BUILD_ID_FILE = ".portal-build-id";
|
|
3257
3809
|
var MODULE_BUILD_ID_FILE = ".module-build-id";
|
|
3258
3810
|
function readFirstNonCommentLine(filePath) {
|
|
3259
3811
|
if (!existsSync14(filePath)) return void 0;
|
|
3260
|
-
const line =
|
|
3812
|
+
const line = readFileSync13(filePath, "utf8").split("\n").map((l) => l.trim()).find((l) => l.length > 0 && !l.startsWith("#"));
|
|
3261
3813
|
return line || void 0;
|
|
3262
3814
|
}
|
|
3263
3815
|
function stripToBaseline(raw) {
|
|
@@ -3271,71 +3823,71 @@ function stripToBaseline(raw) {
|
|
|
3271
3823
|
}
|
|
3272
3824
|
async function runMigrate(ctx, _options = {}) {
|
|
3273
3825
|
const root = ctx.projectRoot;
|
|
3274
|
-
const wphsRoot =
|
|
3826
|
+
const wphsRoot = resolve18(root, WPHS_WORKSPACE_DIR);
|
|
3275
3827
|
const actions = [];
|
|
3276
3828
|
for (const legacy of LEGACY_DIRS) {
|
|
3277
|
-
const legacyPath =
|
|
3829
|
+
const legacyPath = resolve18(root, legacy);
|
|
3278
3830
|
if (!existsSync14(legacyPath)) continue;
|
|
3279
3831
|
actions.push(`Move ${legacy}/ \u2192 .wphs/`);
|
|
3280
3832
|
if (ctx.global.dryRun) continue;
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
const localConfig =
|
|
3284
|
-
const exampleConfig =
|
|
3833
|
+
mkdirSync6(join4(wphsRoot, "config"), { recursive: true });
|
|
3834
|
+
mkdirSync6(join4(wphsRoot, "assets", "branding"), { recursive: true });
|
|
3835
|
+
const localConfig = join4(legacyPath, "module-config.local.json");
|
|
3836
|
+
const exampleConfig = join4(legacyPath, "module-config.example.json");
|
|
3285
3837
|
if (existsSync14(localConfig)) {
|
|
3286
|
-
|
|
3838
|
+
cpSync4(localConfig, join4(wphsRoot, "config", "module-config.local.json"));
|
|
3287
3839
|
}
|
|
3288
3840
|
if (existsSync14(exampleConfig)) {
|
|
3289
|
-
|
|
3841
|
+
cpSync4(exampleConfig, join4(wphsRoot, "config", "module-config.example.json"));
|
|
3290
3842
|
}
|
|
3291
|
-
const branding =
|
|
3843
|
+
const branding = join4(legacyPath, "branding-assets");
|
|
3292
3844
|
if (existsSync14(branding)) {
|
|
3293
3845
|
for (const file of readdirSync3(branding)) {
|
|
3294
|
-
|
|
3846
|
+
cpSync4(join4(branding, file), join4(wphsRoot, "assets", "branding", file));
|
|
3295
3847
|
}
|
|
3296
3848
|
}
|
|
3297
|
-
|
|
3849
|
+
rmSync4(legacyPath, { recursive: true, force: true });
|
|
3298
3850
|
}
|
|
3299
|
-
const legacyBaseline =
|
|
3300
|
-
const targetBaseline =
|
|
3851
|
+
const legacyBaseline = resolve18(root, LEGACY_BASELINE);
|
|
3852
|
+
const targetBaseline = join4(wphsRoot, "audit", "baseline");
|
|
3301
3853
|
if (existsSync14(legacyBaseline)) {
|
|
3302
3854
|
actions.push(`Move ${LEGACY_BASELINE}/ \u2192 .wphs/audit/baseline/`);
|
|
3303
3855
|
if (!ctx.global.dryRun) {
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3856
|
+
mkdirSync6(targetBaseline, { recursive: true });
|
|
3857
|
+
cpSync4(legacyBaseline, targetBaseline, { recursive: true });
|
|
3858
|
+
rmSync4(legacyBaseline, { recursive: true, force: true });
|
|
3307
3859
|
}
|
|
3308
3860
|
}
|
|
3309
|
-
const
|
|
3310
|
-
const woodscliPath =
|
|
3311
|
-
if (existsSync14(
|
|
3861
|
+
const legacyConfigPath = resolve18(root, LEGACY_WOODSPORTAL_HS_FILE);
|
|
3862
|
+
const woodscliPath = resolve18(root, WOODSCLI_CONFIG_FILE);
|
|
3863
|
+
if (existsSync14(legacyConfigPath) && !existsSync14(woodscliPath)) {
|
|
3312
3864
|
actions.push(`Rename ${LEGACY_WOODSPORTAL_HS_FILE} \u2192 ${WOODSCLI_CONFIG_FILE}`);
|
|
3313
3865
|
if (!ctx.global.dryRun) {
|
|
3314
|
-
renameSync(
|
|
3866
|
+
renameSync(legacyConfigPath, woodscliPath);
|
|
3315
3867
|
}
|
|
3316
|
-
} else if (existsSync14(
|
|
3868
|
+
} else if (existsSync14(legacyConfigPath) && existsSync14(woodscliPath)) {
|
|
3317
3869
|
actions.push(`Remove duplicate ${LEGACY_WOODSPORTAL_HS_FILE}`);
|
|
3318
3870
|
if (!ctx.global.dryRun) {
|
|
3319
|
-
|
|
3871
|
+
rmSync4(legacyConfigPath, { force: true });
|
|
3320
3872
|
}
|
|
3321
3873
|
}
|
|
3322
|
-
const legacyBuildIdPath =
|
|
3323
|
-
const moduleBuildIdPath =
|
|
3874
|
+
const legacyBuildIdPath = resolve18(root, LEGACY_BUILD_ID_FILE);
|
|
3875
|
+
const moduleBuildIdPath = resolve18(root, MODULE_BUILD_ID_FILE);
|
|
3324
3876
|
if (existsSync14(legacyBuildIdPath) && !existsSync14(moduleBuildIdPath)) {
|
|
3325
3877
|
const baseline = stripToBaseline(readFirstNonCommentLine(legacyBuildIdPath)) ?? "v0.0.1-dev";
|
|
3326
3878
|
actions.push(`Migrate ${LEGACY_BUILD_ID_FILE} \u2192 ${MODULE_BUILD_ID_FILE} (baseline ${baseline})`);
|
|
3327
3879
|
if (!ctx.global.dryRun) {
|
|
3328
|
-
|
|
3880
|
+
writeFileSync9(
|
|
3329
3881
|
moduleBuildIdPath,
|
|
3330
3882
|
`# Channel baseline for local CDN deploys (wp deploy auto-bumps per developer).
|
|
3331
3883
|
${baseline}
|
|
3332
3884
|
`
|
|
3333
3885
|
);
|
|
3334
|
-
|
|
3886
|
+
rmSync4(legacyBuildIdPath, { force: true });
|
|
3335
3887
|
}
|
|
3336
3888
|
}
|
|
3337
3889
|
if (existsSync14(woodscliPath)) {
|
|
3338
|
-
const raw = JSON.parse(
|
|
3890
|
+
const raw = JSON.parse(readFileSync13(woodscliPath, "utf8"));
|
|
3339
3891
|
let configChanged = false;
|
|
3340
3892
|
if (raw?.dev?.moduleConfigDir && raw.dev.moduleConfigDir !== WPHS_WORKSPACE_DIR) {
|
|
3341
3893
|
actions.push(`Update dev.moduleConfigDir \u2192 .wphs in project config`);
|
|
@@ -3352,7 +3904,7 @@ ${baseline}
|
|
|
3352
3904
|
}
|
|
3353
3905
|
}
|
|
3354
3906
|
if (configChanged && !ctx.global.dryRun) {
|
|
3355
|
-
|
|
3907
|
+
writeFileSync9(woodscliPath, `${JSON.stringify(raw, null, 2)}
|
|
3356
3908
|
`);
|
|
3357
3909
|
}
|
|
3358
3910
|
}
|
|
@@ -3370,20 +3922,16 @@ ${baseline}
|
|
|
3370
3922
|
remediation: "Run: wp migrate --yes"
|
|
3371
3923
|
});
|
|
3372
3924
|
}
|
|
3373
|
-
|
|
3374
|
-
cwd: root,
|
|
3375
|
-
logger: ctx.logger,
|
|
3376
|
-
dryRun: false
|
|
3377
|
-
});
|
|
3925
|
+
await ensureProjectWphsWorkspace(root);
|
|
3378
3926
|
for (const action of actions) ctx.logger.success(action);
|
|
3379
3927
|
return ExitCode.SUCCESS;
|
|
3380
3928
|
}
|
|
3381
3929
|
|
|
3382
3930
|
// src/cli/commands/examples.ts
|
|
3383
|
-
import { readFileSync as
|
|
3931
|
+
import { readFileSync as readFileSync14 } from "fs";
|
|
3384
3932
|
function loadManifest() {
|
|
3385
3933
|
const manifestPath = kitPath("examples", "manifest.json");
|
|
3386
|
-
const raw = JSON.parse(
|
|
3934
|
+
const raw = JSON.parse(readFileSync14(manifestPath, "utf8"));
|
|
3387
3935
|
return raw.scenarios;
|
|
3388
3936
|
}
|
|
3389
3937
|
async function runExamplesList(ctx, options = {}) {
|
|
@@ -3403,8 +3951,8 @@ async function runExamplesSmoke(ctx, options = {}) {
|
|
|
3403
3951
|
const tier = options.tier ?? "static";
|
|
3404
3952
|
const args = ["--tier", tier];
|
|
3405
3953
|
if (options.scenario) args.push("--scenario", options.scenario);
|
|
3406
|
-
const { runNodeScript
|
|
3407
|
-
|
|
3954
|
+
const { runNodeScript } = await import("./exec-VY6BGWF3.js");
|
|
3955
|
+
runNodeScript(kitPath("examples", "smoke", "run-matrix.mjs"), args, {
|
|
3408
3956
|
cwd: ctx.projectRoot,
|
|
3409
3957
|
logger: ctx.logger,
|
|
3410
3958
|
dryRun: ctx.global.dryRun
|
|
@@ -3416,13 +3964,13 @@ async function runExamplesSmoke(ctx, options = {}) {
|
|
|
3416
3964
|
}
|
|
3417
3965
|
|
|
3418
3966
|
// src/cli/commands/fields.ts
|
|
3419
|
-
import { cpSync as
|
|
3420
|
-
import { resolve as
|
|
3967
|
+
import { cpSync as cpSync5, existsSync as existsSync15, readFileSync as readFileSync15, writeFileSync as writeFileSync10 } from "fs";
|
|
3968
|
+
import { resolve as resolve19 } from "path";
|
|
3421
3969
|
async function runFieldsSync(ctx, options = {}) {
|
|
3422
3970
|
const direction = options.direction ?? "pull";
|
|
3423
|
-
const fieldsPath =
|
|
3424
|
-
const cacheDir =
|
|
3425
|
-
const cachePath =
|
|
3971
|
+
const fieldsPath = resolve19(ctx.projectRoot, ctx.config.dev.fieldsJson);
|
|
3972
|
+
const cacheDir = resolve19(ctx.projectRoot, ctx.config.dev.moduleConfigDir, "cache", "fields-sync");
|
|
3973
|
+
const cachePath = resolve19(cacheDir, "fields.json");
|
|
3426
3974
|
if (direction === "pull") {
|
|
3427
3975
|
if (!existsSync15(fieldsPath)) {
|
|
3428
3976
|
throw new WpHsError(`Missing ${ctx.config.dev.fieldsJson}`, {
|
|
@@ -3434,9 +3982,9 @@ async function runFieldsSync(ctx, options = {}) {
|
|
|
3434
3982
|
ctx.logger.info(`[dry-run] Would cache ${fieldsPath} \u2192 ${cachePath}`);
|
|
3435
3983
|
return ExitCode.SUCCESS;
|
|
3436
3984
|
}
|
|
3437
|
-
const { mkdirSync:
|
|
3438
|
-
|
|
3439
|
-
|
|
3985
|
+
const { mkdirSync: mkdirSync7 } = await import("fs");
|
|
3986
|
+
mkdirSync7(cacheDir, { recursive: true });
|
|
3987
|
+
cpSync5(fieldsPath, cachePath);
|
|
3440
3988
|
ctx.logger.success(`Cached fields \u2192 ${cachePath}`);
|
|
3441
3989
|
return ExitCode.SUCCESS;
|
|
3442
3990
|
}
|
|
@@ -3449,14 +3997,14 @@ async function runFieldsSync(ctx, options = {}) {
|
|
|
3449
3997
|
ctx.logger.info(`[dry-run] Would write ${cachePath} \u2192 ${fieldsPath}`);
|
|
3450
3998
|
return ExitCode.SUCCESS;
|
|
3451
3999
|
}
|
|
3452
|
-
|
|
4000
|
+
writeFileSync10(fieldsPath, readFileSync15(cachePath, "utf8"));
|
|
3453
4001
|
ctx.logger.success(`Restored fields from cache \u2192 ${fieldsPath}`);
|
|
3454
4002
|
return ExitCode.SUCCESS;
|
|
3455
4003
|
}
|
|
3456
4004
|
|
|
3457
4005
|
// src/cli/commands/regression.ts
|
|
3458
4006
|
import { existsSync as existsSync16 } from "fs";
|
|
3459
|
-
import { resolve as
|
|
4007
|
+
import { resolve as resolve20 } from "path";
|
|
3460
4008
|
var LEGACY_SHIM_SCRIPTS = [
|
|
3461
4009
|
"build-cdn.mjs",
|
|
3462
4010
|
"run-vite-build.mjs",
|
|
@@ -3479,7 +4027,7 @@ async function runRegressionSmoke(ctx, options = {}) {
|
|
|
3479
4027
|
if (!validateOk) failures.push("wp config validate failed");
|
|
3480
4028
|
if (options.checkLegacyShims !== false) {
|
|
3481
4029
|
for (const script of LEGACY_SHIM_SCRIPTS) {
|
|
3482
|
-
const shim =
|
|
4030
|
+
const shim = resolve20(ctx.projectRoot, "scripts", script);
|
|
3483
4031
|
const kitScript = kitPath("scripts", script);
|
|
3484
4032
|
const shimOk = existsSync16(shim) && existsSync16(kitScript);
|
|
3485
4033
|
checks.push({ name: `shim:${script}`, ok: shimOk });
|
|
@@ -3487,12 +4035,12 @@ async function runRegressionSmoke(ctx, options = {}) {
|
|
|
3487
4035
|
}
|
|
3488
4036
|
}
|
|
3489
4037
|
for (const rel of FORBIDDEN_CONSUMER_PATHS) {
|
|
3490
|
-
const forbidden =
|
|
4038
|
+
const forbidden = resolve20(ctx.projectRoot, rel);
|
|
3491
4039
|
const absent = !existsSync16(forbidden);
|
|
3492
4040
|
checks.push({ name: `no-${rel}`, ok: absent });
|
|
3493
4041
|
if (!absent) failures.push(`Forbidden dev path still present: ${rel}`);
|
|
3494
4042
|
}
|
|
3495
|
-
const woodscliConfig =
|
|
4043
|
+
const woodscliConfig = resolve20(ctx.projectRoot, WOODSCLI_CONFIG_FILE);
|
|
3496
4044
|
const configOk = existsSync16(woodscliConfig);
|
|
3497
4045
|
checks.push({ name: WOODSCLI_CONFIG_FILE, ok: configOk });
|
|
3498
4046
|
if (!configOk) failures.push(`${WOODSCLI_CONFIG_FILE} missing`);
|
|
@@ -3527,14 +4075,7 @@ async function runSetup(ctx, options = {}) {
|
|
|
3527
4075
|
title: "Preparing .wphs workspace",
|
|
3528
4076
|
task: async () => {
|
|
3529
4077
|
if (ctx.global.dryRun) return;
|
|
3530
|
-
|
|
3531
|
-
runNodeScript(kitPath("scripts", "ensure-wphs-workspace.mjs"), [], {
|
|
3532
|
-
cwd: ctx.projectRoot,
|
|
3533
|
-
logger: ctx.logger,
|
|
3534
|
-
dryRun: false
|
|
3535
|
-
});
|
|
3536
|
-
} catch {
|
|
3537
|
-
}
|
|
4078
|
+
await ensureProjectWphsWorkspace(ctx.projectRoot);
|
|
3538
4079
|
}
|
|
3539
4080
|
},
|
|
3540
4081
|
{
|
|
@@ -3603,11 +4144,11 @@ async function runSetup(ctx, options = {}) {
|
|
|
3603
4144
|
}
|
|
3604
4145
|
|
|
3605
4146
|
// src/cli/commands/portal.ts
|
|
3606
|
-
import * as
|
|
4147
|
+
import * as p6 from "@clack/prompts";
|
|
3607
4148
|
|
|
3608
4149
|
// src/lib/hubspot-portals.ts
|
|
3609
|
-
import { existsSync as existsSync17, readFileSync as
|
|
3610
|
-
import { resolve as
|
|
4150
|
+
import { existsSync as existsSync17, readFileSync as readFileSync16 } from "fs";
|
|
4151
|
+
import { resolve as resolve21 } from "path";
|
|
3611
4152
|
function stripYamlScalar(value) {
|
|
3612
4153
|
const trimmed = value.trim();
|
|
3613
4154
|
if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
|
|
@@ -3645,20 +4186,20 @@ function parseHubSpotConfigPortals(raw) {
|
|
|
3645
4186
|
return { defaultPortal, portals };
|
|
3646
4187
|
}
|
|
3647
4188
|
function readHubSpotPortals(projectRoot) {
|
|
3648
|
-
const configPath =
|
|
4189
|
+
const configPath = resolve21(projectRoot, HUBSPOT_CONFIG_FILE);
|
|
3649
4190
|
if (!existsSync17(configPath)) {
|
|
3650
4191
|
throw new WpHsError(`${HUBSPOT_CONFIG_FILE} not found in project root.`, {
|
|
3651
4192
|
exitCode: ExitCode.USER_ERROR,
|
|
3652
4193
|
remediation: "Run wp setup, then add your portal + PAT."
|
|
3653
4194
|
});
|
|
3654
4195
|
}
|
|
3655
|
-
const raw =
|
|
4196
|
+
const raw = readFileSync16(configPath, "utf8");
|
|
3656
4197
|
const { defaultPortal, portals } = parseHubSpotConfigPortals(raw);
|
|
3657
4198
|
return { configPath, defaultPortal, portals };
|
|
3658
4199
|
}
|
|
3659
4200
|
function resolvePortalName(portals, nameOrId) {
|
|
3660
4201
|
const needle = nameOrId.trim();
|
|
3661
|
-
return portals.find((
|
|
4202
|
+
return portals.find((p9) => p9.name === needle) ?? portals.find((p9) => p9.portalId === needle) ?? null;
|
|
3662
4203
|
}
|
|
3663
4204
|
|
|
3664
4205
|
// src/cli/commands/portal.ts
|
|
@@ -3713,7 +4254,7 @@ async function runPortalUse(ctx, options = {}) {
|
|
|
3713
4254
|
remediation: "wp portal use <name> \xB7 wp portal list"
|
|
3714
4255
|
});
|
|
3715
4256
|
}
|
|
3716
|
-
const answer = await
|
|
4257
|
+
const answer = await p6.select({
|
|
3717
4258
|
message: "Set default HubSpot portal",
|
|
3718
4259
|
options: data.portals.map((portal2) => ({
|
|
3719
4260
|
value: portal2.name,
|
|
@@ -3721,7 +4262,7 @@ async function runPortalUse(ctx, options = {}) {
|
|
|
3721
4262
|
hint: portal2.name === data.defaultPortal ? `current default \xB7 ${portal2.portalId ?? "no id"}` : portal2.portalId ?? void 0
|
|
3722
4263
|
}))
|
|
3723
4264
|
});
|
|
3724
|
-
if (
|
|
4265
|
+
if (p6.isCancel(answer)) {
|
|
3725
4266
|
throw new WpHsError("Portal selection cancelled", { exitCode: ExitCode.USER_ERROR });
|
|
3726
4267
|
}
|
|
3727
4268
|
targetName = answer;
|
|
@@ -3755,6 +4296,311 @@ async function runPortalUse(ctx, options = {}) {
|
|
|
3755
4296
|
return ExitCode.SUCCESS;
|
|
3756
4297
|
}
|
|
3757
4298
|
|
|
4299
|
+
// src/cli/commands/page.ts
|
|
4300
|
+
import * as p7 from "@clack/prompts";
|
|
4301
|
+
function cancelIfNeeded2(value) {
|
|
4302
|
+
if (p7.isCancel(value)) {
|
|
4303
|
+
throw new WpHsError("Page scaffold cancelled", { exitCode: ExitCode.USER_ERROR });
|
|
4304
|
+
}
|
|
4305
|
+
return value;
|
|
4306
|
+
}
|
|
4307
|
+
function resolveAccessFlags(options) {
|
|
4308
|
+
if (options.isPublic && options.isProtected) {
|
|
4309
|
+
throw new WpHsError("Use only one of --public or --protected.", {
|
|
4310
|
+
exitCode: ExitCode.USER_ERROR
|
|
4311
|
+
});
|
|
4312
|
+
}
|
|
4313
|
+
if (options.isPublic) {
|
|
4314
|
+
return "public";
|
|
4315
|
+
}
|
|
4316
|
+
if (options.isProtected) {
|
|
4317
|
+
return "protected";
|
|
4318
|
+
}
|
|
4319
|
+
return void 0;
|
|
4320
|
+
}
|
|
4321
|
+
async function promptPageAddOptions(ctx, options) {
|
|
4322
|
+
const nonInteractive = ctx.global.ci || ctx.global.yes || ctx.global.json;
|
|
4323
|
+
const hasNavConfig = navConfigFileExists(ctx.projectRoot);
|
|
4324
|
+
let slug = options.slug ? normalizePageSlug(options.slug) : void 0;
|
|
4325
|
+
if (!slug && nonInteractive) {
|
|
4326
|
+
throw new WpHsError("Page slug is required in non-interactive mode.", {
|
|
4327
|
+
exitCode: ExitCode.USER_ERROR,
|
|
4328
|
+
remediation: "Pass a slug: wp page add settings"
|
|
4329
|
+
});
|
|
4330
|
+
}
|
|
4331
|
+
if (!slug) {
|
|
4332
|
+
const slugAnswer = cancelIfNeeded2(
|
|
4333
|
+
await p7.text({
|
|
4334
|
+
message: "Page slug (URL segment)",
|
|
4335
|
+
placeholder: "settings",
|
|
4336
|
+
validate: (value) => {
|
|
4337
|
+
const normalized = normalizePageSlug(value ?? "");
|
|
4338
|
+
if (!normalized || !/^[a-z][a-z0-9-]*$/.test(normalized)) {
|
|
4339
|
+
return "Use lowercase letters, numbers, and hyphens";
|
|
4340
|
+
}
|
|
4341
|
+
return void 0;
|
|
4342
|
+
}
|
|
4343
|
+
})
|
|
4344
|
+
);
|
|
4345
|
+
slug = normalizePageSlug(slugAnswer);
|
|
4346
|
+
}
|
|
4347
|
+
let path = options.path ? normalizePagePath(options.path, slug) : void 0;
|
|
4348
|
+
if (!path && !nonInteractive) {
|
|
4349
|
+
const pathAnswer = cancelIfNeeded2(
|
|
4350
|
+
await p7.text({
|
|
4351
|
+
message: "Route path",
|
|
4352
|
+
initialValue: `/${slug}`,
|
|
4353
|
+
validate: (value) => {
|
|
4354
|
+
const normalized = normalizePagePath(value ?? "", slug);
|
|
4355
|
+
if (!normalized.startsWith("/")) {
|
|
4356
|
+
return "Path must start with /";
|
|
4357
|
+
}
|
|
4358
|
+
return void 0;
|
|
4359
|
+
}
|
|
4360
|
+
})
|
|
4361
|
+
);
|
|
4362
|
+
path = normalizePagePath(pathAnswer, slug);
|
|
4363
|
+
}
|
|
4364
|
+
path ??= normalizePagePath(`/${slug}`, slug);
|
|
4365
|
+
let title = options.title?.trim() || titleFromSlug(slug);
|
|
4366
|
+
if (!options.title && !nonInteractive) {
|
|
4367
|
+
const titleAnswer = cancelIfNeeded2(
|
|
4368
|
+
await p7.text({
|
|
4369
|
+
message: "Page title",
|
|
4370
|
+
initialValue: title,
|
|
4371
|
+
validate: (value) => value?.trim() ? void 0 : "Title is required"
|
|
4372
|
+
})
|
|
4373
|
+
);
|
|
4374
|
+
title = titleAnswer.trim();
|
|
4375
|
+
}
|
|
4376
|
+
let access = resolveAccessFlags(options);
|
|
4377
|
+
if (!access && nonInteractive) {
|
|
4378
|
+
throw new WpHsError("Access mode is required in non-interactive mode.", {
|
|
4379
|
+
exitCode: ExitCode.USER_ERROR,
|
|
4380
|
+
remediation: "Pass --public or --protected."
|
|
4381
|
+
});
|
|
4382
|
+
}
|
|
4383
|
+
if (!access) {
|
|
4384
|
+
const accessAnswer = cancelIfNeeded2(
|
|
4385
|
+
await p7.select({
|
|
4386
|
+
message: "Who can access this page?",
|
|
4387
|
+
initialValue: "public",
|
|
4388
|
+
options: [
|
|
4389
|
+
{
|
|
4390
|
+
value: "public",
|
|
4391
|
+
label: "Public",
|
|
4392
|
+
hint: "Visible without signing in"
|
|
4393
|
+
},
|
|
4394
|
+
{
|
|
4395
|
+
value: "protected",
|
|
4396
|
+
label: "Protected",
|
|
4397
|
+
hint: "Requires portal sign-in"
|
|
4398
|
+
}
|
|
4399
|
+
]
|
|
4400
|
+
})
|
|
4401
|
+
);
|
|
4402
|
+
access = accessAnswer;
|
|
4403
|
+
}
|
|
4404
|
+
let addToNav;
|
|
4405
|
+
if (options.addToNav) {
|
|
4406
|
+
addToNav = true;
|
|
4407
|
+
} else if (options.skipNav) {
|
|
4408
|
+
addToNav = false;
|
|
4409
|
+
} else if (!hasNavConfig) {
|
|
4410
|
+
addToNav = false;
|
|
4411
|
+
} else if (nonInteractive) {
|
|
4412
|
+
throw new WpHsError("Navigation choice is required in non-interactive mode.", {
|
|
4413
|
+
exitCode: ExitCode.USER_ERROR,
|
|
4414
|
+
remediation: "Pass --nav or --no-nav when portal-nav.ts or starter-nav.ts exists."
|
|
4415
|
+
});
|
|
4416
|
+
} else {
|
|
4417
|
+
const navAnswer = cancelIfNeeded2(
|
|
4418
|
+
await p7.confirm({
|
|
4419
|
+
message: "Add to navigation menu?",
|
|
4420
|
+
initialValue: true
|
|
4421
|
+
})
|
|
4422
|
+
);
|
|
4423
|
+
addToNav = navAnswer;
|
|
4424
|
+
}
|
|
4425
|
+
return { slug, path, title, access, addToNav };
|
|
4426
|
+
}
|
|
4427
|
+
async function runPageAdd(ctx, options) {
|
|
4428
|
+
const resolved = await promptPageAddOptions(ctx, options);
|
|
4429
|
+
const result = await scaffoldPage({
|
|
4430
|
+
projectRoot: ctx.projectRoot,
|
|
4431
|
+
config: ctx.config,
|
|
4432
|
+
slug: resolved.slug,
|
|
4433
|
+
path: resolved.path,
|
|
4434
|
+
title: resolved.title,
|
|
4435
|
+
access: resolved.access,
|
|
4436
|
+
addToNav: resolved.addToNav,
|
|
4437
|
+
dryRun: ctx.global.dryRun,
|
|
4438
|
+
logger: ctx.logger
|
|
4439
|
+
});
|
|
4440
|
+
if (ctx.global.json) {
|
|
4441
|
+
ctx.logger.json({
|
|
4442
|
+
ok: true,
|
|
4443
|
+
slug: resolved.slug,
|
|
4444
|
+
path: resolved.path,
|
|
4445
|
+
access: resolved.access,
|
|
4446
|
+
addToNav: resolved.addToNav,
|
|
4447
|
+
...result
|
|
4448
|
+
});
|
|
4449
|
+
return ExitCode.SUCCESS;
|
|
4450
|
+
}
|
|
4451
|
+
if (ctx.global.dryRun) {
|
|
4452
|
+
ctx.logger.info("[dry-run] Page scaffold planned.");
|
|
4453
|
+
return ExitCode.SUCCESS;
|
|
4454
|
+
}
|
|
4455
|
+
const lines = [
|
|
4456
|
+
`Created ${result.routeFile}`,
|
|
4457
|
+
`Created ${result.pageFile}`,
|
|
4458
|
+
result.routeTreeRegenerated ? "Regenerated src/routeTree.gen.ts" : result.routeTreeWarning ?? "Restart wp dev if navigation types look stale."
|
|
4459
|
+
];
|
|
4460
|
+
if (!ctx.global.quiet) {
|
|
4461
|
+
p7.log.success(`Page "${resolved.title}" scaffolded at ${resolved.path}`);
|
|
4462
|
+
p7.note(lines.join("\n"), "Next steps");
|
|
4463
|
+
} else {
|
|
4464
|
+
ctx.logger.success(lines.join("\n"));
|
|
4465
|
+
}
|
|
4466
|
+
return ExitCode.SUCCESS;
|
|
4467
|
+
}
|
|
4468
|
+
async function runPageList(ctx) {
|
|
4469
|
+
const { listScaffoldedPages } = await import("./scaffold-page-lifecycle-EHMPWPPE.js");
|
|
4470
|
+
const pages = listScaffoldedPages(ctx.projectRoot, ctx.config);
|
|
4471
|
+
if (ctx.global.json) {
|
|
4472
|
+
ctx.logger.json({ pages });
|
|
4473
|
+
return ExitCode.SUCCESS;
|
|
4474
|
+
}
|
|
4475
|
+
if (pages.length === 0) {
|
|
4476
|
+
ctx.logger.info("No scaffolded pages in src/features/pages/ (use wp page add).");
|
|
4477
|
+
return ExitCode.SUCCESS;
|
|
4478
|
+
}
|
|
4479
|
+
if (!ctx.global.quiet) {
|
|
4480
|
+
p7.log.info(`Scaffolded pages (${pages.length})`);
|
|
4481
|
+
for (const page of pages) {
|
|
4482
|
+
const navFlag = page.inNav ? "nav" : "no-nav";
|
|
4483
|
+
p7.log.message(
|
|
4484
|
+
` ${page.slug} ${page.path} ${page.access} ${navFlag} ${page.routeFile}`
|
|
4485
|
+
);
|
|
4486
|
+
}
|
|
4487
|
+
} else {
|
|
4488
|
+
for (const page of pages) {
|
|
4489
|
+
ctx.logger.info(`${page.slug} ${page.path} ${page.access} ${page.inNav}`);
|
|
4490
|
+
}
|
|
4491
|
+
}
|
|
4492
|
+
return ExitCode.SUCCESS;
|
|
4493
|
+
}
|
|
4494
|
+
async function runPageRemove(ctx, options) {
|
|
4495
|
+
const { removeScaffoldedPage } = await import("./scaffold-page-lifecycle-EHMPWPPE.js");
|
|
4496
|
+
const result = removeScaffoldedPage({
|
|
4497
|
+
projectRoot: ctx.projectRoot,
|
|
4498
|
+
config: ctx.config,
|
|
4499
|
+
slug: options.slug,
|
|
4500
|
+
removeFromNav: !options.skipNav,
|
|
4501
|
+
dryRun: ctx.global.dryRun,
|
|
4502
|
+
logger: ctx.logger
|
|
4503
|
+
});
|
|
4504
|
+
if (ctx.global.json) {
|
|
4505
|
+
ctx.logger.json({ ok: true, ...result });
|
|
4506
|
+
return ExitCode.SUCCESS;
|
|
4507
|
+
}
|
|
4508
|
+
if (ctx.global.dryRun) {
|
|
4509
|
+
ctx.logger.info("[dry-run] Page removal planned.");
|
|
4510
|
+
return ExitCode.SUCCESS;
|
|
4511
|
+
}
|
|
4512
|
+
p7.log.success(`Removed page "${result.slug}"`);
|
|
4513
|
+
return ExitCode.SUCCESS;
|
|
4514
|
+
}
|
|
4515
|
+
|
|
4516
|
+
// src/cli/commands/scaffold.ts
|
|
4517
|
+
import * as p8 from "@clack/prompts";
|
|
4518
|
+
import { writeFileSync as writeFileSync11 } from "fs";
|
|
4519
|
+
import { resolve as resolve22 } from "path";
|
|
4520
|
+
function persistScaffoldConfig(projectRoot, config, patch) {
|
|
4521
|
+
const auth = patch.auth ?? config.scaffold?.auth ?? "none";
|
|
4522
|
+
const router = patch.router ?? config.scaffold?.router ?? "hash";
|
|
4523
|
+
const scaffold = { router, auth };
|
|
4524
|
+
if (auth !== "none") {
|
|
4525
|
+
scaffold.nav = patch.nav ?? config.scaffold?.nav ?? "top";
|
|
4526
|
+
}
|
|
4527
|
+
const next = { ...config, scaffold };
|
|
4528
|
+
writeFileSync11(resolve22(projectRoot, WOODSCLI_CONFIG_FILE), `${JSON.stringify(next, null, 2)}
|
|
4529
|
+
`);
|
|
4530
|
+
return next;
|
|
4531
|
+
}
|
|
4532
|
+
function parseAuthTier(value) {
|
|
4533
|
+
if (value === "starter" || value === "sso" || value === "full") {
|
|
4534
|
+
return value;
|
|
4535
|
+
}
|
|
4536
|
+
return void 0;
|
|
4537
|
+
}
|
|
4538
|
+
function runScaffoldAuthUpgrade(ctx, options) {
|
|
4539
|
+
const routerMode = ctx.config.scaffold?.router ?? "hash";
|
|
4540
|
+
const nav = ctx.config.scaffold?.nav ?? "top";
|
|
4541
|
+
const current = readProjectAuthTier(ctx.projectRoot);
|
|
4542
|
+
if (ctx.global.dryRun) {
|
|
4543
|
+
ctx.logger.info(
|
|
4544
|
+
`[dry-run] Would upgrade auth from "${current}" to "${options.tier}" in ${ctx.projectRoot}`
|
|
4545
|
+
);
|
|
4546
|
+
return ExitCode.SUCCESS;
|
|
4547
|
+
}
|
|
4548
|
+
const applied = applyAuthTierUpgrade(
|
|
4549
|
+
ctx.projectRoot,
|
|
4550
|
+
options.tier,
|
|
4551
|
+
routerMode,
|
|
4552
|
+
ctx.config.projectType,
|
|
4553
|
+
nav
|
|
4554
|
+
);
|
|
4555
|
+
persistScaffoldConfig(ctx.projectRoot, ctx.config, {
|
|
4556
|
+
auth: applied,
|
|
4557
|
+
nav: applied !== "none" ? nav : void 0
|
|
4558
|
+
});
|
|
4559
|
+
if (ctx.global.json) {
|
|
4560
|
+
ctx.logger.json({ ok: true, previousTier: current, tier: applied });
|
|
4561
|
+
return ExitCode.SUCCESS;
|
|
4562
|
+
}
|
|
4563
|
+
p8.log.success(`Portal auth upgraded to "${applied}" (was "${current}")`);
|
|
4564
|
+
p8.note("Review new routes under src/routes/_auth/ and run wp dev.", "Next steps");
|
|
4565
|
+
return ExitCode.SUCCESS;
|
|
4566
|
+
}
|
|
4567
|
+
function runScaffoldNav(ctx, options) {
|
|
4568
|
+
const currentNav = ctx.config.scaffold?.nav ?? "top";
|
|
4569
|
+
if (ctx.config.scaffold?.auth === "none" || readProjectAuthTier(ctx.projectRoot) === "none") {
|
|
4570
|
+
throw new WpHsError("Nav scaffold requires portal auth.", {
|
|
4571
|
+
exitCode: ExitCode.USER_ERROR,
|
|
4572
|
+
remediation: "Run wp scaffold auth upgrade --tier starter first."
|
|
4573
|
+
});
|
|
4574
|
+
}
|
|
4575
|
+
if (ctx.global.dryRun) {
|
|
4576
|
+
ctx.logger.info(`[dry-run] Would apply nav shell "${options.mode}" (was "${currentNav}")`);
|
|
4577
|
+
return ExitCode.SUCCESS;
|
|
4578
|
+
}
|
|
4579
|
+
applyNavScaffoldUpgrade(ctx.projectRoot, options.mode);
|
|
4580
|
+
persistScaffoldConfig(ctx.projectRoot, ctx.config, { nav: options.mode });
|
|
4581
|
+
if (ctx.global.json) {
|
|
4582
|
+
ctx.logger.json({ ok: true, previousMode: currentNav, mode: options.mode });
|
|
4583
|
+
return ExitCode.SUCCESS;
|
|
4584
|
+
}
|
|
4585
|
+
p8.log.success(`Navigation shell set to "${options.mode}" (was "${currentNav}")`);
|
|
4586
|
+
return ExitCode.SUCCESS;
|
|
4587
|
+
}
|
|
4588
|
+
function parseScaffoldAuthTierArg(value) {
|
|
4589
|
+
const tier = value ? parseAuthTier(value) : void 0;
|
|
4590
|
+
if (!tier) {
|
|
4591
|
+
throw new WpHsError("Auth tier is required: starter | sso | full", {
|
|
4592
|
+
exitCode: ExitCode.USER_ERROR
|
|
4593
|
+
});
|
|
4594
|
+
}
|
|
4595
|
+
return tier;
|
|
4596
|
+
}
|
|
4597
|
+
function parseScaffoldNavModeArg(value) {
|
|
4598
|
+
if (value === "top" || value === "sidebar") {
|
|
4599
|
+
return value;
|
|
4600
|
+
}
|
|
4601
|
+
throw new WpHsError("Nav mode is required: top | sidebar", { exitCode: ExitCode.USER_ERROR });
|
|
4602
|
+
}
|
|
4603
|
+
|
|
3758
4604
|
// src/cli/commands/watch.ts
|
|
3759
4605
|
async function runWatch(ctx, options) {
|
|
3760
4606
|
ensureHubSpotConfigForCms(ctx);
|
|
@@ -3780,14 +4626,19 @@ async function runWatch(ctx, options) {
|
|
|
3780
4626
|
}
|
|
3781
4627
|
|
|
3782
4628
|
// src/cli/program.ts
|
|
3783
|
-
function
|
|
4629
|
+
function resolveProjectRootForVersionCheck(project) {
|
|
4630
|
+
if (project) {
|
|
4631
|
+
return project;
|
|
4632
|
+
}
|
|
3784
4633
|
try {
|
|
3785
|
-
|
|
3786
|
-
return pkg.version ?? "0.0.0";
|
|
4634
|
+
return findProjectRoot();
|
|
3787
4635
|
} catch {
|
|
3788
|
-
return
|
|
4636
|
+
return process.cwd();
|
|
3789
4637
|
}
|
|
3790
4638
|
}
|
|
4639
|
+
function readVersion() {
|
|
4640
|
+
return readKitVersion();
|
|
4641
|
+
}
|
|
3791
4642
|
function buildContext(global) {
|
|
3792
4643
|
const logger = createLogger({
|
|
3793
4644
|
verbose: global.verbose,
|
|
@@ -3795,7 +4646,7 @@ function buildContext(global) {
|
|
|
3795
4646
|
json: global.json,
|
|
3796
4647
|
ci: global.ci
|
|
3797
4648
|
});
|
|
3798
|
-
const projectRoot = global.project ??
|
|
4649
|
+
const projectRoot = global.project ?? findProjectRoot();
|
|
3799
4650
|
const { config } = loadConfig(projectRoot);
|
|
3800
4651
|
return { projectRoot, config, logger, global };
|
|
3801
4652
|
}
|
|
@@ -3805,7 +4656,7 @@ function attachGlobals(command) {
|
|
|
3805
4656
|
async function handle(fn) {
|
|
3806
4657
|
try {
|
|
3807
4658
|
const code = await fn();
|
|
3808
|
-
|
|
4659
|
+
await exitCli(code);
|
|
3809
4660
|
} catch (error) {
|
|
3810
4661
|
if (isWpHsError(error)) {
|
|
3811
4662
|
process.stderr.write(`Error: ${error.message}
|
|
@@ -3814,16 +4665,30 @@ async function handle(fn) {
|
|
|
3814
4665
|
process.stderr.write(`\u2192 ${error.remediation}
|
|
3815
4666
|
`);
|
|
3816
4667
|
}
|
|
3817
|
-
|
|
4668
|
+
await exitCli(error.exitCode);
|
|
3818
4669
|
}
|
|
3819
4670
|
process.stderr.write(`Error: ${error instanceof Error ? error.message : String(error)}
|
|
3820
4671
|
`);
|
|
3821
|
-
|
|
4672
|
+
await exitCli(ExitCode.INTERNAL);
|
|
3822
4673
|
}
|
|
3823
4674
|
}
|
|
3824
4675
|
function createProgram() {
|
|
3825
4676
|
const program2 = new Command().name("wp").description("WoodsCLI \u2014 HubSpot CMS dev kit by WoodsPortal (modules + themes)").version(readVersion()).addHelpText("after", "\nWoodsCLI \xB7 WoodsPortal HubSpot CLI \xB7 npm @woodsportal/hubspot-kit");
|
|
3826
4677
|
attachGlobals(program2);
|
|
4678
|
+
program2.hook("preAction", async (_thisCommand, actionCommand) => {
|
|
4679
|
+
const global = program2.opts();
|
|
4680
|
+
const name = actionCommand.name();
|
|
4681
|
+
if (name === "version" || name === "help") {
|
|
4682
|
+
return;
|
|
4683
|
+
}
|
|
4684
|
+
await maybeWarnKitUpdate({
|
|
4685
|
+
current: readKitVersion(),
|
|
4686
|
+
projectRoot: resolveProjectRootForVersionCheck(global.project),
|
|
4687
|
+
ci: global.ci,
|
|
4688
|
+
json: global.json,
|
|
4689
|
+
quiet: global.quiet
|
|
4690
|
+
});
|
|
4691
|
+
});
|
|
3827
4692
|
program2.command("doctor").description("Check prerequisites (Node, HubSpot CLI, .wphs workspace, config)").action(async () => {
|
|
3828
4693
|
const global = program2.opts();
|
|
3829
4694
|
await handle(() => runDoctor(buildContext(global)));
|
|
@@ -3842,6 +4707,12 @@ function createProgram() {
|
|
|
3842
4707
|
await handle(() => runConfigValidate(buildContext(global)));
|
|
3843
4708
|
});
|
|
3844
4709
|
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(
|
|
4710
|
+
"--auth <tier>",
|
|
4711
|
+
"Portal auth: none | starter | sso | full \u2014 modules with router \u2260 none"
|
|
4712
|
+
).option(
|
|
4713
|
+
"--nav <mode>",
|
|
4714
|
+
"Authenticated nav shell: top | sidebar (requires --auth starter|sso|full)"
|
|
4715
|
+
).option(
|
|
3845
4716
|
"--cdn-github <org/repo>",
|
|
3846
4717
|
"Public GitHub CDN mirror repo (hybrid CDN; use with --ci / -y)"
|
|
3847
4718
|
).action(
|
|
@@ -3860,12 +4731,16 @@ function createProgram() {
|
|
|
3860
4731
|
global
|
|
3861
4732
|
};
|
|
3862
4733
|
const routerMode = opts.router === "browser" || opts.router === "none" || opts.router === "hash" ? opts.router : void 0;
|
|
4734
|
+
const authMode = opts.auth === "none" || opts.auth === "starter" || opts.auth === "sso" || opts.auth === "full" ? opts.auth : void 0;
|
|
4735
|
+
const navMode = opts.nav === "top" || opts.nav === "sidebar" ? opts.nav : void 0;
|
|
3863
4736
|
await handle(
|
|
3864
4737
|
() => runInit(ctx, {
|
|
3865
4738
|
template: opts.template,
|
|
3866
4739
|
name: opts.name,
|
|
3867
4740
|
directory: opts.directory,
|
|
3868
4741
|
router: routerMode,
|
|
4742
|
+
auth: authMode,
|
|
4743
|
+
nav: navMode,
|
|
3869
4744
|
cdnGithub: opts.cdnGithub
|
|
3870
4745
|
})
|
|
3871
4746
|
);
|
|
@@ -3998,6 +4873,45 @@ function createProgram() {
|
|
|
3998
4873
|
const global = program2.opts();
|
|
3999
4874
|
await handle(() => runPortalUse(buildContext(global), { name }));
|
|
4000
4875
|
});
|
|
4876
|
+
const page = program2.command("page").description("Scaffold routed pages");
|
|
4877
|
+
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(
|
|
4878
|
+
async (slug, opts) => {
|
|
4879
|
+
const global = program2.opts();
|
|
4880
|
+
await handle(
|
|
4881
|
+
() => runPageAdd(buildContext(global), {
|
|
4882
|
+
slug,
|
|
4883
|
+
path: opts.path,
|
|
4884
|
+
title: opts.title,
|
|
4885
|
+
isPublic: opts.public,
|
|
4886
|
+
isProtected: opts.protected,
|
|
4887
|
+
addToNav: opts.nav,
|
|
4888
|
+
skipNav: opts.noNav
|
|
4889
|
+
})
|
|
4890
|
+
);
|
|
4891
|
+
}
|
|
4892
|
+
);
|
|
4893
|
+
page.command("list").alias("ls").description("List scaffolded pages under src/features/pages/").action(async () => {
|
|
4894
|
+
const global = program2.opts();
|
|
4895
|
+
await handle(() => runPageList(buildContext(global)));
|
|
4896
|
+
});
|
|
4897
|
+
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) => {
|
|
4898
|
+
const global = program2.opts();
|
|
4899
|
+
await handle(
|
|
4900
|
+
() => runPageRemove(buildContext(global), { slug, skipNav: opts.noNav })
|
|
4901
|
+
);
|
|
4902
|
+
});
|
|
4903
|
+
const scaffold = program2.command("scaffold").description("Re-apply portal scaffold layers");
|
|
4904
|
+
const scaffoldAuth = scaffold.command("auth").description("Portal auth tier upgrades");
|
|
4905
|
+
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) => {
|
|
4906
|
+
const global = program2.opts();
|
|
4907
|
+
const tier = parseScaffoldAuthTierArg(opts.tier);
|
|
4908
|
+
await handle(async () => runScaffoldAuthUpgrade(buildContext(global), { tier }));
|
|
4909
|
+
});
|
|
4910
|
+
scaffold.command("nav").description("Re-apply authenticated navigation shell (top | sidebar)").requiredOption("--mode <mode>", "top | sidebar").action(async (opts) => {
|
|
4911
|
+
const global = program2.opts();
|
|
4912
|
+
const mode = parseScaffoldNavModeArg(opts.mode);
|
|
4913
|
+
await handle(async () => runScaffoldNav(buildContext(global), { mode }));
|
|
4914
|
+
});
|
|
4001
4915
|
return program2;
|
|
4002
4916
|
}
|
|
4003
4917
|
|