@woodsportal/hubspot-kit 1.0.30 → 1.0.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/chunk-4GOJPOW3.js +258 -0
  3. package/dist/chunk-4GOJPOW3.js.map +1 -0
  4. package/dist/cli.js +546 -240
  5. package/dist/cli.js.map +1 -1
  6. package/dist/{exec-U2OJATR3.js → exec-VY6BGWF3.js} +6 -2
  7. package/examples/greenfield/module-hybrid-cdn/public/fields.json +7 -2
  8. package/examples/greenfield/module-hybrid-cdn/public/module.html +22 -0
  9. package/examples/greenfield/module-hybrid-cdn/src/cdn/app-entry.ts +5 -0
  10. package/examples/greenfield/module-hybrid-cdn/src/portal/api-endpoint.ts +11 -0
  11. package/examples/greenfield/module-hybrid-cdn/vite.config.cdn-app.mjs +1 -0
  12. package/examples/greenfield/module-hybrid-cdn/vite.config.cdn-esm.mjs +1 -0
  13. package/examples/greenfield/module-hybrid-cdn/vite.config.cdn-vendor.mjs +1 -0
  14. package/package.json +1 -1
  15. package/scripts/build-cdn.mjs +11 -7
  16. package/scripts/hubspot-module-postbuild.mjs +10 -6
  17. package/scripts/portal-subprocess-stdio.mjs +38 -0
  18. package/scripts/publish-cdn-github.mjs +20 -2
  19. package/scripts/resolve-vite-config.mjs +26 -0
  20. package/scripts/run-hubspot-module-postbuild.mjs +4 -1
  21. package/scripts/run-vite-build.mjs +13 -5
  22. package/templates/module-hybrid-cdn/public/fields.json +7 -2
  23. package/templates/module-hybrid-cdn/public/module.html +22 -0
  24. package/templates/module-hybrid-cdn/src/cdn/app-entry.ts +5 -0
  25. package/templates/module-hybrid-cdn/src/portal/api-endpoint.ts +11 -0
  26. package/templates/module-hybrid-cdn/vite.config.cdn-app.mjs +1 -0
  27. package/templates/module-hybrid-cdn/vite.config.cdn-esm.mjs +1 -0
  28. package/templates/module-hybrid-cdn/vite.config.cdn-vendor.mjs +1 -0
  29. package/vite/vite.config.hubspot.mjs +8 -2
  30. package/dist/chunk-GTTIJVAR.js +0 -127
  31. package/dist/chunk-GTTIJVAR.js.map +0 -1
  32. /package/dist/{exec-U2OJATR3.js.map → exec-VY6BGWF3.js.map} +0 -0
package/dist/cli.js CHANGED
@@ -4,9 +4,11 @@ import {
4
4
  WpHsError,
5
5
  isWpHsError,
6
6
  runCommand,
7
+ runCommandAsync,
7
8
  runNodeScript,
9
+ runNodeScriptAsync,
8
10
  runNodeScriptLongRunning
9
- } from "./chunk-GTTIJVAR.js";
11
+ } from "./chunk-4GOJPOW3.js";
10
12
 
11
13
  // src/cli/program.ts
12
14
  import { readFileSync as readFileSync15 } from "fs";
@@ -248,34 +250,41 @@ import pc from "picocolors";
248
250
  function shouldUseColor() {
249
251
  if (process.env.NO_COLOR !== void 0) return false;
250
252
  if (process.env.CI === "true") return false;
251
- return process.stdout.isTTY === true;
253
+ return process.stderr.isTTY === true;
252
254
  }
253
255
  function createLogger(options = {}) {
254
256
  const useColor = shouldUseColor();
255
257
  const { verbose = false, quiet = false, json = false } = options;
256
- const emit = (level, message, meta) => {
258
+ const write = (line) => {
259
+ process.stderr.write(`${line}
260
+ `);
261
+ };
262
+ const emit = (level, message) => {
257
263
  if (json) return;
258
264
  if (quiet && level !== "error" && level !== "warn") return;
259
265
  if (level === "debug" && !verbose) return;
260
266
  const prefix = level === "error" ? useColor ? pc.red("error") : "error" : level === "warn" ? useColor ? pc.yellow("warn") : "warn" : level === "debug" ? useColor ? pc.dim("debug") : "debug" : useColor ? pc.cyan("info") : "info";
261
- const line = meta ? `${message} ${JSON.stringify(meta)}` : message;
262
- const stream = level === "error" || level === "warn" ? process.stderr : process.stdout;
263
- stream.write(`[${prefix}] ${line}
264
- `);
267
+ write(`[${prefix}] ${message}`);
265
268
  };
266
269
  return {
267
- debug: (message, meta) => emit("debug", message, meta),
268
- info: (message, meta) => emit("info", message, meta),
269
- warn: (message, meta) => emit("warn", message, meta),
270
- error: (message, meta) => emit("error", message, meta),
271
- success: (message, meta) => {
272
- if (json) return;
273
- if (quiet) return;
270
+ debug: (message) => emit("debug", message),
271
+ info: (message) => emit("info", message),
272
+ warn: (message) => emit("warn", message),
273
+ error: (message) => emit("error", message),
274
+ success: (message) => {
275
+ if (json || quiet) return;
274
276
  const prefix = useColor ? pc.green("ok") : "ok";
275
- process.stdout.write(`[${prefix}] ${message}
276
- `);
277
- if (meta) process.stdout.write(`${JSON.stringify(meta)}
278
- `);
277
+ write(`[${prefix}] ${message}`);
278
+ },
279
+ step: (message) => {
280
+ if (json || quiet) return;
281
+ const bullet = useColor ? pc.cyan("\u2192") : "\u2192";
282
+ write(`${bullet} ${message}`);
283
+ },
284
+ stepDone: (message) => {
285
+ if (json || quiet) return;
286
+ const mark = useColor ? pc.green("\u2713") : "\u2713";
287
+ write(` ${mark} ${message}`);
279
288
  },
280
289
  json: (payload) => {
281
290
  if (!json) return;
@@ -305,16 +314,73 @@ function isInteractiveCli(ctx) {
305
314
  }
306
315
  function shouldShowProgress(ctx, override) {
307
316
  if (override === false) return false;
317
+ if (ctx.global.verbose) return false;
308
318
  return isInteractiveCli(ctx);
309
319
  }
310
- async function runCliTasks(ctx, tasks2, options = {}) {
320
+ function isVerboseStepLog(ctx) {
321
+ return ctx.global.verbose === true && !ctx.global.quiet && !ctx.global.json;
322
+ }
323
+ async function yieldForSpinnerPaint() {
324
+ await new Promise((resolve24) => {
325
+ setImmediate(resolve24);
326
+ });
327
+ await new Promise((resolve24) => {
328
+ setTimeout(resolve24, 48);
329
+ });
330
+ }
331
+ async function runCliTasks(ctx, tasks, options = {}) {
311
332
  if (!shouldShowProgress(ctx, options.progress)) {
312
- for (const step of tasks2) {
313
- await step.task();
333
+ const verbose = isVerboseStepLog(ctx);
334
+ if (verbose && options.intro) {
335
+ ctx.logger.info(options.intro);
314
336
  }
337
+ for (const step of tasks) {
338
+ if (verbose) {
339
+ ctx.logger.step(step.title);
340
+ }
341
+ try {
342
+ await step.task();
343
+ if (verbose) {
344
+ ctx.logger.stepDone(step.title);
345
+ }
346
+ } catch (error) {
347
+ if (verbose) {
348
+ ctx.logger.error(step.title);
349
+ }
350
+ throw error;
351
+ }
352
+ }
353
+ return;
354
+ }
355
+ if (tasks.length === 0) {
315
356
  return;
316
357
  }
317
- await p.tasks(tasks2);
358
+ const spinner2 = p.spinner();
359
+ const firstTitle = tasks[0].title;
360
+ spinner2.start(options.intro ?? firstTitle);
361
+ await yieldForSpinnerPaint();
362
+ for (let i = 0; i < tasks.length; i++) {
363
+ const step = tasks[i];
364
+ if (i > 0) {
365
+ spinner2.start(step.title);
366
+ await yieldForSpinnerPaint();
367
+ } else if (options.intro) {
368
+ spinner2.message(firstTitle);
369
+ await yieldForSpinnerPaint();
370
+ }
371
+ try {
372
+ await step.task();
373
+ spinner2.stop(step.title);
374
+ } catch (error) {
375
+ spinner2.stop(pc2.red(step.title));
376
+ throw error;
377
+ }
378
+ }
379
+ }
380
+ function shouldQuietSubprocess(ctx, options = {}) {
381
+ if (ctx.global.verbose) return false;
382
+ if (options.quietSubprocess === true || options.quietVite === true) return true;
383
+ return shouldShowProgress(ctx, options.progress);
318
384
  }
319
385
  function printCommandDone(ctx, message) {
320
386
  if (isInteractiveCli(ctx)) {
@@ -533,6 +599,91 @@ function templateDevDependencies() {
533
599
  };
534
600
  }
535
601
 
602
+ // src/lib/hubspot-fields-json.ts
603
+ var HUBSPOT_MODULE_FIELD_TYPES = /* @__PURE__ */ new Set([
604
+ "boolean",
605
+ "choice",
606
+ "color",
607
+ "cta",
608
+ "date",
609
+ "datetime",
610
+ "email",
611
+ "embed",
612
+ "file",
613
+ "followup_email",
614
+ "font",
615
+ "form",
616
+ "group",
617
+ "html",
618
+ "hubdb_row",
619
+ "hubdb_table",
620
+ "icon",
621
+ "image",
622
+ "link",
623
+ "logo",
624
+ "meeting",
625
+ "menu",
626
+ "number",
627
+ "page",
628
+ "payment",
629
+ "richtext",
630
+ "salesforce_campaign",
631
+ "simple_menu",
632
+ "spacing",
633
+ "tag",
634
+ "text",
635
+ "url",
636
+ "video",
637
+ "workflow"
638
+ ]);
639
+ function isFieldRecord(value) {
640
+ return typeof value === "object" && value !== null && !Array.isArray(value);
641
+ }
642
+ function validateFieldNode(node, path, issues) {
643
+ if (!isFieldRecord(node)) {
644
+ issues.push({ path, message: "expected a field object" });
645
+ return;
646
+ }
647
+ const type = node.type;
648
+ if (typeof type !== "string" || type.length === 0) {
649
+ issues.push({ path: `${path}.type`, message: "missing field type" });
650
+ } else if (!HUBSPOT_MODULE_FIELD_TYPES.has(type)) {
651
+ issues.push({
652
+ path: `${path}.type`,
653
+ message: type === "textarea" ? `'textarea' is not a HubSpot field type \u2014 use "type": "text" with "display": "textarea"` : `'${type}' is not a valid HubSpot field type`
654
+ });
655
+ }
656
+ if (type !== "group") {
657
+ const name = node.name;
658
+ if (typeof name !== "string" || name.trim().length === 0) {
659
+ issues.push({ path: `${path}.name`, message: "missing field name" });
660
+ }
661
+ const label = node.label;
662
+ if (typeof label !== "string" || label.trim().length === 0) {
663
+ issues.push({ path: `${path}.label`, message: "missing field label" });
664
+ }
665
+ } else {
666
+ const children = node.children;
667
+ if (!Array.isArray(children)) {
668
+ issues.push({ path: `${path}.children`, message: "group fields require a children array" });
669
+ } else {
670
+ children.forEach((child, index) => {
671
+ validateFieldNode(child, `${path}.children[${index}]`, issues);
672
+ });
673
+ }
674
+ }
675
+ }
676
+ function validateHubSpotFieldsJson(fields) {
677
+ if (!Array.isArray(fields)) {
678
+ return [{ path: "$", message: "fields.json must be a top-level array" }];
679
+ }
680
+ const issues = [];
681
+ fields.forEach((field, index) => {
682
+ validateFieldNode(field, `[${index}]`, issues);
683
+ });
684
+ return issues;
685
+ }
686
+
536
687
  // src/lib/upload-only-module.ts
537
688
  import { existsSync as existsSync4 } from "fs";
538
689
  import { resolve as resolve5 } from "path";
@@ -656,6 +807,89 @@ function checkDevViteConfig(ctx) {
656
807
  severity: "error"
657
808
  };
658
809
  }
810
+ function checkFieldsJson(ctx) {
811
+ if (isThemeProject(ctx.config)) return null;
812
+ const path = resolve6(ctx.projectRoot, "public/fields.json");
813
+ if (!existsSync5(path)) {
814
+ return {
815
+ name: "fields-json",
816
+ ok: false,
817
+ message: "public/fields.json missing",
818
+ remediation: "Copy fields.json from @woodsportal/hubspot-kit templates for your project type",
819
+ severity: "error"
820
+ };
821
+ }
822
+ try {
823
+ const fields = JSON.parse(readFileSync4(path, "utf8"));
824
+ const issues = validateHubSpotFieldsJson(fields);
825
+ if (issues.length === 0) {
826
+ return {
827
+ name: "fields-json",
828
+ ok: true,
829
+ message: "public/fields.json uses valid HubSpot field types",
830
+ severity: "error"
831
+ };
832
+ }
833
+ const preview = issues.slice(0, 3).map((issue) => `${issue.path}: ${issue.message}`).join("; ");
834
+ return {
835
+ name: "fields-json",
836
+ ok: false,
837
+ message: `public/fields.json has ${issues.length} issue(s) \u2014 ${preview}`,
838
+ remediation: 'Use HubSpot field types (e.g. "text" + "display": "textarea", not type "textarea"). See templates/module-hybrid-cdn/public/fields.json',
839
+ severity: "error"
840
+ };
841
+ } catch (error) {
842
+ const message = error instanceof Error ? error.message : String(error);
843
+ return {
844
+ name: "fields-json",
845
+ ok: false,
846
+ message: `public/fields.json is invalid JSON \u2014 ${message}`,
847
+ remediation: "Fix JSON syntax in public/fields.json",
848
+ severity: "error"
849
+ };
850
+ }
851
+ }
852
+ function checkHybridModuleHtml(ctx) {
853
+ if (isThemeProject(ctx.config) || !usesCdnPipeline(ctx.config)) return null;
854
+ const path = resolve6(ctx.projectRoot, "public/module.html");
855
+ if (!existsSync5(path)) {
856
+ return {
857
+ name: "module-html",
858
+ ok: false,
859
+ message: "public/module.html missing",
860
+ remediation: "Copy module.html from @woodsportal/hubspot-kit templates/module-hybrid-cdn",
861
+ severity: "error"
862
+ };
863
+ }
864
+ const html = readFileSync4(path, "utf8");
865
+ const needs = ["__PORTAL_CDN_BASE__", "require_js", "require_css", "hubSpotData", 'id="app"'];
866
+ const missing = needs.filter((token) => !html.includes(token));
867
+ const usesUndeclaredCdnFields = /module\.portal_(cdn_base_url|vendor_script|app_script|app_css)/.test(
868
+ html
869
+ );
870
+ const ok = missing.length === 0 && !usesUndeclaredCdnFields;
871
+ return {
872
+ name: "module-html-hybrid",
873
+ ok,
874
+ message: ok ? "public/module.html includes CDN require_js + hubSpotData" : usesUndeclaredCdnFields ? "public/module.html references undeclared module.portal_cdn_* fields" : `public/module.html missing hybrid CDN markers: ${missing.join(", ")}`,
875
+ remediation: ok ? void 0 : usesUndeclaredCdnFields ? "Use __PORTAL_CDN_BASE__ placeholders in module.html (see templates/module-hybrid-cdn) \u2014 CDN URLs are injected at build time" : "Update public/module.html from templates/module-hybrid-cdn (hybrid loads app from jsDelivr)",
876
+ severity: "error"
877
+ };
878
+ }
879
+ function checkCdnViteConfigs(ctx) {
880
+ if (isThemeProject(ctx.config) || !usesCdnPipeline(ctx.config)) return [];
881
+ return ["vite.config.cdn-vendor.mjs", "vite.config.cdn-app.mjs"].map((file) => {
882
+ const path = resolve6(ctx.projectRoot, file);
883
+ const ok = existsSync5(path);
884
+ return {
885
+ name: file.replace(/\./g, "-"),
886
+ ok,
887
+ message: ok ? `${file} present` : `${file} missing`,
888
+ remediation: ok ? void 0 : `Add ${file} that re-exports @woodsportal/hubspot-kit/vite/${file}`,
889
+ severity: "error"
890
+ };
891
+ });
892
+ }
659
893
  function checkDevEntry(ctx) {
660
894
  const checks = [];
661
895
  const indexHtml = resolve6(ctx.projectRoot, "index.html");
@@ -825,7 +1059,9 @@ async function gatherDoctorChecks(ctx) {
825
1059
  if (uploadOnlyNote) {
826
1060
  checks.push(uploadOnlyNote);
827
1061
  } else {
828
- checks.push(checkDevViteConfig(ctx), ...checkDevEntry(ctx));
1062
+ checks.push(checkDevViteConfig(ctx), ...checkCdnViteConfigs(ctx), ...checkDevEntry(ctx));
1063
+ const hybridHtml = checkHybridModuleHtml(ctx);
1064
+ if (hybridHtml) checks.push(hybridHtml);
829
1065
  const viteCheck = checkViteInstalled(ctx);
830
1066
  if (viteCheck) checks.push(viteCheck);
831
1067
  checks.push(...checkModuleConfigUiDeps(ctx));
@@ -836,6 +1072,8 @@ async function gatherDoctorChecks(ctx) {
836
1072
  title: "Checking woodscli.json and project layout",
837
1073
  task: async () => {
838
1074
  checks.push(...checkConfig(ctx), ...checkWphsWorkspace(ctx));
1075
+ const fieldsCheck = checkFieldsJson(ctx);
1076
+ if (fieldsCheck) checks.push(fieldsCheck);
839
1077
  const adapterCheck = checkAdapter(ctx);
840
1078
  if (adapterCheck) checks.push(adapterCheck);
841
1079
  }
@@ -930,148 +1168,21 @@ async function runConfigValidate(ctx) {
930
1168
  }
931
1169
 
932
1170
  // src/cli/commands/build.ts
933
- import { resolve as resolve7 } from "path";
934
-
935
- // src/lib/build-tier.ts
936
- var DEFAULT_BUILD_TIER = "dev";
937
- function parseBuildTier(value) {
938
- if (value === "dev" || value === "stg" || value === "prod") return value;
939
- return DEFAULT_BUILD_TIER;
940
- }
941
-
942
- // src/cli/commands/build.ts
943
- function tierToStgFlag(tier) {
944
- return tier === "dev" || tier === "stg" ? ["--stg"] : [];
945
- }
946
- function buildTasks(ctx, options, run) {
947
- const tier = options.tier ?? DEFAULT_BUILD_TIER;
948
- const isTheme = isThemeProject(ctx.config);
949
- const useCdn = usesCdnPipeline(ctx.config) && !isTheme;
950
- const tasks2 = [];
951
- if (isTheme && !options.cdnOnly) {
952
- return tasks2;
953
- }
954
- if (ctx.config.projectType === "module-monolith" && !options.cdnOnly) {
955
- tasks2.push({
956
- title: `Building monolith module (${tier})`,
957
- task: async () => {
958
- run("run-vite-build.mjs", [
959
- resolve7(ctx.projectRoot, "vite.config.monolith.mjs"),
960
- ...tierToStgFlag(tier)
961
- ]);
962
- run("run-hubspot-module-postbuild.mjs");
963
- }
964
- });
965
- return tasks2;
966
- }
967
- if (!options.hubspotOnly && useCdn) {
968
- tasks2.push({
969
- title: `Building CDN (${tier})`,
970
- task: async () => {
971
- if (options.cdnEsm) {
972
- run("run-vite-build.mjs", [
973
- resolve7(ctx.projectRoot, "vite.config.cdn-esm.mjs"),
974
- ...tierToStgFlag(tier)
975
- ]);
976
- } else {
977
- run("build-cdn.mjs", tierToStgFlag(tier));
978
- }
979
- run("generate-cdn-manifest.mjs");
980
- }
981
- });
982
- }
983
- if (!options.cdnOnly) {
984
- tasks2.push({
985
- title: `Building HubSpot module (${tier})`,
986
- task: async () => {
987
- run("run-vite-build.mjs", [
988
- resolve7(ctx.projectRoot, "vite.config.hubspot.mjs"),
989
- ...tierToStgFlag(tier)
990
- ]);
991
- run("run-hubspot-module-postbuild.mjs");
992
- }
993
- });
994
- }
995
- if (!options.cdnOnly && !options.hubspotOnly) {
996
- tasks2.push({
997
- title: "Running bundle checks",
998
- task: async () => {
999
- run("check-hubspot-bundle-size.mjs");
1000
- run("write-audit-artifacts.mjs");
1001
- }
1002
- });
1003
- }
1004
- return tasks2;
1005
- }
1006
- async function runBuild(ctx, options) {
1007
- const tier = options.tier ?? DEFAULT_BUILD_TIER;
1008
- const env = { ...process.env, WP_HS_PROJECT_ROOT: ctx.projectRoot };
1009
- const isTheme = isThemeProject(ctx.config);
1010
- const execOpts = {
1011
- cwd: ctx.projectRoot,
1012
- env,
1013
- logger: ctx.logger,
1014
- dryRun: ctx.global.dryRun
1015
- };
1016
- const run = (script, args = []) => {
1017
- runNodeScript(kitPath("scripts", script), args, execOpts);
1018
- };
1019
- try {
1020
- if (isTheme && !options.cdnOnly) {
1021
- if (ctx.global.json) {
1022
- ctx.logger.json({ command: "build", tier, ok: true, projectType: ctx.config.projectType });
1023
- } else if (!options.progress) {
1024
- ctx.logger.info("Theme project \u2014 skipping CDN/module.js build (use wp upload for theme assets)");
1025
- printCommandDone(ctx, "Theme build complete (no-op)");
1026
- } else {
1027
- await runCliTasks(
1028
- ctx,
1029
- [{ title: "Theme project (no module build)", task: async () => {
1030
- } }],
1031
- options
1032
- );
1033
- printCommandDone(ctx, "Theme build complete (no-op)");
1034
- }
1035
- return ExitCode.SUCCESS;
1036
- }
1037
- const tasks2 = buildTasks(ctx, options, run);
1038
- await runCliTasks(ctx, tasks2, options);
1039
- if (ctx.global.json) {
1040
- ctx.logger.json({
1041
- command: "build",
1042
- tier,
1043
- ok: true,
1044
- outDir: resolve7(ctx.projectRoot, ctx.config.module.outDir)
1045
- });
1046
- } else {
1047
- printCommandDone(ctx, "Build complete");
1048
- }
1049
- return ExitCode.SUCCESS;
1050
- } catch (error) {
1051
- throw new WpHsError(error instanceof Error ? error.message : String(error), {
1052
- exitCode: ExitCode.BUILD_FAILED
1053
- });
1054
- }
1055
- }
1056
-
1057
- // src/cli/commands/dev.ts
1058
- import { existsSync as existsSync7 } from "fs";
1059
- import { resolve as resolve10 } from "path";
1060
- import { spawn } from "child_process";
1171
+ import { resolve as resolve8 } from "path";
1061
1172
 
1062
1173
  // src/lib/cli-banner.ts
1063
1174
  import pc3 from "picocolors";
1064
1175
 
1065
1176
  // src/lib/kit-version.ts
1066
1177
  import { readFileSync as readFileSync5 } from "fs";
1067
- import { resolve as resolve8 } from "path";
1178
+ import { resolve as resolve7 } from "path";
1068
1179
  var cachedVersion = null;
1069
1180
  function readKitVersion() {
1070
1181
  if (cachedVersion) {
1071
1182
  return cachedVersion;
1072
1183
  }
1073
1184
  try {
1074
- const pkg = JSON.parse(readFileSync5(resolve8(getKitRoot(), "package.json"), "utf8"));
1185
+ const pkg = JSON.parse(readFileSync5(resolve7(getKitRoot(), "package.json"), "utf8"));
1075
1186
  cachedVersion = pkg.version ?? "0.0.0";
1076
1187
  } catch {
1077
1188
  cachedVersion = "0.0.0";
@@ -1179,6 +1290,153 @@ ${tips}
1179
1290
  `);
1180
1291
  }
1181
1292
 
1293
+ // src/lib/build-tier.ts
1294
+ var DEFAULT_BUILD_TIER = "dev";
1295
+ function parseBuildTier(value) {
1296
+ if (value === "dev" || value === "stg" || value === "prod") return value;
1297
+ return DEFAULT_BUILD_TIER;
1298
+ }
1299
+
1300
+ // src/cli/commands/build.ts
1301
+ function tierToStgFlag(tier) {
1302
+ return tier === "dev" || tier === "stg" ? ["--stg"] : [];
1303
+ }
1304
+ function buildTasks(ctx, options, run) {
1305
+ const tier = options.tier ?? DEFAULT_BUILD_TIER;
1306
+ const isTheme = isThemeProject(ctx.config);
1307
+ const useCdn = usesCdnPipeline(ctx.config) && !isTheme;
1308
+ const tasks = [];
1309
+ if (isTheme && !options.cdnOnly) {
1310
+ return tasks;
1311
+ }
1312
+ if (ctx.config.projectType === "module-monolith" && !options.cdnOnly) {
1313
+ tasks.push({
1314
+ title: `Building monolith module (${tier})`,
1315
+ task: async () => {
1316
+ await run("run-vite-build.mjs", [
1317
+ resolve8(ctx.projectRoot, "vite.config.monolith.mjs"),
1318
+ ...tierToStgFlag(tier)
1319
+ ]);
1320
+ await run("run-hubspot-module-postbuild.mjs");
1321
+ }
1322
+ });
1323
+ return tasks;
1324
+ }
1325
+ if (!options.hubspotOnly && useCdn) {
1326
+ tasks.push({
1327
+ title: `Building CDN (${tier})`,
1328
+ task: async () => {
1329
+ if (options.cdnEsm) {
1330
+ await run("run-vite-build.mjs", [
1331
+ resolve8(ctx.projectRoot, "vite.config.cdn-esm.mjs"),
1332
+ ...tierToStgFlag(tier)
1333
+ ]);
1334
+ } else {
1335
+ await run("build-cdn.mjs", tierToStgFlag(tier));
1336
+ }
1337
+ await run("generate-cdn-manifest.mjs");
1338
+ }
1339
+ });
1340
+ }
1341
+ if (!options.cdnOnly) {
1342
+ tasks.push({
1343
+ title: `Building HubSpot module (${tier})`,
1344
+ task: async () => {
1345
+ await run("run-vite-build.mjs", [
1346
+ resolve8(ctx.projectRoot, "vite.config.hubspot.mjs"),
1347
+ ...tierToStgFlag(tier)
1348
+ ]);
1349
+ await run("run-hubspot-module-postbuild.mjs");
1350
+ }
1351
+ });
1352
+ }
1353
+ if (!options.cdnOnly && !options.hubspotOnly) {
1354
+ tasks.push({
1355
+ title: "Running bundle checks",
1356
+ task: async () => {
1357
+ await run("check-hubspot-bundle-size.mjs");
1358
+ await run("write-audit-artifacts.mjs");
1359
+ }
1360
+ });
1361
+ }
1362
+ return tasks;
1363
+ }
1364
+ function buildIntroLine(ctx, tier, options) {
1365
+ const isTheme = isThemeProject(ctx.config);
1366
+ const useCdn = usesCdnPipeline(ctx.config) && !isTheme;
1367
+ const parts = [];
1368
+ if (!options.hubspotOnly && useCdn) parts.push("CDN");
1369
+ if (!options.cdnOnly) parts.push("HubSpot module");
1370
+ if (!options.cdnOnly && !options.hubspotOnly && !isTheme) parts.push("bundle checks");
1371
+ if (parts.length === 0) return void 0;
1372
+ return `${formatProjectLabel(ctx.config)} \xB7 ${parts.join(" + ")} (${tier})`;
1373
+ }
1374
+ async function runBuild(ctx, options) {
1375
+ const tier = options.tier ?? DEFAULT_BUILD_TIER;
1376
+ const quietSubprocess = shouldQuietSubprocess(ctx, options);
1377
+ const env = {
1378
+ ...process.env,
1379
+ WP_HS_PROJECT_ROOT: ctx.projectRoot,
1380
+ ...quietSubprocess ? { WP_HS_QUIET_BUILD: "1" } : {}
1381
+ };
1382
+ const isTheme = isThemeProject(ctx.config);
1383
+ const execOpts = {
1384
+ cwd: ctx.projectRoot,
1385
+ projectRoot: ctx.projectRoot,
1386
+ env,
1387
+ logger: ctx.logger,
1388
+ dryRun: ctx.global.dryRun,
1389
+ streamOutput: ctx.global.verbose
1390
+ };
1391
+ const run = (script, args = []) => {
1392
+ return runNodeScriptAsync(kitPath("scripts", script), args, execOpts);
1393
+ };
1394
+ try {
1395
+ if (isTheme && !options.cdnOnly) {
1396
+ if (ctx.global.json) {
1397
+ ctx.logger.json({ command: "build", tier, ok: true, projectType: ctx.config.projectType });
1398
+ } else if (!options.progress) {
1399
+ ctx.logger.info("Theme project \u2014 skipping CDN/module.js build (use wp upload for theme assets)");
1400
+ printCommandDone(ctx, "Theme build complete (no-op)");
1401
+ } else {
1402
+ await runCliTasks(
1403
+ ctx,
1404
+ [{ title: "Theme project (no module build)", task: async () => {
1405
+ } }],
1406
+ options
1407
+ );
1408
+ printCommandDone(ctx, "Theme build complete (no-op)");
1409
+ }
1410
+ return ExitCode.SUCCESS;
1411
+ }
1412
+ const tasks = buildTasks(ctx, options, run);
1413
+ await runCliTasks(ctx, tasks, {
1414
+ ...options,
1415
+ intro: buildIntroLine(ctx, tier, options)
1416
+ });
1417
+ if (ctx.global.json) {
1418
+ ctx.logger.json({
1419
+ command: "build",
1420
+ tier,
1421
+ ok: true,
1422
+ outDir: resolve8(ctx.projectRoot, ctx.config.module.outDir)
1423
+ });
1424
+ } else if (!options.quiet) {
1425
+ printCommandDone(ctx, "Build complete");
1426
+ }
1427
+ return ExitCode.SUCCESS;
1428
+ } catch (error) {
1429
+ throw new WpHsError(error instanceof Error ? error.message : String(error), {
1430
+ exitCode: ExitCode.BUILD_FAILED
1431
+ });
1432
+ }
1433
+ }
1434
+
1435
+ // src/cli/commands/dev.ts
1436
+ import { existsSync as existsSync7 } from "fs";
1437
+ import { resolve as resolve10 } from "path";
1438
+ import { spawn } from "child_process";
1439
+
1182
1440
  // src/vite/resolve-dev-config.ts
1183
1441
  import { existsSync as existsSync6 } from "fs";
1184
1442
  import { resolve as resolve9 } from "path";
@@ -1318,64 +1576,6 @@ ${toAppend}
1318
1576
  `);
1319
1577
  }
1320
1578
 
1321
- // src/cli/commands/publish.ts
1322
- async function runPublish(ctx, options) {
1323
- if (!usesCdnPipeline(ctx.config)) {
1324
- throw new WpHsError("This project does not use the CDN pipeline.", {
1325
- exitCode: ExitCode.USER_ERROR,
1326
- remediation: "module-monolith / cdn.mode none: use wp build && wp upload (or wp deploy without CDN publish)."
1327
- });
1328
- }
1329
- const tier = options.tier ?? DEFAULT_BUILD_TIER;
1330
- const env = mergeProjectEnv(ctx.projectRoot, {
1331
- ...process.env,
1332
- WP_HS_PROJECT_ROOT: ctx.projectRoot
1333
- });
1334
- const execOpts = {
1335
- cwd: ctx.projectRoot,
1336
- env,
1337
- logger: ctx.logger,
1338
- dryRun: ctx.global.dryRun || options.dryRun
1339
- };
1340
- const verifyFlag = tier === "prod" ? ["--prod"] : ["--stg"];
1341
- const publishArgs = options.dryRun ? ["--dry-run"] : [];
1342
- try {
1343
- await runCliTasks(
1344
- ctx,
1345
- [
1346
- {
1347
- title: `Verifying CDN API endpoint (${tier})`,
1348
- task: async () => {
1349
- if (options.dryRun) return;
1350
- runNodeScript(kitPath("scripts", "verify-cdn-api-endpoint.mjs"), verifyFlag, execOpts);
1351
- }
1352
- },
1353
- {
1354
- title: options.dryRun ? `CDN publish dry-run (${tier})` : `Publishing CDN (${tier})`,
1355
- task: async () => {
1356
- runNodeScript(kitPath("scripts", "publish-cdn-github.mjs"), publishArgs, execOpts);
1357
- }
1358
- }
1359
- ],
1360
- options
1361
- );
1362
- if (ctx.global.json) {
1363
- ctx.logger.json({ command: "publish", tier, dryRun: Boolean(options.dryRun), ok: true });
1364
- } else {
1365
- printCommandDone(
1366
- ctx,
1367
- options.dryRun ? "CDN publish dry-run complete" : "CDN publish complete"
1368
- );
1369
- }
1370
- return ExitCode.SUCCESS;
1371
- } catch (error) {
1372
- throw new WpHsError(error instanceof Error ? error.message : String(error), {
1373
- exitCode: ExitCode.PUBLISH_FAILED,
1374
- remediation: "Check CDN PAT, portal.cdn.json, and verify-cdn-api-endpoint."
1375
- });
1376
- }
1377
- }
1378
-
1379
1579
  // src/cli/commands/upload.ts
1380
1580
  import { resolve as resolve12 } from "path";
1381
1581
  async function uploadModuleOrTheme(ctx) {
@@ -1385,19 +1585,23 @@ async function uploadModuleOrTheme(ctx) {
1385
1585
  throw new WpHsError("theme block missing in woodscli.json", { exitCode: ExitCode.USER_ERROR });
1386
1586
  }
1387
1587
  const srcDir = resolve12(ctx.projectRoot, theme.srcDir);
1388
- runCommand("hs", ["cms", "upload", srcDir, theme.uploadPath], {
1588
+ await runCommandAsync("hs", ["cms", "upload", srcDir, theme.uploadPath], {
1389
1589
  cwd: ctx.projectRoot,
1590
+ projectRoot: ctx.projectRoot,
1390
1591
  logger: ctx.logger,
1391
- dryRun: ctx.global.dryRun
1592
+ dryRun: ctx.global.dryRun,
1593
+ streamOutput: ctx.global.verbose
1392
1594
  });
1393
1595
  return `Uploaded theme ${srcDir} \u2192 ${theme.uploadPath}`;
1394
1596
  }
1395
1597
  const outDir = resolve12(ctx.projectRoot, ctx.config.module.outDir);
1396
1598
  const uploadPath = ctx.config.module.uploadPath;
1397
- runCommand("hs", ["cms", "upload", outDir, uploadPath], {
1599
+ await runCommandAsync("hs", ["cms", "upload", outDir, uploadPath], {
1398
1600
  cwd: ctx.projectRoot,
1601
+ projectRoot: ctx.projectRoot,
1399
1602
  logger: ctx.logger,
1400
- dryRun: ctx.global.dryRun
1603
+ dryRun: ctx.global.dryRun,
1604
+ streamOutput: ctx.global.verbose
1401
1605
  });
1402
1606
  return `Uploaded ${outDir} \u2192 ${uploadPath}`;
1403
1607
  }
@@ -1434,73 +1638,110 @@ async function runUpload(ctx, options = {}) {
1434
1638
  }
1435
1639
  return ExitCode.SUCCESS;
1436
1640
  } catch (error) {
1437
- throw new WpHsError("HubSpot upload failed", {
1438
- exitCode: ExitCode.PUBLISH_FAILED,
1439
- cause: error,
1440
- remediation: "Run wp auth, then build the module before upload."
1441
- });
1641
+ const detail = error instanceof WpHsError ? error.message : error instanceof Error ? error.message : String(error);
1642
+ throw new WpHsError(
1643
+ detail.includes("HubSpot upload failed") ? detail : `HubSpot upload failed
1644
+ ${detail}`,
1645
+ {
1646
+ exitCode: ExitCode.PUBLISH_FAILED,
1647
+ cause: error,
1648
+ remediation: "Run wp auth, then build the module before upload."
1649
+ }
1650
+ );
1442
1651
  }
1443
1652
  }
1444
1653
 
1445
1654
  // src/cli/commands/deploy.ts
1446
1655
  async function runDeploy(ctx, options) {
1447
- ensureHubSpotCliSetup(ctx, "cms");
1448
1656
  const tier = options.tier ?? DEFAULT_BUILD_TIER;
1449
- const env = { ...process.env, WP_HS_PROJECT_ROOT: ctx.projectRoot };
1657
+ const intro = `${formatProjectLabel(ctx.config)} \xB7 deploy (${tier})`;
1658
+ const quietSubprocess = shouldQuietSubprocess(ctx, options);
1659
+ const nested = {
1660
+ progress: false,
1661
+ quiet: true,
1662
+ quietSubprocess: true
1663
+ };
1664
+ const baseEnv = mergeProjectEnv(ctx.projectRoot, {
1665
+ ...process.env,
1666
+ WP_HS_PROJECT_ROOT: ctx.projectRoot,
1667
+ ...quietSubprocess ? { WP_HS_QUIET_BUILD: "1" } : {}
1668
+ });
1450
1669
  const execOpts = {
1451
1670
  cwd: ctx.projectRoot,
1452
- env,
1671
+ projectRoot: ctx.projectRoot,
1672
+ env: baseEnv,
1453
1673
  logger: ctx.logger,
1454
- dryRun: ctx.global.dryRun
1674
+ dryRun: ctx.global.dryRun,
1675
+ streamOutput: ctx.global.verbose
1455
1676
  };
1456
1677
  const useCdn = usesCdnPipeline(ctx.config) && !isThemeProject(ctx.config);
1457
- const nested = { progress: false };
1458
- const tasks2 = [];
1678
+ const verifyFlag = tier === "prod" ? ["--prod"] : ["--stg"];
1679
+ const tasks = [
1680
+ {
1681
+ title: "Checking HubSpot CLI",
1682
+ task: async () => {
1683
+ ensureHubSpotCliSetup(ctx, "cms");
1684
+ }
1685
+ }
1686
+ ];
1459
1687
  if (useCdn) {
1460
- tasks2.push({
1688
+ tasks.push({
1461
1689
  title: `Building CDN (${tier})`,
1462
1690
  task: async () => {
1463
1691
  await runBuild(ctx, { tier, cdnOnly: true, ...nested });
1464
1692
  }
1465
1693
  });
1466
1694
  if (!options.skipPublish) {
1467
- tasks2.push({
1468
- title: `Publishing CDN (${tier})`,
1695
+ tasks.push({
1696
+ title: `Verifying CDN API (${tier})`,
1697
+ task: async () => {
1698
+ await runNodeScriptAsync(kitPath("scripts", "verify-cdn-api-endpoint.mjs"), verifyFlag, execOpts);
1699
+ }
1700
+ });
1701
+ tasks.push({
1702
+ title: `Publishing CDN to GitHub (${tier})`,
1469
1703
  task: async () => {
1470
- await runPublish(ctx, { tier, ...nested });
1704
+ await runNodeScriptAsync(kitPath("scripts", "publish-cdn-github.mjs"), [], execOpts);
1471
1705
  }
1472
1706
  });
1473
1707
  }
1474
- tasks2.push({
1708
+ tasks.push({
1475
1709
  title: `Building HubSpot module (${tier})`,
1476
1710
  task: async () => {
1477
1711
  await runBuild(ctx, { tier, hubspotOnly: true, ...nested });
1478
1712
  }
1479
1713
  });
1480
1714
  } else {
1481
- tasks2.push({
1715
+ tasks.push({
1482
1716
  title: `Building module (${tier})`,
1483
1717
  task: async () => {
1484
1718
  await runBuild(ctx, { tier, ...nested });
1485
1719
  }
1486
1720
  });
1487
1721
  }
1488
- tasks2.push(
1722
+ tasks.push(
1489
1723
  {
1490
1724
  title: "Running bundle checks",
1491
1725
  task: async () => {
1492
- runNodeScript(kitPath("scripts", "check-hubspot-bundle-size.mjs"), [], execOpts);
1493
- runNodeScript(kitPath("scripts", "write-audit-artifacts.mjs"), [], execOpts);
1726
+ await runNodeScriptAsync(kitPath("scripts", "check-hubspot-bundle-size.mjs"), [], execOpts);
1727
+ await runNodeScriptAsync(kitPath("scripts", "write-audit-artifacts.mjs"), [], execOpts);
1494
1728
  }
1495
1729
  },
1496
1730
  {
1497
1731
  title: "Uploading to HubSpot",
1498
1732
  task: async () => {
1499
- await runUpload(ctx, { tier, ...nested, quiet: true, skipPrereqCheck: true });
1733
+ await runUpload(ctx, {
1734
+ tier,
1735
+ ...nested,
1736
+ skipPrereqCheck: true
1737
+ });
1500
1738
  }
1501
1739
  }
1502
1740
  );
1503
- await runCliTasks(ctx, tasks2, options);
1741
+ await runCliTasks(ctx, tasks, {
1742
+ ...options,
1743
+ intro
1744
+ });
1504
1745
  if (!ctx.global.json) {
1505
1746
  printCommandDone(
1506
1747
  ctx,
@@ -1510,6 +1751,71 @@ async function runDeploy(ctx, options) {
1510
1751
  return ExitCode.SUCCESS;
1511
1752
  }
1512
1753
 
1754
+ // src/cli/commands/publish.ts
1755
+ async function runPublish(ctx, options) {
1756
+ if (!usesCdnPipeline(ctx.config)) {
1757
+ throw new WpHsError("This project does not use the CDN pipeline.", {
1758
+ exitCode: ExitCode.USER_ERROR,
1759
+ remediation: "module-monolith / cdn.mode none: use wp build && wp upload (or wp deploy without CDN publish)."
1760
+ });
1761
+ }
1762
+ const tier = options.tier ?? DEFAULT_BUILD_TIER;
1763
+ const quietSubprocess = shouldQuietSubprocess(ctx, options);
1764
+ const env = mergeProjectEnv(ctx.projectRoot, {
1765
+ ...process.env,
1766
+ WP_HS_PROJECT_ROOT: ctx.projectRoot,
1767
+ ...quietSubprocess ? { WP_HS_QUIET_BUILD: "1" } : {}
1768
+ });
1769
+ const execOpts = {
1770
+ cwd: ctx.projectRoot,
1771
+ projectRoot: ctx.projectRoot,
1772
+ env,
1773
+ logger: ctx.logger,
1774
+ dryRun: ctx.global.dryRun || options.dryRun,
1775
+ streamOutput: ctx.global.verbose
1776
+ };
1777
+ const verifyFlag = tier === "prod" ? ["--prod"] : ["--stg"];
1778
+ const publishArgs = options.dryRun ? ["--dry-run"] : [];
1779
+ try {
1780
+ await runCliTasks(
1781
+ ctx,
1782
+ [
1783
+ {
1784
+ title: `Verifying CDN API endpoint (${tier})`,
1785
+ task: async () => {
1786
+ if (options.dryRun) return;
1787
+ await runNodeScriptAsync(kitPath("scripts", "verify-cdn-api-endpoint.mjs"), verifyFlag, execOpts);
1788
+ }
1789
+ },
1790
+ {
1791
+ title: options.dryRun ? `CDN publish dry-run (${tier})` : `Publishing CDN to GitHub (${tier})`,
1792
+ task: async () => {
1793
+ await runNodeScriptAsync(kitPath("scripts", "publish-cdn-github.mjs"), publishArgs, execOpts);
1794
+ }
1795
+ }
1796
+ ],
1797
+ {
1798
+ ...options,
1799
+ intro: options.quiet ? void 0 : `${formatProjectLabel(ctx.config)} \xB7 CDN publish (${tier})`
1800
+ }
1801
+ );
1802
+ if (ctx.global.json) {
1803
+ ctx.logger.json({ command: "publish", tier, dryRun: Boolean(options.dryRun), ok: true });
1804
+ } else if (!options.quiet) {
1805
+ printCommandDone(
1806
+ ctx,
1807
+ options.dryRun ? "CDN publish dry-run complete" : "CDN publish complete"
1808
+ );
1809
+ }
1810
+ return ExitCode.SUCCESS;
1811
+ } catch (error) {
1812
+ throw new WpHsError(error instanceof Error ? error.message : String(error), {
1813
+ exitCode: ExitCode.PUBLISH_FAILED,
1814
+ remediation: "Check CDN PAT, portal.cdn.json, and verify-cdn-api-endpoint."
1815
+ });
1816
+ }
1817
+ }
1818
+
1513
1819
  // src/cli/commands/audit.ts
1514
1820
  import { createHash } from "crypto";
1515
1821
  import { cpSync, existsSync as existsSync9, mkdirSync, readFileSync as readFileSync7, readdirSync, rmSync, statSync } from "fs";
@@ -2519,7 +2825,7 @@ async function runExamplesSmoke(ctx, options = {}) {
2519
2825
  const tier = options.tier ?? "static";
2520
2826
  const args = ["--tier", tier];
2521
2827
  if (options.scenario) args.push("--scenario", options.scenario);
2522
- const { runNodeScript: runNodeScript2 } = await import("./exec-U2OJATR3.js");
2828
+ const { runNodeScript: runNodeScript2 } = await import("./exec-VY6BGWF3.js");
2523
2829
  runNodeScript2(kitPath("examples", "smoke", "run-matrix.mjs"), args, {
2524
2830
  cwd: ctx.projectRoot,
2525
2831
  logger: ctx.logger,
@@ -2910,7 +3216,7 @@ function buildContext(global) {
2910
3216
  return { projectRoot, config, logger, global };
2911
3217
  }
2912
3218
  function attachGlobals(command) {
2913
- command.option("--project <path>", "Project root (woodscli.json)").option("--ci", "Non-interactive CI mode", false).option("--json", "Machine-readable JSON output", false).option("--verbose", "Verbose logging", false).option("--quiet", "Errors only", false).option("--dry-run", "Print planned actions without executing", false).option("-y, --yes", "Assume yes (non-interactive)", false);
3219
+ command.option("--project <path>", "Project root (woodscli.json)").option("--ci", "Non-interactive CI mode", false).option("--json", "Machine-readable JSON output", false).option("--verbose", "Step-by-step logs and child-process output (disables spinners)", false).option("--quiet", "Errors only", false).option("--dry-run", "Print planned actions without executing", false).option("-y, --yes", "Assume yes (non-interactive)", false);
2914
3220
  }
2915
3221
  async function handle(fn) {
2916
3222
  try {
@@ -3090,7 +3396,7 @@ function createProgram() {
3090
3396
  program2.command("auth").description("Authenticate HubSpot CLI (hs auth)").action(async () => {
3091
3397
  const global = program2.opts();
3092
3398
  const ctx = buildContext(global);
3093
- const { runCommand: runCommand2 } = await import("./exec-U2OJATR3.js");
3399
+ const { runCommand: runCommand2 } = await import("./exec-VY6BGWF3.js");
3094
3400
  runCommand2("hs", ["auth"], {
3095
3401
  cwd: ctx.projectRoot,
3096
3402
  logger: ctx.logger,