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