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