@woodsportal/hubspot-kit 1.0.3 → 1.0.5

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 (78) hide show
  1. package/dist/cli.js +24 -1
  2. package/dist/cli.js.map +1 -1
  3. package/examples/greenfield/module-hybrid-cdn/src/assets/css/main.dev.css +3 -0
  4. package/examples/greenfield/module-hybrid-cdn/src/defaultData.ts +7 -0
  5. package/examples/greenfield/module-hybrid-cdn/src/hubspot/types.ts +17 -0
  6. package/examples/greenfield/module-hybrid-cdn/src/main.tsx +4 -2
  7. package/examples/greenfield/module-monolith/src/defaultData.ts +7 -0
  8. package/package.json +5 -1
  9. package/src/dev/bootstrap-greenfield.tsx +51 -0
  10. package/src/dev/bootstrap.tsx +46 -0
  11. package/src/dev-module-config/__tests__/fixtures/fields.json +3014 -0
  12. package/src/dev-module-config/__tests__/fixtures/golden-module.fixture.ts +102 -0
  13. package/src/dev-module-config/__tests__/module-config-storage.test.ts +40 -0
  14. package/src/dev-module-config/__tests__/module-config-transform.test.ts +55 -0
  15. package/src/dev-module-config/__tests__/normalize-repeater-items.test.ts +46 -0
  16. package/src/dev-module-config/runtime/module-config-bridge.ts +27 -0
  17. package/src/dev-module-config/runtime/module-config-runtime.ts +185 -0
  18. package/src/dev-module-config/schema/build-default-item.ts +7 -0
  19. package/src/dev-module-config/schema/deep-merge.ts +27 -0
  20. package/src/dev-module-config/schema/extract-defaults.ts +62 -0
  21. package/src/dev-module-config/schema/normalize-choices.ts +18 -0
  22. package/src/dev-module-config/schema/normalize-repeater-items.ts +31 -0
  23. package/src/dev-module-config/schema/parse-fields-json.ts +2 -0
  24. package/src/dev-module-config/schema/types.ts +75 -0
  25. package/src/dev-module-config/schema/validate-module-config.ts +81 -0
  26. package/src/dev-module-config/schema/visibility.ts +130 -0
  27. package/src/dev-module-config/storage/config-json.ts +46 -0
  28. package/src/dev-module-config/storage/workspace-store.ts +139 -0
  29. package/src/dev-module-config/transform/build-hubspot-data.ts +409 -0
  30. package/src/dev-module-config/ui/FieldRenderer.tsx +353 -0
  31. package/src/dev-module-config/ui/ModuleConfigAnimatedPresence.tsx +72 -0
  32. package/src/dev-module-config/ui/ModuleConfigCollapsible.tsx +43 -0
  33. package/src/dev-module-config/ui/ModuleConfigDockSideIcon.tsx +27 -0
  34. package/src/dev-module-config/ui/ModuleConfigOverlay.tsx +134 -0
  35. package/src/dev-module-config/ui/ModuleConfigPersistenceStatus.tsx +89 -0
  36. package/src/dev-module-config/ui/ModuleConfigShell.tsx +354 -0
  37. package/src/dev-module-config/ui/ModuleConfigThemeToggle.tsx +32 -0
  38. package/src/dev-module-config/ui/ModuleConfigUiContext.tsx +65 -0
  39. package/src/dev-module-config/ui/editors/DeveloperOptionsEditor.tsx +61 -0
  40. package/src/dev-module-config/ui/editors/MenuOptionsEditor.tsx +103 -0
  41. package/src/dev-module-config/ui/hubspot/HsColorField.tsx +106 -0
  42. package/src/dev-module-config/ui/hubspot/HsColorPickerPopover.tsx +210 -0
  43. package/src/dev-module-config/ui/hubspot/HsFieldLabel.tsx +24 -0
  44. package/src/dev-module-config/ui/hubspot/HsFieldWidgets.tsx +248 -0
  45. package/src/dev-module-config/ui/hubspot/HsImageField.tsx +112 -0
  46. package/src/dev-module-config/ui/hubspot/HsOccurrenceGroupEditor.tsx +243 -0
  47. package/src/dev-module-config/ui/hubspot/HsRepeaterList.tsx +178 -0
  48. package/src/dev-module-config/ui/hubspot/HsSectionHeader.tsx +20 -0
  49. package/src/dev-module-config/ui/hubspot/HsToggle.tsx +39 -0
  50. package/src/dev-module-config/ui/hubspot/HsToggleRow.tsx +30 -0
  51. package/src/dev-module-config/ui/hubspot/hs-color-palette.ts +11 -0
  52. package/src/dev-module-config/ui/hubspot/module-config-typography.ts +26 -0
  53. package/src/dev-module-config/ui/module-config-motion.css +134 -0
  54. package/src/dev-module-config/ui/module-config-motion.ts +28 -0
  55. package/src/dev-module-config/ui/module-config-overlay-side.ts +29 -0
  56. package/src/dev-module-config/ui/module-config-surfaces.ts +27 -0
  57. package/src/dev-module-config/ui/module-config-theme.css +483 -0
  58. package/src/dev-module-config/ui/module-config-toolbar-theme.ts +35 -0
  59. package/src/dev-module-config/ui/navigation/ModuleConfigNavContext.tsx +188 -0
  60. package/src/dev-module-config/ui/use-dev-module-config-live-revision.ts +22 -0
  61. package/src/dev-module-config/ui/use-hubspot-live-exports.ts +27 -0
  62. package/src/dev-module-config/ui/use-module-config.ts +124 -0
  63. package/src/routing/build-api-routes.ts +37 -0
  64. package/src/routing/route-format.ts +19 -0
  65. package/templates/module-hybrid-cdn/index.html +12 -1
  66. package/templates/module-hybrid-cdn/package.json +1 -1
  67. package/templates/module-hybrid-cdn/src/assets/css/main.dev.css +3 -0
  68. package/templates/module-hybrid-cdn/src/defaultData.ts +7 -0
  69. package/templates/module-hybrid-cdn/src/hubspot/types.ts +17 -0
  70. package/templates/module-hybrid-cdn/src/main.tsx +4 -2
  71. package/templates/module-monolith/index.html +12 -0
  72. package/templates/module-monolith/package.json +1 -1
  73. package/templates/module-monolith/src/assets/css/main.dev.css +3 -0
  74. package/templates/module-monolith/src/defaultData.ts +7 -0
  75. package/templates/module-monolith/src/hubspot/types.ts +17 -0
  76. package/templates/module-monolith/src/main.tsx +4 -0
  77. package/templates/theme-starter/package.json +1 -1
  78. package/vite/vite.config.dev.mjs +7 -1
@@ -0,0 +1,35 @@
1
+ export type ModuleConfigToolbarTheme = 'light' | 'dark'
2
+
3
+ const STORAGE_KEY = 'woodsportal:module-config-toolbar-theme'
4
+
5
+ export function detectPortalTheme(): ModuleConfigToolbarTheme {
6
+ if (typeof document === 'undefined') return 'light'
7
+ return document.documentElement.classList.contains('dark') ? 'dark' : 'light'
8
+ }
9
+
10
+ export function loadModuleConfigToolbarTheme(): ModuleConfigToolbarTheme {
11
+ if (typeof window === 'undefined') return 'light'
12
+ try {
13
+ const stored = window.localStorage.getItem(STORAGE_KEY)
14
+ if (stored === 'dark' || stored === 'light') return stored
15
+ } catch {
16
+ // ignore
17
+ }
18
+ return detectPortalTheme()
19
+ }
20
+
21
+ export function saveModuleConfigToolbarTheme(theme: ModuleConfigToolbarTheme): void {
22
+ try {
23
+ window.localStorage.setItem(STORAGE_KEY, theme)
24
+ } catch {
25
+ // ignore
26
+ }
27
+ }
28
+
29
+ export function toggleModuleConfigToolbarTheme(
30
+ theme: ModuleConfigToolbarTheme,
31
+ ): ModuleConfigToolbarTheme {
32
+ const next: ModuleConfigToolbarTheme = theme === 'dark' ? 'light' : 'dark'
33
+ saveModuleConfigToolbarTheme(next)
34
+ return next
35
+ }
@@ -0,0 +1,188 @@
1
+ import {
2
+ createContext,
3
+ useCallback,
4
+ useContext,
5
+ useMemo,
6
+ useState,
7
+ type ReactNode,
8
+ } from 'react'
9
+
10
+ export type BreadcrumbSegment = {
11
+ id: string
12
+ label: string
13
+ onClick?: () => void
14
+ }
15
+
16
+ type ModuleConfigNavContextValue = {
17
+ segments: Array<BreadcrumbSegment>
18
+ drillDownPanel: ReactNode | null
19
+ pushSegment: (segment: Omit<BreadcrumbSegment, 'onClick'> & { onPop?: () => void }) => void
20
+ popTo: (id: string) => void
21
+ resetNav: () => void
22
+ registerPopHandler: (id: string, handler: () => void) => void
23
+ /** @deprecated use registerDrillDownPanel */
24
+ setDrillDownPanel: (panel: ReactNode | null) => void
25
+ registerDrillDownPanel: (id: string, panel: ReactNode | null) => void
26
+ }
27
+
28
+ const ROOT_SEGMENT: BreadcrumbSegment = { id: 'contents', label: 'Contents' }
29
+
30
+ const ModuleConfigNavContext = createContext<ModuleConfigNavContextValue | null>(null)
31
+
32
+ export function ModuleConfigNavProvider({ children }: { children: ReactNode }) {
33
+ const [stack, setStack] = useState<Array<BreadcrumbSegment>>([ROOT_SEGMENT])
34
+ const [popHandlers, setPopHandlers] = useState<Record<string, () => void>>({})
35
+ const [drillDownPanels, setDrillDownPanels] = useState<Record<string, ReactNode>>({})
36
+ const [drillDownOrder, setDrillDownOrder] = useState<string[]>([])
37
+
38
+ const registerDrillDownPanel = useCallback((id: string, panel: ReactNode | null) => {
39
+ if (panel === null) {
40
+ setDrillDownPanels((prev) => {
41
+ if (!(id in prev)) return prev
42
+ const next = { ...prev }
43
+ delete next[id]
44
+ return next
45
+ })
46
+ setDrillDownOrder((order) => order.filter((entry) => entry !== id))
47
+ return
48
+ }
49
+
50
+ setDrillDownPanels((prev) => ({ ...prev, [id]: panel }))
51
+ setDrillDownOrder((order) => (order.includes(id) ? order : [...order, id]))
52
+ }, [])
53
+
54
+ const clearAllDrillDownPanels = useCallback(() => {
55
+ setDrillDownPanels({})
56
+ setDrillDownOrder([])
57
+ }, [])
58
+
59
+ const drillDownPanel = useMemo(() => {
60
+ const topId = drillDownOrder[drillDownOrder.length - 1]
61
+ return topId ? (drillDownPanels[topId] ?? null) : null
62
+ }, [drillDownOrder, drillDownPanels])
63
+
64
+ const setDrillDownPanel = useCallback(
65
+ (panel: ReactNode | null) => {
66
+ if (panel === null) {
67
+ clearAllDrillDownPanels()
68
+ return
69
+ }
70
+ registerDrillDownPanel('__legacy__', panel)
71
+ },
72
+ [clearAllDrillDownPanels, registerDrillDownPanel],
73
+ )
74
+
75
+ const registerPopHandler = useCallback((id: string, handler: () => void) => {
76
+ setPopHandlers((prev) => ({ ...prev, [id]: handler }))
77
+ }, [])
78
+
79
+ const popTo = useCallback(
80
+ (id: string) => {
81
+ if (id === 'module' || id === 'contents') {
82
+ for (const segment of stack.slice(1)) {
83
+ popHandlers[segment.id]?.()
84
+ }
85
+ setStack([ROOT_SEGMENT])
86
+ clearAllDrillDownPanels()
87
+ return
88
+ }
89
+
90
+ setStack((prev) => {
91
+ const index = prev.findIndex((segment) => segment.id === id)
92
+ if (index < 0) return prev
93
+
94
+ const removed = prev.slice(index + 1)
95
+ for (const segment of removed) {
96
+ popHandlers[segment.id]?.()
97
+ }
98
+
99
+ return prev.slice(0, index + 1)
100
+ })
101
+ },
102
+ [clearAllDrillDownPanels, popHandlers, stack],
103
+ )
104
+
105
+ const pushSegment = useCallback(
106
+ (segment: Omit<BreadcrumbSegment, 'onClick'> & { onPop?: () => void }) => {
107
+ if (segment.onPop) {
108
+ setPopHandlers((prev) => ({ ...prev, [segment.id]: segment.onPop! }))
109
+ }
110
+ setStack((prev) => {
111
+ const existing = prev.findIndex((item) => item.id === segment.id)
112
+ if (existing >= 0) {
113
+ return [...prev.slice(0, existing + 1), { id: segment.id, label: segment.label }]
114
+ }
115
+ return [...prev, { id: segment.id, label: segment.label }]
116
+ })
117
+ },
118
+ [],
119
+ )
120
+
121
+ const resetNav = useCallback(() => {
122
+ setStack([ROOT_SEGMENT])
123
+ clearAllDrillDownPanels()
124
+ }, [clearAllDrillDownPanels])
125
+
126
+ const segments = useMemo(() => {
127
+ const moduleTitle: BreadcrumbSegment = {
128
+ id: 'module',
129
+ label: 'woodsportal-module',
130
+ onClick: stack.length > 1 ? () => popTo('module') : undefined,
131
+ }
132
+
133
+ if (stack.length <= 1) {
134
+ return [ROOT_SEGMENT, moduleTitle]
135
+ }
136
+
137
+ const middle = stack.slice(1).map((segment) => ({
138
+ ...segment,
139
+ onClick: () => popTo(segment.id),
140
+ }))
141
+
142
+ if (middle.length > 2) {
143
+ const collapsed: Array<BreadcrumbSegment> = [
144
+ middle[0]!,
145
+ { id: 'ellipsis', label: '…' },
146
+ middle[middle.length - 1]!,
147
+ ]
148
+ return [ROOT_SEGMENT, moduleTitle, ...collapsed]
149
+ }
150
+
151
+ return [ROOT_SEGMENT, moduleTitle, ...middle]
152
+ }, [popTo, stack])
153
+
154
+ const value = useMemo(
155
+ () => ({
156
+ segments,
157
+ drillDownPanel,
158
+ pushSegment,
159
+ popTo,
160
+ resetNav,
161
+ registerPopHandler,
162
+ setDrillDownPanel,
163
+ registerDrillDownPanel,
164
+ }),
165
+ [
166
+ segments,
167
+ drillDownPanel,
168
+ pushSegment,
169
+ popTo,
170
+ resetNav,
171
+ registerPopHandler,
172
+ setDrillDownPanel,
173
+ registerDrillDownPanel,
174
+ ],
175
+ )
176
+
177
+ return (
178
+ <ModuleConfigNavContext.Provider value={value}>{children}</ModuleConfigNavContext.Provider>
179
+ )
180
+ }
181
+
182
+ export function useModuleConfigNav() {
183
+ const ctx = useContext(ModuleConfigNavContext)
184
+ if (!ctx) {
185
+ throw new Error('useModuleConfigNav must be used within ModuleConfigNavProvider')
186
+ }
187
+ return ctx
188
+ }
@@ -0,0 +1,22 @@
1
+ import { useEffect, useState } from 'react'
2
+ import { MODULE_CONFIG_CHANGED } from '../schema/types'
3
+
4
+ /**
5
+ * DEV-only: bump when module config is applied so the portal can re-read hubSpotData exports.
6
+ */
7
+ export function useDevModuleConfigLiveRevision(): number {
8
+ const [revision, setRevision] = useState(0)
9
+
10
+ useEffect(() => {
11
+ if (!import.meta.env.DEV) return
12
+
13
+ const onApplied = () => {
14
+ setRevision((value) => value + 1)
15
+ }
16
+
17
+ window.addEventListener(MODULE_CONFIG_CHANGED, onApplied)
18
+ return () => window.removeEventListener(MODULE_CONFIG_CHANGED, onApplied)
19
+ }, [])
20
+
21
+ return revision
22
+ }
@@ -0,0 +1,27 @@
1
+ import { useMemo } from 'react'
2
+ import { getDevHubSpotLiveExports, type DevHubSpotLiveExports } from '@/data/dev-hubspot-sync'
3
+ import { refreshHubSpotUserDetails } from '@/data/hubSpotData'
4
+ import { useDevModuleConfigLiveRevision } from './use-dev-module-config-live-revision'
5
+
6
+ /**
7
+ * DEV-only: re-read hubSpotData exports after module config Apply.
8
+ * Production callers receive revision 0 and should keep using module-level imports.
9
+ */
10
+ export function useHubSpotLiveExports(): {
11
+ revision: number
12
+ live: DevHubSpotLiveExports | null
13
+ } {
14
+ const revision = useDevModuleConfigLiveRevision()
15
+
16
+ return useMemo(() => {
17
+ if (!import.meta.env.DEV) {
18
+ return { revision: 0, live: null }
19
+ }
20
+
21
+ refreshHubSpotUserDetails()
22
+ return {
23
+ revision,
24
+ live: getDevHubSpotLiveExports(),
25
+ }
26
+ }, [revision])
27
+ }
@@ -0,0 +1,124 @@
1
+ import { useCallback, useEffect, useState } from 'react'
2
+ import {
3
+ getModuleConfigState,
4
+ getModuleFieldValues,
5
+ initModuleConfigRuntime,
6
+ updateModuleFieldValues,
7
+ } from '../runtime/module-config-runtime'
8
+ import { validateModuleConfig, type ValidationError } from '../schema/validate-module-config'
9
+ import { loadWorkspaceMeta } from '../storage/workspace-store'
10
+ import { MODULE_CONFIG_CHANGED } from '../schema/types'
11
+ import type { ModuleConfigState } from '../runtime/module-config-runtime'
12
+ import type { ModuleFieldValues } from '../schema/types'
13
+
14
+ export function useModuleConfig() {
15
+ const [state, setState] = useState<ModuleConfigState | null>(() => getModuleConfigState())
16
+ const [draftValues, setDraftValues] = useState<ModuleFieldValues>(() => getModuleFieldValues())
17
+ const [appliedValues, setAppliedValues] = useState<ModuleFieldValues>(() => getModuleFieldValues())
18
+ const [validationErrors, setValidationErrors] = useState<Array<ValidationError>>([])
19
+ const [persistenceSource, setPersistenceSource] = useState<'local' | 'example' | 'empty'>('empty')
20
+ const [isApplying, setIsApplying] = useState(false)
21
+
22
+ const isDirty =
23
+ JSON.stringify(draftValues) !== JSON.stringify(appliedValues)
24
+
25
+ useEffect(() => {
26
+ void loadWorkspaceMeta().then((source) => setPersistenceSource(source ?? 'empty'))
27
+ }, [appliedValues])
28
+
29
+ useEffect(() => {
30
+ const onChange = (event: Event) => {
31
+ const detail = (event as CustomEvent<ModuleConfigState>).detail
32
+ setState(detail)
33
+ setDraftValues(detail.moduleValues)
34
+ setAppliedValues(detail.moduleValues)
35
+ setValidationErrors([])
36
+ }
37
+ window.addEventListener(MODULE_CONFIG_CHANGED, onChange)
38
+ return () => window.removeEventListener(MODULE_CONFIG_CHANGED, onChange)
39
+ }, [])
40
+
41
+ const setFieldValue = useCallback((path: string, value: unknown) => {
42
+ setDraftValues((current) => {
43
+ const next = structuredClone(current)
44
+ const parts = path.split('.')
45
+ let cursor: Record<string, unknown> = next
46
+ for (let i = 0; i < parts.length - 1; i++) {
47
+ const key = parts[i]!
48
+ if (cursor[key] === undefined || cursor[key] === null) cursor[key] = {}
49
+ cursor = cursor[key] as Record<string, unknown>
50
+ }
51
+ cursor[parts[parts.length - 1]!] = value
52
+ return next
53
+ })
54
+ setValidationErrors((errors) => errors.filter((error) => !error.path.startsWith(path)))
55
+ }, [])
56
+
57
+ const applyChanges = useCallback(async (): Promise<boolean> => {
58
+ const currentState = getModuleConfigState()
59
+ if (!currentState) return false
60
+
61
+ const errors = validateModuleConfig(currentState.schema, draftValues)
62
+ if (errors.length > 0) {
63
+ setValidationErrors(errors)
64
+ return false
65
+ }
66
+
67
+ setIsApplying(true)
68
+ try {
69
+ const updated = await updateModuleFieldValues(draftValues)
70
+ setState(updated)
71
+ setDraftValues(updated.moduleValues)
72
+ setAppliedValues(updated.moduleValues)
73
+ setValidationErrors([])
74
+ const source = await loadWorkspaceMeta()
75
+ setPersistenceSource(source ?? 'local')
76
+ return true
77
+ } catch (error) {
78
+ console.error('[module-config] Apply failed', error)
79
+ setValidationErrors([
80
+ {
81
+ path: '_apply',
82
+ label: 'Apply',
83
+ message:
84
+ error instanceof Error
85
+ ? error.message
86
+ : 'Failed to save config. Restart yarn local if the dev server was started before the module-config plugin was added.',
87
+ },
88
+ ])
89
+ return false
90
+ } finally {
91
+ setIsApplying(false)
92
+ }
93
+ }, [draftValues])
94
+
95
+ const discardDraft = useCallback(() => {
96
+ setDraftValues(appliedValues)
97
+ setValidationErrors([])
98
+ }, [appliedValues])
99
+
100
+ const confirmDiscardIfDirty = useCallback((): boolean => {
101
+ if (!isDirty) return true
102
+ return window.confirm('You have unsaved changes. Discard them?')
103
+ }, [isDirty])
104
+
105
+ return {
106
+ state,
107
+ moduleValues: draftValues,
108
+ appliedValues,
109
+ isDirty,
110
+ isApplying,
111
+ validationErrors,
112
+ persistenceSource,
113
+ setFieldValue,
114
+ applyChanges,
115
+ discardDraft,
116
+ confirmDiscardIfDirty,
117
+ }
118
+ }
119
+
120
+ export async function ensureModuleConfigReady() {
121
+ if (!getModuleConfigState()) {
122
+ await initModuleConfigRuntime()
123
+ }
124
+ }
@@ -0,0 +1,37 @@
1
+ import { formatCustomObjectLabel, formatPath } from './route-format'
2
+
3
+ export const makeLink = (menuItem: Record<string, unknown>) => {
4
+ let link = menuItem?.hubspotObjectTypeId as string | undefined
5
+
6
+ if (menuItem?.companyAsMediator) {
7
+ link = `${link}-c`
8
+ }
9
+
10
+ if (menuItem?.specPipeLine && menuItem?.pipeLineId) {
11
+ link = `${link}-${menuItem?.pipeLineId}`
12
+ }
13
+
14
+ if (!link) {
15
+ return `/${formatPath((menuItem.tabName || menuItem.label) as string)}`
16
+ }
17
+
18
+ return `/${link}`
19
+ }
20
+
21
+ export function buildApiRoutes(sidebarMenuOptions: Array<Record<string, unknown>>) {
22
+ return sidebarMenuOptions.map((menuItem) => ({
23
+ hubspotObjectTypeId: `${menuItem.hubspotObjectTypeId ?? ''}`,
24
+ path: makeLink(menuItem),
25
+ title: formatCustomObjectLabel((menuItem.tabName || menuItem.label) as string),
26
+ icon: menuItem.icon,
27
+ isRequiredAuth: true,
28
+ isHeader: true,
29
+ companyAsMediator: menuItem.companyAsMediator,
30
+ pipeLineId: menuItem.pipeLineId,
31
+ specPipeLine: menuItem.specPipeLine,
32
+ objectDescription: menuItem.objectDescription,
33
+ objectUserProperties: menuItem.objectUserProperties,
34
+ homeCardsView: menuItem.homeCardsView,
35
+ objectUserPropertiesView: menuItem.objectUserPropertiesView,
36
+ }))
37
+ }
@@ -0,0 +1,19 @@
1
+ /** Route path / label helpers — runtime-safe, no consumer path aliases. */
2
+
3
+ export const formatPath = (key: unknown) => {
4
+ if (!key || typeof key !== 'string') return ''
5
+
6
+ return key
7
+ .replace(/\s+/g, '-')
8
+ .replace(/#/g, '%23')
9
+ .replace(/\b\w/g, (l: string) => l.toLowerCase())
10
+ }
11
+
12
+ export function formatCustomObjectLabel(key = '') {
13
+ if (!key || typeof key !== 'string') return ''
14
+
15
+ return key
16
+ .replace(/-/g, ' ')
17
+ .replace(/%23/g, '#')
18
+ .replace(/\b[a-z]/g, (l: string) => l.toUpperCase())
19
+ }
@@ -1 +1,12 @@
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>
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>WoodsCLI dev</title>
7
+ </head>
8
+ <body>
9
+ <div id="app"></div>
10
+ <script type="module" src="/src/main.tsx"></script>
11
+ </body>
12
+ </html>
@@ -9,7 +9,7 @@
9
9
  "deploy:prod": "wp deploy --tier prod"
10
10
  },
11
11
  "devDependencies": {
12
- "@woodsportal/hubspot-kit": "^1.0.3",
12
+ "@woodsportal/hubspot-kit": "^1.0.5",
13
13
  "@tailwindcss/vite": "^4.0.6",
14
14
  "@tanstack/router-plugin": "^1.121.2",
15
15
  "@vitejs/plugin-react": "^4.3.4",
@@ -0,0 +1,3 @@
1
+ @import 'tailwindcss';
2
+
3
+ @source '../../.wphs/**/*.{js,jsx,ts,tsx}';
@@ -0,0 +1,7 @@
1
+ /** Local dev fixtures for module-config runtime (greenfield wp init). */
2
+ export const defaultData = {
3
+ dataSourceSet: [],
4
+ hubId: 'dev',
5
+ userData: {},
6
+ objectList: [] as unknown[],
7
+ }
@@ -0,0 +1,17 @@
1
+ /** Runtime contract for window.hubSpotData (greenfield stub). */
2
+ export type HubSpotPortalData = {
3
+ dataSourceSet?: unknown
4
+ hubId?: string | number
5
+ apiBaseUrlOption?: string
6
+ userData?: unknown
7
+ objectList?: unknown[]
8
+ isInEditor?: boolean
9
+ }
10
+
11
+ declare global {
12
+ interface Window {
13
+ hubSpotData?: HubSpotPortalData
14
+ }
15
+ }
16
+
17
+ export {}
@@ -1,2 +1,4 @@
1
- import { mountDevPortal } from '@woodsportal/hubspot-kit/dev/bootstrap'
2
- void mountDevPortal()
1
+ import './assets/css/main.dev.css'
2
+ import { mountGreenfieldDev } from '@woodsportal/hubspot-kit/dev/bootstrap-greenfield'
3
+
4
+ void mountGreenfieldDev()
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>WoodsCLI dev</title>
7
+ </head>
8
+ <body>
9
+ <div id="app"></div>
10
+ <script type="module" src="/src/main.tsx"></script>
11
+ </body>
12
+ </html>
@@ -9,7 +9,7 @@
9
9
  "deploy:prod": "wp deploy --tier prod"
10
10
  },
11
11
  "devDependencies": {
12
- "@woodsportal/hubspot-kit": "^1.0.3",
12
+ "@woodsportal/hubspot-kit": "^1.0.5",
13
13
  "@tailwindcss/vite": "^4.0.6",
14
14
  "@tanstack/router-plugin": "^1.121.2",
15
15
  "@vitejs/plugin-react": "^4.3.4",
@@ -0,0 +1,3 @@
1
+ @import 'tailwindcss';
2
+
3
+ @source '../../.wphs/**/*.{js,jsx,ts,tsx}';
@@ -0,0 +1,7 @@
1
+ /** Local dev fixtures for module-config runtime (greenfield wp init). */
2
+ export const defaultData = {
3
+ dataSourceSet: [],
4
+ hubId: 'dev',
5
+ userData: {},
6
+ objectList: [] as unknown[],
7
+ }
@@ -0,0 +1,17 @@
1
+ /** Runtime contract for window.hubSpotData (greenfield stub). */
2
+ export type HubSpotPortalData = {
3
+ dataSourceSet?: unknown
4
+ hubId?: string | number
5
+ apiBaseUrlOption?: string
6
+ userData?: unknown
7
+ objectList?: unknown[]
8
+ isInEditor?: boolean
9
+ }
10
+
11
+ declare global {
12
+ interface Window {
13
+ hubSpotData?: HubSpotPortalData
14
+ }
15
+ }
16
+
17
+ export {}
@@ -0,0 +1,4 @@
1
+ import './assets/css/main.dev.css'
2
+ import { mountGreenfieldDev } from '@woodsportal/hubspot-kit/dev/bootstrap-greenfield'
3
+
4
+ void mountGreenfieldDev()
@@ -9,7 +9,7 @@
9
9
  "deploy:prod": "wp deploy --tier prod"
10
10
  },
11
11
  "devDependencies": {
12
- "@woodsportal/hubspot-kit": "^1.0.3",
12
+ "@woodsportal/hubspot-kit": "^1.0.5",
13
13
  "@tailwindcss/vite": "^4.0.6",
14
14
  "@tanstack/router-plugin": "^1.121.2",
15
15
  "@vitejs/plugin-react": "^4.3.4",
@@ -1,6 +1,7 @@
1
1
  import { defineConfig } from 'vite'
2
2
  import viteReact from '@vitejs/plugin-react'
3
3
  import { TanStackRouterVite } from '@tanstack/router-plugin/vite'
4
+ import { existsSync } from 'node:fs'
4
5
  import { dirname, resolve } from 'node:path'
5
6
  import { fileURLToPath } from 'node:url'
6
7
  import tailwindcss from '@tailwindcss/vite'
@@ -14,10 +15,14 @@ import { createDevReactPluginOptions } from '../scripts/vite/react-dev-plugin-op
14
15
  import { ensureDevKitShims } from '../scripts/vite/ensure-dev-kit-shims.js'
15
16
  import isPrefix from '../scripts/tailwind/prefix.env.js'
16
17
  import { findProjectRoot, getKitRoot } from '../scripts/project-root.mjs'
18
+ import { readWoodsportalConfig } from '../scripts/read-woodsportal-config.mjs'
17
19
 
18
20
  const kitRoot = getKitRoot()
19
21
  const projectRoot = findProjectRoot()
20
22
  const { moduleConfigRoot, bootstrapShim } = ensureDevKitShims(projectRoot, kitRoot)
23
+ const { config } = readWoodsportalConfig(projectRoot)
24
+ const routesDir = resolve(projectRoot, 'src/routes')
25
+ const hasTanStackRoutes = existsSync(routesDir)
21
26
 
22
27
  export default defineConfig(({ mode, command }) => {
23
28
  return {
@@ -27,7 +32,7 @@ export default defineConfig(({ mode, command }) => {
27
32
  ...(command === 'serve' && mode === 'dev'
28
33
  ? [sdkLogBridge(), moduleConfigStore(projectRoot)]
29
34
  : []),
30
- TanStackRouterVite({ autoCodeSplitting: false }),
35
+ ...(hasTanStackRoutes ? [TanStackRouterVite({ autoCodeSplitting: false })] : []),
31
36
  viteReact(createDevReactPluginOptions()),
32
37
  tailwindcss(),
33
38
  ...(isPrefix(mode) ? [tailwindPrefixPlugin(), TailwindContentPlugin()] : []),
@@ -37,6 +42,7 @@ export default defineConfig(({ mode, command }) => {
37
42
  alias: {
38
43
  '@/dev/module-config': moduleConfigRoot,
39
44
  '@woodsportal/hubspot-kit/dev/bootstrap': bootstrapShim,
45
+ '@wphs/adapter': resolve(projectRoot, config?.dev?.adapter ?? './wphs.adapter.ts'),
40
46
  '@': resolve(projectRoot, './src'),
41
47
  },
42
48
  },