@woodsportal/hubspot-kit 1.0.31 → 1.0.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +45 -0
- package/dist/cli.js +798 -281
- package/dist/cli.js.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/examples/fixtures/adopted-module-hybrid/woodscli.json +1 -1
- package/examples/fixtures/adopted-module-monolith/woodscli.json +1 -1
- package/examples/greenfield/module-hybrid-cdn/public/fields.json +26 -20
- package/examples/greenfield/module-hybrid-cdn/public/meta.json +9 -0
- package/examples/greenfield/module-hybrid-cdn/public/module.html +3 -4
- package/examples/greenfield/module-hybrid-cdn/src/assets/css/main.prod.css +6 -0
- package/examples/greenfield/module-hybrid-cdn/src/cdn/app-entry.ts +5 -0
- package/examples/greenfield/module-hybrid-cdn/src/hubspot/bootstrap.ts +6 -0
- package/examples/greenfield/module-hybrid-cdn/src/portal/ensure-portal-cdn-stylesheet.ts +58 -0
- package/examples/greenfield/module-hybrid-cdn/woodscli.json +1 -1
- package/examples/greenfield/module-monolith/woodscli.json +1 -1
- package/package.json +2 -2
- package/scripts/build-cdn.mjs +6 -1
- package/scripts/bump-module-build-id.mjs +28 -0
- package/scripts/hubspot-module-postbuild.mjs +35 -1
- package/scripts/load-portal-cdn-config.mjs +21 -3
- package/scripts/portal-build-id.mjs +346 -0
- package/scripts/portal-build-id.test.mjs +132 -0
- package/scripts/publish-cdn-github.mjs +60 -15
- package/scripts/resolve-cdn-base-url.mjs +2 -1
- package/scripts/resolve-module-build-id-meta.mjs +7 -0
- package/scripts/resolve-portal-build-id.mjs +16 -56
- package/scripts/tailwind/prefix-plugin.js +46 -8
- package/scripts/tailwind/tailwind-content-plugin.js +0 -1
- package/scripts/vite/portal-build-shared.js +3 -1
- package/src/dev-module-config/__tests__/flatten-visible-fields.test.ts +107 -0
- package/src/dev-module-config/runtime/module-config-bridge.ts +10 -2
- package/src/dev-module-config/schema/types.ts +10 -0
- package/src/dev-module-config/schema/visibility.ts +42 -4
- package/src/dev-module-config/ui/ModuleConfigShell.tsx +10 -9
- package/src/dev-module-config/ui/hubspot/HsOccurrenceGroupEditor.tsx +3 -3
- package/templates/module-hybrid-cdn/public/fields.json +52 -41
- package/templates/module-hybrid-cdn/public/meta.json +9 -0
- package/templates/module-hybrid-cdn/public/module.html +18 -4
- package/templates/module-hybrid-cdn/src/assets/css/main.dev.css +8 -0
- package/templates/module-hybrid-cdn/src/assets/css/main.prod.css +12 -0
- package/templates/module-hybrid-cdn/src/assets/css/tailwind.css.config.css +7 -0
- package/templates/module-hybrid-cdn/src/cdn/app-entry.ts +14 -3
- package/templates/module-hybrid-cdn/src/features/starter/components/starter-home.tsx +9 -11
- package/templates/module-hybrid-cdn/src/features/starter/hooks/starter-preview-values.ts +23 -5
- package/templates/module-hybrid-cdn/src/features/starter/hooks/use-starter-preview.dev.ts +1 -0
- package/templates/module-hybrid-cdn/src/hubspot/bootstrap.ts +6 -0
- package/templates/module-hybrid-cdn/src/portal/ensure-portal-cdn-stylesheet.ts +115 -0
- package/templates/module-hybrid-cdn/src/portal/mount-portal.tsx +35 -5
- package/templates/module-hybrid-cdn/woodscli.json +1 -1
- package/templates/module-monolith/public/fields.json +23 -16
- package/templates/module-monolith/public/meta.json +9 -0
- package/templates/module-monolith/src/features/starter/components/starter-home.tsx +9 -11
- package/templates/module-monolith/src/features/starter/hooks/starter-preview-values.ts +23 -5
- package/templates/module-monolith/src/features/starter/hooks/use-starter-preview.dev.ts +1 -0
- package/templates/module-monolith/woodscli.json +1 -1
- package/vite/vite.config.dev.mjs +16 -10
package/dist/index.js
CHANGED
|
@@ -30,7 +30,7 @@ var WoodsPortalHsSchema = z.object({
|
|
|
30
30
|
}).optional(),
|
|
31
31
|
cdn: z.object({
|
|
32
32
|
mode: z.enum(["jsdelivr", "custom", "local", "none"]).default("jsdelivr"),
|
|
33
|
-
buildIdFile: z.string().default(".
|
|
33
|
+
buildIdFile: z.string().default(".module-build-id"),
|
|
34
34
|
mirror: z.string().default("portal.cdn.json")
|
|
35
35
|
}).default({}),
|
|
36
36
|
entries: z.object({
|
|
@@ -65,7 +65,7 @@ var DEFAULT_WOODSPORTAL_HS = {
|
|
|
65
65
|
},
|
|
66
66
|
cdn: {
|
|
67
67
|
mode: "jsdelivr",
|
|
68
|
-
buildIdFile: ".
|
|
68
|
+
buildIdFile: ".module-build-id",
|
|
69
69
|
mirror: "portal.cdn.json"
|
|
70
70
|
},
|
|
71
71
|
entries: {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +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 /** Monolith production IIFE entry (wp build --hubspot-only) */\n module: z.string().default('src/hubspot/module-entry.tsx'),\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 scaffold: z\n .object({\n /** hash = woodsportal-client-frontend (HubSpot CMS); browser = local clean URLs; none = single page */\n router: z.enum(['hash', 'browser', 'none']).default('hash'),\n })\n .optional(),\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 module: 'src/hubspot/module-entry.tsx',\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. 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":[]}
|
|
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('.module-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 /** Monolith production IIFE entry (wp build --hubspot-only) */\n module: z.string().default('src/hubspot/module-entry.tsx'),\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 scaffold: z\n .object({\n /** hash = woodsportal-client-frontend (HubSpot CMS); browser = local clean URLs; none = single page */\n router: z.enum(['hash', 'browser', 'none']).default('hash'),\n })\n .optional(),\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: '.module-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 module: 'src/hubspot/module-entry.tsx',\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. 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":[]}
|
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
"configVersion": 1,
|
|
3
3
|
"projectType": "module-hybrid-cdn",
|
|
4
4
|
"module": {"name": "demo", "uploadPath": "dw-modules/demo.module", "outDir": "dist/Demo.Module"},
|
|
5
|
-
"cdn": {"mode": "jsdelivr", "buildIdFile": ".
|
|
5
|
+
"cdn": {"mode": "jsdelivr", "buildIdFile": ".module-build-id", "mirror": "portal.cdn.json"},
|
|
6
6
|
"dev": {"moduleConfigDir": ".wphs", "adapter": "./wphs.adapter.ts", "fieldsJson": "public/fields.json"}
|
|
7
7
|
}
|
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
"configVersion": 1,
|
|
3
3
|
"projectType": "module-monolith",
|
|
4
4
|
"module": {"name": "demo", "uploadPath": "dw-modules/demo.module", "outDir": "dist/Demo.Module"},
|
|
5
|
-
"cdn": {"mode": "none", "buildIdFile": ".
|
|
5
|
+
"cdn": {"mode": "none", "buildIdFile": ".module-build-id", "mirror": "portal.cdn.json"},
|
|
6
6
|
"dev": {"moduleConfigDir": ".wphs", "fieldsJson": "public/fields.json"}
|
|
7
7
|
}
|
|
@@ -24,26 +24,32 @@
|
|
|
24
24
|
"default": true
|
|
25
25
|
},
|
|
26
26
|
{
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"
|
|
27
|
+
"type": "group",
|
|
28
|
+
"name": "styles",
|
|
29
|
+
"label": "Styles",
|
|
30
30
|
"tab": "STYLE",
|
|
31
|
-
"
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
31
|
+
"children": [
|
|
32
|
+
{
|
|
33
|
+
"name": "accent_color",
|
|
34
|
+
"label": "Accent color",
|
|
35
|
+
"type": "color",
|
|
36
|
+
"default": {
|
|
37
|
+
"color": "#0091ae",
|
|
38
|
+
"opacity": 100
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"name": "hero_style",
|
|
43
|
+
"label": "Hero style",
|
|
44
|
+
"type": "choice",
|
|
45
|
+
"display": "select",
|
|
46
|
+
"choices": [
|
|
47
|
+
["gradient", "Gradient"],
|
|
48
|
+
["minimal", "Minimal"],
|
|
49
|
+
["bold", "Bold"]
|
|
50
|
+
],
|
|
51
|
+
"default": "gradient"
|
|
52
|
+
}
|
|
53
|
+
]
|
|
48
54
|
}
|
|
49
55
|
]
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"label": "WoodsPortal hybrid module",
|
|
3
|
+
"css_assets": [],
|
|
4
|
+
"external_js": [],
|
|
5
|
+
"global": false,
|
|
6
|
+
"host_template_types": ["PAGE", "BLOG_POST", "BLOG_LISTING"],
|
|
7
|
+
"content_types": ["SITE_PAGE", "LANDING_PAGE", "BLOG_POST", "BLOG_LISTING"],
|
|
8
|
+
"is_available_for_new_content": true
|
|
9
|
+
}
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
title: {{ module.title|tojson }},
|
|
7
7
|
tagline: {{ module.tagline|tojson }},
|
|
8
8
|
show_feature_cards: {{ module.show_feature_cards|lower }},
|
|
9
|
-
accent_color: {{ module.accent_color|tojson }},
|
|
10
|
-
hero_style: {{ module.hero_style|tojson }},
|
|
9
|
+
accent_color: {{ module.styles.accent_color|tojson }},
|
|
10
|
+
hero_style: {{ module.styles.hero_style|tojson }},
|
|
11
11
|
assetBaseUrl: "__PORTAL_CDN_BASE__",
|
|
12
12
|
buildId: "__PORTAL_BUILD_ID__",
|
|
13
13
|
isInEditor: {{ in_cms_editor|lower }}
|
|
@@ -16,8 +16,7 @@
|
|
|
16
16
|
{% set portal_cdn_base = "__PORTAL_CDN_BASE__" %}
|
|
17
17
|
{% set portal_vendor_script = "__PORTAL_VENDOR_FILE__" %}
|
|
18
18
|
{% set portal_app_script = "__PORTAL_APP_FILE__" %}
|
|
19
|
-
{
|
|
20
|
-
{{ require_css(portal_cdn_base ~ '/' ~ portal_app_css, { position: 'footer' }) }}
|
|
19
|
+
{# App styles ship in module.css (HubSpot editor cannot rely on external CDN CSS). #}
|
|
21
20
|
{{ require_js(portal_cdn_base ~ '/' ~ portal_vendor_script, { position: 'footer', defer: true }) }}
|
|
22
21
|
{{ require_js(portal_cdn_base ~ '/' ~ portal_app_script, { position: 'footer', defer: true }) }}
|
|
23
22
|
<div id="app"></div>
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
@import 'tailwindcss' prefix(dw);
|
|
2
|
+
|
|
3
|
+
/* Prefixed utilities from tailwind-content.js (.temp) — must match prefix plugin on TSX */
|
|
4
|
+
@source '../../../.temp/**/*.{js,jsx,ts,tsx}';
|
|
5
|
+
@source '../../../.wphs/.kit-module-config/**/*.{ts,tsx}';
|
|
6
|
+
@source '../../../node_modules/@woodsportal/hubspot-kit/src/dev-module-config/**/*.{ts,tsx}';
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* CDN application chunk — mounts after vendor.js (HubSpot require_js order).
|
|
3
|
+
*
|
|
4
|
+
* Build extracts main.dev.css → app.{buildId}.css (linked from module.html).
|
|
5
|
+
* Runtime also injects that stylesheet when HubSpot preview skips require_css.
|
|
3
6
|
*/
|
|
4
7
|
import '@/assets/css/main.dev.css'
|
|
5
8
|
|
|
6
9
|
import { resolvePortalApiEndpoint } from '@/portal/api-endpoint'
|
|
10
|
+
import { ensurePortalCdnStylesheet } from '@/portal/ensure-portal-cdn-stylesheet'
|
|
7
11
|
import { mountPortal } from '@/portal/mount-portal'
|
|
8
12
|
|
|
9
13
|
declare const __PORTAL_BUILD_ID__: string | undefined
|
|
@@ -31,6 +35,7 @@ async function autoMount() {
|
|
|
31
35
|
if (!document.getElementById('app')) {
|
|
32
36
|
return
|
|
33
37
|
}
|
|
38
|
+
await ensurePortalCdnStylesheet()
|
|
34
39
|
await mountPortal()
|
|
35
40
|
}
|
|
36
41
|
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Thin HubSpot module.js bootstrap — JS app loads from CDN via require_js in module.html.
|
|
3
|
+
*
|
|
4
|
+
* CSS imports are extracted to module.css by the HubSpot Vite build (same pattern as
|
|
5
|
+
* digitalwoods.io-woodsportal-client-frontend). HubSpot CMS editor does not reliably
|
|
6
|
+
* load external jsDelivr stylesheets from module.html.
|
|
3
7
|
*/
|
|
8
|
+
import '@/assets/css/main.dev.css'
|
|
9
|
+
|
|
4
10
|
declare global {
|
|
5
11
|
interface Window {
|
|
6
12
|
WoodsPortalHubSpotBootstrap?: { ready: true }
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
declare global {
|
|
2
|
+
interface Window {
|
|
3
|
+
hubSpotData?: {
|
|
4
|
+
assetBaseUrl?: string
|
|
5
|
+
buildId?: string
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Resolve app.{buildId}.css on the CDN mirror (jsDelivr / custom base). */
|
|
11
|
+
export function portalCdnStylesheetUrl(): string | null {
|
|
12
|
+
const base = window.hubSpotData?.assetBaseUrl?.replace(/\/$/, '')
|
|
13
|
+
const buildId = window.hubSpotData?.buildId?.trim()
|
|
14
|
+
if (!base || !buildId) {
|
|
15
|
+
return null
|
|
16
|
+
}
|
|
17
|
+
return `${base}/app.${buildId}.css`
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function waitForStylesheet(link: HTMLLinkElement): Promise<void> {
|
|
21
|
+
if (link.sheet) {
|
|
22
|
+
return Promise.resolve()
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return new Promise((resolve) => {
|
|
26
|
+
link.addEventListener('load', () => resolve(), { once: true })
|
|
27
|
+
link.addEventListener('error', () => resolve(), { once: true })
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* HubSpot module.html uses require_css for app.*.css, but the page editor preview
|
|
33
|
+
* can skip external footer stylesheets. Inject the CDN stylesheet before React mounts.
|
|
34
|
+
*/
|
|
35
|
+
export async function ensurePortalCdnStylesheet(): Promise<void> {
|
|
36
|
+
const href = portalCdnStylesheetUrl()
|
|
37
|
+
if (!href) {
|
|
38
|
+
return
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const marker = 'data-portal-cdn-css'
|
|
42
|
+
const existing = document.querySelector<HTMLLinkElement>(`link[${marker}]`)
|
|
43
|
+
if (existing) {
|
|
44
|
+
if (existing.href === href) {
|
|
45
|
+
await waitForStylesheet(existing)
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
existing.remove()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const link = document.createElement('link')
|
|
52
|
+
link.rel = 'stylesheet'
|
|
53
|
+
link.href = href
|
|
54
|
+
link.crossOrigin = 'anonymous'
|
|
55
|
+
link.setAttribute(marker, 'true')
|
|
56
|
+
document.head.appendChild(link)
|
|
57
|
+
await waitForStylesheet(link)
|
|
58
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"configVersion": 1,
|
|
3
3
|
"projectType": "module-hybrid-cdn",
|
|
4
4
|
"module": {"name": "my-portal-module", "uploadPath": "dw-modules/my-portal-module.module", "outDir": "dist/MyPortal.Module"},
|
|
5
|
-
"cdn": {"mode": "jsdelivr", "buildIdFile": ".
|
|
5
|
+
"cdn": {"mode": "jsdelivr", "buildIdFile": ".module-build-id", "mirror": "portal.cdn.json"},
|
|
6
6
|
"entries": {"app": "src/cdn/app-entry.ts", "vendor": "src/cdn/vendor-entry.ts", "hubspotBootstrap": "src/hubspot/bootstrap.ts", "localDev": "src/main.tsx"},
|
|
7
7
|
"dev": {"moduleConfigDir": ".wphs", "adapter": "./wphs.adapter.ts", "fieldsJson": "public/fields.json"}
|
|
8
8
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@woodsportal/hubspot-kit",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.34",
|
|
4
4
|
"description": "WoodsCLI — HubSpot CMS dev kit by WoodsPortal. wp CLI, Vite presets, module-config overlay, and theme/module build pipelines.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"build": "tsup",
|
|
52
52
|
"dev": "tsup --watch",
|
|
53
53
|
"typecheck": "tsc --noEmit",
|
|
54
|
-
"test": "node --import tsx --test 'src/lib/**/*.test.ts' 'src/schema/**/*.test.ts' 'src/vite/**/*.test.ts' && vitest run src/dev-module-config/__tests__/normalize-repeater-items.test.ts src/dev-module-config/__tests__/module-config-transform.test.ts",
|
|
54
|
+
"test": "node --import tsx --test 'src/lib/**/*.test.ts' 'src/schema/**/*.test.ts' 'src/vite/**/*.test.ts' 'scripts/portal-build-id.test.mjs' && vitest run src/dev-module-config/__tests__/normalize-repeater-items.test.ts src/dev-module-config/__tests__/module-config-transform.test.ts src/dev-module-config/__tests__/flatten-visible-fields.test.ts",
|
|
55
55
|
"test:examples": "node examples/smoke/run-matrix.mjs --tier static",
|
|
56
56
|
"lint": "eslint src",
|
|
57
57
|
"verify": "npm run typecheck && npm run test && npm run test:examples && npm run build",
|
package/scripts/build-cdn.mjs
CHANGED
|
@@ -9,7 +9,8 @@ import { findProjectRoot } from './project-root.mjs'
|
|
|
9
9
|
|
|
10
10
|
import { envWithPortalBuildId } from './resolve-portal-build-id.mjs'
|
|
11
11
|
import { envForPortalBuild, defaultDatadogFlag } from './portal-vite-env.mjs'
|
|
12
|
-
import { resolvePortalViteMode } from './portal-env-mode.mjs'
|
|
12
|
+
import { isPrefixBuildMode, resolvePortalViteMode } from './portal-env-mode.mjs'
|
|
13
|
+
import tailwindContent from './tailwind/tailwind-content.js'
|
|
13
14
|
import { resolveViteConfig } from './resolve-vite-config.mjs'
|
|
14
15
|
import {
|
|
15
16
|
exitOnSpawnFailure,
|
|
@@ -21,6 +22,10 @@ const root = findProjectRoot()
|
|
|
21
22
|
|
|
22
23
|
const mode = resolvePortalViteMode()
|
|
23
24
|
|
|
25
|
+
if (isPrefixBuildMode(mode)) {
|
|
26
|
+
await tailwindContent()
|
|
27
|
+
}
|
|
28
|
+
|
|
24
29
|
const env = envForPortalBuild(mode, root, {
|
|
25
30
|
VITE_DATADOG_RUM_ENABLED: defaultDatadogFlag(mode),
|
|
26
31
|
})
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Bump .wphs/state/module-build-id.json and print JSON result to stdout.
|
|
4
|
+
* Used by wp deploy / wp build --cdn-only before CDN builds.
|
|
5
|
+
*/
|
|
6
|
+
import { bumpModuleBuildId, clearPortalBuildIdCache } from './portal-build-id.mjs'
|
|
7
|
+
import { findProjectRoot } from './project-root.mjs'
|
|
8
|
+
|
|
9
|
+
const args = new Set(process.argv.slice(2))
|
|
10
|
+
const dryRun = args.has('--dry-run')
|
|
11
|
+
const tierArg = [...args].find((a) => a.startsWith('--tier='))
|
|
12
|
+
const tier = tierArg?.split('=')[1]
|
|
13
|
+
|
|
14
|
+
clearPortalBuildIdCache()
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
const result = bumpModuleBuildId({
|
|
18
|
+
projectRoot: findProjectRoot(),
|
|
19
|
+
tier,
|
|
20
|
+
dryRun,
|
|
21
|
+
env: process.env,
|
|
22
|
+
})
|
|
23
|
+
process.stdout.write(`${JSON.stringify(result)}\n`)
|
|
24
|
+
} catch (error) {
|
|
25
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
26
|
+
console.error(`[bump-module-build-id] ERROR: ${message}`)
|
|
27
|
+
process.exit(1)
|
|
28
|
+
}
|
|
@@ -22,6 +22,7 @@ const ALLOWED_DIST_FILES = new Set([
|
|
|
22
22
|
'module.js',
|
|
23
23
|
'module.css',
|
|
24
24
|
'fields.json',
|
|
25
|
+
'meta.json',
|
|
25
26
|
])
|
|
26
27
|
|
|
27
28
|
/**
|
|
@@ -89,6 +90,35 @@ export function injectCdnManifestIntoModuleHtml(html, manifestPath) {
|
|
|
89
90
|
.replaceAll('__PORTAL_APP_CSS_FILE__', appCss.file)
|
|
90
91
|
}
|
|
91
92
|
|
|
93
|
+
/**
|
|
94
|
+
* HubSpot serves module.css with every module instance (including the CMS editor preview).
|
|
95
|
+
* External jsDelivr stylesheets from module.html are often blocked in the editor iframe.
|
|
96
|
+
*/
|
|
97
|
+
export function mergeCdnAppStylesIntoModuleCss(moduleDir, manifestPath) {
|
|
98
|
+
if (!existsSync(manifestPath)) {
|
|
99
|
+
return false
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'))
|
|
103
|
+
const appCss = manifest.styles?.find((s) => s.name === 'app') ?? manifest.styles?.[0]
|
|
104
|
+
if (!appCss?.file) {
|
|
105
|
+
throw new Error('manifest.json must include app styles for module.css merge')
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const cdnCssPath = resolve(moduleDir, '../cdn', appCss.file)
|
|
109
|
+
if (!existsSync(cdnCssPath)) {
|
|
110
|
+
throw new Error(
|
|
111
|
+
`Missing ${cdnCssPath} — run build:cdn before hubspot post-build (needed for module.css)`,
|
|
112
|
+
)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const banner =
|
|
116
|
+
'/* Hybrid CDN app styles — copied from dist/cdn at build time for HubSpot module.css */\n'
|
|
117
|
+
const css = readFileSync(cdnCssPath, 'utf8')
|
|
118
|
+
writeFileSync(resolve(moduleDir, 'module.css'), `${banner}${css}`, 'utf8')
|
|
119
|
+
return true
|
|
120
|
+
}
|
|
121
|
+
|
|
92
122
|
/**
|
|
93
123
|
* @param {string} moduleDir absolute path to dist/WoodsPortal.Module
|
|
94
124
|
* @param {string} publicDir absolute path to public/
|
|
@@ -120,7 +150,7 @@ export function finalizeHubSpotModuleDist(moduleDir, publicDir, options = {}) {
|
|
|
120
150
|
}
|
|
121
151
|
}
|
|
122
152
|
|
|
123
|
-
for (const file of ['module.html', 'fields.json']) {
|
|
153
|
+
for (const file of ['module.html', 'fields.json', 'meta.json']) {
|
|
124
154
|
const src = resolve(publicDir, file)
|
|
125
155
|
if (!existsSync(src)) {
|
|
126
156
|
throw new Error(`Missing public/${file} (required for HubSpot module upload)`)
|
|
@@ -134,6 +164,10 @@ export function finalizeHubSpotModuleDist(moduleDir, publicDir, options = {}) {
|
|
|
134
164
|
}
|
|
135
165
|
}
|
|
136
166
|
|
|
167
|
+
if (existsSync(manifestPath)) {
|
|
168
|
+
mergeCdnAppStylesIntoModuleCss(moduleDir, manifestPath)
|
|
169
|
+
}
|
|
170
|
+
|
|
137
171
|
const escaped = escapeHubSpotModuleJs(readFileSync(jsPath, 'utf8'))
|
|
138
172
|
assertNoForbiddenHubLTokens(escaped, jsPath)
|
|
139
173
|
writeFileSync(jsPath, escaped, 'utf8')
|
|
@@ -28,8 +28,14 @@ function readWoodscliConfig(projectRoot) {
|
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
const LOCAL_CDN_MIRROR_FILE = 'portal.cdn.local.json'
|
|
32
|
+
|
|
31
33
|
function resolveMirrorPath(projectRoot, hsConfig) {
|
|
32
34
|
const mirror = hsConfig?.cdn?.mirror?.trim() || 'portal.cdn.json'
|
|
35
|
+
const localMirror = resolve(projectRoot, LOCAL_CDN_MIRROR_FILE)
|
|
36
|
+
if (existsSync(localMirror)) {
|
|
37
|
+
return localMirror
|
|
38
|
+
}
|
|
33
39
|
return resolve(projectRoot, mirror)
|
|
34
40
|
}
|
|
35
41
|
|
|
@@ -61,6 +67,8 @@ export function loadPortalCdnConfig(env = process.env, projectRoot) {
|
|
|
61
67
|
fileConfig.publishMode ||
|
|
62
68
|
DEFAULTS.publishMode
|
|
63
69
|
|
|
70
|
+
const usingLocalMirror = loadedFrom?.endsWith(LOCAL_CDN_MIRROR_FILE) ?? false
|
|
71
|
+
|
|
64
72
|
return {
|
|
65
73
|
publishMode,
|
|
66
74
|
cdnMode,
|
|
@@ -69,6 +77,7 @@ export function loadPortalCdnConfig(env = process.env, projectRoot) {
|
|
|
69
77
|
cdnPathInRepo: fileConfig.cdnPathInRepo || DEFAULTS.cdnPathInRepo,
|
|
70
78
|
defaultBranch: fileConfig.defaultBranch || DEFAULTS.defaultBranch,
|
|
71
79
|
configPath: loadedFrom,
|
|
80
|
+
usingLocalMirror,
|
|
72
81
|
}
|
|
73
82
|
}
|
|
74
83
|
|
|
@@ -82,10 +91,19 @@ export function envWithPortalCdnConfig(baseEnv = process.env, projectRoot) {
|
|
|
82
91
|
}
|
|
83
92
|
}
|
|
84
93
|
|
|
85
|
-
/**
|
|
86
|
-
|
|
94
|
+
/**
|
|
95
|
+
* jsDelivr base URL for GitHub CDN mirror.
|
|
96
|
+
* Uses the default branch ref (not the git tag): chunk filenames already include buildId,
|
|
97
|
+
* and jsDelivr indexes branches reliably (tag refs often 404 until propagated).
|
|
98
|
+
*/
|
|
99
|
+
export function jsDelivrBaseUrl(
|
|
100
|
+
github,
|
|
101
|
+
cdnPathInRepo = DEFAULTS.cdnPathInRepo,
|
|
102
|
+
branch = DEFAULTS.defaultBranch,
|
|
103
|
+
) {
|
|
87
104
|
const path = cdnPathInRepo.replace(/^\//, '').replace(/\/$/, '')
|
|
88
|
-
|
|
105
|
+
const ref = branch.replace(/^@/, '').trim() || DEFAULTS.defaultBranch
|
|
106
|
+
return `https://cdn.jsdelivr.net/gh/${github}@${ref}/${path}`
|
|
89
107
|
}
|
|
90
108
|
|
|
91
109
|
export { DEFAULTS }
|