@woodsportal/hubspot-kit 1.0.5 → 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 +1378 -339
- 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 +1 -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 +17 -9
- package/src/dev-module-config/dev-module-display-name.ts +5 -0
- package/src/dev-module-config/ui/ModuleConfigPersistenceStatus.tsx +5 -5
- package/src/dev-module-config/ui/ModuleConfigShell.tsx +2 -1
- package/src/dev-module-config/ui/module-config-theme.css +6 -0
- package/src/dev-module-config/ui/navigation/ModuleConfigNavContext.tsx +11 -5
- package/templates/module-hybrid-cdn/README.md +53 -3
- 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 +1 -1
- 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
|
}
|
|
@@ -530,6 +652,21 @@ function checkDevEntry(ctx) {
|
|
|
530
652
|
remediation: hasEntry ? void 0 : "Re-run wp init or restore src/main.tsx from kit templates",
|
|
531
653
|
severity: "error"
|
|
532
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
|
+
}
|
|
533
670
|
return checks;
|
|
534
671
|
}
|
|
535
672
|
function checkKitScripts() {
|
|
@@ -603,6 +740,28 @@ function checkAdapter(ctx) {
|
|
|
603
740
|
severity: "warn"
|
|
604
741
|
};
|
|
605
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
|
+
}
|
|
606
765
|
function checkViteInstalled(ctx) {
|
|
607
766
|
if (isThemeProject(ctx.config) && !existsSync4(resolve5(ctx.projectRoot, "vite.config.dev.mjs"))) {
|
|
608
767
|
return null;
|
|
@@ -617,30 +776,80 @@ function checkViteInstalled(ctx) {
|
|
|
617
776
|
severity: "error"
|
|
618
777
|
};
|
|
619
778
|
}
|
|
620
|
-
async function
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
dryRun
|
|
627
|
-
|
|
628
|
-
|
|
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}`);
|
|
629
848
|
}
|
|
630
849
|
}
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
checkDevViteConfig(ctx),
|
|
635
|
-
...checkDevEntry(ctx),
|
|
636
|
-
checkViteInstalled(ctx),
|
|
637
|
-
...checkConfig(ctx),
|
|
638
|
-
...checkWphsWorkspace(ctx),
|
|
639
|
-
checkAdapter(ctx),
|
|
640
|
-
...doctorHubSpotChecks(ctx.projectRoot),
|
|
641
|
-
checkBuildId(ctx),
|
|
642
|
-
...checkEnvFiles(ctx)
|
|
643
|
-
].filter((c) => c !== null);
|
|
850
|
+
}
|
|
851
|
+
async function runDoctor(ctx) {
|
|
852
|
+
const checks = await gatherDoctorChecks(ctx);
|
|
644
853
|
const failed = checks.filter((c) => !c.ok && (c.severity ?? "error") === "error");
|
|
645
854
|
const warnings = checks.filter((c) => !c.ok && c.severity === "warn");
|
|
646
855
|
if (ctx.global.json) {
|
|
@@ -651,16 +860,10 @@ async function runDoctor(ctx) {
|
|
|
651
860
|
checks
|
|
652
861
|
});
|
|
653
862
|
} else {
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
ctx.logger.warn(check.message);
|
|
659
|
-
if (check.remediation) ctx.logger.info(` \u2192 ${check.remediation}`);
|
|
660
|
-
} else {
|
|
661
|
-
ctx.logger.error(check.message);
|
|
662
|
-
if (check.remediation) ctx.logger.info(` \u2192 ${check.remediation}`);
|
|
663
|
-
}
|
|
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);
|
|
664
867
|
}
|
|
665
868
|
}
|
|
666
869
|
if (failed.length > 0) {
|
|
@@ -698,43 +901,43 @@ async function runConfigValidate(ctx) {
|
|
|
698
901
|
|
|
699
902
|
// src/cli/commands/build.ts
|
|
700
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
|
|
701
913
|
function tierToStgFlag(tier) {
|
|
702
914
|
return tier === "dev" || tier === "stg" ? ["--stg"] : [];
|
|
703
915
|
}
|
|
704
|
-
|
|
705
|
-
const tier = options.tier ??
|
|
706
|
-
const env = { ...process.env, WP_HS_PROJECT_ROOT: ctx.projectRoot };
|
|
916
|
+
function buildTasks(ctx, options, run) {
|
|
917
|
+
const tier = options.tier ?? DEFAULT_BUILD_TIER;
|
|
707
918
|
const isTheme = isThemeProject(ctx.config);
|
|
708
919
|
const useCdn = usesCdnPipeline(ctx.config) && !isTheme;
|
|
709
|
-
const
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
ctx.logger.json({ command: "build", tier, ok: true, projectType: ctx.config.projectType });
|
|
723
|
-
} else {
|
|
724
|
-
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");
|
|
725
933
|
}
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
]);
|
|
734
|
-
run("run-hubspot-module-postbuild.mjs");
|
|
735
|
-
} else {
|
|
736
|
-
if (!options.hubspotOnly && useCdn) {
|
|
737
|
-
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 () => {
|
|
738
941
|
if (options.cdnEsm) {
|
|
739
942
|
run("run-vite-build.mjs", [
|
|
740
943
|
resolve6(ctx.projectRoot, "vite.config.cdn-esm.mjs"),
|
|
@@ -745,19 +948,64 @@ async function runBuild(ctx, options) {
|
|
|
745
948
|
}
|
|
746
949
|
run("generate-cdn-manifest.mjs");
|
|
747
950
|
}
|
|
748
|
-
|
|
749
|
-
|
|
951
|
+
});
|
|
952
|
+
}
|
|
953
|
+
if (!options.cdnOnly) {
|
|
954
|
+
tasks2.push({
|
|
955
|
+
title: `Building HubSpot module (${tier})`,
|
|
956
|
+
task: async () => {
|
|
750
957
|
run("run-vite-build.mjs", [
|
|
751
958
|
resolve6(ctx.projectRoot, "vite.config.hubspot.mjs"),
|
|
752
959
|
...tierToStgFlag(tier)
|
|
753
960
|
]);
|
|
754
961
|
run("run-hubspot-module-postbuild.mjs");
|
|
755
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;
|
|
756
1006
|
}
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
run("write-audit-artifacts.mjs");
|
|
760
|
-
}
|
|
1007
|
+
const tasks2 = buildTasks(ctx, options, run);
|
|
1008
|
+
await runCliTasks(ctx, tasks2, options);
|
|
761
1009
|
if (ctx.global.json) {
|
|
762
1010
|
ctx.logger.json({
|
|
763
1011
|
command: "build",
|
|
@@ -766,7 +1014,7 @@ async function runBuild(ctx, options) {
|
|
|
766
1014
|
outDir: resolve6(ctx.projectRoot, ctx.config.module.outDir)
|
|
767
1015
|
});
|
|
768
1016
|
} else {
|
|
769
|
-
ctx
|
|
1017
|
+
printCommandDone(ctx, "Build complete");
|
|
770
1018
|
}
|
|
771
1019
|
return ExitCode.SUCCESS;
|
|
772
1020
|
} catch (error) {
|
|
@@ -778,22 +1026,142 @@ async function runBuild(ctx, options) {
|
|
|
778
1026
|
|
|
779
1027
|
// src/cli/commands/dev.ts
|
|
780
1028
|
import { existsSync as existsSync6 } from "fs";
|
|
781
|
-
import { resolve as
|
|
1029
|
+
import { resolve as resolve9 } from "path";
|
|
782
1030
|
import { spawn } from "child_process";
|
|
783
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
|
+
|
|
784
1152
|
// src/vite/resolve-dev-config.ts
|
|
785
1153
|
import { existsSync as existsSync5 } from "fs";
|
|
786
|
-
import { resolve as
|
|
1154
|
+
import { resolve as resolve8 } from "path";
|
|
787
1155
|
var CONSUMER_DEV_CONFIG = "vite.config.dev.mjs";
|
|
788
1156
|
function resolveDevViteConfigPath(projectRoot) {
|
|
789
|
-
const consumerConfig =
|
|
1157
|
+
const consumerConfig = resolve8(projectRoot, CONSUMER_DEV_CONFIG);
|
|
790
1158
|
if (existsSync5(consumerConfig)) {
|
|
791
1159
|
return consumerConfig;
|
|
792
1160
|
}
|
|
793
1161
|
return kitPath("vite", "vite.config.dev.mjs");
|
|
794
1162
|
}
|
|
795
1163
|
function resolveProjectViteBin(projectRoot) {
|
|
796
|
-
const localBin =
|
|
1164
|
+
const localBin = resolve8(projectRoot, "node_modules", ".bin", "vite");
|
|
797
1165
|
if (existsSync5(localBin)) {
|
|
798
1166
|
return localBin;
|
|
799
1167
|
}
|
|
@@ -802,7 +1170,6 @@ function resolveProjectViteBin(projectRoot) {
|
|
|
802
1170
|
|
|
803
1171
|
// src/cli/commands/dev.ts
|
|
804
1172
|
async function runDev(ctx, options) {
|
|
805
|
-
ensureHubSpotCliSetup(ctx, "local");
|
|
806
1173
|
const port = options.port ?? 3e3;
|
|
807
1174
|
const env = {
|
|
808
1175
|
...process.env,
|
|
@@ -820,13 +1187,18 @@ async function runDev(ctx, options) {
|
|
|
820
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)."
|
|
821
1188
|
});
|
|
822
1189
|
}
|
|
823
|
-
const consumerConfig =
|
|
1190
|
+
const consumerConfig = resolve9(ctx.projectRoot, "vite.config.dev.mjs");
|
|
824
1191
|
if (!existsSync6(consumerConfig)) {
|
|
825
1192
|
throw new WpHsError("vite.config.dev.mjs is missing in the project root.", {
|
|
826
1193
|
exitCode: ExitCode.USER_ERROR,
|
|
827
1194
|
remediation: "Add vite.config.dev.mjs (see @woodsportal/hubspot-kit templates/module-hybrid-cdn)."
|
|
828
1195
|
});
|
|
829
1196
|
}
|
|
1197
|
+
printWoodsCliDevBanner({
|
|
1198
|
+
config: ctx.config,
|
|
1199
|
+
port,
|
|
1200
|
+
global: ctx.global
|
|
1201
|
+
});
|
|
830
1202
|
return new Promise((resolvePromise) => {
|
|
831
1203
|
const child = spawn(
|
|
832
1204
|
viteBin,
|
|
@@ -837,6 +1209,7 @@ async function runDev(ctx, options) {
|
|
|
837
1209
|
stdio: "inherit"
|
|
838
1210
|
}
|
|
839
1211
|
);
|
|
1212
|
+
setImmediate(() => ensureHubSpotCliSetup(ctx, "local"));
|
|
840
1213
|
child.on("exit", (code) => {
|
|
841
1214
|
resolvePromise(code ?? ExitCode.USER_ERROR);
|
|
842
1215
|
});
|
|
@@ -844,11 +1217,17 @@ async function runDev(ctx, options) {
|
|
|
844
1217
|
}
|
|
845
1218
|
|
|
846
1219
|
// src/cli/commands/deploy.ts
|
|
847
|
-
import { resolve as
|
|
1220
|
+
import { resolve as resolve11 } from "path";
|
|
848
1221
|
|
|
849
1222
|
// src/cli/commands/publish.ts
|
|
850
1223
|
async function runPublish(ctx, options) {
|
|
851
|
-
|
|
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;
|
|
852
1231
|
const env = { ...process.env, WP_HS_PROJECT_ROOT: ctx.projectRoot };
|
|
853
1232
|
const execOpts = {
|
|
854
1233
|
cwd: ctx.projectRoot,
|
|
@@ -859,16 +1238,32 @@ async function runPublish(ctx, options) {
|
|
|
859
1238
|
const verifyFlag = tier === "prod" ? ["--prod"] : ["--stg"];
|
|
860
1239
|
const publishArgs = options.dryRun ? ["--dry-run"] : [];
|
|
861
1240
|
try {
|
|
862
|
-
|
|
863
|
-
ctx
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
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
|
+
);
|
|
868
1260
|
if (ctx.global.json) {
|
|
869
1261
|
ctx.logger.json({ command: "publish", tier, dryRun: Boolean(options.dryRun), ok: true });
|
|
870
1262
|
} else {
|
|
871
|
-
|
|
1263
|
+
printCommandDone(
|
|
1264
|
+
ctx,
|
|
1265
|
+
options.dryRun ? "CDN publish dry-run complete" : "CDN publish complete"
|
|
1266
|
+
);
|
|
872
1267
|
}
|
|
873
1268
|
return ExitCode.SUCCESS;
|
|
874
1269
|
} catch (error) {
|
|
@@ -880,10 +1275,32 @@ async function runPublish(ctx, options) {
|
|
|
880
1275
|
}
|
|
881
1276
|
|
|
882
1277
|
// src/cli/commands/upload.ts
|
|
883
|
-
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
|
+
}
|
|
884
1302
|
async function runUpload(ctx, options = {}) {
|
|
885
|
-
|
|
886
|
-
const tier = options.tier ?? "prod";
|
|
1303
|
+
const tier = options.tier ?? DEFAULT_BUILD_TIER;
|
|
887
1304
|
if (tier === "prod" && !ctx.global.yes && !ctx.global.ci) {
|
|
888
1305
|
const confirmed = process.env.WP_HS_CONFIRM_PROD === "1";
|
|
889
1306
|
if (!confirmed) {
|
|
@@ -893,39 +1310,25 @@ async function runUpload(ctx, options = {}) {
|
|
|
893
1310
|
});
|
|
894
1311
|
}
|
|
895
1312
|
}
|
|
896
|
-
if (
|
|
897
|
-
|
|
898
|
-
if (!theme) {
|
|
899
|
-
throw new WpHsError("theme block missing in woodscli.json", { exitCode: ExitCode.USER_ERROR });
|
|
900
|
-
}
|
|
901
|
-
const srcDir = resolve9(ctx.projectRoot, theme.srcDir);
|
|
902
|
-
try {
|
|
903
|
-
runCommand("hs", ["cms", "upload", srcDir, theme.uploadPath], {
|
|
904
|
-
cwd: ctx.projectRoot,
|
|
905
|
-
logger: ctx.logger,
|
|
906
|
-
dryRun: ctx.global.dryRun
|
|
907
|
-
});
|
|
908
|
-
if (!ctx.global.json && !ctx.global.quiet) {
|
|
909
|
-
ctx.logger.success(`Uploaded theme ${srcDir} \u2192 ${theme.uploadPath}`);
|
|
910
|
-
}
|
|
911
|
-
return ExitCode.SUCCESS;
|
|
912
|
-
} catch (error) {
|
|
913
|
-
throw new WpHsError("HubSpot theme upload failed", {
|
|
914
|
-
exitCode: ExitCode.PUBLISH_FAILED,
|
|
915
|
-
cause: error
|
|
916
|
-
});
|
|
917
|
-
}
|
|
1313
|
+
if (!options.skipPrereqCheck) {
|
|
1314
|
+
ensureHubSpotCliSetup(ctx, "cms");
|
|
918
1315
|
}
|
|
919
|
-
const outDir = resolve9(ctx.projectRoot, ctx.config.module.outDir);
|
|
920
|
-
const uploadPath = ctx.config.module.uploadPath;
|
|
921
1316
|
try {
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
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);
|
|
929
1332
|
}
|
|
930
1333
|
return ExitCode.SUCCESS;
|
|
931
1334
|
} catch (error) {
|
|
@@ -940,7 +1343,7 @@ async function runUpload(ctx, options = {}) {
|
|
|
940
1343
|
// src/cli/commands/deploy.ts
|
|
941
1344
|
async function runDeploy(ctx, options) {
|
|
942
1345
|
ensureHubSpotCliSetup(ctx, "cms");
|
|
943
|
-
const tier = options.tier ??
|
|
1346
|
+
const tier = options.tier ?? DEFAULT_BUILD_TIER;
|
|
944
1347
|
const env = { ...process.env, WP_HS_PROJECT_ROOT: ctx.projectRoot };
|
|
945
1348
|
const execOpts = {
|
|
946
1349
|
cwd: ctx.projectRoot,
|
|
@@ -948,24 +1351,67 @@ async function runDeploy(ctx, options) {
|
|
|
948
1351
|
logger: ctx.logger,
|
|
949
1352
|
dryRun: ctx.global.dryRun
|
|
950
1353
|
};
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
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);
|
|
959
1402
|
if (!ctx.global.json) {
|
|
960
|
-
|
|
1403
|
+
printCommandDone(
|
|
1404
|
+
ctx,
|
|
1405
|
+
`Deploy complete (${resolve11(ctx.projectRoot, ctx.config.module.outDir)} \u2192 ${ctx.config.module.uploadPath})`
|
|
1406
|
+
);
|
|
961
1407
|
}
|
|
962
1408
|
return ExitCode.SUCCESS;
|
|
963
1409
|
}
|
|
964
1410
|
|
|
965
1411
|
// src/cli/commands/audit.ts
|
|
966
1412
|
import { createHash } from "crypto";
|
|
967
|
-
import { cpSync, existsSync as existsSync7, mkdirSync, readFileSync as
|
|
968
|
-
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";
|
|
969
1415
|
var FORBIDDEN_SHIP_TOKENS = [
|
|
970
1416
|
"ModuleConfigOverlay",
|
|
971
1417
|
"module-config-store",
|
|
@@ -974,7 +1420,7 @@ var FORBIDDEN_SHIP_TOKENS = [
|
|
|
974
1420
|
"dev.module-config"
|
|
975
1421
|
];
|
|
976
1422
|
function hashFile(path) {
|
|
977
|
-
return createHash("sha256").update(
|
|
1423
|
+
return createHash("sha256").update(readFileSync6(path)).digest("hex");
|
|
978
1424
|
}
|
|
979
1425
|
function walkFiles(dir) {
|
|
980
1426
|
if (!existsSync7(dir)) return [];
|
|
@@ -993,14 +1439,14 @@ function walkFiles(dir) {
|
|
|
993
1439
|
async function runAuditShipBundle(ctx) {
|
|
994
1440
|
const root = ctx.projectRoot;
|
|
995
1441
|
const targets = [
|
|
996
|
-
|
|
997
|
-
|
|
1442
|
+
resolve12(root, "dist/cdn"),
|
|
1443
|
+
resolve12(root, ctx.config.module.outDir)
|
|
998
1444
|
];
|
|
999
1445
|
const violations = [];
|
|
1000
1446
|
for (const dir of targets) {
|
|
1001
1447
|
for (const file of walkFiles(dir)) {
|
|
1002
1448
|
if (!/\.(js|css|html)$/i.test(file)) continue;
|
|
1003
|
-
const content =
|
|
1449
|
+
const content = readFileSync6(file, "utf8");
|
|
1004
1450
|
for (const token of FORBIDDEN_SHIP_TOKENS) {
|
|
1005
1451
|
if (content.includes(token)) {
|
|
1006
1452
|
violations.push(`${file}: contains forbidden token "${token}"`);
|
|
@@ -1027,8 +1473,8 @@ async function runAuditBaselineCapture(ctx, options) {
|
|
|
1027
1473
|
const root = ctx.projectRoot;
|
|
1028
1474
|
const baseline = options.baselineDir ?? defaultBaselineDir(root, ctx.config);
|
|
1029
1475
|
const sources = [
|
|
1030
|
-
{ label: "cdn", path:
|
|
1031
|
-
{ label: "hubspot", path:
|
|
1476
|
+
{ label: "cdn", path: resolve12(root, "dist/cdn") },
|
|
1477
|
+
{ label: "hubspot", path: resolve12(root, ctx.config.module.outDir) }
|
|
1032
1478
|
];
|
|
1033
1479
|
if (ctx.global.dryRun) {
|
|
1034
1480
|
ctx.logger.info(`[dry-run] Would capture baseline to ${baseline}`);
|
|
@@ -1041,7 +1487,7 @@ async function runAuditBaselineCapture(ctx, options) {
|
|
|
1041
1487
|
remediation: "Run wp build before wp audit baseline --capture."
|
|
1042
1488
|
});
|
|
1043
1489
|
}
|
|
1044
|
-
const target =
|
|
1490
|
+
const target = resolve12(baseline, label);
|
|
1045
1491
|
rmSync(target, { recursive: true, force: true });
|
|
1046
1492
|
mkdirSync(baseline, { recursive: true });
|
|
1047
1493
|
cpSync(path, target, { recursive: true });
|
|
@@ -1057,12 +1503,12 @@ async function runAuditParity(ctx, options) {
|
|
|
1057
1503
|
const root = ctx.projectRoot;
|
|
1058
1504
|
const baseline = options.baselineDir ?? defaultBaselineDir(root, ctx.config);
|
|
1059
1505
|
const currentDirs = [
|
|
1060
|
-
{ label: "cdn", path:
|
|
1061
|
-
{ label: "hubspot", path:
|
|
1506
|
+
{ label: "cdn", path: resolve12(root, "dist/cdn") },
|
|
1507
|
+
{ label: "hubspot", path: resolve12(root, ctx.config.module.outDir) }
|
|
1062
1508
|
];
|
|
1063
1509
|
const mismatches = [];
|
|
1064
1510
|
for (const { label, path } of currentDirs) {
|
|
1065
|
-
const baselinePath =
|
|
1511
|
+
const baselinePath = resolve12(baseline, label);
|
|
1066
1512
|
if (!existsSync7(baselinePath)) {
|
|
1067
1513
|
ctx.logger.warn(`No baseline at ${baselinePath} \u2014 skipping ${label} parity`);
|
|
1068
1514
|
continue;
|
|
@@ -1078,7 +1524,7 @@ async function runAuditParity(ctx, options) {
|
|
|
1078
1524
|
}
|
|
1079
1525
|
for (const file of baselineFiles) {
|
|
1080
1526
|
const rel = file.replace(baselinePath, "");
|
|
1081
|
-
const current =
|
|
1527
|
+
const current = resolve12(path, rel.slice(1));
|
|
1082
1528
|
if (!existsSync7(current)) continue;
|
|
1083
1529
|
const bh = hashFile(file);
|
|
1084
1530
|
const ch = hashFile(current);
|
|
@@ -1106,13 +1552,13 @@ var SECRET_PATTERNS = [
|
|
|
1106
1552
|
];
|
|
1107
1553
|
async function runAuditSecrets(ctx) {
|
|
1108
1554
|
const root = ctx.projectRoot;
|
|
1109
|
-
const scanRoots = ["src", "public", ".wphs/config"].map((d) =>
|
|
1555
|
+
const scanRoots = ["src", "public", ".wphs/config"].map((d) => resolve12(root, d));
|
|
1110
1556
|
const findings = [];
|
|
1111
1557
|
for (const dir of scanRoots) {
|
|
1112
1558
|
for (const file of walkFiles(dir)) {
|
|
1113
1559
|
if (!/\.(ts|tsx|js|jsx|json|env|yml|yaml|html)$/i.test(file)) continue;
|
|
1114
1560
|
if (file.includes("node_modules")) continue;
|
|
1115
|
-
const content =
|
|
1561
|
+
const content = readFileSync6(file, "utf8");
|
|
1116
1562
|
for (const pattern of SECRET_PATTERNS) {
|
|
1117
1563
|
pattern.lastIndex = 0;
|
|
1118
1564
|
if (pattern.test(content)) {
|
|
@@ -1138,29 +1584,340 @@ async function runAuditSecrets(ctx) {
|
|
|
1138
1584
|
}
|
|
1139
1585
|
|
|
1140
1586
|
// src/cli/commands/init.ts
|
|
1141
|
-
import { cpSync as cpSync2, existsSync as
|
|
1142
|
-
import { basename, resolve as
|
|
1143
|
-
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";
|
|
1144
1590
|
|
|
1145
|
-
// src/lib/
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
"
|
|
1150
|
-
"
|
|
1151
|
-
"
|
|
1152
|
-
|
|
1153
|
-
"
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
var
|
|
1157
|
-
|
|
1158
|
-
|
|
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);
|
|
1159
1794
|
return {
|
|
1160
|
-
|
|
1161
|
-
|
|
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}`
|
|
1162
1812
|
};
|
|
1163
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
|
+
}
|
|
1164
1921
|
|
|
1165
1922
|
// src/lib/project-name.ts
|
|
1166
1923
|
var PROJECT_NAME_PATTERN = /^[a-z][a-z0-9-]*$/;
|
|
@@ -1194,7 +1951,11 @@ var TEMPLATE_SCRIPTS = {
|
|
|
1194
1951
|
"module-monolith": {
|
|
1195
1952
|
dev: "wp dev",
|
|
1196
1953
|
local: "wp dev",
|
|
1197
|
-
"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",
|
|
1198
1959
|
doctor: "wp doctor"
|
|
1199
1960
|
},
|
|
1200
1961
|
"theme-starter": {
|
|
@@ -1203,7 +1964,7 @@ var TEMPLATE_SCRIPTS = {
|
|
|
1203
1964
|
doctor: "wp doctor"
|
|
1204
1965
|
}
|
|
1205
1966
|
};
|
|
1206
|
-
function buildConfigForProject(template, projectName) {
|
|
1967
|
+
function buildConfigForProject(template, projectName, router) {
|
|
1207
1968
|
const base = { ...DEFAULT_WOODSPORTAL_HS, projectType: template };
|
|
1208
1969
|
if (template === "theme-starter") {
|
|
1209
1970
|
return {
|
|
@@ -1216,7 +1977,7 @@ function buildConfigForProject(template, projectName) {
|
|
|
1216
1977
|
}
|
|
1217
1978
|
};
|
|
1218
1979
|
}
|
|
1219
|
-
|
|
1980
|
+
const config = {
|
|
1220
1981
|
...base,
|
|
1221
1982
|
module: {
|
|
1222
1983
|
name: projectName,
|
|
@@ -1228,9 +1989,13 @@ function buildConfigForProject(template, projectName) {
|
|
|
1228
1989
|
mode: template === "module-monolith" ? "none" : base.cdn.mode
|
|
1229
1990
|
}
|
|
1230
1991
|
};
|
|
1992
|
+
if (isModuleTemplate(template) && router) {
|
|
1993
|
+
config.scaffold = { router };
|
|
1994
|
+
}
|
|
1995
|
+
return config;
|
|
1231
1996
|
}
|
|
1232
1997
|
function assertTargetDirIsScaffoldable(targetDir) {
|
|
1233
|
-
if (!
|
|
1998
|
+
if (!existsSync10(targetDir)) return;
|
|
1234
1999
|
const entries = readdirSync2(targetDir).filter((entry) => entry !== ".DS_Store");
|
|
1235
2000
|
if (entries.length > 0) {
|
|
1236
2001
|
throw new WpHsError(`Target directory is not empty: ${targetDir}`, {
|
|
@@ -1241,7 +2006,7 @@ function assertTargetDirIsScaffoldable(targetDir) {
|
|
|
1241
2006
|
}
|
|
1242
2007
|
function resolveInitTargetDir(options, interactiveName) {
|
|
1243
2008
|
if (options.directory) {
|
|
1244
|
-
return
|
|
2009
|
+
return resolve15(options.directory);
|
|
1245
2010
|
}
|
|
1246
2011
|
const parentDir = process.cwd();
|
|
1247
2012
|
const slug = options.name ?? interactiveName;
|
|
@@ -1255,15 +2020,16 @@ function resolveInitTargetDir(options, interactiveName) {
|
|
|
1255
2020
|
if (error) {
|
|
1256
2021
|
throw new WpHsError(error, { exitCode: ExitCode.USER_ERROR });
|
|
1257
2022
|
}
|
|
1258
|
-
return
|
|
2023
|
+
return resolve15(parentDir, slug);
|
|
1259
2024
|
}
|
|
1260
2025
|
async function runInit(ctx, options) {
|
|
1261
2026
|
const nonInteractive = ctx.global.ci || ctx.global.yes || ctx.global.json;
|
|
1262
2027
|
let template = options.template;
|
|
1263
2028
|
let projectName = options.name ? normalizeProjectName(options.name) : void 0;
|
|
2029
|
+
let router = options.router;
|
|
1264
2030
|
if (!nonInteractive) {
|
|
1265
2031
|
if (!template) {
|
|
1266
|
-
const type = await
|
|
2032
|
+
const type = await p2.select({
|
|
1267
2033
|
message: "Project type",
|
|
1268
2034
|
options: [
|
|
1269
2035
|
{
|
|
@@ -1275,58 +2041,92 @@ async function runInit(ctx, options) {
|
|
|
1275
2041
|
{ value: "theme-starter", label: "Theme starter", hint: "CMS theme templates" }
|
|
1276
2042
|
]
|
|
1277
2043
|
});
|
|
1278
|
-
if (
|
|
2044
|
+
if (p2.isCancel(type)) throw new WpHsError("Init cancelled", { exitCode: ExitCode.USER_ERROR });
|
|
1279
2045
|
template = type;
|
|
1280
2046
|
}
|
|
1281
2047
|
if (!options.directory && !projectName) {
|
|
1282
|
-
const nameAnswer = await
|
|
2048
|
+
const nameAnswer = await p2.text({
|
|
1283
2049
|
message: "Project name (folder name)",
|
|
1284
2050
|
placeholder: "my-portal-module",
|
|
1285
2051
|
validate: (value) => validateProjectName(normalizeProjectName(value ?? ""))
|
|
1286
2052
|
});
|
|
1287
|
-
if (
|
|
2053
|
+
if (p2.isCancel(nameAnswer)) {
|
|
1288
2054
|
throw new WpHsError("Init cancelled", { exitCode: ExitCode.USER_ERROR });
|
|
1289
2055
|
}
|
|
1290
2056
|
projectName = normalizeProjectName(nameAnswer);
|
|
1291
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
|
+
}
|
|
1292
2085
|
}
|
|
1293
2086
|
if (!template) {
|
|
1294
2087
|
template = "module-hybrid-cdn";
|
|
1295
2088
|
}
|
|
2089
|
+
if (!router && isModuleTemplate(template)) {
|
|
2090
|
+
router = "hash";
|
|
2091
|
+
}
|
|
1296
2092
|
const targetDir = resolveInitTargetDir(options, projectName);
|
|
1297
|
-
const slug = projectName ??
|
|
2093
|
+
const slug = projectName ?? basename2(targetDir);
|
|
1298
2094
|
assertTargetDirIsScaffoldable(targetDir);
|
|
1299
2095
|
const templateDir = kitPath("templates", template);
|
|
1300
|
-
if (!
|
|
2096
|
+
if (!existsSync10(templateDir)) {
|
|
1301
2097
|
throw new WpHsError(`Template not found: ${template}`, { exitCode: ExitCode.USER_ERROR });
|
|
1302
2098
|
}
|
|
1303
2099
|
if (ctx.global.dryRun) {
|
|
1304
2100
|
ctx.logger.info(`[dry-run] Would scaffold ${template} in ${targetDir}`);
|
|
1305
2101
|
return ExitCode.SUCCESS;
|
|
1306
2102
|
}
|
|
1307
|
-
|
|
2103
|
+
mkdirSync3(targetDir, { recursive: true });
|
|
1308
2104
|
cpSync2(templateDir, targetDir, { recursive: true });
|
|
1309
|
-
|
|
1310
|
-
|
|
2105
|
+
copySharedSetupExamples(targetDir);
|
|
2106
|
+
const config = buildConfigForProject(template, slug, router);
|
|
2107
|
+
writeFileSync3(resolve15(targetDir, WOODSCLI_CONFIG_FILE), `${JSON.stringify(config, null, 2)}
|
|
1311
2108
|
`);
|
|
1312
|
-
const pkgPath =
|
|
1313
|
-
if (
|
|
1314
|
-
const pkg = JSON.parse(
|
|
2109
|
+
const pkgPath = resolve15(targetDir, "package.json");
|
|
2110
|
+
if (existsSync10(pkgPath)) {
|
|
2111
|
+
const pkg = JSON.parse(readFileSync8(pkgPath, "utf8"));
|
|
1315
2112
|
pkg.name = slug;
|
|
1316
2113
|
pkg.scripts = { ...pkg.scripts, ...TEMPLATE_SCRIPTS[template] };
|
|
1317
2114
|
pkg.devDependencies = {
|
|
1318
2115
|
...pkg.devDependencies,
|
|
1319
2116
|
...templateDevDependencies()
|
|
1320
2117
|
};
|
|
1321
|
-
|
|
2118
|
+
writeFileSync3(pkgPath, `${JSON.stringify(pkg, null, 2)}
|
|
1322
2119
|
`);
|
|
1323
2120
|
}
|
|
2121
|
+
if (router && isModuleTemplate(template)) {
|
|
2122
|
+
applyRouterScaffold(targetDir, router, template);
|
|
2123
|
+
}
|
|
1324
2124
|
runNodeScript(kitPath("scripts/ensure-wphs-workspace.mjs"), [], {
|
|
1325
2125
|
cwd: targetDir,
|
|
1326
2126
|
logger: ctx.logger,
|
|
1327
2127
|
dryRun: false
|
|
1328
2128
|
});
|
|
1329
|
-
const gitignorePath =
|
|
2129
|
+
const gitignorePath = resolve15(targetDir, ".gitignore");
|
|
1330
2130
|
const gitignoreExtras = [
|
|
1331
2131
|
"hubspot.config.yml",
|
|
1332
2132
|
".wphs/config/module-config.local.json",
|
|
@@ -1336,18 +2136,19 @@ async function runInit(ctx, options) {
|
|
|
1336
2136
|
".wphs/state/",
|
|
1337
2137
|
".wphs/fixtures/hubspot-data.json",
|
|
1338
2138
|
".wphs/.kit-module-config",
|
|
1339
|
-
".wphs/.kit-bootstrap.tsx"
|
|
2139
|
+
".wphs/.kit-bootstrap.tsx",
|
|
2140
|
+
".wphs/.kit-bootstrap-greenfield.tsx"
|
|
1340
2141
|
];
|
|
1341
|
-
const existing =
|
|
2142
|
+
const existing = existsSync10(gitignorePath) ? readFileSync8(gitignorePath, "utf8") : "";
|
|
1342
2143
|
const toAppend = gitignoreExtras.filter((line) => !existing.includes(line)).join("\n");
|
|
1343
2144
|
if (toAppend) {
|
|
1344
|
-
|
|
2145
|
+
writeFileSync3(gitignorePath, `${existing.trimEnd()}
|
|
1345
2146
|
${toAppend}
|
|
1346
2147
|
`);
|
|
1347
2148
|
}
|
|
1348
2149
|
if (!nonInteractive) {
|
|
1349
|
-
|
|
1350
|
-
|
|
2150
|
+
p2.log.success(`Scaffolded ${template} in ${targetDir}`);
|
|
2151
|
+
p2.note(`cd ${slug} && yarn install && wp setup && wp doctor && wp dev`, "Getting started");
|
|
1351
2152
|
} else {
|
|
1352
2153
|
ctx.logger.success(`Scaffolded ${template} in ${targetDir}`);
|
|
1353
2154
|
}
|
|
@@ -1355,10 +2156,10 @@ ${toAppend}
|
|
|
1355
2156
|
}
|
|
1356
2157
|
|
|
1357
2158
|
// src/cli/commands/adopt.ts
|
|
1358
|
-
import { existsSync as
|
|
1359
|
-
import { resolve as
|
|
2159
|
+
import { existsSync as existsSync11, readFileSync as readFileSync9, writeFileSync as writeFileSync4 } from "fs";
|
|
2160
|
+
import { resolve as resolve16 } from "path";
|
|
1360
2161
|
import { pathToFileURL } from "url";
|
|
1361
|
-
import * as
|
|
2162
|
+
import * as p3 from "@clack/prompts";
|
|
1362
2163
|
async function loadDetectLayout() {
|
|
1363
2164
|
const mod = await import(pathToFileURL(kitPath("scripts", "detect-layout.mjs")).href);
|
|
1364
2165
|
return mod.detectProjectLayout;
|
|
@@ -1366,7 +2167,7 @@ async function loadDetectLayout() {
|
|
|
1366
2167
|
async function runAdopt(ctx, _options = {}) {
|
|
1367
2168
|
const detectProjectLayout = await loadDetectLayout();
|
|
1368
2169
|
const layout = detectProjectLayout(ctx.projectRoot);
|
|
1369
|
-
const configPath =
|
|
2170
|
+
const configPath = resolve16(ctx.projectRoot, WOODSCLI_CONFIG_FILE);
|
|
1370
2171
|
if (layout.track === "unknown") {
|
|
1371
2172
|
throw new WpHsError("Could not detect module or theme layout.", {
|
|
1372
2173
|
exitCode: ExitCode.USER_ERROR,
|
|
@@ -1378,7 +2179,7 @@ async function runAdopt(ctx, _options = {}) {
|
|
|
1378
2179
|
projectType = layout.projectType;
|
|
1379
2180
|
} else if (layout.track === "theme") {
|
|
1380
2181
|
projectType = "theme-starter";
|
|
1381
|
-
} else if (
|
|
2182
|
+
} else if (existsSync11(resolve16(ctx.projectRoot, "vite.config.monolith.mjs"))) {
|
|
1382
2183
|
projectType = "module-monolith";
|
|
1383
2184
|
} else {
|
|
1384
2185
|
projectType = "module-hybrid-cdn";
|
|
@@ -1399,13 +2200,13 @@ async function runAdopt(ctx, _options = {}) {
|
|
|
1399
2200
|
ctx.logger.info(`[dry-run] Would adopt as ${projectType} (${layout.track})`);
|
|
1400
2201
|
return ExitCode.SUCCESS;
|
|
1401
2202
|
}
|
|
1402
|
-
if (!
|
|
1403
|
-
|
|
2203
|
+
if (!existsSync11(configPath)) {
|
|
2204
|
+
writeFileSync4(configPath, `${JSON.stringify(config, null, 2)}
|
|
1404
2205
|
`);
|
|
1405
2206
|
ctx.logger.success(`Created ${WOODSCLI_CONFIG_FILE}`);
|
|
1406
2207
|
} else if (!layout.projectType) {
|
|
1407
|
-
const existing = JSON.parse(
|
|
1408
|
-
|
|
2208
|
+
const existing = JSON.parse(readFileSync9(configPath, "utf8"));
|
|
2209
|
+
writeFileSync4(configPath, `${JSON.stringify({ ...existing, projectType }, null, 2)}
|
|
1409
2210
|
`);
|
|
1410
2211
|
ctx.logger.info(`Set projectType \u2192 ${projectType}`);
|
|
1411
2212
|
}
|
|
@@ -1414,9 +2215,9 @@ async function runAdopt(ctx, _options = {}) {
|
|
|
1414
2215
|
logger: ctx.logger,
|
|
1415
2216
|
dryRun: false
|
|
1416
2217
|
});
|
|
1417
|
-
const adapterPath =
|
|
1418
|
-
if (!
|
|
1419
|
-
|
|
2218
|
+
const adapterPath = resolve16(ctx.projectRoot, "wphs.adapter.ts");
|
|
2219
|
+
if (!existsSync11(adapterPath) && isModuleProject(config)) {
|
|
2220
|
+
writeFileSync4(
|
|
1420
2221
|
adapterPath,
|
|
1421
2222
|
`import type { HubSpotKitAdapter } from '@woodsportal/hubspot-kit'
|
|
1422
2223
|
|
|
@@ -1432,11 +2233,11 @@ export default adapter
|
|
|
1432
2233
|
ctx.logger.info("Created wphs.adapter.ts stub");
|
|
1433
2234
|
}
|
|
1434
2235
|
if (!ctx.global.ci && !ctx.global.quiet) {
|
|
1435
|
-
|
|
2236
|
+
p3.note(
|
|
1436
2237
|
[
|
|
1437
2238
|
`Review ${WOODSCLI_CONFIG_FILE} upload paths`,
|
|
1438
2239
|
"Implement wphs.adapter.ts for your app (if module)",
|
|
1439
|
-
"
|
|
2240
|
+
"Run wp setup (copies hubspot.config.yml, .env.*, vite shims)",
|
|
1440
2241
|
"Patch main.dev.css @source to .wphs/.kit-module-config/**"
|
|
1441
2242
|
].join("\n"),
|
|
1442
2243
|
"Manual follow-ups"
|
|
@@ -1447,67 +2248,67 @@ export default adapter
|
|
|
1447
2248
|
}
|
|
1448
2249
|
|
|
1449
2250
|
// src/cli/commands/migrate.ts
|
|
1450
|
-
import { cpSync as cpSync3, existsSync as
|
|
1451
|
-
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";
|
|
1452
2253
|
var LEGACY_DIRS = [".dev", ".woodsportal"];
|
|
1453
2254
|
var LEGACY_BASELINE = ".wp-hs-baseline";
|
|
1454
2255
|
async function runMigrate(ctx, _options = {}) {
|
|
1455
2256
|
const root = ctx.projectRoot;
|
|
1456
|
-
const wphsRoot =
|
|
2257
|
+
const wphsRoot = resolve17(root, WPHS_WORKSPACE_DIR);
|
|
1457
2258
|
const actions = [];
|
|
1458
2259
|
for (const legacy of LEGACY_DIRS) {
|
|
1459
|
-
const legacyPath =
|
|
1460
|
-
if (!
|
|
2260
|
+
const legacyPath = resolve17(root, legacy);
|
|
2261
|
+
if (!existsSync12(legacyPath)) continue;
|
|
1461
2262
|
actions.push(`Move ${legacy}/ \u2192 .wphs/`);
|
|
1462
2263
|
if (ctx.global.dryRun) continue;
|
|
1463
|
-
|
|
1464
|
-
|
|
2264
|
+
mkdirSync4(join3(wphsRoot, "config"), { recursive: true });
|
|
2265
|
+
mkdirSync4(join3(wphsRoot, "assets", "branding"), { recursive: true });
|
|
1465
2266
|
const localConfig = join3(legacyPath, "module-config.local.json");
|
|
1466
2267
|
const exampleConfig = join3(legacyPath, "module-config.example.json");
|
|
1467
|
-
if (
|
|
2268
|
+
if (existsSync12(localConfig)) {
|
|
1468
2269
|
cpSync3(localConfig, join3(wphsRoot, "config", "module-config.local.json"));
|
|
1469
2270
|
}
|
|
1470
|
-
if (
|
|
2271
|
+
if (existsSync12(exampleConfig)) {
|
|
1471
2272
|
cpSync3(exampleConfig, join3(wphsRoot, "config", "module-config.example.json"));
|
|
1472
2273
|
}
|
|
1473
2274
|
const branding = join3(legacyPath, "branding-assets");
|
|
1474
|
-
if (
|
|
2275
|
+
if (existsSync12(branding)) {
|
|
1475
2276
|
for (const file of readdirSync3(branding)) {
|
|
1476
2277
|
cpSync3(join3(branding, file), join3(wphsRoot, "assets", "branding", file));
|
|
1477
2278
|
}
|
|
1478
2279
|
}
|
|
1479
|
-
|
|
2280
|
+
rmSync3(legacyPath, { recursive: true, force: true });
|
|
1480
2281
|
}
|
|
1481
|
-
const legacyBaseline =
|
|
2282
|
+
const legacyBaseline = resolve17(root, LEGACY_BASELINE);
|
|
1482
2283
|
const targetBaseline = join3(wphsRoot, "audit", "baseline");
|
|
1483
|
-
if (
|
|
2284
|
+
if (existsSync12(legacyBaseline)) {
|
|
1484
2285
|
actions.push(`Move ${LEGACY_BASELINE}/ \u2192 .wphs/audit/baseline/`);
|
|
1485
2286
|
if (!ctx.global.dryRun) {
|
|
1486
|
-
|
|
2287
|
+
mkdirSync4(targetBaseline, { recursive: true });
|
|
1487
2288
|
cpSync3(legacyBaseline, targetBaseline, { recursive: true });
|
|
1488
|
-
|
|
2289
|
+
rmSync3(legacyBaseline, { recursive: true, force: true });
|
|
1489
2290
|
}
|
|
1490
2291
|
}
|
|
1491
|
-
const legacyConfigPath2 =
|
|
1492
|
-
const woodscliPath =
|
|
1493
|
-
if (
|
|
2292
|
+
const legacyConfigPath2 = resolve17(root, LEGACY_WOODSPORTAL_HS_FILE);
|
|
2293
|
+
const woodscliPath = resolve17(root, WOODSCLI_CONFIG_FILE);
|
|
2294
|
+
if (existsSync12(legacyConfigPath2) && !existsSync12(woodscliPath)) {
|
|
1494
2295
|
actions.push(`Rename ${LEGACY_WOODSPORTAL_HS_FILE} \u2192 ${WOODSCLI_CONFIG_FILE}`);
|
|
1495
2296
|
if (!ctx.global.dryRun) {
|
|
1496
2297
|
renameSync(legacyConfigPath2, woodscliPath);
|
|
1497
2298
|
}
|
|
1498
|
-
} else if (
|
|
2299
|
+
} else if (existsSync12(legacyConfigPath2) && existsSync12(woodscliPath)) {
|
|
1499
2300
|
actions.push(`Remove duplicate ${LEGACY_WOODSPORTAL_HS_FILE}`);
|
|
1500
2301
|
if (!ctx.global.dryRun) {
|
|
1501
|
-
|
|
2302
|
+
rmSync3(legacyConfigPath2, { force: true });
|
|
1502
2303
|
}
|
|
1503
2304
|
}
|
|
1504
|
-
if (
|
|
1505
|
-
const raw = JSON.parse(
|
|
2305
|
+
if (existsSync12(woodscliPath)) {
|
|
2306
|
+
const raw = JSON.parse(readFileSync10(woodscliPath, "utf8"));
|
|
1506
2307
|
if (raw?.dev?.moduleConfigDir && raw.dev.moduleConfigDir !== WPHS_WORKSPACE_DIR) {
|
|
1507
2308
|
actions.push(`Update dev.moduleConfigDir \u2192 .wphs in project config`);
|
|
1508
2309
|
if (!ctx.global.dryRun) {
|
|
1509
2310
|
raw.dev.moduleConfigDir = WPHS_WORKSPACE_DIR;
|
|
1510
|
-
|
|
2311
|
+
writeFileSync5(woodscliPath, `${JSON.stringify(raw, null, 2)}
|
|
1511
2312
|
`);
|
|
1512
2313
|
}
|
|
1513
2314
|
}
|
|
@@ -1536,10 +2337,10 @@ async function runMigrate(ctx, _options = {}) {
|
|
|
1536
2337
|
}
|
|
1537
2338
|
|
|
1538
2339
|
// src/cli/commands/examples.ts
|
|
1539
|
-
import { readFileSync as
|
|
2340
|
+
import { readFileSync as readFileSync11 } from "fs";
|
|
1540
2341
|
function loadManifest() {
|
|
1541
2342
|
const manifestPath = kitPath("examples", "manifest.json");
|
|
1542
|
-
const raw = JSON.parse(
|
|
2343
|
+
const raw = JSON.parse(readFileSync11(manifestPath, "utf8"));
|
|
1543
2344
|
return raw.scenarios;
|
|
1544
2345
|
}
|
|
1545
2346
|
async function runExamplesList(ctx, options = {}) {
|
|
@@ -1559,7 +2360,7 @@ async function runExamplesSmoke(ctx, options = {}) {
|
|
|
1559
2360
|
const tier = options.tier ?? "static";
|
|
1560
2361
|
const args = ["--tier", tier];
|
|
1561
2362
|
if (options.scenario) args.push("--scenario", options.scenario);
|
|
1562
|
-
const { runNodeScript: runNodeScript2 } = await import("./exec-
|
|
2363
|
+
const { runNodeScript: runNodeScript2 } = await import("./exec-U2OJATR3.js");
|
|
1563
2364
|
runNodeScript2(kitPath("examples", "smoke", "run-matrix.mjs"), args, {
|
|
1564
2365
|
cwd: ctx.projectRoot,
|
|
1565
2366
|
logger: ctx.logger,
|
|
@@ -1572,15 +2373,15 @@ async function runExamplesSmoke(ctx, options = {}) {
|
|
|
1572
2373
|
}
|
|
1573
2374
|
|
|
1574
2375
|
// src/cli/commands/fields.ts
|
|
1575
|
-
import { cpSync as cpSync4, existsSync as
|
|
1576
|
-
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";
|
|
1577
2378
|
async function runFieldsSync(ctx, options = {}) {
|
|
1578
2379
|
const direction = options.direction ?? "pull";
|
|
1579
|
-
const fieldsPath =
|
|
1580
|
-
const cacheDir =
|
|
1581
|
-
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");
|
|
1582
2383
|
if (direction === "pull") {
|
|
1583
|
-
if (!
|
|
2384
|
+
if (!existsSync13(fieldsPath)) {
|
|
1584
2385
|
throw new WpHsError(`Missing ${ctx.config.dev.fieldsJson}`, {
|
|
1585
2386
|
exitCode: ExitCode.USER_ERROR,
|
|
1586
2387
|
remediation: "Add fields.json or run wp init."
|
|
@@ -1590,13 +2391,13 @@ async function runFieldsSync(ctx, options = {}) {
|
|
|
1590
2391
|
ctx.logger.info(`[dry-run] Would cache ${fieldsPath} \u2192 ${cachePath}`);
|
|
1591
2392
|
return ExitCode.SUCCESS;
|
|
1592
2393
|
}
|
|
1593
|
-
const { mkdirSync:
|
|
1594
|
-
|
|
2394
|
+
const { mkdirSync: mkdirSync5 } = await import("fs");
|
|
2395
|
+
mkdirSync5(cacheDir, { recursive: true });
|
|
1595
2396
|
cpSync4(fieldsPath, cachePath);
|
|
1596
2397
|
ctx.logger.success(`Cached fields \u2192 ${cachePath}`);
|
|
1597
2398
|
return ExitCode.SUCCESS;
|
|
1598
2399
|
}
|
|
1599
|
-
if (!
|
|
2400
|
+
if (!existsSync13(cachePath)) {
|
|
1600
2401
|
throw new WpHsError("No cached fields \u2014 run wp fields sync pull first.", {
|
|
1601
2402
|
exitCode: ExitCode.USER_ERROR
|
|
1602
2403
|
});
|
|
@@ -1605,14 +2406,14 @@ async function runFieldsSync(ctx, options = {}) {
|
|
|
1605
2406
|
ctx.logger.info(`[dry-run] Would write ${cachePath} \u2192 ${fieldsPath}`);
|
|
1606
2407
|
return ExitCode.SUCCESS;
|
|
1607
2408
|
}
|
|
1608
|
-
|
|
2409
|
+
writeFileSync6(fieldsPath, readFileSync12(cachePath, "utf8"));
|
|
1609
2410
|
ctx.logger.success(`Restored fields from cache \u2192 ${fieldsPath}`);
|
|
1610
2411
|
return ExitCode.SUCCESS;
|
|
1611
2412
|
}
|
|
1612
2413
|
|
|
1613
2414
|
// src/cli/commands/regression.ts
|
|
1614
|
-
import { existsSync as
|
|
1615
|
-
import { resolve as
|
|
2415
|
+
import { existsSync as existsSync14 } from "fs";
|
|
2416
|
+
import { resolve as resolve19 } from "path";
|
|
1616
2417
|
var LEGACY_SHIM_SCRIPTS = [
|
|
1617
2418
|
"build-cdn.mjs",
|
|
1618
2419
|
"run-vite-build.mjs",
|
|
@@ -1635,21 +2436,21 @@ async function runRegressionSmoke(ctx, options = {}) {
|
|
|
1635
2436
|
if (!validateOk) failures.push("wp config validate failed");
|
|
1636
2437
|
if (options.checkLegacyShims !== false) {
|
|
1637
2438
|
for (const script of LEGACY_SHIM_SCRIPTS) {
|
|
1638
|
-
const shim =
|
|
2439
|
+
const shim = resolve19(ctx.projectRoot, "scripts", script);
|
|
1639
2440
|
const kitScript = kitPath("scripts", script);
|
|
1640
|
-
const shimOk =
|
|
2441
|
+
const shimOk = existsSync14(shim) && existsSync14(kitScript);
|
|
1641
2442
|
checks.push({ name: `shim:${script}`, ok: shimOk });
|
|
1642
2443
|
if (!shimOk) failures.push(`Missing legacy shim or kit script: ${script}`);
|
|
1643
2444
|
}
|
|
1644
2445
|
}
|
|
1645
2446
|
for (const rel of FORBIDDEN_CONSUMER_PATHS) {
|
|
1646
|
-
const forbidden =
|
|
1647
|
-
const absent = !
|
|
2447
|
+
const forbidden = resolve19(ctx.projectRoot, rel);
|
|
2448
|
+
const absent = !existsSync14(forbidden);
|
|
1648
2449
|
checks.push({ name: `no-${rel}`, ok: absent });
|
|
1649
2450
|
if (!absent) failures.push(`Forbidden dev path still present: ${rel}`);
|
|
1650
2451
|
}
|
|
1651
|
-
const woodscliConfig =
|
|
1652
|
-
const configOk =
|
|
2452
|
+
const woodscliConfig = resolve19(ctx.projectRoot, WOODSCLI_CONFIG_FILE);
|
|
2453
|
+
const configOk = existsSync14(woodscliConfig);
|
|
1653
2454
|
checks.push({ name: WOODSCLI_CONFIG_FILE, ok: configOk });
|
|
1654
2455
|
if (!configOk) failures.push(`${WOODSCLI_CONFIG_FILE} missing`);
|
|
1655
2456
|
if (ctx.global.json) {
|
|
@@ -1673,43 +2474,260 @@ async function runRegressionSmoke(ctx, options = {}) {
|
|
|
1673
2474
|
return ExitCode.SUCCESS;
|
|
1674
2475
|
}
|
|
1675
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
|
+
|
|
1676
2698
|
// src/cli/commands/watch.ts
|
|
1677
2699
|
async function runWatch(ctx, options) {
|
|
1678
|
-
|
|
2700
|
+
ensureHubSpotConfigForCms(ctx);
|
|
1679
2701
|
const tier = options.tier ?? "dev";
|
|
1680
|
-
const args = [];
|
|
1681
|
-
if (tier === "prod") args.push("--prod");
|
|
2702
|
+
const args = ["--tier", tier];
|
|
1682
2703
|
if (options.rebuild) args.push("--rebuild");
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
return
|
|
1691
|
-
}
|
|
1692
|
-
|
|
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}`, {
|
|
1693
2715
|
exitCode: ExitCode.BUILD_FAILED,
|
|
1694
|
-
|
|
1695
|
-
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."
|
|
1696
2717
|
});
|
|
1697
2718
|
}
|
|
2719
|
+
return ExitCode.SUCCESS;
|
|
1698
2720
|
}
|
|
1699
2721
|
|
|
1700
2722
|
// src/cli/program.ts
|
|
1701
2723
|
function readVersion() {
|
|
1702
2724
|
try {
|
|
1703
|
-
const pkg = JSON.parse(
|
|
2725
|
+
const pkg = JSON.parse(readFileSync14(resolve21(getKitRoot(), "package.json"), "utf8"));
|
|
1704
2726
|
return pkg.version ?? "0.0.0";
|
|
1705
2727
|
} catch {
|
|
1706
2728
|
return "0.0.0";
|
|
1707
2729
|
}
|
|
1708
2730
|
}
|
|
1709
|
-
function parseTier(value) {
|
|
1710
|
-
if (value === "dev" || value === "stg" || value === "prod") return value;
|
|
1711
|
-
return "prod";
|
|
1712
|
-
}
|
|
1713
2731
|
function buildContext(global) {
|
|
1714
2732
|
const logger = createLogger({
|
|
1715
2733
|
verbose: global.verbose,
|
|
@@ -1750,33 +2768,45 @@ function createProgram() {
|
|
|
1750
2768
|
const global = program2.opts();
|
|
1751
2769
|
await handle(() => runDoctor(buildContext(global)));
|
|
1752
2770
|
});
|
|
1753
|
-
|
|
1754
|
-
configCmd.command("validate").description("Validate woodscli.json").action(async () => {
|
|
1755
|
-
const global = program2.opts();
|
|
1756
|
-
await handle(() => runConfigValidate(buildContext(global)));
|
|
1757
|
-
});
|
|
1758
|
-
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) => {
|
|
1759
2772
|
const global = program2.opts();
|
|
1760
|
-
const logger = createLogger({
|
|
1761
|
-
verbose: global.verbose,
|
|
1762
|
-
quiet: global.quiet,
|
|
1763
|
-
json: global.json,
|
|
1764
|
-
ci: global.ci
|
|
1765
|
-
});
|
|
1766
|
-
const ctx = {
|
|
1767
|
-
projectRoot: process.cwd(),
|
|
1768
|
-
config: DEFAULT_WOODSPORTAL_HS,
|
|
1769
|
-
logger,
|
|
1770
|
-
global
|
|
1771
|
-
};
|
|
1772
2773
|
await handle(
|
|
1773
|
-
() =>
|
|
1774
|
-
|
|
1775
|
-
name: opts.name,
|
|
1776
|
-
directory: opts.directory
|
|
2774
|
+
() => runSetup(buildContext(global), {
|
|
2775
|
+
tier: opts.tier ? parseBuildTier(opts.tier) : void 0
|
|
1777
2776
|
})
|
|
1778
2777
|
);
|
|
1779
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
|
+
);
|
|
1780
2810
|
program2.command("adopt").description("Detect existing module/theme repo and add WoodsCLI config + .wphs/").action(async () => {
|
|
1781
2811
|
const global = program2.opts();
|
|
1782
2812
|
await handle(() => runAdopt(buildContext(global)));
|
|
@@ -1789,12 +2819,12 @@ function createProgram() {
|
|
|
1789
2819
|
const global = program2.opts();
|
|
1790
2820
|
await handle(() => runDev(buildContext(global), { port: Number(opts.port) }));
|
|
1791
2821
|
});
|
|
1792
|
-
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(
|
|
1793
2823
|
async (opts) => {
|
|
1794
2824
|
const global = program2.opts();
|
|
1795
2825
|
await handle(
|
|
1796
2826
|
() => runBuild(buildContext(global), {
|
|
1797
|
-
tier:
|
|
2827
|
+
tier: parseBuildTier(opts.tier),
|
|
1798
2828
|
cdnOnly: opts.cdnOnly,
|
|
1799
2829
|
hubspotOnly: opts.hubspotOnly,
|
|
1800
2830
|
cdnEsm: opts.cdnEsm
|
|
@@ -1802,20 +2832,20 @@ function createProgram() {
|
|
|
1802
2832
|
);
|
|
1803
2833
|
}
|
|
1804
2834
|
);
|
|
1805
|
-
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) => {
|
|
1806
2836
|
const global = program2.opts();
|
|
1807
2837
|
await handle(
|
|
1808
2838
|
() => runDeploy(buildContext(global), {
|
|
1809
|
-
tier:
|
|
2839
|
+
tier: parseBuildTier(opts.tier),
|
|
1810
2840
|
skipPublish: opts.skipPublish
|
|
1811
2841
|
})
|
|
1812
2842
|
);
|
|
1813
2843
|
});
|
|
1814
|
-
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) => {
|
|
1815
2845
|
const global = program2.opts();
|
|
1816
2846
|
await handle(
|
|
1817
2847
|
() => runPublish(buildContext(global), {
|
|
1818
|
-
tier:
|
|
2848
|
+
tier: parseBuildTier(opts.tier),
|
|
1819
2849
|
dryRun: opts.dryRun
|
|
1820
2850
|
})
|
|
1821
2851
|
);
|
|
@@ -1871,9 +2901,9 @@ function createProgram() {
|
|
|
1871
2901
|
() => runRegressionSmoke(buildContext(global), { skipDoctor: opts.skipDoctor })
|
|
1872
2902
|
);
|
|
1873
2903
|
});
|
|
1874
|
-
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) => {
|
|
1875
2905
|
const global = program2.opts();
|
|
1876
|
-
const tier =
|
|
2906
|
+
const tier = parseBuildTier(opts.tier);
|
|
1877
2907
|
await handle(
|
|
1878
2908
|
() => runWatch(buildContext(global), {
|
|
1879
2909
|
tier: tier === "prod" ? "prod" : tier === "stg" ? "stg" : "dev",
|
|
@@ -1881,16 +2911,16 @@ function createProgram() {
|
|
|
1881
2911
|
})
|
|
1882
2912
|
);
|
|
1883
2913
|
});
|
|
1884
|
-
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) => {
|
|
1885
2915
|
const global = program2.opts();
|
|
1886
2916
|
await handle(
|
|
1887
|
-
() => runUpload(buildContext(global), { tier:
|
|
2917
|
+
() => runUpload(buildContext(global), { tier: parseBuildTier(opts.tier) })
|
|
1888
2918
|
);
|
|
1889
2919
|
});
|
|
1890
2920
|
program2.command("auth").description("Authenticate HubSpot CLI (hs auth)").action(async () => {
|
|
1891
2921
|
const global = program2.opts();
|
|
1892
2922
|
const ctx = buildContext(global);
|
|
1893
|
-
const { runCommand: runCommand2 } = await import("./exec-
|
|
2923
|
+
const { runCommand: runCommand2 } = await import("./exec-U2OJATR3.js");
|
|
1894
2924
|
runCommand2("hs", ["auth"], {
|
|
1895
2925
|
cwd: ctx.projectRoot,
|
|
1896
2926
|
logger: ctx.logger,
|
|
@@ -1898,6 +2928,15 @@ function createProgram() {
|
|
|
1898
2928
|
});
|
|
1899
2929
|
process.exit(ExitCode.SUCCESS);
|
|
1900
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
|
+
});
|
|
1901
2940
|
return program2;
|
|
1902
2941
|
}
|
|
1903
2942
|
|