@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/index.js ADDED
@@ -0,0 +1,204 @@
1
+ import {
2
+ ExitCode,
3
+ WOODSCLI_CONFIG_FILE,
4
+ WpHsError,
5
+ createViteConfig,
6
+ findProjectRoot,
7
+ getKitRoot,
8
+ getViteConfigPath,
9
+ kitPath,
10
+ resolveProjectConfig
11
+ } from "./chunk-U4KFGTEW.js";
12
+
13
+ // src/schema/woodsportal-hs.ts
14
+ import { z } from "zod";
15
+ var CONFIG_VERSION = 1;
16
+ var WPHS_WORKSPACE_DIR = ".wphs";
17
+ var WoodsPortalHsSchema = z.object({
18
+ configVersion: z.number().int().default(CONFIG_VERSION),
19
+ projectType: z.enum(["module-hybrid-cdn", "module-monolith", "theme-starter"]),
20
+ module: z.object({
21
+ name: z.string().min(1),
22
+ uploadPath: z.string().min(1),
23
+ outDir: z.string().default("dist/WoodsPortal.Module")
24
+ }),
25
+ theme: z.object({
26
+ name: z.string().min(1),
27
+ uploadPath: z.string().min(1),
28
+ srcDir: z.string().default("src/theme"),
29
+ themeJson: z.string().default("src/theme/theme.json")
30
+ }).optional(),
31
+ cdn: z.object({
32
+ mode: z.enum(["jsdelivr", "custom", "local", "none"]).default("jsdelivr"),
33
+ buildIdFile: z.string().default(".portal-build-id"),
34
+ mirror: z.string().default("portal.cdn.json")
35
+ }).default({}),
36
+ entries: z.object({
37
+ app: z.string().default("src/cdn/app-entry.ts"),
38
+ vendor: z.string().default("src/cdn/vendor-entry.ts"),
39
+ hubspotBootstrap: z.string().default("src/hubspot/bootstrap.ts"),
40
+ localDev: z.string().default("src/main.tsx")
41
+ }).default({}),
42
+ env: z.object({
43
+ tiers: z.array(z.enum(["dev", "stg", "prod"])).default(["dev", "stg", "prod"])
44
+ }).default({}),
45
+ dev: z.object({
46
+ moduleConfigDir: z.literal(WPHS_WORKSPACE_DIR).default(WPHS_WORKSPACE_DIR),
47
+ adapter: z.string().default("./wphs.adapter.ts"),
48
+ fieldsJson: z.string().default("public/fields.json"),
49
+ themeFieldsJson: z.string().optional()
50
+ }).default({})
51
+ });
52
+ var DEFAULT_WOODSPORTAL_HS = {
53
+ configVersion: CONFIG_VERSION,
54
+ projectType: "module-hybrid-cdn",
55
+ module: {
56
+ name: "woodsportal-module",
57
+ uploadPath: "dw-modules/woodsportal-module.module",
58
+ outDir: "dist/WoodsPortal.Module"
59
+ },
60
+ cdn: {
61
+ mode: "jsdelivr",
62
+ buildIdFile: ".portal-build-id",
63
+ mirror: "portal.cdn.json"
64
+ },
65
+ entries: {
66
+ app: "src/cdn/app-entry.ts",
67
+ vendor: "src/cdn/vendor-entry.ts",
68
+ hubspotBootstrap: "src/hubspot/bootstrap.ts",
69
+ localDev: "src/main.tsx"
70
+ },
71
+ env: {
72
+ tiers: ["dev", "stg", "prod"]
73
+ },
74
+ dev: {
75
+ moduleConfigDir: WPHS_WORKSPACE_DIR,
76
+ adapter: "./wphs.adapter.ts",
77
+ fieldsJson: "public/fields.json"
78
+ }
79
+ };
80
+ function isModuleProject(config) {
81
+ return config.projectType === "module-hybrid-cdn" || config.projectType === "module-monolith";
82
+ }
83
+ function isThemeProject(config) {
84
+ return config.projectType === "theme-starter";
85
+ }
86
+ function usesCdnPipeline(config) {
87
+ return config.projectType === "module-hybrid-cdn" && config.cdn.mode !== "none";
88
+ }
89
+
90
+ // src/lib/config.ts
91
+ import { readFileSync } from "fs";
92
+ function loadConfig(projectRoot) {
93
+ const root = projectRoot ?? findProjectRoot();
94
+ const resolved = resolveProjectConfig(root);
95
+ if (!resolved) {
96
+ throw new WpHsError(`Missing ${WOODSCLI_CONFIG_FILE}`, {
97
+ exitCode: ExitCode.USER_ERROR,
98
+ remediation: "Run wp init or wp adopt. Legacy woodsportal.hs.json \u2192 run wp migrate --yes."
99
+ });
100
+ }
101
+ let raw;
102
+ try {
103
+ raw = JSON.parse(readFileSync(resolved.path, "utf8"));
104
+ } catch (cause) {
105
+ throw new WpHsError(`Invalid JSON in ${resolved.fileName}`, {
106
+ exitCode: ExitCode.USER_ERROR,
107
+ cause
108
+ });
109
+ }
110
+ const parsed = WoodsPortalHsSchema.safeParse(raw);
111
+ if (!parsed.success) {
112
+ throw new WpHsError(`${resolved.fileName} validation failed: ${parsed.error.message}`, {
113
+ exitCode: ExitCode.USER_ERROR,
114
+ remediation: "Run wp config validate for details."
115
+ });
116
+ }
117
+ return {
118
+ root,
119
+ config: parsed.data,
120
+ configPath: resolved.path,
121
+ configFileName: resolved.fileName
122
+ };
123
+ }
124
+ function validateConfigFile(projectRoot) {
125
+ try {
126
+ const { config } = loadConfig(projectRoot);
127
+ return { valid: true, errors: [], config };
128
+ } catch (error) {
129
+ if (error instanceof WpHsError) {
130
+ return { valid: false, errors: [error.message] };
131
+ }
132
+ return { valid: false, errors: [String(error)] };
133
+ }
134
+ }
135
+
136
+ // src/adapters/types.ts
137
+ var MODULE_CONFIG_CHANGED = "woodsportal:module-config-changed";
138
+ function bindAdapterToModuleConfig(adapter) {
139
+ if (typeof globalThis.window === "undefined") return;
140
+ globalThis.window.addEventListener(MODULE_CONFIG_CHANGED, (event) => {
141
+ const detail = event.detail;
142
+ adapter.syncLiveData(detail.built);
143
+ });
144
+ }
145
+ function syncAdapterFromModuleState(adapter, state) {
146
+ adapter.syncLiveData(state.built);
147
+ }
148
+ var defaultHubSpotKitAdapter = {
149
+ syncLiveData(built) {
150
+ const w = globalThis;
151
+ w.__WPHS_DATA__ = built;
152
+ },
153
+ getDefaultHubSpotData() {
154
+ return {};
155
+ }
156
+ };
157
+
158
+ // src/lib/generic-field-mapper.ts
159
+ function flattenField(field, values, out) {
160
+ const key = field.name;
161
+ const value = values[key];
162
+ if (field.type === "group" && field.children && value && typeof value === "object") {
163
+ const groupValues = value;
164
+ for (const child of field.children) {
165
+ flattenField(child, groupValues, out);
166
+ }
167
+ return;
168
+ }
169
+ if (field.type === "repeater" && Array.isArray(value)) {
170
+ out[key] = value;
171
+ return;
172
+ }
173
+ out[key] = value ?? field.default ?? null;
174
+ }
175
+ function mapFieldsToRuntime(schema, values) {
176
+ const out = {};
177
+ for (const field of schema) {
178
+ flattenField(field, values, out);
179
+ }
180
+ return out;
181
+ }
182
+ export {
183
+ CONFIG_VERSION,
184
+ DEFAULT_WOODSPORTAL_HS,
185
+ ExitCode,
186
+ WPHS_WORKSPACE_DIR,
187
+ WoodsPortalHsSchema,
188
+ WpHsError,
189
+ bindAdapterToModuleConfig,
190
+ createViteConfig,
191
+ defaultHubSpotKitAdapter,
192
+ findProjectRoot,
193
+ getKitRoot,
194
+ getViteConfigPath,
195
+ isModuleProject,
196
+ isThemeProject,
197
+ kitPath,
198
+ loadConfig,
199
+ mapFieldsToRuntime,
200
+ syncAdapterFromModuleState,
201
+ usesCdnPipeline,
202
+ validateConfigFile
203
+ };
204
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/schema/woodsportal-hs.ts","../src/lib/config.ts","../src/adapters/types.ts","../src/lib/generic-field-mapper.ts"],"sourcesContent":["import { z } from 'zod'\n\nexport const CONFIG_VERSION = 1\n\nexport const WPHS_WORKSPACE_DIR = '.wphs' as const\n\nexport const WoodsPortalHsSchema = z.object({\n configVersion: z.number().int().default(CONFIG_VERSION),\n projectType: z.enum(['module-hybrid-cdn', 'module-monolith', 'theme-starter']),\n module: z.object({\n name: z.string().min(1),\n uploadPath: z.string().min(1),\n outDir: z.string().default('dist/WoodsPortal.Module'),\n }),\n theme: z\n .object({\n name: z.string().min(1),\n uploadPath: z.string().min(1),\n srcDir: z.string().default('src/theme'),\n themeJson: z.string().default('src/theme/theme.json'),\n })\n .optional(),\n cdn: z\n .object({\n mode: z.enum(['jsdelivr', 'custom', 'local', 'none']).default('jsdelivr'),\n buildIdFile: z.string().default('.portal-build-id'),\n mirror: z.string().default('portal.cdn.json'),\n })\n .default({}),\n entries: z\n .object({\n app: z.string().default('src/cdn/app-entry.ts'),\n vendor: z.string().default('src/cdn/vendor-entry.ts'),\n hubspotBootstrap: z.string().default('src/hubspot/bootstrap.ts'),\n localDev: z.string().default('src/main.tsx'),\n })\n .default({}),\n env: z\n .object({\n tiers: z.array(z.enum(['dev', 'stg', 'prod'])).default(['dev', 'stg', 'prod']),\n })\n .default({}),\n dev: z\n .object({\n moduleConfigDir: z.literal(WPHS_WORKSPACE_DIR).default(WPHS_WORKSPACE_DIR),\n adapter: z.string().default('./wphs.adapter.ts'),\n fieldsJson: z.string().default('public/fields.json'),\n themeFieldsJson: z.string().optional(),\n })\n .default({}),\n})\n\nexport type WoodsPortalHsConfig = z.infer<typeof WoodsPortalHsSchema>\n\nexport const DEFAULT_WOODSPORTAL_HS: WoodsPortalHsConfig = {\n configVersion: CONFIG_VERSION,\n projectType: 'module-hybrid-cdn',\n module: {\n name: 'woodsportal-module',\n uploadPath: 'dw-modules/woodsportal-module.module',\n outDir: 'dist/WoodsPortal.Module',\n },\n cdn: {\n mode: 'jsdelivr',\n buildIdFile: '.portal-build-id',\n mirror: 'portal.cdn.json',\n },\n entries: {\n app: 'src/cdn/app-entry.ts',\n vendor: 'src/cdn/vendor-entry.ts',\n hubspotBootstrap: 'src/hubspot/bootstrap.ts',\n localDev: 'src/main.tsx',\n },\n env: {\n tiers: ['dev', 'stg', 'prod'],\n },\n dev: {\n moduleConfigDir: WPHS_WORKSPACE_DIR,\n adapter: './wphs.adapter.ts',\n fieldsJson: 'public/fields.json',\n },\n}\n\nexport function isModuleProject(config: WoodsPortalHsConfig): boolean {\n return config.projectType === 'module-hybrid-cdn' || config.projectType === 'module-monolith'\n}\n\nexport function isThemeProject(config: WoodsPortalHsConfig): boolean {\n return config.projectType === 'theme-starter'\n}\n\nexport function usesCdnPipeline(config: WoodsPortalHsConfig): boolean {\n return config.projectType === 'module-hybrid-cdn' && config.cdn.mode !== 'none'\n}\n","import { readFileSync } from 'node:fs'\n\nimport {\n resolveProjectConfig,\n WOODSCLI_CONFIG_FILE,\n} from './config-files.js'\nimport { WoodsPortalHsSchema, type WoodsPortalHsConfig } from '../schema/woodsportal-hs.js'\nimport { WpHsError, ExitCode } from './errors.js'\nimport { findProjectRoot } from './paths.js'\n\nexport type LoadedProjectConfig = {\n root: string\n config: WoodsPortalHsConfig\n configPath: string\n configFileName: string\n}\n\nexport function loadConfig(projectRoot?: string): LoadedProjectConfig {\n const root = projectRoot ?? findProjectRoot()\n const resolved = resolveProjectConfig(root)\n\n if (!resolved) {\n throw new WpHsError(`Missing ${WOODSCLI_CONFIG_FILE}`, {\n exitCode: ExitCode.USER_ERROR,\n remediation: 'Run wp init or wp adopt. Legacy woodsportal.hs.json → run wp migrate --yes.',\n })\n }\n\n let raw: unknown\n try {\n raw = JSON.parse(readFileSync(resolved.path, 'utf8'))\n } catch (cause) {\n throw new WpHsError(`Invalid JSON in ${resolved.fileName}`, {\n exitCode: ExitCode.USER_ERROR,\n cause,\n })\n }\n\n const parsed = WoodsPortalHsSchema.safeParse(raw)\n if (!parsed.success) {\n throw new WpHsError(`${resolved.fileName} validation failed: ${parsed.error.message}`, {\n exitCode: ExitCode.USER_ERROR,\n remediation: 'Run wp config validate for details.',\n })\n }\n\n return {\n root,\n config: parsed.data,\n configPath: resolved.path,\n configFileName: resolved.fileName,\n }\n}\n\nexport function validateConfigFile(projectRoot?: string): {\n valid: boolean\n errors: string[]\n config?: WoodsPortalHsConfig\n} {\n try {\n const { config } = loadConfig(projectRoot)\n return { valid: true, errors: [], config }\n } catch (error) {\n if (error instanceof WpHsError) {\n return { valid: false, errors: [error.message] }\n }\n return { valid: false, errors: [String(error)] }\n }\n}\n","/** Must match dev-module-config/schema/types.ts */\nexport const MODULE_CONFIG_CHANGED = 'woodsportal:module-config-changed'\n\nexport type ModuleConfigBuiltState = {\n built: Record<string, unknown>\n}\n\n/** WoodsCLI consumer hook — mount app + sync module-config live data. */\nexport interface HubSpotKitAdapter {\n syncLiveData(built: Record<string, unknown>): void\n getDefaultHubSpotData?(): Record<string, unknown>\n mountDevApp?(root: unknown): void | Promise<void>\n}\n\nexport function bindAdapterToModuleConfig(adapter: HubSpotKitAdapter): void {\n if (typeof globalThis.window === 'undefined') return\n globalThis.window.addEventListener(MODULE_CONFIG_CHANGED, (event: Event) => {\n const detail = (event as CustomEvent<ModuleConfigBuiltState>).detail\n adapter.syncLiveData(detail.built)\n })\n}\n\nexport function syncAdapterFromModuleState(\n adapter: HubSpotKitAdapter,\n state: ModuleConfigBuiltState,\n): void {\n adapter.syncLiveData(state.built)\n}\n\n/** Minimal adapter for greenfield templates without a full app. */\nexport const defaultHubSpotKitAdapter: HubSpotKitAdapter = {\n syncLiveData(built) {\n const w = globalThis as typeof globalThis & { __WPHS_DATA__?: Record<string, unknown> }\n w.__WPHS_DATA__ = built\n },\n getDefaultHubSpotData() {\n return {}\n },\n}\n","/**\n * Generic fields.json → flat runtime object (WoodsCLI default transform).\n */\nexport type GenericFieldDefinition = {\n name: string\n type: string\n default?: unknown\n children?: GenericFieldDefinition[]\n}\n\nexport type GenericFieldValues = Record<string, unknown>\n\nfunction flattenField(\n field: GenericFieldDefinition,\n values: GenericFieldValues,\n out: Record<string, unknown>,\n): void {\n const key = field.name\n const value = values[key]\n\n if (field.type === 'group' && field.children && value && typeof value === 'object') {\n const groupValues = value as GenericFieldValues\n for (const child of field.children) {\n flattenField(child, groupValues, out)\n }\n return\n }\n\n if (field.type === 'repeater' && Array.isArray(value)) {\n out[key] = value\n return\n }\n\n out[key] = value ?? field.default ?? null\n}\n\nexport function mapFieldsToRuntime(\n schema: GenericFieldDefinition[],\n values: GenericFieldValues,\n): Record<string, unknown> {\n const out: Record<string, unknown> = {}\n for (const field of schema) {\n flattenField(field, values, out)\n }\n return out\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,17 @@
1
+ type VitePreset = 'dev' | 'hubspot' | 'cdn-vendor' | 'cdn-app' | 'cdn-esm';
2
+ declare function getViteConfigPath(preset: VitePreset): string;
3
+
4
+ type CreateViteConfigOptions = {
5
+ /** Vite preset shipped with @woodsportal/hubspot-kit */
6
+ preset: VitePreset;
7
+ /** Consumer project root (sets WP_HS_PROJECT_ROOT for kit scripts) */
8
+ projectRoot?: string;
9
+ };
10
+ /**
11
+ * Returns the absolute path to a kit Vite config preset.
12
+ * Consumer repos typically re-export: `export { default } from '@woodsportal/hubspot-kit/vite/vite.config.hubspot.mjs'`
13
+ * or use this helper in a thin wrapper config.
14
+ */
15
+ declare function createViteConfig(presetOrOptions: VitePreset | CreateViteConfigOptions): string;
16
+
17
+ export { type CreateViteConfigOptions, type VitePreset, createViteConfig, getViteConfigPath };
@@ -0,0 +1,9 @@
1
+ import {
2
+ createViteConfig,
3
+ getViteConfigPath
4
+ } from "../chunk-U4KFGTEW.js";
5
+ export {
6
+ createViteConfig,
7
+ getViteConfigPath
8
+ };
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,12 @@
1
+ # WoodsCLI examples
2
+
3
+ Runnable catalog of module/theme setups and CLI use cases. See [docs/EXAMPLES.md](../docs/EXAMPLES.md).
4
+
5
+ ```bash
6
+ npm run test:examples # static smoke (CI)
7
+ wp examples list # print manifest
8
+ wp examples smoke --tier static
9
+ wp examples smoke --scenario existing-module-hybrid
10
+ ```
11
+
12
+ Fixtures under `fixtures/` simulate existing, adopted, and greenfield layouts. Greenfield live smokes require full templates (Phase 2).
@@ -0,0 +1 @@
1
+ [{"name":"title","label":"Title","type":"text","default":"Hello"}]
@@ -0,0 +1 @@
1
+ <div>{{ module.title }}</div>
@@ -0,0 +1,7 @@
1
+ {
2
+ "configVersion": 1,
3
+ "projectType": "module-hybrid-cdn",
4
+ "module": {"name": "demo", "uploadPath": "dw-modules/demo.module", "outDir": "dist/Demo.Module"},
5
+ "cdn": {"mode": "jsdelivr", "buildIdFile": ".portal-build-id", "mirror": "portal.cdn.json"},
6
+ "dev": {"moduleConfigDir": ".wphs", "adapter": "./wphs.adapter.ts", "fieldsJson": "public/fields.json"}
7
+ }
@@ -0,0 +1 @@
1
+ [{"name":"title","label":"Title","type":"text","default":"Hello"}]
@@ -0,0 +1,7 @@
1
+ {
2
+ "configVersion": 1,
3
+ "projectType": "module-monolith",
4
+ "module": {"name": "demo", "uploadPath": "dw-modules/demo.module", "outDir": "dist/Demo.Module"},
5
+ "cdn": {"mode": "none", "buildIdFile": ".portal-build-id", "mirror": "portal.cdn.json"},
6
+ "dev": {"moduleConfigDir": ".wphs", "fieldsJson": "public/fields.json"}
7
+ }
@@ -0,0 +1 @@
1
+ <html><body>{% block body %}{% endblock %}</body></html>
@@ -0,0 +1 @@
1
+ {"label":"Minimal","preview_path":"templates/page.html"}
@@ -0,0 +1,7 @@
1
+ {
2
+ "configVersion": 1,
3
+ "projectType": "theme-starter",
4
+ "module": {"name": "n/a", "uploadPath": "n/a", "outDir": "dist/n/a"},
5
+ "theme": {"name": "minimal", "uploadPath": "themes/minimal", "srcDir": "src/theme", "themeJson": "src/theme/theme.json"},
6
+ "dev": {"moduleConfigDir": ".wphs", "fieldsJson": "public/fields.json"}
7
+ }
@@ -0,0 +1 @@
1
+ [{"name":"title","label":"Title","type":"text","default":"Hello"}]
@@ -0,0 +1 @@
1
+ <div>{{ module.title }}</div>
@@ -0,0 +1 @@
1
+ <html><body>{% block body %}{% endblock %}</body></html>
@@ -0,0 +1 @@
1
+ {"label":"Minimal","preview_path":"templates/page.html"}
@@ -0,0 +1,9 @@
1
+ # HubSpot portal module (hybrid CDN)
2
+
3
+ Scaffolded by `wp init --template module-hybrid-cdn`.
4
+
5
+ ```bash
6
+ npm install
7
+ wp doctor
8
+ wp dev
9
+ ```
@@ -0,0 +1 @@
1
+ <!doctype html><html><head><meta charset="UTF-8"/><title>WoodsCLI dev</title></head><body><div id="app"></div><script type="module" src="/src/main.tsx"></script></body></html>
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "my-hubspot-portal-module",
3
+ "private": true,
4
+ "type": "module",
5
+ "scripts": {
6
+ "local": "wp dev",
7
+ "doctor": "wp doctor",
8
+ "build:prod": "wp build --tier prod",
9
+ "deploy:prod": "wp deploy --tier prod"
10
+ },
11
+ "devDependencies": {
12
+ "@woodsportal/hubspot-kit": "^1.0.0"
13
+ }
14
+ }
@@ -0,0 +1 @@
1
+ [{"name":"title","label":"Title","type":"text","default":"WoodsCLI module"}]
@@ -0,0 +1 @@
1
+ <div id="app"></div>
@@ -0,0 +1,2 @@
1
+ import { mountDevPortal } from '@woodsportal/hubspot-kit/dev/bootstrap'
2
+ void mountDevPortal()
@@ -0,0 +1 @@
1
+ export { default } from '@woodsportal/hubspot-kit/vite/vite.config.dev.mjs'
@@ -0,0 +1 @@
1
+ export { default } from '@woodsportal/hubspot-kit/vite/vite.config.hubspot.mjs'
@@ -0,0 +1,8 @@
1
+ {
2
+ "configVersion": 1,
3
+ "projectType": "module-hybrid-cdn",
4
+ "module": {"name": "my-portal-module", "uploadPath": "dw-modules/my-portal-module.module", "outDir": "dist/MyPortal.Module"},
5
+ "cdn": {"mode": "jsdelivr", "buildIdFile": ".portal-build-id", "mirror": "portal.cdn.json"},
6
+ "entries": {"app": "src/cdn/app-entry.ts", "vendor": "src/cdn/vendor-entry.ts", "hubspotBootstrap": "src/hubspot/bootstrap.ts", "localDev": "src/main.tsx"},
7
+ "dev": {"moduleConfigDir": ".wphs", "adapter": "./wphs.adapter.ts", "fieldsJson": "public/fields.json"}
8
+ }
@@ -0,0 +1,7 @@
1
+ import type { HubSpotKitAdapter } from '@woodsportal/hubspot-kit'
2
+ const adapter: HubSpotKitAdapter = {
3
+ syncLiveData(built) {
4
+ ;(window as Window & { __WPHS_DATA__?: Record<string, unknown> }).__WPHS_DATA__ = built
5
+ },
6
+ }
7
+ export default adapter
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "my-hubspot-monolith-module",
3
+ "private": true,
4
+ "type": "module",
5
+ "scripts": {
6
+ "local": "wp dev",
7
+ "build:prod": "wp build --tier prod --hubspot-only"
8
+ },
9
+ "devDependencies": {
10
+ "@woodsportal/hubspot-kit": "^1.0.0"
11
+ }
12
+ }
@@ -0,0 +1 @@
1
+ [{"name":"title","label":"Title","type":"text","default":"WoodsCLI module"}]
@@ -0,0 +1 @@
1
+ export { default } from '@woodsportal/hubspot-kit/vite/vite.config.dev.mjs'
@@ -0,0 +1 @@
1
+ export { default } from '@woodsportal/hubspot-kit/vite/vite.config.monolith.mjs'
@@ -0,0 +1,25 @@
1
+ {
2
+ "configVersion": 1,
3
+ "projectType": "module-monolith",
4
+ "module": {
5
+ "name": "my-portal-module",
6
+ "uploadPath": "dw-modules/my-portal-module.module",
7
+ "outDir": "dist/MyPortal.Module"
8
+ },
9
+ "cdn": {
10
+ "mode": "none",
11
+ "buildIdFile": ".portal-build-id",
12
+ "mirror": "portal.cdn.json"
13
+ },
14
+ "entries": {
15
+ "app": "src/cdn/app-entry.ts",
16
+ "vendor": "src/cdn/vendor-entry.ts",
17
+ "hubspotBootstrap": "src/hubspot/bootstrap.ts",
18
+ "localDev": "src/main.tsx"
19
+ },
20
+ "dev": {
21
+ "moduleConfigDir": ".wphs",
22
+ "adapter": "./wphs.adapter.ts",
23
+ "fieldsJson": "public/fields.json"
24
+ }
25
+ }
@@ -0,0 +1,7 @@
1
+ import type { HubSpotKitAdapter } from '@woodsportal/hubspot-kit'
2
+ const adapter: HubSpotKitAdapter = {
3
+ syncLiveData(built) {
4
+ ;(window as Window & { __WPHS_DATA__?: Record<string, unknown> }).__WPHS_DATA__ = built
5
+ },
6
+ }
7
+ export default adapter
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "my-hubspot-theme",
3
+ "private": true,
4
+ "type": "module",
5
+ "scripts": {
6
+ "doctor": "wp doctor"
7
+ },
8
+ "devDependencies": {
9
+ "@woodsportal/hubspot-kit": "^1.0.0"
10
+ }
11
+ }
@@ -0,0 +1 @@
1
+ body { font-family: system-ui; }
@@ -0,0 +1 @@
1
+ <!doctype html><html><head><link rel="stylesheet" href="../css/main.css"/></head><body>{% block body %}{% endblock %}</body></html>
@@ -0,0 +1 @@
1
+ {% extends "./base.html" %}{% block body %}<h1>WoodsCLI theme</h1>{% endblock %}
@@ -0,0 +1 @@
1
+ {"label":"My Theme","preview_path":"templates/page.html"}
@@ -0,0 +1,6 @@
1
+ {
2
+ "label": "My WoodsPortal Theme",
3
+ "preview_path": "./templates/home.html",
4
+ "enable_domain_stylesheets": false,
5
+ "is_available_for_new_content": true
6
+ }
@@ -0,0 +1 @@
1
+ export { default } from '@woodsportal/hubspot-kit/vite/vite.config.dev.mjs'
@@ -0,0 +1,7 @@
1
+ {
2
+ "configVersion": 1,
3
+ "projectType": "theme-starter",
4
+ "module": {"name": "n/a", "uploadPath": "n/a", "outDir": "dist/n/a"},
5
+ "theme": {"name": "my-theme", "uploadPath": "themes/my-theme", "srcDir": "src/theme", "themeJson": "src/theme/theme.json"},
6
+ "dev": {"moduleConfigDir": ".wphs", "fieldsJson": "public/fields.json"}
7
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "version": 1,
3
+ "scenarios": [
4
+ { "id": "greenfield-module-hybrid-cdn", "setup": "greenfield", "track": "module", "projectType": "module-hybrid-cdn", "smokeTier": "live", "fixture": "greenfield/module-hybrid-cdn", "checks": ["layout"] },
5
+ { "id": "greenfield-module-monolith", "setup": "greenfield", "track": "module", "projectType": "module-monolith", "smokeTier": "live", "fixture": "greenfield/module-monolith", "checks": ["layout"] },
6
+ { "id": "greenfield-theme-starter", "setup": "greenfield", "track": "theme", "projectType": "theme-starter", "smokeTier": "live", "fixture": "greenfield/theme-starter", "checks": ["layout"] },
7
+ { "id": "existing-module-hybrid", "setup": "existing", "track": "module", "smokeTier": "static", "fixture": "fixtures/existing-module-hybrid" },
8
+ { "id": "existing-theme-minimal", "setup": "existing", "track": "theme", "smokeTier": "static", "fixture": "fixtures/existing-theme-minimal" },
9
+ { "id": "adopt-module-hybrid", "setup": "adopted", "track": "module", "projectType": "module-hybrid-cdn", "smokeTier": "static", "fixture": "fixtures/adopted-module-hybrid" },
10
+ { "id": "adopt-theme-minimal", "setup": "adopted", "track": "theme", "projectType": "theme-starter", "smokeTier": "static", "fixture": "fixtures/adopted-theme-minimal" },
11
+ { "id": "cdn-mode-jsdelivr", "setup": "adopted", "track": "module", "projectType": "module-hybrid-cdn", "smokeTier": "static", "fixture": "fixtures/adopted-module-hybrid", "checks": ["cdn.mode=jsdelivr"] },
12
+ { "id": "cdn-mode-none-monolith", "setup": "adopted", "track": "module", "projectType": "module-monolith", "smokeTier": "static", "fixture": "fixtures/adopted-module-monolith" },
13
+ { "id": "build-split-cdn-only", "setup": "adopted", "track": "module", "smokeTier": "static", "fixture": "fixtures/adopted-module-hybrid", "checks": ["build.cdn-only.dry-run"] },
14
+ { "id": "build-split-hubspot-only", "setup": "adopted", "track": "module", "smokeTier": "static", "fixture": "fixtures/adopted-module-hybrid", "checks": ["build.hubspot-only.dry-run"] },
15
+ { "id": "audit-ship-bundle", "setup": "adopted", "track": "module", "smokeTier": "static", "checks": ["audit.ship-bundle.doc"] },
16
+ { "id": "audit-parity-baseline", "setup": "adopted", "track": "module", "smokeTier": "static", "checks": ["audit.baseline.path"] },
17
+ { "id": "migrate-dev-to-wphs", "setup": "adopted", "track": "module", "smokeTier": "static", "checks": ["migrate.doc"] },
18
+ { "id": "module-config-overlay", "setup": "adopted", "track": "module", "smokeTier": "live", "fixture": "fixtures/adopted-module-hybrid", "checks": ["wphs.config.dir"] }
19
+ ]
20
+ }
@@ -0,0 +1,100 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Run examples smoke matrix (static tier default — no HubSpot account required).
4
+ */
5
+ import { existsSync, mkdirSync, readFileSync } from 'node:fs'
6
+ import { dirname, join, resolve } from 'node:path'
7
+ import { fileURLToPath } from 'node:url'
8
+
9
+ import { detectProjectLayout } from '../../scripts/detect-layout.mjs'
10
+
11
+ const __dirname = dirname(fileURLToPath(import.meta.url))
12
+ const examplesRoot = resolve(__dirname, '..')
13
+ const kitRoot = resolve(examplesRoot, '..')
14
+
15
+ const tierArg = process.argv.indexOf('--tier')
16
+ const tier = tierArg >= 0 ? process.argv[tierArg + 1] : 'static'
17
+ const scenarioArg = process.argv.indexOf('--scenario')
18
+ const scenarioFilter = scenarioArg >= 0 ? process.argv[scenarioArg + 1] : null
19
+
20
+ const manifest = JSON.parse(readFileSync(join(examplesRoot, 'manifest.json'), 'utf8'))
21
+
22
+ /** @param {import('../../scripts/detect-layout.mjs').detectProjectLayout extends Function ? ReturnType<import('../../scripts/detect-layout.mjs').detectProjectLayout> : never} layout */
23
+ function assertLayout(layout, expectedTrack) {
24
+ if (layout.track !== expectedTrack) {
25
+ throw new Error(`Expected track ${expectedTrack}, got ${layout.track}`)
26
+ }
27
+ }
28
+
29
+ /** @param {string} fixtureRel */
30
+ function fixtureRoot(fixtureRel) {
31
+ return resolve(examplesRoot, fixtureRel)
32
+ }
33
+
34
+ /** @param {{ id: string; track?: string; fixture?: string; checks?: string[]; projectType?: string }} scenario */
35
+ function runStaticScenario(scenario) {
36
+ const checks = scenario.checks ?? ['layout', 'wphs.tree']
37
+
38
+ if (checks.includes('layout') && scenario.fixture) {
39
+ const root = fixtureRoot(scenario.fixture)
40
+ if (!existsSync(root)) {
41
+ throw new Error(`Missing fixture ${scenario.fixture}`)
42
+ }
43
+ const layout = detectProjectLayout(root)
44
+ if (scenario.track && layout.track !== scenario.track) {
45
+ throw new Error(`${scenario.id}: track mismatch`)
46
+ }
47
+ if (scenario.projectType && layout.projectType !== scenario.projectType) {
48
+ throw new Error(`${scenario.id}: projectType mismatch`)
49
+ }
50
+ }
51
+
52
+ if (checks.includes('wphs.tree') || checks.includes('wphs.config.dir')) {
53
+ const root = scenario.fixture ? fixtureRoot(scenario.fixture) : kitRoot
54
+ const layout = detectProjectLayout(root)
55
+ if (layout.hasWphs || existsSync(join(root, '.wphs', 'config'))) {
56
+ // ok
57
+ } else if (scenario.setup === 'existing') {
58
+ // existing fixtures may lack .wphs until adopt
59
+ } else if (scenario.setup === 'adopted') {
60
+ throw new Error(`${scenario.id}: expected .wphs workspace`)
61
+ }
62
+ }
63
+
64
+ if (checks.includes('audit.baseline.path')) {
65
+ const baseline = join(examplesRoot, 'fixtures', 'adopted-module-hybrid', '.wphs', 'audit', 'baseline')
66
+ if (!existsSync(baseline)) {
67
+ mkdirSync(baseline, { recursive: true })
68
+ }
69
+ }
70
+
71
+ return { id: scenario.id, ok: true }
72
+ }
73
+
74
+ const results = []
75
+ let scenarios = manifest.scenarios
76
+ if (scenarioFilter) scenarios = scenarios.filter((s) => s.id === scenarioFilter)
77
+
78
+ for (const scenario of scenarios) {
79
+ if (tier === 'live' && scenario.smokeTier !== 'live') {
80
+ results.push({ id: scenario.id, ok: true, skipped: 'not live tier' })
81
+ continue
82
+ }
83
+ if (tier === 'static') {
84
+ try {
85
+ runStaticScenario(scenario)
86
+ results.push({ id: scenario.id, ok: true })
87
+ console.log(`[examples:smoke] OK ${scenario.id}`)
88
+ } catch (error) {
89
+ results.push({ id: scenario.id, ok: false, error: error instanceof Error ? error.message : String(error) })
90
+ console.error(`[examples:smoke] FAIL ${scenario.id}:`, error)
91
+ }
92
+ }
93
+ }
94
+
95
+ const failed = results.filter((r) => r.ok === false)
96
+ if (failed.length > 0) {
97
+ process.exit(1)
98
+ }
99
+
100
+ console.log(`[examples:smoke] ${results.length} scenario(s) passed (${tier})`)