@woodsportal/hubspot-kit 1.0.2

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 (135) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/LICENSE +21 -0
  3. package/README.md +62 -0
  4. package/SECURITY.md +27 -0
  5. package/dist/chunk-JDGIAYHX.js +87 -0
  6. package/dist/chunk-JDGIAYHX.js.map +1 -0
  7. package/dist/chunk-U4KFGTEW.js +136 -0
  8. package/dist/chunk-U4KFGTEW.js.map +1 -0
  9. package/dist/cli.js +1849 -0
  10. package/dist/cli.js.map +1 -0
  11. package/dist/exec-K5C6LZP4.js +10 -0
  12. package/dist/exec-K5C6LZP4.js.map +1 -0
  13. package/dist/index.d.ts +228 -0
  14. package/dist/index.js +204 -0
  15. package/dist/index.js.map +1 -0
  16. package/dist/vite/index.d.ts +17 -0
  17. package/dist/vite/index.js +9 -0
  18. package/dist/vite/index.js.map +1 -0
  19. package/examples/README.md +12 -0
  20. package/examples/fixtures/adopted-module-hybrid/public/fields.json +1 -0
  21. package/examples/fixtures/adopted-module-hybrid/public/module.html +1 -0
  22. package/examples/fixtures/adopted-module-hybrid/woodscli.json +7 -0
  23. package/examples/fixtures/adopted-module-monolith/public/fields.json +1 -0
  24. package/examples/fixtures/adopted-module-monolith/woodscli.json +7 -0
  25. package/examples/fixtures/adopted-theme-minimal/src/theme/templates/page.html +1 -0
  26. package/examples/fixtures/adopted-theme-minimal/src/theme/theme.json +1 -0
  27. package/examples/fixtures/adopted-theme-minimal/woodscli.json +7 -0
  28. package/examples/fixtures/existing-module-hybrid/public/fields.json +1 -0
  29. package/examples/fixtures/existing-module-hybrid/public/module.html +1 -0
  30. package/examples/fixtures/existing-theme-minimal/templates/page.html +1 -0
  31. package/examples/fixtures/existing-theme-minimal/theme.json +1 -0
  32. package/examples/greenfield/module-hybrid-cdn/README.md +9 -0
  33. package/examples/greenfield/module-hybrid-cdn/index.html +1 -0
  34. package/examples/greenfield/module-hybrid-cdn/package.json +14 -0
  35. package/examples/greenfield/module-hybrid-cdn/public/fields.json +1 -0
  36. package/examples/greenfield/module-hybrid-cdn/public/module.html +1 -0
  37. package/examples/greenfield/module-hybrid-cdn/src/main.tsx +2 -0
  38. package/examples/greenfield/module-hybrid-cdn/vite.config.dev.mjs +1 -0
  39. package/examples/greenfield/module-hybrid-cdn/vite.config.hubspot.mjs +1 -0
  40. package/examples/greenfield/module-hybrid-cdn/woodscli.json +8 -0
  41. package/examples/greenfield/module-hybrid-cdn/wphs.adapter.ts +7 -0
  42. package/examples/greenfield/module-monolith/package.json +12 -0
  43. package/examples/greenfield/module-monolith/public/fields.json +1 -0
  44. package/examples/greenfield/module-monolith/vite.config.dev.mjs +1 -0
  45. package/examples/greenfield/module-monolith/vite.config.monolith.mjs +1 -0
  46. package/examples/greenfield/module-monolith/woodscli.json +25 -0
  47. package/examples/greenfield/module-monolith/wphs.adapter.ts +7 -0
  48. package/examples/greenfield/theme-starter/package.json +11 -0
  49. package/examples/greenfield/theme-starter/src/theme/css/main.css +1 -0
  50. package/examples/greenfield/theme-starter/src/theme/templates/base.html +1 -0
  51. package/examples/greenfield/theme-starter/src/theme/templates/page.html +1 -0
  52. package/examples/greenfield/theme-starter/src/theme/theme.json +1 -0
  53. package/examples/greenfield/theme-starter/theme.json +6 -0
  54. package/examples/greenfield/theme-starter/vite.config.dev.mjs +1 -0
  55. package/examples/greenfield/theme-starter/woodscli.json +7 -0
  56. package/examples/manifest.json +20 -0
  57. package/examples/smoke/run-matrix.mjs +100 -0
  58. package/package.json +94 -0
  59. package/scripts/audit-module-config-fields.mjs +73 -0
  60. package/scripts/audit-parity.mjs +22 -0
  61. package/scripts/audit-tdz-split.mjs +111 -0
  62. package/scripts/build-cdn.mjs +36 -0
  63. package/scripts/check-hubspot-bundle-size.mjs +58 -0
  64. package/scripts/config-files.mjs +53 -0
  65. package/scripts/detect-layout.mjs +70 -0
  66. package/scripts/ensure-wphs-workspace.mjs +53 -0
  67. package/scripts/generate-cdn-manifest.mjs +92 -0
  68. package/scripts/generate-module-config-example.mjs +57 -0
  69. package/scripts/hubspot-module-postbuild.mjs +151 -0
  70. package/scripts/load-portal-cdn-config.mjs +91 -0
  71. package/scripts/migrate-sdk-3-api.mjs +144 -0
  72. package/scripts/module-config-dir.mjs +12 -0
  73. package/scripts/portal-env-mode.mjs +24 -0
  74. package/scripts/portal-vite-env.mjs +39 -0
  75. package/scripts/project-root.mjs +30 -0
  76. package/scripts/publish-cdn-github.mjs +273 -0
  77. package/scripts/read-woodsportal-config.mjs +40 -0
  78. package/scripts/resolve-cdn-base-url.mjs +32 -0
  79. package/scripts/resolve-consumer-dep.mjs +25 -0
  80. package/scripts/resolve-portal-build-id.mjs +67 -0
  81. package/scripts/run-dev.mjs +93 -0
  82. package/scripts/run-hubspot-module-postbuild.mjs +19 -0
  83. package/scripts/run-vite-build.mjs +33 -0
  84. package/scripts/tailwind/prefix-plugin.js +291 -0
  85. package/scripts/tailwind/prefix.env.js +11 -0
  86. package/scripts/tailwind/tailwind-content-plugin.js +27 -0
  87. package/scripts/tailwind/tailwind-content.js +267 -0
  88. package/scripts/verify-cdn-api-endpoint.mjs +68 -0
  89. package/scripts/vite/ensure-dev-kit-shims.js +49 -0
  90. package/scripts/vite/ensure-dev-module-config-shim.js +1 -0
  91. package/scripts/vite/module-config-store.js +242 -0
  92. package/scripts/vite/plugins.d.ts +9 -0
  93. package/scripts/vite/portal-build-shared.js +110 -0
  94. package/scripts/vite/react-dev-plugin-options.js +21 -0
  95. package/scripts/vite/resolve-from-project-root.js +31 -0
  96. package/scripts/vite/sdk-log-bridge.js +104 -0
  97. package/scripts/watch-hubspot.mjs +86 -0
  98. package/scripts/wphs-paths.mjs +115 -0
  99. package/scripts/write-audit-artifacts.mjs +56 -0
  100. package/templates/hubspot.config.yml.example +10 -0
  101. package/templates/module-hybrid-cdn/README.md +9 -0
  102. package/templates/module-hybrid-cdn/hubspot.config.yml.example +10 -0
  103. package/templates/module-hybrid-cdn/index.html +1 -0
  104. package/templates/module-hybrid-cdn/package.json +14 -0
  105. package/templates/module-hybrid-cdn/public/fields.json +1 -0
  106. package/templates/module-hybrid-cdn/public/module.html +1 -0
  107. package/templates/module-hybrid-cdn/src/main.tsx +2 -0
  108. package/templates/module-hybrid-cdn/vite.config.dev.mjs +1 -0
  109. package/templates/module-hybrid-cdn/vite.config.hubspot.mjs +1 -0
  110. package/templates/module-hybrid-cdn/woodscli.json +8 -0
  111. package/templates/module-hybrid-cdn/wphs.adapter.ts +7 -0
  112. package/templates/module-monolith/hubspot.config.yml.example +10 -0
  113. package/templates/module-monolith/package.json +12 -0
  114. package/templates/module-monolith/public/fields.json +1 -0
  115. package/templates/module-monolith/vite.config.dev.mjs +1 -0
  116. package/templates/module-monolith/vite.config.monolith.mjs +1 -0
  117. package/templates/module-monolith/woodscli.json +25 -0
  118. package/templates/module-monolith/wphs.adapter.ts +7 -0
  119. package/templates/theme-starter/hubspot.config.yml.example +10 -0
  120. package/templates/theme-starter/package.json +11 -0
  121. package/templates/theme-starter/src/theme/css/main.css +1 -0
  122. package/templates/theme-starter/src/theme/templates/base.html +1 -0
  123. package/templates/theme-starter/src/theme/templates/page.html +1 -0
  124. package/templates/theme-starter/src/theme/theme.json +1 -0
  125. package/templates/theme-starter/theme.json +6 -0
  126. package/templates/theme-starter/vite.config.dev.mjs +1 -0
  127. package/templates/theme-starter/woodscli.json +7 -0
  128. package/vite/vite.config.audit.mjs +33 -0
  129. package/vite/vite.config.cdn-app.mjs +44 -0
  130. package/vite/vite.config.cdn-esm.mjs +35 -0
  131. package/vite/vite.config.cdn-vendor.mjs +33 -0
  132. package/vite/vite.config.dev.mjs +62 -0
  133. package/vite/vite.config.hubspot.mjs +33 -0
  134. package/vite/vite.config.monolith.mjs +55 -0
  135. package/vite/vite.config.tdz-audit.mjs +58 -0
package/dist/cli.js ADDED
@@ -0,0 +1,1849 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ ExitCode,
4
+ WpHsError,
5
+ isWpHsError,
6
+ runCommand,
7
+ runNodeScript
8
+ } from "./chunk-JDGIAYHX.js";
9
+
10
+ // src/cli/program.ts
11
+ import { readFileSync as readFileSync10 } from "fs";
12
+ import { resolve as resolve17 } from "path";
13
+ import { Command } from "commander";
14
+
15
+ // src/lib/config.ts
16
+ import { readFileSync as readFileSync2 } from "fs";
17
+
18
+ // src/lib/config-files.ts
19
+ import { existsSync } from "fs";
20
+ import { resolve } from "path";
21
+ var WOODSCLI_CONFIG_FILE = "woodscli.json";
22
+ var LEGACY_WOODSPORTAL_HS_FILE = "woodsportal.hs.json";
23
+ function envConfigPath(projectRoot) {
24
+ const fromEnv = process.env.WOODSCLI_CONFIG?.trim() || process.env.WP_HS_CONFIG?.trim() || null;
25
+ if (!fromEnv) return null;
26
+ return resolve(projectRoot, fromEnv);
27
+ }
28
+ function legacyConfigPath(projectRoot) {
29
+ return resolve(projectRoot, LEGACY_WOODSPORTAL_HS_FILE);
30
+ }
31
+ function woodscliConfigPath(projectRoot) {
32
+ return resolve(projectRoot, WOODSCLI_CONFIG_FILE);
33
+ }
34
+ function legacyConfigConflict(projectRoot) {
35
+ const hasWoodscli = existsSync(woodscliConfigPath(projectRoot));
36
+ const hasLegacy = existsSync(legacyConfigPath(projectRoot));
37
+ if (hasLegacy && hasWoodscli) return "duplicate";
38
+ if (hasLegacy && !hasWoodscli) return "legacy-only";
39
+ return null;
40
+ }
41
+ function resolveProjectConfig(projectRoot) {
42
+ const envPath = envConfigPath(projectRoot);
43
+ if (envPath) {
44
+ if (!existsSync(envPath)) return null;
45
+ return { path: envPath, fileName: WOODSCLI_CONFIG_FILE };
46
+ }
47
+ const path = woodscliConfigPath(projectRoot);
48
+ if (existsSync(path)) {
49
+ return { path, fileName: WOODSCLI_CONFIG_FILE };
50
+ }
51
+ return null;
52
+ }
53
+ function findProjectRoot(from = process.cwd()) {
54
+ if (process.env.WP_HS_PROJECT_ROOT?.trim()) {
55
+ return resolve(process.env.WP_HS_PROJECT_ROOT.trim());
56
+ }
57
+ let dir = resolve(from);
58
+ while (true) {
59
+ if (resolveProjectConfig(dir)) {
60
+ return dir;
61
+ }
62
+ const parent = resolve(dir, "..");
63
+ if (parent === dir) {
64
+ return null;
65
+ }
66
+ dir = parent;
67
+ }
68
+ }
69
+
70
+ // src/schema/woodsportal-hs.ts
71
+ import { z } from "zod";
72
+ var CONFIG_VERSION = 1;
73
+ var WPHS_WORKSPACE_DIR = ".wphs";
74
+ var WoodsPortalHsSchema = z.object({
75
+ configVersion: z.number().int().default(CONFIG_VERSION),
76
+ projectType: z.enum(["module-hybrid-cdn", "module-monolith", "theme-starter"]),
77
+ module: z.object({
78
+ name: z.string().min(1),
79
+ uploadPath: z.string().min(1),
80
+ outDir: z.string().default("dist/WoodsPortal.Module")
81
+ }),
82
+ theme: z.object({
83
+ name: z.string().min(1),
84
+ uploadPath: z.string().min(1),
85
+ srcDir: z.string().default("src/theme"),
86
+ themeJson: z.string().default("src/theme/theme.json")
87
+ }).optional(),
88
+ cdn: z.object({
89
+ mode: z.enum(["jsdelivr", "custom", "local", "none"]).default("jsdelivr"),
90
+ buildIdFile: z.string().default(".portal-build-id"),
91
+ mirror: z.string().default("portal.cdn.json")
92
+ }).default({}),
93
+ entries: z.object({
94
+ app: z.string().default("src/cdn/app-entry.ts"),
95
+ vendor: z.string().default("src/cdn/vendor-entry.ts"),
96
+ hubspotBootstrap: z.string().default("src/hubspot/bootstrap.ts"),
97
+ localDev: z.string().default("src/main.tsx")
98
+ }).default({}),
99
+ env: z.object({
100
+ tiers: z.array(z.enum(["dev", "stg", "prod"])).default(["dev", "stg", "prod"])
101
+ }).default({}),
102
+ dev: z.object({
103
+ moduleConfigDir: z.literal(WPHS_WORKSPACE_DIR).default(WPHS_WORKSPACE_DIR),
104
+ adapter: z.string().default("./wphs.adapter.ts"),
105
+ fieldsJson: z.string().default("public/fields.json"),
106
+ themeFieldsJson: z.string().optional()
107
+ }).default({})
108
+ });
109
+ var DEFAULT_WOODSPORTAL_HS = {
110
+ configVersion: CONFIG_VERSION,
111
+ projectType: "module-hybrid-cdn",
112
+ module: {
113
+ name: "woodsportal-module",
114
+ uploadPath: "dw-modules/woodsportal-module.module",
115
+ outDir: "dist/WoodsPortal.Module"
116
+ },
117
+ cdn: {
118
+ mode: "jsdelivr",
119
+ buildIdFile: ".portal-build-id",
120
+ mirror: "portal.cdn.json"
121
+ },
122
+ entries: {
123
+ app: "src/cdn/app-entry.ts",
124
+ vendor: "src/cdn/vendor-entry.ts",
125
+ hubspotBootstrap: "src/hubspot/bootstrap.ts",
126
+ localDev: "src/main.tsx"
127
+ },
128
+ env: {
129
+ tiers: ["dev", "stg", "prod"]
130
+ },
131
+ dev: {
132
+ moduleConfigDir: WPHS_WORKSPACE_DIR,
133
+ adapter: "./wphs.adapter.ts",
134
+ fieldsJson: "public/fields.json"
135
+ }
136
+ };
137
+ function isModuleProject(config) {
138
+ return config.projectType === "module-hybrid-cdn" || config.projectType === "module-monolith";
139
+ }
140
+ function isThemeProject(config) {
141
+ return config.projectType === "theme-starter";
142
+ }
143
+ function usesCdnPipeline(config) {
144
+ return config.projectType === "module-hybrid-cdn" && config.cdn.mode !== "none";
145
+ }
146
+
147
+ // src/lib/paths.ts
148
+ import { existsSync as existsSync2, readFileSync } from "fs";
149
+ import { dirname, resolve as resolve2 } from "path";
150
+ import { fileURLToPath } from "url";
151
+ var KIT_PACKAGE_NAME = "@woodsportal/hubspot-kit";
152
+ function resolveKitRoot() {
153
+ let dir = dirname(fileURLToPath(import.meta.url));
154
+ while (true) {
155
+ const pkgPath = resolve2(dir, "package.json");
156
+ if (existsSync2(pkgPath)) {
157
+ try {
158
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
159
+ if (pkg.name === KIT_PACKAGE_NAME) {
160
+ return dir;
161
+ }
162
+ } catch {
163
+ }
164
+ }
165
+ const parent = dirname(dir);
166
+ if (parent === dir) {
167
+ break;
168
+ }
169
+ dir = parent;
170
+ }
171
+ throw new WpHsError("Could not resolve @woodsportal/hubspot-kit package root.", {
172
+ exitCode: ExitCode.INTERNAL
173
+ });
174
+ }
175
+ var kitRoot = resolveKitRoot();
176
+ function getKitRoot() {
177
+ return kitRoot;
178
+ }
179
+ function findProjectRoot2(from = process.cwd()) {
180
+ const root = findProjectRoot(from);
181
+ if (!root) {
182
+ throw new WpHsError("Could not find woodscli.json in this directory or any parent.", {
183
+ exitCode: ExitCode.USER_ERROR,
184
+ remediation: "Run wp init, wp adopt, or pass --project <path>. Legacy config: wp migrate --yes."
185
+ });
186
+ }
187
+ return root;
188
+ }
189
+ function kitPath(...segments) {
190
+ return resolve2(kitRoot, ...segments);
191
+ }
192
+
193
+ // src/lib/config.ts
194
+ function loadConfig(projectRoot) {
195
+ const root = projectRoot ?? findProjectRoot2();
196
+ const resolved = resolveProjectConfig(root);
197
+ if (!resolved) {
198
+ throw new WpHsError(`Missing ${WOODSCLI_CONFIG_FILE}`, {
199
+ exitCode: ExitCode.USER_ERROR,
200
+ remediation: "Run wp init or wp adopt. Legacy woodsportal.hs.json \u2192 run wp migrate --yes."
201
+ });
202
+ }
203
+ let raw;
204
+ try {
205
+ raw = JSON.parse(readFileSync2(resolved.path, "utf8"));
206
+ } catch (cause) {
207
+ throw new WpHsError(`Invalid JSON in ${resolved.fileName}`, {
208
+ exitCode: ExitCode.USER_ERROR,
209
+ cause
210
+ });
211
+ }
212
+ const parsed = WoodsPortalHsSchema.safeParse(raw);
213
+ if (!parsed.success) {
214
+ throw new WpHsError(`${resolved.fileName} validation failed: ${parsed.error.message}`, {
215
+ exitCode: ExitCode.USER_ERROR,
216
+ remediation: "Run wp config validate for details."
217
+ });
218
+ }
219
+ return {
220
+ root,
221
+ config: parsed.data,
222
+ configPath: resolved.path,
223
+ configFileName: resolved.fileName
224
+ };
225
+ }
226
+ function validateConfigFile(projectRoot) {
227
+ try {
228
+ const { config } = loadConfig(projectRoot);
229
+ return { valid: true, errors: [], config };
230
+ } catch (error) {
231
+ if (error instanceof WpHsError) {
232
+ return { valid: false, errors: [error.message] };
233
+ }
234
+ return { valid: false, errors: [String(error)] };
235
+ }
236
+ }
237
+
238
+ // src/lib/logger.ts
239
+ import pc from "picocolors";
240
+ function shouldUseColor() {
241
+ if (process.env.NO_COLOR !== void 0) return false;
242
+ if (process.env.CI === "true") return false;
243
+ return process.stdout.isTTY === true;
244
+ }
245
+ function createLogger(options = {}) {
246
+ const useColor = shouldUseColor();
247
+ const { verbose = false, quiet = false, json = false } = options;
248
+ const emit = (level, message, meta) => {
249
+ if (json) return;
250
+ if (quiet && level !== "error" && level !== "warn") return;
251
+ if (level === "debug" && !verbose) return;
252
+ 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";
253
+ const line = meta ? `${message} ${JSON.stringify(meta)}` : message;
254
+ const stream = level === "error" || level === "warn" ? process.stderr : process.stdout;
255
+ stream.write(`[${prefix}] ${line}
256
+ `);
257
+ };
258
+ return {
259
+ debug: (message, meta) => emit("debug", message, meta),
260
+ info: (message, meta) => emit("info", message, meta),
261
+ warn: (message, meta) => emit("warn", message, meta),
262
+ error: (message, meta) => emit("error", message, meta),
263
+ success: (message, meta) => {
264
+ if (json) return;
265
+ if (quiet) return;
266
+ const prefix = useColor ? pc.green("ok") : "ok";
267
+ process.stdout.write(`[${prefix}] ${message}
268
+ `);
269
+ if (meta) process.stdout.write(`${JSON.stringify(meta)}
270
+ `);
271
+ },
272
+ json: (payload) => {
273
+ if (!json) return;
274
+ process.stdout.write(`${JSON.stringify(payload)}
275
+ `);
276
+ }
277
+ };
278
+ }
279
+
280
+ // src/lib/wphs-baseline.ts
281
+ import { resolve as resolve3 } from "path";
282
+ function defaultBaselineDir(projectRoot, config) {
283
+ const workspace = config.dev.moduleConfigDir ?? WPHS_WORKSPACE_DIR;
284
+ return resolve3(projectRoot, workspace, "audit", "baseline");
285
+ }
286
+
287
+ // src/cli/commands/doctor.ts
288
+ import { existsSync as existsSync4 } from "fs";
289
+ import { join, resolve as resolve5 } from "path";
290
+
291
+ // src/lib/hubspot-cli-setup.ts
292
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
293
+ import { resolve as resolve4 } from "path";
294
+ import { spawnSync } from "child_process";
295
+ var HUBSPOT_CONFIG_FILE = "hubspot.config.yml";
296
+ var HUBSPOT_CONFIG_EXAMPLE = "hubspot.config.yml.example";
297
+ var PLACEHOLDER_PATTERNS = [
298
+ /YOUR_PAT_HERE/i,
299
+ /your-portal-id/i,
300
+ /personalAccessKey:\s*$/m,
301
+ /portalId:\s*12345678\b/
302
+ ];
303
+ function assessHubSpotCliSetup(projectRoot) {
304
+ const configPath = resolve4(projectRoot, HUBSPOT_CONFIG_FILE);
305
+ const examplePath = resolve4(projectRoot, HUBSPOT_CONFIG_EXAMPLE);
306
+ const configPresent = existsSync3(configPath);
307
+ const examplePresent = existsSync3(examplePath);
308
+ let looksLikePlaceholder = false;
309
+ if (configPresent) {
310
+ const raw = readFileSync3(configPath, "utf8");
311
+ looksLikePlaceholder = PLACEHOLDER_PATTERNS.some((pattern) => pattern.test(raw));
312
+ } else {
313
+ looksLikePlaceholder = true;
314
+ }
315
+ const hsVersion = spawnSync("hs", ["--version"], { encoding: "utf8" });
316
+ const hsCliInstalled = hsVersion.status === 0;
317
+ const hsCliVersion = hsCliInstalled ? (hsVersion.stdout || hsVersion.stderr || "").trim() : null;
318
+ let authenticated = false;
319
+ let accountsHint = null;
320
+ if (hsCliInstalled) {
321
+ const accounts = spawnSync("hs", ["accounts", "list"], {
322
+ cwd: projectRoot,
323
+ encoding: "utf8",
324
+ timeout: 15e3
325
+ });
326
+ const output = `${accounts.stdout || ""}
327
+ ${accounts.stderr || ""}`.trim();
328
+ accountsHint = output || null;
329
+ authenticated = accounts.status === 0 && /Account:|portalId|portal/i.test(output) && !/No accounts/i.test(output);
330
+ }
331
+ return {
332
+ configPath,
333
+ examplePath,
334
+ configPresent,
335
+ examplePresent,
336
+ looksLikePlaceholder,
337
+ hsCliInstalled,
338
+ hsCliVersion,
339
+ authenticated,
340
+ accountsHint
341
+ };
342
+ }
343
+ function hubSpotSetupRemediation(projectRoot) {
344
+ const assessment = assessHubSpotCliSetup(projectRoot);
345
+ const steps = [];
346
+ if (!assessment.hsCliInstalled) {
347
+ steps.push("Install HubSpot CLI: npm i -g @hubspot/cli");
348
+ }
349
+ if (!assessment.configPresent) {
350
+ if (assessment.examplePresent) {
351
+ steps.push(`Copy ${HUBSPOT_CONFIG_EXAMPLE} \u2192 ${HUBSPOT_CONFIG_FILE} and add your portal + PAT`);
352
+ } else {
353
+ steps.push(`Create ${HUBSPOT_CONFIG_FILE} (see docs/HUBSPOT-CLI-SETUP.md)`);
354
+ }
355
+ steps.push("Generate a PAT: https://app.hubspot.com/l/personal-access-key");
356
+ } else if (assessment.looksLikePlaceholder) {
357
+ steps.push(`Edit ${HUBSPOT_CONFIG_FILE} \u2014 replace placeholder portalId and personalAccessKey`);
358
+ }
359
+ if (assessment.configPresent && !assessment.looksLikePlaceholder && !assessment.authenticated) {
360
+ steps.push("Run wp auth (or hs auth) and confirm with: hs accounts list");
361
+ }
362
+ return steps;
363
+ }
364
+ function ensureHubSpotCliSetup(ctx, mode) {
365
+ 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
+ if (mode === "local") {
372
+ ctx.logger.warn(headline);
373
+ for (const step of steps) {
374
+ ctx.logger.info(` \u2192 ${step}`);
375
+ }
376
+ ctx.logger.info(" \u2192 Docs: @woodsportal/hubspot-kit/docs/HUBSPOT-CLI-SETUP.md");
377
+ return;
378
+ }
379
+ throw new WpHsError(headline, {
380
+ exitCode: ExitCode.PREREQUISITE,
381
+ remediation: steps.join("; ")
382
+ });
383
+ }
384
+ function doctorHubSpotChecks(projectRoot) {
385
+ const assessment = assessHubSpotCliSetup(projectRoot);
386
+ const steps = hubSpotSetupRemediation(projectRoot);
387
+ const remediation = steps.length > 0 ? steps.join(" \xB7 ") : void 0;
388
+ return [
389
+ {
390
+ name: "hubspot-cli",
391
+ ok: assessment.hsCliInstalled,
392
+ message: assessment.hsCliInstalled ? `HubSpot CLI ${assessment.hsCliVersion ?? ""}`.trim() : "HubSpot CLI not installed",
393
+ remediation: assessment.hsCliInstalled ? void 0 : "npm i -g @hubspot/cli",
394
+ severity: "error"
395
+ },
396
+ {
397
+ name: "hubspot-config",
398
+ ok: assessment.configPresent && !assessment.looksLikePlaceholder,
399
+ message: !assessment.configPresent ? `${HUBSPOT_CONFIG_FILE} missing (required for wp upload / wp watch)` : assessment.looksLikePlaceholder ? `${HUBSPOT_CONFIG_FILE} still has placeholder values` : `${HUBSPOT_CONFIG_FILE} present`,
400
+ remediation,
401
+ severity: "warn"
402
+ },
403
+ {
404
+ name: "hubspot-auth",
405
+ ok: assessment.authenticated,
406
+ message: assessment.authenticated ? "HubSpot CLI authenticated for this project" : "HubSpot CLI not authenticated (or no portal in hubspot.config.yml)",
407
+ remediation: assessment.authenticated ? void 0 : "wp auth \xB7 hs accounts list",
408
+ severity: "warn"
409
+ }
410
+ ];
411
+ }
412
+
413
+ // src/cli/commands/doctor.ts
414
+ function checkNode() {
415
+ const major = Number(process.versions.node.split(".")[0]);
416
+ const ok = major >= 20;
417
+ return {
418
+ name: "node",
419
+ ok,
420
+ message: ok ? `Node ${process.versions.node}` : `Node ${process.versions.node} (need >= 20)`,
421
+ remediation: ok ? void 0 : "Install Node 20 LTS or newer.",
422
+ severity: "error"
423
+ };
424
+ }
425
+ function checkLegacyConfig(projectRoot) {
426
+ const conflict = legacyConfigConflict(projectRoot);
427
+ if (conflict === "legacy-only") {
428
+ return {
429
+ name: "config-filename",
430
+ ok: false,
431
+ message: `Legacy ${LEGACY_WOODSPORTAL_HS_FILE} \u2014 ${WOODSCLI_CONFIG_FILE} is required`,
432
+ remediation: "Run wp migrate --yes",
433
+ severity: "error"
434
+ };
435
+ }
436
+ if (conflict === "duplicate") {
437
+ return {
438
+ name: "config-filename",
439
+ ok: false,
440
+ message: `Both ${WOODSCLI_CONFIG_FILE} and ${LEGACY_WOODSPORTAL_HS_FILE} present`,
441
+ remediation: "Run wp migrate --yes to remove the legacy file",
442
+ severity: "error"
443
+ };
444
+ }
445
+ return null;
446
+ }
447
+ function checkConfig(ctx) {
448
+ const legacy = checkLegacyConfig(ctx.projectRoot);
449
+ if (legacy) {
450
+ return [legacy];
451
+ }
452
+ try {
453
+ const loaded = loadConfig(ctx.projectRoot);
454
+ return [
455
+ {
456
+ name: "config",
457
+ ok: true,
458
+ message: `${loaded.configFileName} valid`,
459
+ severity: "error"
460
+ }
461
+ ];
462
+ } catch (error) {
463
+ const message = error instanceof Error ? error.message : String(error);
464
+ return [
465
+ {
466
+ name: "config",
467
+ ok: false,
468
+ message,
469
+ remediation: "Run wp config validate",
470
+ severity: "error"
471
+ }
472
+ ];
473
+ }
474
+ }
475
+ function checkEnvFiles(ctx) {
476
+ return ctx.config.env.tiers.map((tier) => {
477
+ const file = tier === "dev" ? ".env.dev" : tier === "stg" ? ".env.stg" : ".env.prod";
478
+ const path = resolve5(ctx.projectRoot, file);
479
+ const example = resolve5(ctx.projectRoot, `${file}.example`);
480
+ const ok = existsSync4(path);
481
+ return {
482
+ name: `env-${tier}`,
483
+ ok,
484
+ message: ok ? `${file} present` : `${file} missing`,
485
+ remediation: ok ? void 0 : existsSync4(example) ? `cp ${file}.example ${file}` : `Create ${file}`,
486
+ severity: "warn"
487
+ };
488
+ });
489
+ }
490
+ function checkBuildId(ctx) {
491
+ if (isThemeProject(ctx.config) || !usesCdnPipeline(ctx.config)) return null;
492
+ const file = resolve5(ctx.projectRoot, ctx.config.cdn.buildIdFile);
493
+ const ok = existsSync4(file);
494
+ return {
495
+ name: "build-id",
496
+ ok,
497
+ message: ok ? `${ctx.config.cdn.buildIdFile} present` : `${ctx.config.cdn.buildIdFile} missing`,
498
+ remediation: ok ? void 0 : `Create ${ctx.config.cdn.buildIdFile} with your release tag (e.g. v0.0.1-dev.1)`,
499
+ severity: "warn"
500
+ };
501
+ }
502
+ function checkDevViteConfig(ctx) {
503
+ const path = resolve5(ctx.projectRoot, "vite.config.dev.mjs");
504
+ const ok = existsSync4(path);
505
+ return {
506
+ name: "vite-dev-config",
507
+ ok,
508
+ message: ok ? "vite.config.dev.mjs present" : "vite.config.dev.mjs missing",
509
+ remediation: ok ? void 0 : "Add vite.config.dev.mjs that re-exports @woodsportal/hubspot-kit/vite/vite.config.dev.mjs",
510
+ severity: "error"
511
+ };
512
+ }
513
+ function checkKitScripts() {
514
+ const script = kitPath("scripts", "build-cdn.mjs");
515
+ const ok = existsSync4(script);
516
+ return {
517
+ name: "kit-scripts",
518
+ ok,
519
+ message: ok ? "@woodsportal/hubspot-kit scripts installed" : "Kit build scripts missing",
520
+ remediation: ok ? void 0 : "Reinstall @woodsportal/hubspot-kit",
521
+ severity: "error"
522
+ };
523
+ }
524
+ function checkWphsWorkspace(ctx) {
525
+ const workspace = ctx.config.dev.moduleConfigDir;
526
+ const checks = [];
527
+ for (const legacy of [".dev", ".woodsportal"]) {
528
+ const legacyPath = resolve5(ctx.projectRoot, legacy);
529
+ if (existsSync4(legacyPath)) {
530
+ checks.push({
531
+ name: `legacy-${legacy}`,
532
+ ok: false,
533
+ message: `Legacy workspace ${legacy}/ still present`,
534
+ remediation: "Run wp migrate --yes",
535
+ severity: "error"
536
+ });
537
+ }
538
+ }
539
+ if (existsSync4(resolve5(ctx.projectRoot, ".wp-hs-baseline"))) {
540
+ checks.push({
541
+ name: "legacy-baseline",
542
+ ok: false,
543
+ message: "Legacy .wp-hs-baseline/ at repo root",
544
+ remediation: "Run wp migrate --yes",
545
+ severity: "error"
546
+ });
547
+ }
548
+ if (workspace !== WPHS_WORKSPACE_DIR) {
549
+ checks.push({
550
+ name: "wphs-dir",
551
+ ok: false,
552
+ message: `dev.moduleConfigDir must be "${WPHS_WORKSPACE_DIR}" (got "${workspace}")`,
553
+ remediation: "Run wp migrate --yes",
554
+ severity: "error"
555
+ });
556
+ return checks;
557
+ }
558
+ const wphsRoot = resolve5(ctx.projectRoot, workspace);
559
+ const required = ["config", "assets/branding", "fixtures", "cache", "audit/baseline", "state"];
560
+ for (const sub of required) {
561
+ const ok = existsSync4(join(wphsRoot, sub));
562
+ checks.push({
563
+ name: `wphs-${sub.replace(/\//g, "-")}`,
564
+ ok,
565
+ message: ok ? `.wphs/${sub}/ present` : `.wphs/${sub}/ missing`,
566
+ remediation: ok ? void 0 : "Run wp doctor (auto-scaffolds) or wp init",
567
+ severity: "error"
568
+ });
569
+ }
570
+ return checks;
571
+ }
572
+ function checkAdapter(ctx) {
573
+ if (isThemeProject(ctx.config)) return null;
574
+ const adapterPath = resolve5(ctx.projectRoot, ctx.config.dev.adapter);
575
+ const ok = existsSync4(adapterPath);
576
+ return {
577
+ name: "wphs-adapter",
578
+ ok,
579
+ message: ok ? `${ctx.config.dev.adapter} present` : `${ctx.config.dev.adapter} missing`,
580
+ remediation: ok ? void 0 : "Run wp adopt or create wphs.adapter.ts",
581
+ severity: "warn"
582
+ };
583
+ }
584
+ async function runDoctor(ctx) {
585
+ if (!ctx.global.dryRun) {
586
+ try {
587
+ runNodeScript(kitPath("scripts/ensure-wphs-workspace.mjs"), [], {
588
+ cwd: ctx.projectRoot,
589
+ logger: ctx.logger,
590
+ dryRun: false
591
+ });
592
+ } catch {
593
+ }
594
+ }
595
+ const checks = [
596
+ checkNode(),
597
+ checkKitScripts(),
598
+ checkDevViteConfig(ctx),
599
+ ...checkConfig(ctx),
600
+ ...checkWphsWorkspace(ctx),
601
+ checkAdapter(ctx),
602
+ ...doctorHubSpotChecks(ctx.projectRoot),
603
+ checkBuildId(ctx),
604
+ ...checkEnvFiles(ctx)
605
+ ].filter((c) => c !== null);
606
+ const failed = checks.filter((c) => !c.ok && (c.severity ?? "error") === "error");
607
+ const warnings = checks.filter((c) => !c.ok && c.severity === "warn");
608
+ if (ctx.global.json) {
609
+ ctx.logger.json({
610
+ command: "doctor",
611
+ ok: failed.length === 0,
612
+ warnings: warnings.length,
613
+ checks
614
+ });
615
+ } else {
616
+ for (const check of checks) {
617
+ if (check.ok) {
618
+ ctx.logger.success(check.message);
619
+ } else if (check.severity === "warn") {
620
+ ctx.logger.warn(check.message);
621
+ if (check.remediation) ctx.logger.info(` \u2192 ${check.remediation}`);
622
+ } else {
623
+ ctx.logger.error(check.message);
624
+ if (check.remediation) ctx.logger.info(` \u2192 ${check.remediation}`);
625
+ }
626
+ }
627
+ }
628
+ if (failed.length > 0) {
629
+ throw new WpHsError(`${failed.length} doctor check(s) failed`, {
630
+ exitCode: ExitCode.PREREQUISITE
631
+ });
632
+ }
633
+ if (warnings.length > 0 && !ctx.global.quiet && !ctx.global.json) {
634
+ ctx.logger.warn(
635
+ `${warnings.length} optional check(s) \u2014 local dev may work; fix before wp upload / wp watch`
636
+ );
637
+ }
638
+ return ExitCode.SUCCESS;
639
+ }
640
+
641
+ // src/cli/commands/config-validate.ts
642
+ async function runConfigValidate(ctx) {
643
+ const result = validateConfigFile(ctx.projectRoot);
644
+ if (ctx.global.json) {
645
+ ctx.logger.json({
646
+ command: "config validate",
647
+ valid: result.valid,
648
+ errors: result.errors,
649
+ config: result.config
650
+ });
651
+ } else if (result.valid) {
652
+ ctx.logger.success(`${WOODSCLI_CONFIG_FILE} is valid`);
653
+ } else {
654
+ for (const err of result.errors) {
655
+ ctx.logger.error(err);
656
+ }
657
+ }
658
+ return result.valid ? ExitCode.SUCCESS : ExitCode.USER_ERROR;
659
+ }
660
+
661
+ // src/cli/commands/build.ts
662
+ import { resolve as resolve6 } from "path";
663
+ function tierToStgFlag(tier) {
664
+ return tier === "dev" || tier === "stg" ? ["--stg"] : [];
665
+ }
666
+ async function runBuild(ctx, options) {
667
+ const tier = options.tier ?? "prod";
668
+ const env = { ...process.env, WP_HS_PROJECT_ROOT: ctx.projectRoot };
669
+ const isTheme = isThemeProject(ctx.config);
670
+ const useCdn = usesCdnPipeline(ctx.config) && !isTheme;
671
+ const execOpts = {
672
+ cwd: ctx.projectRoot,
673
+ env,
674
+ logger: ctx.logger,
675
+ dryRun: ctx.global.dryRun
676
+ };
677
+ const run = (script, args = []) => {
678
+ runNodeScript(kitPath("scripts", script), args, execOpts);
679
+ };
680
+ try {
681
+ if (isTheme && !options.cdnOnly) {
682
+ ctx.logger.info("Theme project \u2014 skipping CDN/module.js build (use wp upload for theme assets)");
683
+ if (ctx.global.json) {
684
+ ctx.logger.json({ command: "build", tier, ok: true, projectType: ctx.config.projectType });
685
+ } else {
686
+ ctx.logger.success("Theme build complete (no-op)");
687
+ }
688
+ return ExitCode.SUCCESS;
689
+ }
690
+ if (ctx.config.projectType === "module-monolith" && !options.cdnOnly) {
691
+ ctx.logger.info(`Building monolith module (${tier})\u2026`);
692
+ run("run-vite-build.mjs", [
693
+ resolve6(ctx.projectRoot, "vite.config.monolith.mjs"),
694
+ ...tierToStgFlag(tier)
695
+ ]);
696
+ run("run-hubspot-module-postbuild.mjs");
697
+ } else {
698
+ if (!options.hubspotOnly && useCdn) {
699
+ ctx.logger.info(`Building CDN (${tier})\u2026`);
700
+ if (options.cdnEsm) {
701
+ run("run-vite-build.mjs", [
702
+ resolve6(ctx.projectRoot, "vite.config.cdn-esm.mjs"),
703
+ ...tierToStgFlag(tier)
704
+ ]);
705
+ } else {
706
+ run("build-cdn.mjs", tierToStgFlag(tier));
707
+ }
708
+ run("generate-cdn-manifest.mjs");
709
+ }
710
+ if (!options.cdnOnly) {
711
+ ctx.logger.info(`Building HubSpot module (${tier})\u2026`);
712
+ run("run-vite-build.mjs", [
713
+ resolve6(ctx.projectRoot, "vite.config.hubspot.mjs"),
714
+ ...tierToStgFlag(tier)
715
+ ]);
716
+ run("run-hubspot-module-postbuild.mjs");
717
+ }
718
+ }
719
+ if (!options.cdnOnly && !options.hubspotOnly) {
720
+ run("check-hubspot-bundle-size.mjs");
721
+ run("write-audit-artifacts.mjs");
722
+ }
723
+ if (ctx.global.json) {
724
+ ctx.logger.json({
725
+ command: "build",
726
+ tier,
727
+ ok: true,
728
+ outDir: resolve6(ctx.projectRoot, ctx.config.module.outDir)
729
+ });
730
+ } else {
731
+ ctx.logger.success("Build complete");
732
+ }
733
+ return ExitCode.SUCCESS;
734
+ } catch (error) {
735
+ throw new WpHsError(error instanceof Error ? error.message : String(error), {
736
+ exitCode: ExitCode.BUILD_FAILED
737
+ });
738
+ }
739
+ }
740
+
741
+ // src/cli/commands/dev.ts
742
+ import { existsSync as existsSync6 } from "fs";
743
+ import { resolve as resolve8 } from "path";
744
+ import { spawn } from "child_process";
745
+
746
+ // src/vite/resolve-dev-config.ts
747
+ import { existsSync as existsSync5 } from "fs";
748
+ import { resolve as resolve7 } from "path";
749
+ var CONSUMER_DEV_CONFIG = "vite.config.dev.mjs";
750
+ function resolveDevViteConfigPath(projectRoot) {
751
+ const consumerConfig = resolve7(projectRoot, CONSUMER_DEV_CONFIG);
752
+ if (existsSync5(consumerConfig)) {
753
+ return consumerConfig;
754
+ }
755
+ return kitPath("vite", "vite.config.dev.mjs");
756
+ }
757
+ function resolveProjectViteBin(projectRoot) {
758
+ const localBin = resolve7(projectRoot, "node_modules", ".bin", "vite");
759
+ if (existsSync5(localBin)) {
760
+ return localBin;
761
+ }
762
+ return "vite";
763
+ }
764
+
765
+ // src/cli/commands/dev.ts
766
+ async function runDev(ctx, options) {
767
+ ensureHubSpotCliSetup(ctx, "local");
768
+ const port = options.port ?? 3e3;
769
+ const env = {
770
+ ...process.env,
771
+ WP_HS_PROJECT_ROOT: ctx.projectRoot
772
+ };
773
+ const configPath = resolveDevViteConfigPath(ctx.projectRoot);
774
+ const viteBin = resolveProjectViteBin(ctx.projectRoot);
775
+ if (ctx.global.dryRun) {
776
+ ctx.logger.info(`[dry-run] ${viteBin} --config ${configPath} --port ${port}`);
777
+ return ExitCode.SUCCESS;
778
+ }
779
+ if (viteBin === "vite") {
780
+ throw new WpHsError("vite is not installed in this project.", {
781
+ exitCode: ExitCode.PREREQUISITE,
782
+ remediation: "Run yarn install in the project root (vite is a devDependency)."
783
+ });
784
+ }
785
+ const consumerConfig = resolve8(ctx.projectRoot, "vite.config.dev.mjs");
786
+ if (!existsSync6(consumerConfig)) {
787
+ throw new WpHsError("vite.config.dev.mjs is missing in the project root.", {
788
+ exitCode: ExitCode.USER_ERROR,
789
+ remediation: "Add vite.config.dev.mjs (see @woodsportal/hubspot-kit templates/module-hybrid-cdn)."
790
+ });
791
+ }
792
+ return new Promise((resolvePromise) => {
793
+ const child = spawn(
794
+ viteBin,
795
+ ["--config", configPath, "--port", String(port), "--mode", "dev"],
796
+ {
797
+ cwd: ctx.projectRoot,
798
+ env,
799
+ stdio: "inherit"
800
+ }
801
+ );
802
+ child.on("exit", (code) => {
803
+ resolvePromise(code ?? ExitCode.USER_ERROR);
804
+ });
805
+ });
806
+ }
807
+
808
+ // src/cli/commands/deploy.ts
809
+ import { resolve as resolve10 } from "path";
810
+
811
+ // src/cli/commands/publish.ts
812
+ async function runPublish(ctx, options) {
813
+ const tier = options.tier ?? "prod";
814
+ const env = { ...process.env, WP_HS_PROJECT_ROOT: ctx.projectRoot };
815
+ const execOpts = {
816
+ cwd: ctx.projectRoot,
817
+ env,
818
+ logger: ctx.logger,
819
+ dryRun: ctx.global.dryRun || options.dryRun
820
+ };
821
+ const verifyFlag = tier === "prod" ? ["--prod"] : ["--stg"];
822
+ const publishArgs = options.dryRun ? ["--dry-run"] : [];
823
+ try {
824
+ if (!options.dryRun) {
825
+ ctx.logger.info(`Verifying CDN API endpoint (${tier})\u2026`);
826
+ runNodeScript(kitPath("scripts", "verify-cdn-api-endpoint.mjs"), verifyFlag, execOpts);
827
+ }
828
+ ctx.logger.info(options.dryRun ? "CDN publish dry-run\u2026" : `Publishing CDN (${tier})\u2026`);
829
+ runNodeScript(kitPath("scripts", "publish-cdn-github.mjs"), publishArgs, execOpts);
830
+ if (ctx.global.json) {
831
+ ctx.logger.json({ command: "publish", tier, dryRun: Boolean(options.dryRun), ok: true });
832
+ } else {
833
+ ctx.logger.success(options.dryRun ? "CDN publish dry-run complete" : "CDN publish complete");
834
+ }
835
+ return ExitCode.SUCCESS;
836
+ } catch (error) {
837
+ throw new WpHsError(error instanceof Error ? error.message : String(error), {
838
+ exitCode: ExitCode.PUBLISH_FAILED,
839
+ remediation: "Check CDN PAT, portal.cdn.json, and verify-cdn-api-endpoint."
840
+ });
841
+ }
842
+ }
843
+
844
+ // src/cli/commands/upload.ts
845
+ import { resolve as resolve9 } from "path";
846
+ async function runUpload(ctx, options = {}) {
847
+ ensureHubSpotCliSetup(ctx, "cms");
848
+ const tier = options.tier ?? "prod";
849
+ if (tier === "prod" && !ctx.global.yes && !ctx.global.ci) {
850
+ const confirmed = process.env.WP_HS_CONFIRM_PROD === "1";
851
+ if (!confirmed) {
852
+ throw new WpHsError("Production upload blocked \u2014 pass --yes or set WP_HS_CONFIRM_PROD=1", {
853
+ exitCode: ExitCode.USER_ERROR,
854
+ remediation: "wp upload --tier prod --yes"
855
+ });
856
+ }
857
+ }
858
+ if (isThemeProject(ctx.config)) {
859
+ const theme = ctx.config.theme;
860
+ if (!theme) {
861
+ throw new WpHsError("theme block missing in woodscli.json", { exitCode: ExitCode.USER_ERROR });
862
+ }
863
+ const srcDir = resolve9(ctx.projectRoot, theme.srcDir);
864
+ try {
865
+ runCommand("hs", ["cms", "upload", srcDir, theme.uploadPath], {
866
+ cwd: ctx.projectRoot,
867
+ logger: ctx.logger,
868
+ dryRun: ctx.global.dryRun
869
+ });
870
+ if (!ctx.global.json && !ctx.global.quiet) {
871
+ ctx.logger.success(`Uploaded theme ${srcDir} \u2192 ${theme.uploadPath}`);
872
+ }
873
+ return ExitCode.SUCCESS;
874
+ } catch (error) {
875
+ throw new WpHsError("HubSpot theme upload failed", {
876
+ exitCode: ExitCode.PUBLISH_FAILED,
877
+ cause: error
878
+ });
879
+ }
880
+ }
881
+ const outDir = resolve9(ctx.projectRoot, ctx.config.module.outDir);
882
+ const uploadPath = ctx.config.module.uploadPath;
883
+ try {
884
+ runCommand("hs", ["cms", "upload", outDir, uploadPath], {
885
+ cwd: ctx.projectRoot,
886
+ logger: ctx.logger,
887
+ dryRun: ctx.global.dryRun
888
+ });
889
+ if (!ctx.global.json && !ctx.global.quiet) {
890
+ ctx.logger.success(`Uploaded ${outDir} \u2192 ${uploadPath}`);
891
+ }
892
+ return ExitCode.SUCCESS;
893
+ } catch (error) {
894
+ throw new WpHsError("HubSpot upload failed", {
895
+ exitCode: ExitCode.PUBLISH_FAILED,
896
+ cause: error,
897
+ remediation: "Run wp auth, then build the module before upload."
898
+ });
899
+ }
900
+ }
901
+
902
+ // src/cli/commands/deploy.ts
903
+ async function runDeploy(ctx, options) {
904
+ ensureHubSpotCliSetup(ctx, "cms");
905
+ const tier = options.tier ?? "prod";
906
+ const env = { ...process.env, WP_HS_PROJECT_ROOT: ctx.projectRoot };
907
+ const execOpts = {
908
+ cwd: ctx.projectRoot,
909
+ env,
910
+ logger: ctx.logger,
911
+ dryRun: ctx.global.dryRun
912
+ };
913
+ await runBuild(ctx, { tier, cdnOnly: true });
914
+ if (!options.skipPublish) {
915
+ await runPublish(ctx, { tier });
916
+ }
917
+ await runBuild(ctx, { tier, hubspotOnly: true });
918
+ runNodeScript(kitPath("scripts", "check-hubspot-bundle-size.mjs"), [], execOpts);
919
+ runNodeScript(kitPath("scripts", "write-audit-artifacts.mjs"), [], execOpts);
920
+ await runUpload(ctx, { tier });
921
+ if (!ctx.global.json) {
922
+ ctx.logger.success(`Deploy complete (${resolve10(ctx.projectRoot, ctx.config.module.outDir)} \u2192 ${ctx.config.module.uploadPath})`);
923
+ }
924
+ return ExitCode.SUCCESS;
925
+ }
926
+
927
+ // src/cli/commands/audit.ts
928
+ import { createHash } from "crypto";
929
+ import { cpSync, existsSync as existsSync7, mkdirSync, readFileSync as readFileSync4, readdirSync, rmSync, statSync } from "fs";
930
+ import { resolve as resolve11, join as join2 } from "path";
931
+ var FORBIDDEN_SHIP_TOKENS = [
932
+ "ModuleConfigOverlay",
933
+ "module-config-store",
934
+ "__dev/module-config",
935
+ "src/dev/module-config",
936
+ "dev.module-config"
937
+ ];
938
+ function hashFile(path) {
939
+ return createHash("sha256").update(readFileSync4(path)).digest("hex");
940
+ }
941
+ function walkFiles(dir) {
942
+ if (!existsSync7(dir)) return [];
943
+ const entries = readdirSync(dir);
944
+ const files = [];
945
+ for (const entry of entries) {
946
+ const full = join2(dir, entry);
947
+ if (statSync(full).isDirectory()) {
948
+ files.push(...walkFiles(full));
949
+ } else {
950
+ files.push(full);
951
+ }
952
+ }
953
+ return files;
954
+ }
955
+ async function runAuditShipBundle(ctx) {
956
+ const root = ctx.projectRoot;
957
+ const targets = [
958
+ resolve11(root, "dist/cdn"),
959
+ resolve11(root, ctx.config.module.outDir)
960
+ ];
961
+ const violations = [];
962
+ for (const dir of targets) {
963
+ for (const file of walkFiles(dir)) {
964
+ if (!/\.(js|css|html)$/i.test(file)) continue;
965
+ const content = readFileSync4(file, "utf8");
966
+ for (const token of FORBIDDEN_SHIP_TOKENS) {
967
+ if (content.includes(token)) {
968
+ violations.push(`${file}: contains forbidden token "${token}"`);
969
+ }
970
+ }
971
+ }
972
+ }
973
+ if (ctx.global.json) {
974
+ ctx.logger.json({ command: "audit:ship-bundle", ok: violations.length === 0, violations });
975
+ } else if (violations.length === 0) {
976
+ ctx.logger.success("Ship bundle audit passed");
977
+ } else {
978
+ for (const v of violations) ctx.logger.error(v);
979
+ }
980
+ if (violations.length > 0) {
981
+ throw new WpHsError("Ship bundle audit failed \u2014 dev tooling leaked into production artifacts", {
982
+ exitCode: ExitCode.AUDIT_FAILED,
983
+ remediation: "Ensure dev-only code is not imported in production build graph."
984
+ });
985
+ }
986
+ return ExitCode.SUCCESS;
987
+ }
988
+ async function runAuditBaselineCapture(ctx, options) {
989
+ const root = ctx.projectRoot;
990
+ const baseline = options.baselineDir ?? defaultBaselineDir(root, ctx.config);
991
+ const sources = [
992
+ { label: "cdn", path: resolve11(root, "dist/cdn") },
993
+ { label: "hubspot", path: resolve11(root, ctx.config.module.outDir) }
994
+ ];
995
+ if (ctx.global.dryRun) {
996
+ ctx.logger.info(`[dry-run] Would capture baseline to ${baseline}`);
997
+ return ExitCode.SUCCESS;
998
+ }
999
+ for (const { label, path } of sources) {
1000
+ if (!existsSync7(path)) {
1001
+ throw new WpHsError(`Cannot capture baseline \u2014 missing ${path}`, {
1002
+ exitCode: ExitCode.USER_ERROR,
1003
+ remediation: "Run wp build before wp audit baseline --capture."
1004
+ });
1005
+ }
1006
+ const target = resolve11(baseline, label);
1007
+ rmSync(target, { recursive: true, force: true });
1008
+ mkdirSync(baseline, { recursive: true });
1009
+ cpSync(path, target, { recursive: true });
1010
+ }
1011
+ if (ctx.global.json) {
1012
+ ctx.logger.json({ command: "audit:baseline", ok: true, baseline });
1013
+ } else {
1014
+ ctx.logger.success(`Baseline captured at ${baseline}`);
1015
+ }
1016
+ return ExitCode.SUCCESS;
1017
+ }
1018
+ async function runAuditParity(ctx, options) {
1019
+ const root = ctx.projectRoot;
1020
+ const baseline = options.baselineDir ?? defaultBaselineDir(root, ctx.config);
1021
+ const currentDirs = [
1022
+ { label: "cdn", path: resolve11(root, "dist/cdn") },
1023
+ { label: "hubspot", path: resolve11(root, ctx.config.module.outDir) }
1024
+ ];
1025
+ const mismatches = [];
1026
+ for (const { label, path } of currentDirs) {
1027
+ const baselinePath = resolve11(baseline, label);
1028
+ if (!existsSync7(baselinePath)) {
1029
+ ctx.logger.warn(`No baseline at ${baselinePath} \u2014 skipping ${label} parity`);
1030
+ continue;
1031
+ }
1032
+ const baselineFiles = walkFiles(baselinePath).filter((f) => !f.endsWith(".map"));
1033
+ const currentFiles = walkFiles(path).filter((f) => !f.endsWith(".map"));
1034
+ const baselineNames = new Set(baselineFiles.map((f) => f.replace(baselinePath, "")));
1035
+ const currentNames = new Set(currentFiles.map((f) => f.replace(path, "")));
1036
+ for (const name of baselineNames) {
1037
+ if (!currentNames.has(name)) {
1038
+ mismatches.push(`missing file: ${label}${name}`);
1039
+ }
1040
+ }
1041
+ for (const file of baselineFiles) {
1042
+ const rel = file.replace(baselinePath, "");
1043
+ const current = resolve11(path, rel.slice(1));
1044
+ if (!existsSync7(current)) continue;
1045
+ const bh = hashFile(file);
1046
+ const ch = hashFile(current);
1047
+ if (bh !== ch) {
1048
+ mismatches.push(`hash mismatch: ${label}${rel}`);
1049
+ }
1050
+ }
1051
+ }
1052
+ if (ctx.global.json) {
1053
+ ctx.logger.json({ command: "audit:parity", ok: mismatches.length === 0, mismatches });
1054
+ } else if (mismatches.length === 0) {
1055
+ ctx.logger.success("Parity audit passed (or no baseline configured)");
1056
+ } else {
1057
+ for (const m of mismatches) ctx.logger.error(m);
1058
+ }
1059
+ if (mismatches.length > 0) {
1060
+ throw new WpHsError("Parity audit failed", { exitCode: ExitCode.AUDIT_FAILED });
1061
+ }
1062
+ return ExitCode.SUCCESS;
1063
+ }
1064
+ var SECRET_PATTERNS = [
1065
+ /(?:api[_-]?key|secret|password|token|private[_-]?key)\s*[:=]\s*['"][^'"]{8,}['"]/gi,
1066
+ /sk_live_[a-zA-Z0-9]{16,}/g,
1067
+ /pat-[a-z]{2}-[a-f0-9-]{20,}/gi
1068
+ ];
1069
+ async function runAuditSecrets(ctx) {
1070
+ const root = ctx.projectRoot;
1071
+ const scanRoots = ["src", "public", ".wphs/config"].map((d) => resolve11(root, d));
1072
+ const findings = [];
1073
+ for (const dir of scanRoots) {
1074
+ for (const file of walkFiles(dir)) {
1075
+ if (!/\.(ts|tsx|js|jsx|json|env|yml|yaml|html)$/i.test(file)) continue;
1076
+ if (file.includes("node_modules")) continue;
1077
+ const content = readFileSync4(file, "utf8");
1078
+ for (const pattern of SECRET_PATTERNS) {
1079
+ pattern.lastIndex = 0;
1080
+ if (pattern.test(content)) {
1081
+ findings.push(`${file}: possible secret pattern matched`);
1082
+ }
1083
+ }
1084
+ }
1085
+ }
1086
+ if (ctx.global.json) {
1087
+ ctx.logger.json({ command: "audit:secrets", ok: findings.length === 0, findings });
1088
+ } else if (findings.length === 0) {
1089
+ ctx.logger.success("Secrets audit passed");
1090
+ } else {
1091
+ for (const f of findings) ctx.logger.error(f);
1092
+ }
1093
+ if (findings.length > 0) {
1094
+ throw new WpHsError("Secrets audit found potential credentials in source files", {
1095
+ exitCode: ExitCode.AUDIT_FAILED,
1096
+ remediation: "Move secrets to env vars; never commit PATs or API keys."
1097
+ });
1098
+ }
1099
+ return ExitCode.SUCCESS;
1100
+ }
1101
+
1102
+ // src/cli/commands/init.ts
1103
+ import { cpSync as cpSync2, existsSync as existsSync8, mkdirSync as mkdirSync2, readdirSync as readdirSync2, readFileSync as readFileSync5, writeFileSync } from "fs";
1104
+ import { basename, resolve as resolve12 } from "path";
1105
+ import * as p from "@clack/prompts";
1106
+
1107
+ // src/lib/project-name.ts
1108
+ var PROJECT_NAME_PATTERN = /^[a-z][a-z0-9-]*$/;
1109
+ function normalizeProjectName(input) {
1110
+ return input.trim().toLowerCase().replace(/[\s_]+/g, "-").replace(/[^a-z0-9-]/g, "").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
1111
+ }
1112
+ function validateProjectName(name) {
1113
+ if (!name) return "Project name is required";
1114
+ if (!PROJECT_NAME_PATTERN.test(name)) {
1115
+ return "Use lowercase letters, numbers, and hyphens (e.g. my-portal-module)";
1116
+ }
1117
+ if (name.length > 64) return "Project name is too long (max 64 characters)";
1118
+ return void 0;
1119
+ }
1120
+ function moduleOutDirFromSlug(slug) {
1121
+ const pascal = slug.split("-").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
1122
+ return `dist/${pascal || "HubSpot"}.Module`;
1123
+ }
1124
+
1125
+ // src/cli/commands/init.ts
1126
+ var TEMPLATE_SCRIPTS = {
1127
+ "module-hybrid-cdn": {
1128
+ dev: "wp dev",
1129
+ local: "wp dev",
1130
+ "build:prod": "wp build --tier prod",
1131
+ "build:dev": "wp build --tier dev",
1132
+ "deploy:prod": "wp deploy --tier prod --yes",
1133
+ "deploy:dev": "wp deploy --tier dev --yes",
1134
+ doctor: "wp doctor"
1135
+ },
1136
+ "module-monolith": {
1137
+ dev: "wp dev",
1138
+ local: "wp dev",
1139
+ "build:prod": "wp build --tier prod --hubspot-only",
1140
+ doctor: "wp doctor"
1141
+ },
1142
+ "theme-starter": {
1143
+ dev: "wp dev",
1144
+ local: "wp dev",
1145
+ doctor: "wp doctor"
1146
+ }
1147
+ };
1148
+ function buildConfigForProject(template, projectName) {
1149
+ const base = { ...DEFAULT_WOODSPORTAL_HS, projectType: template };
1150
+ if (template === "theme-starter") {
1151
+ return {
1152
+ ...base,
1153
+ theme: {
1154
+ name: projectName,
1155
+ uploadPath: `themes/${projectName}`,
1156
+ srcDir: "src/theme",
1157
+ themeJson: "src/theme/theme.json"
1158
+ }
1159
+ };
1160
+ }
1161
+ return {
1162
+ ...base,
1163
+ module: {
1164
+ name: projectName,
1165
+ uploadPath: `dw-modules/${projectName}.module`,
1166
+ outDir: moduleOutDirFromSlug(projectName)
1167
+ },
1168
+ cdn: {
1169
+ ...base.cdn,
1170
+ mode: template === "module-monolith" ? "none" : base.cdn.mode
1171
+ }
1172
+ };
1173
+ }
1174
+ function assertTargetDirIsScaffoldable(targetDir) {
1175
+ if (!existsSync8(targetDir)) return;
1176
+ const entries = readdirSync2(targetDir).filter((entry) => entry !== ".DS_Store");
1177
+ if (entries.length > 0) {
1178
+ throw new WpHsError(`Target directory is not empty: ${targetDir}`, {
1179
+ exitCode: ExitCode.USER_ERROR,
1180
+ remediation: "Choose a different project name or remove existing files."
1181
+ });
1182
+ }
1183
+ }
1184
+ function resolveInitTargetDir(options, interactiveName) {
1185
+ if (options.directory) {
1186
+ return resolve12(options.directory);
1187
+ }
1188
+ const parentDir = process.cwd();
1189
+ const slug = options.name ?? interactiveName;
1190
+ if (!slug) {
1191
+ throw new WpHsError("Project name is required", {
1192
+ exitCode: ExitCode.USER_ERROR,
1193
+ remediation: "Run interactively, or pass --name my-project (or --directory ./my-project)."
1194
+ });
1195
+ }
1196
+ const error = validateProjectName(slug);
1197
+ if (error) {
1198
+ throw new WpHsError(error, { exitCode: ExitCode.USER_ERROR });
1199
+ }
1200
+ return resolve12(parentDir, slug);
1201
+ }
1202
+ async function runInit(ctx, options) {
1203
+ const nonInteractive = ctx.global.ci || ctx.global.yes || ctx.global.json;
1204
+ let template = options.template;
1205
+ let projectName = options.name ? normalizeProjectName(options.name) : void 0;
1206
+ if (!nonInteractive) {
1207
+ if (!template) {
1208
+ const type = await p.select({
1209
+ message: "Project type",
1210
+ options: [
1211
+ {
1212
+ value: "module-hybrid-cdn",
1213
+ label: "Module (hybrid CDN)",
1214
+ hint: "jsDelivr + thin module.js"
1215
+ },
1216
+ { value: "module-monolith", label: "Module (monolith)", hint: "Single module.js IIFE" },
1217
+ { value: "theme-starter", label: "Theme starter", hint: "CMS theme templates" }
1218
+ ]
1219
+ });
1220
+ if (p.isCancel(type)) throw new WpHsError("Init cancelled", { exitCode: ExitCode.USER_ERROR });
1221
+ template = type;
1222
+ }
1223
+ if (!options.directory && !projectName) {
1224
+ const nameAnswer = await p.text({
1225
+ message: "Project name (folder name)",
1226
+ placeholder: "my-portal-module",
1227
+ validate: (value) => validateProjectName(normalizeProjectName(value ?? ""))
1228
+ });
1229
+ if (p.isCancel(nameAnswer)) {
1230
+ throw new WpHsError("Init cancelled", { exitCode: ExitCode.USER_ERROR });
1231
+ }
1232
+ projectName = normalizeProjectName(nameAnswer);
1233
+ }
1234
+ }
1235
+ if (!template) {
1236
+ template = "module-hybrid-cdn";
1237
+ }
1238
+ const targetDir = resolveInitTargetDir(options, projectName);
1239
+ const slug = projectName ?? basename(targetDir);
1240
+ assertTargetDirIsScaffoldable(targetDir);
1241
+ const templateDir = kitPath("templates", template);
1242
+ if (!existsSync8(templateDir)) {
1243
+ throw new WpHsError(`Template not found: ${template}`, { exitCode: ExitCode.USER_ERROR });
1244
+ }
1245
+ if (ctx.global.dryRun) {
1246
+ ctx.logger.info(`[dry-run] Would scaffold ${template} in ${targetDir}`);
1247
+ return ExitCode.SUCCESS;
1248
+ }
1249
+ mkdirSync2(targetDir, { recursive: true });
1250
+ cpSync2(templateDir, targetDir, { recursive: true });
1251
+ const config = buildConfigForProject(template, slug);
1252
+ writeFileSync(resolve12(targetDir, WOODSCLI_CONFIG_FILE), `${JSON.stringify(config, null, 2)}
1253
+ `);
1254
+ const pkgPath = resolve12(targetDir, "package.json");
1255
+ if (existsSync8(pkgPath)) {
1256
+ const pkg = JSON.parse(readFileSync5(pkgPath, "utf8"));
1257
+ pkg.name = slug;
1258
+ pkg.scripts = { ...pkg.scripts, ...TEMPLATE_SCRIPTS[template] };
1259
+ pkg.devDependencies = {
1260
+ ...pkg.devDependencies,
1261
+ "@woodsportal/hubspot-kit": "^1.0.2"
1262
+ };
1263
+ writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}
1264
+ `);
1265
+ }
1266
+ runNodeScript(kitPath("scripts/ensure-wphs-workspace.mjs"), [], {
1267
+ cwd: targetDir,
1268
+ logger: ctx.logger,
1269
+ dryRun: false
1270
+ });
1271
+ const gitignorePath = resolve12(targetDir, ".gitignore");
1272
+ const gitignoreExtras = [
1273
+ "hubspot.config.yml",
1274
+ ".wphs/config/module-config.local.json",
1275
+ ".wphs/assets/",
1276
+ ".wphs/cache/",
1277
+ ".wphs/audit/",
1278
+ ".wphs/state/",
1279
+ ".wphs/fixtures/hubspot-data.json",
1280
+ ".wphs/.kit-module-config",
1281
+ ".wphs/.kit-bootstrap.tsx"
1282
+ ];
1283
+ const existing = existsSync8(gitignorePath) ? readFileSync5(gitignorePath, "utf8") : "";
1284
+ const toAppend = gitignoreExtras.filter((line) => !existing.includes(line)).join("\n");
1285
+ if (toAppend) {
1286
+ writeFileSync(gitignorePath, `${existing.trimEnd()}
1287
+ ${toAppend}
1288
+ `);
1289
+ }
1290
+ if (!nonInteractive) {
1291
+ p.log.success(`Scaffolded ${template} in ${targetDir}`);
1292
+ p.note(`cd ${slug} && npm install && wp doctor && wp dev`, "Getting started");
1293
+ } else {
1294
+ ctx.logger.success(`Scaffolded ${template} in ${targetDir}`);
1295
+ }
1296
+ return ExitCode.SUCCESS;
1297
+ }
1298
+
1299
+ // src/cli/commands/adopt.ts
1300
+ import { existsSync as existsSync9, readFileSync as readFileSync6, writeFileSync as writeFileSync2 } from "fs";
1301
+ import { resolve as resolve13 } from "path";
1302
+ import { pathToFileURL } from "url";
1303
+ import * as p2 from "@clack/prompts";
1304
+ async function loadDetectLayout() {
1305
+ const mod = await import(pathToFileURL(kitPath("scripts", "detect-layout.mjs")).href);
1306
+ return mod.detectProjectLayout;
1307
+ }
1308
+ async function runAdopt(ctx, _options = {}) {
1309
+ const detectProjectLayout = await loadDetectLayout();
1310
+ const layout = detectProjectLayout(ctx.projectRoot);
1311
+ const configPath = resolve13(ctx.projectRoot, WOODSCLI_CONFIG_FILE);
1312
+ if (layout.track === "unknown") {
1313
+ throw new WpHsError("Could not detect module or theme layout.", {
1314
+ exitCode: ExitCode.USER_ERROR,
1315
+ remediation: "Ensure fields.json + module.html (module) or theme.json + templates/ (theme) exist."
1316
+ });
1317
+ }
1318
+ let projectType;
1319
+ if (layout.projectType) {
1320
+ projectType = layout.projectType;
1321
+ } else if (layout.track === "theme") {
1322
+ projectType = "theme-starter";
1323
+ } else if (existsSync9(resolve13(ctx.projectRoot, "vite.config.monolith.mjs"))) {
1324
+ projectType = "module-monolith";
1325
+ } else {
1326
+ projectType = "module-hybrid-cdn";
1327
+ }
1328
+ const config = {
1329
+ ...DEFAULT_WOODSPORTAL_HS,
1330
+ projectType,
1331
+ ...layout.track === "theme" ? {
1332
+ theme: {
1333
+ name: "my-theme",
1334
+ uploadPath: "themes/my-theme",
1335
+ srcDir: "src/theme",
1336
+ themeJson: "src/theme/theme.json"
1337
+ }
1338
+ } : {}
1339
+ };
1340
+ if (ctx.global.dryRun) {
1341
+ ctx.logger.info(`[dry-run] Would adopt as ${projectType} (${layout.track})`);
1342
+ return ExitCode.SUCCESS;
1343
+ }
1344
+ if (!existsSync9(configPath)) {
1345
+ writeFileSync2(configPath, `${JSON.stringify(config, null, 2)}
1346
+ `);
1347
+ ctx.logger.success(`Created ${WOODSCLI_CONFIG_FILE}`);
1348
+ } else if (!layout.projectType) {
1349
+ const existing = JSON.parse(readFileSync6(configPath, "utf8"));
1350
+ writeFileSync2(configPath, `${JSON.stringify({ ...existing, projectType }, null, 2)}
1351
+ `);
1352
+ ctx.logger.info(`Set projectType \u2192 ${projectType}`);
1353
+ }
1354
+ runNodeScript(kitPath("scripts/ensure-wphs-workspace.mjs"), [], {
1355
+ cwd: ctx.projectRoot,
1356
+ logger: ctx.logger,
1357
+ dryRun: false
1358
+ });
1359
+ const adapterPath = resolve13(ctx.projectRoot, "wphs.adapter.ts");
1360
+ if (!existsSync9(adapterPath) && isModuleProject(config)) {
1361
+ writeFileSync2(
1362
+ adapterPath,
1363
+ `import type { HubSpotKitAdapter } from '@woodsportal/hubspot-kit'
1364
+
1365
+ const adapter: HubSpotKitAdapter = {
1366
+ syncLiveData(built) {
1367
+ ;(window as Window & { __WPHS_DATA__?: Record<string, unknown> }).__WPHS_DATA__ = built
1368
+ },
1369
+ }
1370
+
1371
+ export default adapter
1372
+ `
1373
+ );
1374
+ ctx.logger.info("Created wphs.adapter.ts stub");
1375
+ }
1376
+ if (!ctx.global.ci && !ctx.global.quiet) {
1377
+ p2.note(
1378
+ [
1379
+ `Review ${WOODSCLI_CONFIG_FILE} upload paths`,
1380
+ "Implement wphs.adapter.ts for your app (if module)",
1381
+ "Add vite.config.dev.mjs re-export if missing",
1382
+ "Patch main.dev.css @source to .wphs/.kit-module-config/**"
1383
+ ].join("\n"),
1384
+ "Manual follow-ups"
1385
+ );
1386
+ }
1387
+ ctx.logger.success(`Adopted ${layout.track} project (${projectType})`);
1388
+ return ExitCode.SUCCESS;
1389
+ }
1390
+
1391
+ // src/cli/commands/migrate.ts
1392
+ import { cpSync as cpSync3, existsSync as existsSync10, mkdirSync as mkdirSync3, readFileSync as readFileSync7, readdirSync as readdirSync3, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
1393
+ import { join as join3, resolve as resolve14 } from "path";
1394
+ var LEGACY_DIRS = [".dev", ".woodsportal"];
1395
+ var LEGACY_BASELINE = ".wp-hs-baseline";
1396
+ async function runMigrate(ctx, _options = {}) {
1397
+ const root = ctx.projectRoot;
1398
+ const wphsRoot = resolve14(root, WPHS_WORKSPACE_DIR);
1399
+ const actions = [];
1400
+ for (const legacy of LEGACY_DIRS) {
1401
+ const legacyPath = resolve14(root, legacy);
1402
+ if (!existsSync10(legacyPath)) continue;
1403
+ actions.push(`Move ${legacy}/ \u2192 .wphs/`);
1404
+ if (ctx.global.dryRun) continue;
1405
+ mkdirSync3(join3(wphsRoot, "config"), { recursive: true });
1406
+ mkdirSync3(join3(wphsRoot, "assets", "branding"), { recursive: true });
1407
+ const localConfig = join3(legacyPath, "module-config.local.json");
1408
+ const exampleConfig = join3(legacyPath, "module-config.example.json");
1409
+ if (existsSync10(localConfig)) {
1410
+ cpSync3(localConfig, join3(wphsRoot, "config", "module-config.local.json"));
1411
+ }
1412
+ if (existsSync10(exampleConfig)) {
1413
+ cpSync3(exampleConfig, join3(wphsRoot, "config", "module-config.example.json"));
1414
+ }
1415
+ const branding = join3(legacyPath, "branding-assets");
1416
+ if (existsSync10(branding)) {
1417
+ for (const file of readdirSync3(branding)) {
1418
+ cpSync3(join3(branding, file), join3(wphsRoot, "assets", "branding", file));
1419
+ }
1420
+ }
1421
+ rmSync2(legacyPath, { recursive: true, force: true });
1422
+ }
1423
+ const legacyBaseline = resolve14(root, LEGACY_BASELINE);
1424
+ const targetBaseline = join3(wphsRoot, "audit", "baseline");
1425
+ if (existsSync10(legacyBaseline)) {
1426
+ actions.push(`Move ${LEGACY_BASELINE}/ \u2192 .wphs/audit/baseline/`);
1427
+ if (!ctx.global.dryRun) {
1428
+ mkdirSync3(targetBaseline, { recursive: true });
1429
+ cpSync3(legacyBaseline, targetBaseline, { recursive: true });
1430
+ rmSync2(legacyBaseline, { recursive: true, force: true });
1431
+ }
1432
+ }
1433
+ const legacyConfigPath2 = resolve14(root, LEGACY_WOODSPORTAL_HS_FILE);
1434
+ const woodscliPath = resolve14(root, WOODSCLI_CONFIG_FILE);
1435
+ if (existsSync10(legacyConfigPath2) && !existsSync10(woodscliPath)) {
1436
+ actions.push(`Rename ${LEGACY_WOODSPORTAL_HS_FILE} \u2192 ${WOODSCLI_CONFIG_FILE}`);
1437
+ if (!ctx.global.dryRun) {
1438
+ renameSync(legacyConfigPath2, woodscliPath);
1439
+ }
1440
+ } else if (existsSync10(legacyConfigPath2) && existsSync10(woodscliPath)) {
1441
+ actions.push(`Remove duplicate ${LEGACY_WOODSPORTAL_HS_FILE}`);
1442
+ if (!ctx.global.dryRun) {
1443
+ rmSync2(legacyConfigPath2, { force: true });
1444
+ }
1445
+ }
1446
+ if (existsSync10(woodscliPath)) {
1447
+ const raw = JSON.parse(readFileSync7(woodscliPath, "utf8"));
1448
+ if (raw?.dev?.moduleConfigDir && raw.dev.moduleConfigDir !== WPHS_WORKSPACE_DIR) {
1449
+ actions.push(`Update dev.moduleConfigDir \u2192 .wphs in project config`);
1450
+ if (!ctx.global.dryRun) {
1451
+ raw.dev.moduleConfigDir = WPHS_WORKSPACE_DIR;
1452
+ writeFileSync3(woodscliPath, `${JSON.stringify(raw, null, 2)}
1453
+ `);
1454
+ }
1455
+ }
1456
+ }
1457
+ if (actions.length === 0) {
1458
+ ctx.logger.success("Nothing to migrate \u2014 already on .wphs / woodscli.json");
1459
+ return ExitCode.SUCCESS;
1460
+ }
1461
+ if (ctx.global.dryRun) {
1462
+ for (const action of actions) ctx.logger.info(`[dry-run] ${action}`);
1463
+ return ExitCode.SUCCESS;
1464
+ }
1465
+ if (!ctx.global.yes && !ctx.global.ci) {
1466
+ throw new WpHsError("Migration requires --yes.", {
1467
+ exitCode: ExitCode.USER_ERROR,
1468
+ remediation: "Run: wp migrate --yes"
1469
+ });
1470
+ }
1471
+ runNodeScript(kitPath("scripts/ensure-wphs-workspace.mjs"), [], {
1472
+ cwd: root,
1473
+ logger: ctx.logger,
1474
+ dryRun: false
1475
+ });
1476
+ for (const action of actions) ctx.logger.success(action);
1477
+ return ExitCode.SUCCESS;
1478
+ }
1479
+
1480
+ // src/cli/commands/examples.ts
1481
+ import { readFileSync as readFileSync8 } from "fs";
1482
+ function loadManifest() {
1483
+ const manifestPath = kitPath("examples", "manifest.json");
1484
+ const raw = JSON.parse(readFileSync8(manifestPath, "utf8"));
1485
+ return raw.scenarios;
1486
+ }
1487
+ async function runExamplesList(ctx, options = {}) {
1488
+ let scenarios = loadManifest();
1489
+ if (options.track) scenarios = scenarios.filter((s) => s.track === options.track);
1490
+ if (options.setup) scenarios = scenarios.filter((s) => s.setup === options.setup);
1491
+ if (ctx.global.json) {
1492
+ ctx.logger.json({ command: "examples:list", scenarios });
1493
+ } else {
1494
+ for (const s of scenarios) {
1495
+ ctx.logger.info(`${s.id} (${s.setup}/${s.track}) tier=${s.smokeTier}`);
1496
+ }
1497
+ }
1498
+ return ExitCode.SUCCESS;
1499
+ }
1500
+ async function runExamplesSmoke(ctx, options = {}) {
1501
+ const tier = options.tier ?? "static";
1502
+ const args = ["--tier", tier];
1503
+ if (options.scenario) args.push("--scenario", options.scenario);
1504
+ const { runNodeScript: runNodeScript2 } = await import("./exec-K5C6LZP4.js");
1505
+ runNodeScript2(kitPath("examples", "smoke", "run-matrix.mjs"), args, {
1506
+ cwd: ctx.projectRoot,
1507
+ logger: ctx.logger,
1508
+ dryRun: ctx.global.dryRun
1509
+ });
1510
+ if (ctx.global.json) {
1511
+ ctx.logger.json({ command: "examples:smoke", tier, scenario: options.scenario ?? "all", ok: true });
1512
+ }
1513
+ return ExitCode.SUCCESS;
1514
+ }
1515
+
1516
+ // src/cli/commands/fields.ts
1517
+ import { cpSync as cpSync4, existsSync as existsSync11, readFileSync as readFileSync9, writeFileSync as writeFileSync4 } from "fs";
1518
+ import { resolve as resolve15 } from "path";
1519
+ async function runFieldsSync(ctx, options = {}) {
1520
+ const direction = options.direction ?? "pull";
1521
+ const fieldsPath = resolve15(ctx.projectRoot, ctx.config.dev.fieldsJson);
1522
+ const cacheDir = resolve15(ctx.projectRoot, ctx.config.dev.moduleConfigDir, "cache", "fields-sync");
1523
+ const cachePath = resolve15(cacheDir, "fields.json");
1524
+ if (direction === "pull") {
1525
+ if (!existsSync11(fieldsPath)) {
1526
+ throw new WpHsError(`Missing ${ctx.config.dev.fieldsJson}`, {
1527
+ exitCode: ExitCode.USER_ERROR,
1528
+ remediation: "Add fields.json or run wp init."
1529
+ });
1530
+ }
1531
+ if (ctx.global.dryRun || options.dryRun) {
1532
+ ctx.logger.info(`[dry-run] Would cache ${fieldsPath} \u2192 ${cachePath}`);
1533
+ return ExitCode.SUCCESS;
1534
+ }
1535
+ const { mkdirSync: mkdirSync4 } = await import("fs");
1536
+ mkdirSync4(cacheDir, { recursive: true });
1537
+ cpSync4(fieldsPath, cachePath);
1538
+ ctx.logger.success(`Cached fields \u2192 ${cachePath}`);
1539
+ return ExitCode.SUCCESS;
1540
+ }
1541
+ if (!existsSync11(cachePath)) {
1542
+ throw new WpHsError("No cached fields \u2014 run wp fields sync pull first.", {
1543
+ exitCode: ExitCode.USER_ERROR
1544
+ });
1545
+ }
1546
+ if (ctx.global.dryRun || options.dryRun) {
1547
+ ctx.logger.info(`[dry-run] Would write ${cachePath} \u2192 ${fieldsPath}`);
1548
+ return ExitCode.SUCCESS;
1549
+ }
1550
+ writeFileSync4(fieldsPath, readFileSync9(cachePath, "utf8"));
1551
+ ctx.logger.success(`Restored fields from cache \u2192 ${fieldsPath}`);
1552
+ return ExitCode.SUCCESS;
1553
+ }
1554
+
1555
+ // src/cli/commands/regression.ts
1556
+ import { existsSync as existsSync12 } from "fs";
1557
+ import { resolve as resolve16 } from "path";
1558
+ var LEGACY_SHIM_SCRIPTS = [
1559
+ "build-cdn.mjs",
1560
+ "run-vite-build.mjs",
1561
+ "run-hubspot-module-postbuild.mjs",
1562
+ "watch-hubspot.mjs"
1563
+ ];
1564
+ var FORBIDDEN_CONSUMER_PATHS = ["src/dev/module-config", "src/dev/bootstrap.tsx"];
1565
+ async function runRegressionSmoke(ctx, options = {}) {
1566
+ const failures = [];
1567
+ const checks = [];
1568
+ if (!options.skipDoctor) {
1569
+ const doctorCode = await runDoctor(ctx);
1570
+ const doctorOk = doctorCode === ExitCode.SUCCESS;
1571
+ checks.push({ name: "doctor", ok: doctorOk });
1572
+ if (!doctorOk) failures.push("wp doctor failed");
1573
+ }
1574
+ const validateCode = await runConfigValidate(ctx);
1575
+ const validateOk = validateCode === ExitCode.SUCCESS;
1576
+ checks.push({ name: "config-validate", ok: validateOk });
1577
+ if (!validateOk) failures.push("wp config validate failed");
1578
+ if (options.checkLegacyShims !== false) {
1579
+ for (const script of LEGACY_SHIM_SCRIPTS) {
1580
+ const shim = resolve16(ctx.projectRoot, "scripts", script);
1581
+ const kitScript = kitPath("scripts", script);
1582
+ const shimOk = existsSync12(shim) && existsSync12(kitScript);
1583
+ checks.push({ name: `shim:${script}`, ok: shimOk });
1584
+ if (!shimOk) failures.push(`Missing legacy shim or kit script: ${script}`);
1585
+ }
1586
+ }
1587
+ for (const rel of FORBIDDEN_CONSUMER_PATHS) {
1588
+ const forbidden = resolve16(ctx.projectRoot, rel);
1589
+ const absent = !existsSync12(forbidden);
1590
+ checks.push({ name: `no-${rel}`, ok: absent });
1591
+ if (!absent) failures.push(`Forbidden dev path still present: ${rel}`);
1592
+ }
1593
+ const woodscliConfig = resolve16(ctx.projectRoot, WOODSCLI_CONFIG_FILE);
1594
+ const configOk = existsSync12(woodscliConfig);
1595
+ checks.push({ name: WOODSCLI_CONFIG_FILE, ok: configOk });
1596
+ if (!configOk) failures.push(`${WOODSCLI_CONFIG_FILE} missing`);
1597
+ if (ctx.global.json) {
1598
+ ctx.logger.json({
1599
+ command: "regression:smoke",
1600
+ ok: failures.length === 0,
1601
+ checks,
1602
+ failures
1603
+ });
1604
+ } else if (failures.length === 0) {
1605
+ ctx.logger.success("Regression smoke passed");
1606
+ } else {
1607
+ for (const f of failures) ctx.logger.error(f);
1608
+ }
1609
+ if (failures.length > 0) {
1610
+ throw new WpHsError("Regression smoke failed", {
1611
+ exitCode: ExitCode.AUDIT_FAILED,
1612
+ remediation: "Run wp doctor and fix listed checks. See docs/REGRESSION-SMOKE.md."
1613
+ });
1614
+ }
1615
+ return ExitCode.SUCCESS;
1616
+ }
1617
+
1618
+ // src/cli/commands/watch.ts
1619
+ async function runWatch(ctx, options) {
1620
+ ensureHubSpotCliSetup(ctx, "cms");
1621
+ const tier = options.tier ?? "dev";
1622
+ const args = [];
1623
+ if (tier === "prod") args.push("--prod");
1624
+ if (options.rebuild) args.push("--rebuild");
1625
+ try {
1626
+ runNodeScript(kitPath("scripts", "watch-hubspot.mjs"), args, {
1627
+ cwd: ctx.projectRoot,
1628
+ env: { ...process.env, WP_HS_PROJECT_ROOT: ctx.projectRoot },
1629
+ logger: ctx.logger,
1630
+ dryRun: ctx.global.dryRun
1631
+ });
1632
+ return ExitCode.SUCCESS;
1633
+ } catch (error) {
1634
+ throw new WpHsError("Watch failed", {
1635
+ exitCode: ExitCode.BUILD_FAILED,
1636
+ cause: error,
1637
+ remediation: "Ensure HubSpot module is built once before watch, or pass --rebuild."
1638
+ });
1639
+ }
1640
+ }
1641
+
1642
+ // src/cli/program.ts
1643
+ function readVersion() {
1644
+ try {
1645
+ const pkg = JSON.parse(readFileSync10(resolve17(getKitRoot(), "package.json"), "utf8"));
1646
+ return pkg.version ?? "0.0.0";
1647
+ } catch {
1648
+ return "0.0.0";
1649
+ }
1650
+ }
1651
+ function parseTier(value) {
1652
+ if (value === "dev" || value === "stg" || value === "prod") return value;
1653
+ return "prod";
1654
+ }
1655
+ function buildContext(global) {
1656
+ const logger = createLogger({
1657
+ verbose: global.verbose,
1658
+ quiet: global.quiet,
1659
+ json: global.json,
1660
+ ci: global.ci
1661
+ });
1662
+ const projectRoot = global.project ?? findProjectRoot2();
1663
+ const { config } = loadConfig(projectRoot);
1664
+ return { projectRoot, config, logger, global };
1665
+ }
1666
+ function attachGlobals(command) {
1667
+ 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);
1668
+ }
1669
+ async function handle(fn) {
1670
+ try {
1671
+ const code = await fn();
1672
+ process.exit(code);
1673
+ } catch (error) {
1674
+ if (isWpHsError(error)) {
1675
+ process.stderr.write(`Error: ${error.message}
1676
+ `);
1677
+ if (error.remediation) {
1678
+ process.stderr.write(`\u2192 ${error.remediation}
1679
+ `);
1680
+ }
1681
+ process.exit(error.exitCode);
1682
+ }
1683
+ process.stderr.write(`Error: ${error instanceof Error ? error.message : String(error)}
1684
+ `);
1685
+ process.exit(ExitCode.INTERNAL);
1686
+ }
1687
+ }
1688
+ function createProgram() {
1689
+ const program2 = new Command().name("wp").description("WoodsCLI \u2014 HubSpot CMS dev kit by WoodsPortal (modules + themes)").version(readVersion()).addHelpText("after", "\nWoodsCLI \xB7 WoodsPortal HubSpot CLI \xB7 npm @woodsportal/hubspot-kit");
1690
+ attachGlobals(program2);
1691
+ program2.command("doctor").description("Check prerequisites (Node, HubSpot CLI, .wphs workspace, config)").action(async () => {
1692
+ const global = program2.opts();
1693
+ await handle(() => runDoctor(buildContext(global)));
1694
+ });
1695
+ const configCmd = program2.command("config").description("Project configuration");
1696
+ configCmd.command("validate").description("Validate woodscli.json").action(async () => {
1697
+ const global = program2.opts();
1698
+ await handle(() => runConfigValidate(buildContext(global)));
1699
+ });
1700
+ 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) => {
1701
+ const global = program2.opts();
1702
+ const logger = createLogger({
1703
+ verbose: global.verbose,
1704
+ quiet: global.quiet,
1705
+ json: global.json,
1706
+ ci: global.ci
1707
+ });
1708
+ const ctx = {
1709
+ projectRoot: process.cwd(),
1710
+ config: DEFAULT_WOODSPORTAL_HS,
1711
+ logger,
1712
+ global
1713
+ };
1714
+ await handle(
1715
+ () => runInit(ctx, {
1716
+ template: opts.template,
1717
+ name: opts.name,
1718
+ directory: opts.directory
1719
+ })
1720
+ );
1721
+ });
1722
+ program2.command("adopt").description("Detect existing module/theme repo and add WoodsCLI config + .wphs/").action(async () => {
1723
+ const global = program2.opts();
1724
+ await handle(() => runAdopt(buildContext(global)));
1725
+ });
1726
+ program2.command("migrate").description("Migrate legacy .dev/ or .wp-hs-baseline/ to .wphs/").action(async () => {
1727
+ const global = program2.opts();
1728
+ await handle(() => runMigrate(buildContext(global)));
1729
+ });
1730
+ program2.command("dev").alias("local").description("Start local development server (Vite HMR + CMS dev tool)").option("-p, --port <port>", "Port", "3000").action(async (opts) => {
1731
+ const global = program2.opts();
1732
+ await handle(() => runDev(buildContext(global), { port: Number(opts.port) }));
1733
+ });
1734
+ program2.command("build").description("Build CDN + HubSpot module (or theme no-op)").option("--tier <tier>", "dev | stg | prod", "prod").option("--cdn-only", "CDN chunks only").option("--hubspot-only", "HubSpot module only").option("--cdn-esm", "Build CDN via ESM preset").action(
1735
+ async (opts) => {
1736
+ const global = program2.opts();
1737
+ await handle(
1738
+ () => runBuild(buildContext(global), {
1739
+ tier: parseTier(opts.tier),
1740
+ cdnOnly: opts.cdnOnly,
1741
+ hubspotOnly: opts.hubspotOnly,
1742
+ cdnEsm: opts.cdnEsm
1743
+ })
1744
+ );
1745
+ }
1746
+ );
1747
+ program2.command("deploy").description("Build, publish CDN, upload HubSpot module").option("--tier <tier>", "dev | stg | prod", "prod").option("--skip-publish", "Skip CDN publish").action(async (opts) => {
1748
+ const global = program2.opts();
1749
+ await handle(
1750
+ () => runDeploy(buildContext(global), {
1751
+ tier: parseTier(opts.tier),
1752
+ skipPublish: opts.skipPublish
1753
+ })
1754
+ );
1755
+ });
1756
+ program2.command("publish").description("Publish CDN artifacts to jsDelivr mirror").option("--tier <tier>", "dev | stg | prod", "prod").option("--dry-run", "Print publish plan without pushing").action(async (opts) => {
1757
+ const global = program2.opts();
1758
+ await handle(
1759
+ () => runPublish(buildContext(global), {
1760
+ tier: parseTier(opts.tier),
1761
+ dryRun: opts.dryRun
1762
+ })
1763
+ );
1764
+ });
1765
+ const audit = program2.command("audit").description("Build audits");
1766
+ audit.command("ship-bundle").description("Fail if dev tooling appears in dist artifacts").action(async () => {
1767
+ const global = program2.opts();
1768
+ await handle(() => runAuditShipBundle(buildContext(global)));
1769
+ });
1770
+ audit.command("parity").description("Compare dist output to baseline snapshot").option("--baseline <dir>", "Baseline directory").action(async (opts) => {
1771
+ const global = program2.opts();
1772
+ const ctx = buildContext(global);
1773
+ const baseline = opts.baseline ?? defaultBaselineDir(ctx.projectRoot, ctx.config);
1774
+ await handle(() => runAuditParity(ctx, { baselineDir: baseline }));
1775
+ });
1776
+ audit.command("baseline").description("Capture dist output as parity baseline").option("--baseline <dir>", "Baseline directory").action(async (opts) => {
1777
+ const global = program2.opts();
1778
+ const ctx = buildContext(global);
1779
+ const baseline = opts.baseline ?? defaultBaselineDir(ctx.projectRoot, ctx.config);
1780
+ await handle(() => runAuditBaselineCapture(ctx, { baselineDir: baseline }));
1781
+ });
1782
+ audit.command("secrets").description("Scan source for hardcoded secrets / PAT patterns").action(async () => {
1783
+ const global = program2.opts();
1784
+ await handle(() => runAuditSecrets(buildContext(global)));
1785
+ });
1786
+ const fields = program2.command("fields").description("Module fields.json utilities");
1787
+ fields.command("sync").description("Pull/push fields.json vs local cache").option("--direction <dir>", "pull | push", "pull").action(async (opts) => {
1788
+ const global = program2.opts();
1789
+ const direction = opts.direction === "push" ? "push" : "pull";
1790
+ await handle(() => runFieldsSync(buildContext(global), { direction }));
1791
+ });
1792
+ const examples = program2.command("examples").description("Examples smoke matrix");
1793
+ examples.command("list").description("List scenarios from examples/manifest.json").option("--track <track>", "module | theme").option("--setup <setup>", "greenfield | existing | adopted").action(async (opts) => {
1794
+ const global = program2.opts();
1795
+ await handle(
1796
+ () => runExamplesList(buildContext(global), {
1797
+ track: opts.track,
1798
+ setup: opts.setup
1799
+ })
1800
+ );
1801
+ });
1802
+ examples.command("smoke").description("Run static/live smoke checks for example scenarios").option("--scenario <id>", "Single scenario id").option("--tier <tier>", "static | live", "static").action(async (opts) => {
1803
+ const global = program2.opts();
1804
+ const tier = opts.tier === "live" ? "live" : "static";
1805
+ await handle(
1806
+ () => runExamplesSmoke(buildContext(global), { scenario: opts.scenario, tier })
1807
+ );
1808
+ });
1809
+ const regression = program2.command("regression").description("Compatibility regression checks");
1810
+ regression.command("smoke").description("Doctor + config validate + consumer boundary checks").option("--skip-doctor", "Skip prerequisite doctor check").action(async (opts) => {
1811
+ const global = program2.opts();
1812
+ await handle(
1813
+ () => runRegressionSmoke(buildContext(global), { skipDoctor: opts.skipDoctor })
1814
+ );
1815
+ });
1816
+ program2.command("watch").description("Watch HubSpot module or theme folder and re-upload on change").option("--tier <tier>", "dev | stg | prod", "dev").option("--rebuild", "Rebuild module before watch").action(async (opts) => {
1817
+ const global = program2.opts();
1818
+ const tier = parseTier(opts.tier);
1819
+ await handle(
1820
+ () => runWatch(buildContext(global), {
1821
+ tier: tier === "prod" ? "prod" : tier === "stg" ? "stg" : "dev",
1822
+ rebuild: opts.rebuild
1823
+ })
1824
+ );
1825
+ });
1826
+ program2.command("upload").description("Upload built HubSpot module or theme (hs cms upload)").option("--tier <tier>", "dev | stg | prod", "prod").action(async (opts) => {
1827
+ const global = program2.opts();
1828
+ await handle(
1829
+ () => runUpload(buildContext(global), { tier: parseTier(opts.tier) })
1830
+ );
1831
+ });
1832
+ program2.command("auth").description("Authenticate HubSpot CLI (hs auth)").action(async () => {
1833
+ const global = program2.opts();
1834
+ const ctx = buildContext(global);
1835
+ const { runCommand: runCommand2 } = await import("./exec-K5C6LZP4.js");
1836
+ runCommand2("hs", ["auth"], {
1837
+ cwd: ctx.projectRoot,
1838
+ logger: ctx.logger,
1839
+ dryRun: ctx.global.dryRun
1840
+ });
1841
+ process.exit(ExitCode.SUCCESS);
1842
+ });
1843
+ return program2;
1844
+ }
1845
+
1846
+ // src/cli/index.ts
1847
+ var program = createProgram();
1848
+ program.parse(process.argv);
1849
+ //# sourceMappingURL=cli.js.map