@woodsportal/hubspot-kit 5.0.0-beta.2 → 5.0.0-beta.4

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 (27) hide show
  1. package/CHANGELOG.md +0 -6
  2. package/package.json +2 -2
  3. package/scripts/tailwind/prefix-plugin.js +182 -131
  4. package/scripts/tailwind/tailwind-content.js +28 -29
  5. package/scripts/vite/create-dev-server-config.mjs +3 -14
  6. package/scripts/vite/portal-build-shared.js +3 -5
  7. package/src/dev-module-config/__tests__/fixtures/golden-module.fixture.ts +0 -2
  8. package/src/dev-module-config/__tests__/flatten-visible-fields.test.ts +1 -34
  9. package/src/dev-module-config/__tests__/module-config-transform.test.ts +0 -27
  10. package/src/dev-module-config/runtime/module-config-runtime.ts +1 -3
  11. package/src/dev-module-config/schema/validate-module-config.ts +0 -2
  12. package/src/dev-module-config/transform/build-hubspot-data.ts +0 -14
  13. package/src/dev-module-config/ui/FieldRenderer.tsx +0 -2
  14. package/src/dev-module-config/ui/ModuleConfigAnimatedPresence.tsx +2 -4
  15. package/src/dev-module-config/ui/ModuleConfigShell.tsx +4 -16
  16. package/src/dev-module-config/ui/editors/MenuOptionsEditor.tsx +0 -34
  17. package/src/dev-module-config/ui/hubspot/HsOccurrenceGroupEditor.tsx +11 -16
  18. package/src/dev-module-config/ui/navigation/ModuleConfigNavContext.tsx +23 -27
  19. package/src/routing/build-api-routes.ts +15 -45
  20. package/templates/module-hybrid-cdn/src/assets/css/main.prod.css +3 -0
  21. package/scripts/tailwind/prefix-plugin.test.mjs +0 -110
  22. package/src/dev-module-config/__tests__/build-api-routes-custom-menu.test.ts +0 -32
  23. package/src/dev-module-config/__tests__/custom-menu.test.ts +0 -77
  24. package/src/dev-module-config/__tests__/drill-down-panels.test.ts +0 -65
  25. package/src/dev-module-config/schema/custom-menu.ts +0 -124
  26. package/src/dev-module-config/ui/navigation/drill-down-panels.ts +0 -48
  27. package/src/routing/custom-menu-path.ts +0 -14
package/CHANGELOG.md CHANGED
@@ -1,11 +1,5 @@
1
1
  # Changelog
2
2
 
3
- ## Unreleased
4
-
5
- ### CSS prefix
6
- - **Auto-prefix** — class string bindings ending in `_CLASS` (e.g. `CARDS_GRID_CLASS`) are prefixed the same as `*DynamicClassName*`
7
- - **Auto-prefix** — JSX props ending in `ClassName` (`containerClassName`, `pageLinkClassName`, …) and helpers ending in `Class` (`paginationItemBaseClass`, `navBtnClass`) are prefixed
8
-
9
3
  ## [1.0.44] - 2026-08-06
10
4
 
11
5
  ### Portal auth scaffold
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@woodsportal/hubspot-kit",
3
- "version": "5.0.0-beta.2",
3
+ "version": "5.0.0-beta.4",
4
4
  "packageManager": "pnpm@10.12.1",
5
5
  "description": "WoodsCLI — HubSpot CMS dev kit by WoodsPortal. wp CLI, Vite presets, module-config overlay, and theme/module build pipelines.",
6
6
  "type": "module",
@@ -52,7 +52,7 @@
52
52
  "build": "tsup",
53
53
  "dev": "tsup --watch",
54
54
  "typecheck": "tsc --noEmit",
55
- "test": "node --import tsx --test 'src/lib/**/*.test.ts' 'src/cli/**/*.test.ts' 'src/schema/**/*.test.ts' 'src/vite/**/*.test.ts' 'scripts/portal-build-id.test.mjs' 'scripts/resolve-consumer-dep.test.mjs' 'scripts/wp-cli-smoke.test.mjs' 'scripts/vite/ensure-dev-kit-shims.test.mjs' 'scripts/vite/consumer-react-aliases.test.mjs' 'scripts/vite/resolve-from-project-root.test.mjs' 'scripts/tailwind/prefix-plugin.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 src/dev-module-config/__tests__/drill-down-panels.test.ts",
55
+ "test": "node --import tsx --test 'src/lib/**/*.test.ts' 'src/cli/**/*.test.ts' 'src/schema/**/*.test.ts' 'src/vite/**/*.test.ts' 'scripts/portal-build-id.test.mjs' 'scripts/resolve-consumer-dep.test.mjs' 'scripts/wp-cli-smoke.test.mjs' 'scripts/vite/ensure-dev-kit-shims.test.mjs' 'scripts/vite/consumer-react-aliases.test.mjs' 'scripts/vite/resolve-from-project-root.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",
56
56
  "test:examples": "node examples/smoke/run-matrix.mjs --tier static",
57
57
  "test:dist-pack": "node --test scripts/dist-cli-pack-smoke.test.mjs",
58
58
  "lint": "eslint src",
@@ -7,49 +7,15 @@ import { prefix } from './prefix.env.js'
7
7
  const prefix1 = `${prefix}:`
8
8
  const prefix2 = `${prefix}\:`
9
9
 
10
- /** Bindings rewritten even when not in `className=`: `*DynamicClassName*`, `_CLASS`, `*ClassName`, `*Class`. */
11
- export function isClassBindingName(name) {
12
- if (!name) return false
13
- return (
14
- name.includes('DynamicClassName') ||
15
- name.endsWith('_CLASS') ||
16
- /(?:ClassName|Class)$/.test(name)
17
- )
18
- }
19
-
20
- /** JSX / object keys: `className`, `containerClassName`, `pageLinkClassName`, … */
21
- export function isClassAttrName(name) {
22
- if (!name) return false
23
- return name === 'className' || name.endsWith('ClassName')
24
- }
25
-
26
- function jsxAttrName(attr) {
27
- if (t.isJSXIdentifier(attr.name)) return attr.name.name
28
- return ''
29
- }
30
-
31
- function objectKeyName(key) {
32
- if (t.isIdentifier(key)) return key.name
33
- if (t.isStringLiteral(key)) return key.value
34
- return ''
35
- }
36
-
37
- /** Vite serve ids often include `?t=` / `?v=` — strip those before extension checks. */
38
- function filePathFromId(id) {
39
- return (id.split('?')[0] ?? id).replaceAll('\\', '/')
40
- }
41
-
42
10
  export default function TailwindPrefixPlugin() {
43
11
  return {
44
12
  name: 'vite-tailwind-prefix',
45
13
  enforce: 'pre',
46
14
  transform(code, id) {
47
- const filename = filePathFromId(id)
48
-
49
15
  // Handle CSS files: prefix custom class selectors safely
50
- if (filename.endsWith('.css')) {
16
+ if (id.endsWith('.css')) {
51
17
  // don't need to add prefix to override.style.css
52
- if (filename.endsWith('override.style.css')) return code;
18
+ if (id.endsWith('override.style.css')) return code;
53
19
 
54
20
 
55
21
  const lines = code.split('\n')
@@ -95,7 +61,7 @@ export default function TailwindPrefixPlugin() {
95
61
  }
96
62
 
97
63
 
98
- if (!filename.endsWith('.tsx') && !filename.endsWith('.ts')) return null
64
+ if (!id.endsWith('.tsx') && !id.endsWith('.ts')) return null
99
65
 
100
66
  try {
101
67
  const ast = parse(code, {
@@ -129,84 +95,23 @@ export default function TailwindPrefixPlugin() {
129
95
  .join(' ')
130
96
  }
131
97
 
132
- const prefixExpr = (expr) => {
133
- if (!expr) return expr
134
-
135
- if (
136
- t.isTSAsExpression(expr) ||
137
- t.isTSTypeAssertion(expr) ||
138
- t.isTSSatisfiesExpression(expr) ||
139
- t.isTSNonNullExpression(expr) ||
140
- t.isParenthesizedExpression(expr)
141
- ) {
142
- expr.expression = prefixExpr(expr.expression)
143
- return expr
144
- }
145
-
98
+ const prefixStringExpr = (expr) => {
146
99
  if (t.isStringLiteral(expr)) {
147
100
  return t.stringLiteral(prefixClasses(expr.value))
148
101
  }
149
-
150
- if (t.isTemplateLiteral(expr)) {
151
- const quasis = expr.quasis.map((quasi, i) => {
152
- let classes = prefixClasses(quasi.value.raw)
153
- if (i < expr.expressions.length) classes += ' '
154
- if (i > 0 && !classes.startsWith(' ')) classes = ' ' + classes
155
- return t.templateElement({ raw: classes, cooked: classes })
156
- })
157
- const expressions = expr.expressions.map((inner) => prefixExpr(inner))
158
- return t.templateLiteral(quasis, expressions)
159
- }
160
-
161
102
  if (t.isConditionalExpression(expr)) {
162
- expr.consequent = prefixExpr(expr.consequent)
163
- expr.alternate = prefixExpr(expr.alternate)
164
- return expr
165
- }
166
-
167
- if (t.isLogicalExpression(expr)) {
168
- if (t.isStringLiteral(expr.left) || t.isTemplateLiteral(expr.left)) {
169
- expr.left = prefixExpr(expr.left)
170
- }
171
- expr.right = prefixExpr(expr.right)
172
- return expr
173
- }
174
-
175
- if (t.isObjectExpression(expr)) {
176
- expr.properties.forEach((prop) => {
177
- if (!t.isObjectProperty(prop) || prop.computed) return
178
- prop.value = prefixExpr(prop.value)
179
- })
180
- return expr
181
- }
182
-
183
- if (t.isArrayExpression(expr)) {
184
- expr.elements = expr.elements.map((el) => {
185
- if (!el || t.isSpreadElement(el)) return el
186
- return prefixExpr(el)
187
- })
188
- return expr
189
- }
190
-
191
- if (t.isCallExpression(expr)) {
192
- expr.arguments = expr.arguments.map((arg) => {
193
- if (t.isSpreadElement(arg)) return arg
194
- return prefixExpr(arg)
195
- })
196
- return expr
197
- }
198
-
199
- if (t.isArrowFunctionExpression(expr) || t.isFunctionExpression(expr)) {
200
- if (!t.isBlockStatement(expr.body)) {
201
- expr.body = prefixExpr(expr.body)
202
- }
203
- return expr
103
+ return t.conditionalExpression(
104
+ expr.test,
105
+ prefixStringExpr(expr.consequent),
106
+ prefixStringExpr(expr.alternate),
107
+ )
204
108
  }
205
-
206
109
  return expr
207
110
  }
208
111
 
209
112
  traverse.default(ast, {
113
+ // ✅ Handle variables whose name includes "DynamicClassName"
114
+
210
115
  ReturnStatement(path) {
211
116
  const fn = path.getFunctionParent()
212
117
  if (!fn) return
@@ -222,37 +127,186 @@ export default function TailwindPrefixPlugin() {
222
127
  fnName = parent.id.name
223
128
  }
224
129
  }
225
- if (!isClassBindingName(fnName)) return
226
- if (path.node.argument) {
227
- path.node.argument = prefixExpr(path.node.argument)
130
+ const isClassHelper =
131
+ fnName.includes('DynamicClassName') ||
132
+ /(?:ClassName|Class)$/.test(fnName)
133
+ if (!isClassHelper) return
134
+
135
+ if (t.isStringLiteral(path.node.argument)) {
136
+ path.node.argument = t.stringLiteral(prefixClasses(path.node.argument.value))
137
+ } else if (t.isConditionalExpression(path.node.argument)) {
138
+ path.node.argument = prefixStringExpr(path.node.argument)
228
139
  }
229
140
  },
230
141
 
231
142
  JSXAttribute(path) {
232
- if (!isClassAttrName(jsxAttrName(path.node))) return
143
+ if (path.node.name.name !== 'className') return
144
+
233
145
  const value = path.node.value
146
+
147
+ // String literal: className="..."
234
148
  if (t.isStringLiteral(value)) {
235
149
  path.node.value = t.stringLiteral(prefixClasses(value.value))
236
- return
237
150
  }
238
- if (t.isJSXExpressionContainer(value)) {
239
- value.expression = prefixExpr(value.expression)
151
+ // Template literal: className={`...`}
152
+ else if (
153
+ t.isJSXExpressionContainer(value) &&
154
+ t.isTemplateLiteral(value.expression)
155
+ ) {
156
+ const quasis = value.expression.quasis.map((quasi, i) => {
157
+ let classes = prefixClasses(quasi.value.raw)
158
+
159
+ if (i < value.expression.expressions.length) classes += ' '
160
+ if (i > 0 && !classes.startsWith(' ')) classes = ' ' + classes
161
+
162
+ return t.templateElement({ raw: classes, cooked: classes })
163
+ })
164
+
165
+ // 🔥 also fix expressions inside ${ ... }
166
+ const expressions = value.expression.expressions.map((expr) => {
167
+ if (t.isStringLiteral(expr)) {
168
+ return t.stringLiteral(prefixClasses(expr.value))
169
+ }
170
+ if (t.isLogicalExpression(expr)) {
171
+ if (t.isStringLiteral(expr.right)) {
172
+ return t.logicalExpression(
173
+ expr.operator,
174
+ expr.left,
175
+ t.stringLiteral(prefixClasses(expr.right.value)),
176
+ )
177
+ }
178
+ }
179
+ if (t.isConditionalExpression(expr)) {
180
+ const newConsequent = t.isStringLiteral(expr.consequent)
181
+ ? t.stringLiteral(prefixClasses(expr.consequent.value))
182
+ : expr.consequent
183
+ const newAlternate = t.isStringLiteral(expr.alternate)
184
+ ? t.stringLiteral(prefixClasses(expr.alternate.value))
185
+ : expr.alternate
186
+ return t.conditionalExpression(
187
+ expr.test,
188
+ newConsequent,
189
+ newAlternate,
190
+ )
191
+ }
192
+ return expr
193
+ })
194
+
195
+ path.node.value = t.jsxExpressionContainer(
196
+ t.templateLiteral(quasis, expressions),
197
+ )
240
198
  }
241
- },
242
199
 
243
- ObjectProperty(path) {
244
- if (path.node.computed) return
245
- const keyName = objectKeyName(path.node.key)
246
- if (!isClassAttrName(keyName) && !isClassBindingName(keyName)) return
247
- path.node.value = prefixExpr(path.node.value)
248
- },
200
+ // className={condition ? "..." : "..."}
201
+ else if (
202
+ t.isJSXExpressionContainer(value) &&
203
+ t.isConditionalExpression(value.expression)
204
+ ) {
205
+ const expr = value.expression
206
+ path.node.value = t.jsxExpressionContainer(
207
+ t.conditionalExpression(
208
+ expr.test,
209
+ prefixStringExpr(expr.consequent),
210
+ prefixStringExpr(expr.alternate),
211
+ ),
212
+ )
213
+ }
249
214
 
215
+ // Other expressions (e.g., clsx())
216
+ else if (
217
+ t.isJSXExpressionContainer(value) &&
218
+ t.isCallExpression(value.expression)
219
+ ) {
220
+ const args = value.expression.arguments.map((arg) => {
221
+ if (t.isStringLiteral(arg))
222
+ return t.stringLiteral(prefixClasses(arg.value))
223
+ return arg
224
+ })
225
+ path.node.value = t.jsxExpressionContainer(
226
+ t.callExpression(value.expression.callee, args),
227
+ )
228
+ }
229
+ },
250
230
  VariableDeclarator(path) {
251
231
  const id = path.node.id
252
232
  const init = path.node.init
253
- if (!t.isIdentifier(id) || !init) return
254
- if (!isClassBindingName(id.name)) return
255
- path.node.init = prefixExpr(init)
233
+ if (!t.isIdentifier(id)) return
234
+
235
+ // 🔹 only affect DynamicClassName variables
236
+ if (!id.name.includes('DynamicClassName')) return
237
+ if (!init) return
238
+
239
+ const prefixString = (expr) =>
240
+ t.isStringLiteral(expr)
241
+ ? t.stringLiteral(prefixClasses(expr.value))
242
+ : expr
243
+
244
+ const prefixObject = (objExpr) => {
245
+ if (!t.isObjectExpression(objExpr)) return objExpr
246
+ objExpr.properties.forEach((prop) => {
247
+ if (t.isObjectProperty(prop)) {
248
+ if (t.isStringLiteral(prop.value)) {
249
+ prop.value = t.stringLiteral(
250
+ prefixClasses(prop.value.value),
251
+ )
252
+ } else if (t.isTemplateLiteral(prop.value)) {
253
+ const quasis = prop.value.quasis.map((quasi) => {
254
+ const raw = prefixClasses(quasi.value.raw)
255
+ return t.templateElement({ raw, cooked: raw })
256
+ })
257
+ prop.value = t.templateLiteral(
258
+ quasis,
259
+ prop.value.expressions,
260
+ )
261
+ } else if (t.isObjectExpression(prop.value)) {
262
+ prop.value = prefixObject(prop.value) // recurse
263
+ } else if (t.isConditionalExpression(prop.value)) {
264
+ prop.value = t.conditionalExpression(
265
+ prop.value.test,
266
+ prefixString(prop.value.consequent),
267
+ prefixString(prop.value.alternate),
268
+ )
269
+ } else if (t.isLogicalExpression(prop.value)) {
270
+ const right = t.isStringLiteral(prop.value.right)
271
+ ? t.stringLiteral(prefixClasses(prop.value.right.value))
272
+ : prop.value.right
273
+ prop.value = t.logicalExpression(
274
+ prop.value.operator,
275
+ prop.value.left,
276
+ right,
277
+ )
278
+ }
279
+ }
280
+ })
281
+ return objExpr
282
+ }
283
+
284
+ if (t.isStringLiteral(init)) {
285
+ path.node.init = t.stringLiteral(prefixClasses(init.value))
286
+ } else if (t.isTemplateLiteral(init)) {
287
+ const quasis = init.quasis.map((quasi) => {
288
+ const raw = prefixClasses(quasi.value.raw)
289
+ return t.templateElement({ raw, cooked: raw })
290
+ })
291
+ path.node.init = t.templateLiteral(quasis, init.expressions)
292
+ } else if (t.isObjectExpression(init)) {
293
+ path.node.init = prefixObject(init)
294
+ } else if (t.isConditionalExpression(init)) {
295
+ path.node.init = t.conditionalExpression(
296
+ init.test,
297
+ prefixString(init.consequent),
298
+ prefixString(init.alternate),
299
+ )
300
+ } else if (t.isLogicalExpression(init)) {
301
+ const right = t.isStringLiteral(init.right)
302
+ ? t.stringLiteral(prefixClasses(init.right.value))
303
+ : init.right
304
+ path.node.init = t.logicalExpression(
305
+ init.operator,
306
+ init.left,
307
+ right,
308
+ )
309
+ }
256
310
  },
257
311
  })
258
312
 
@@ -268,11 +322,8 @@ export default function TailwindPrefixPlugin() {
268
322
 
269
323
  // Handles:
270
324
  // ✅ className="..."
271
- // ✅ containerClassName / *ClassName props
272
- // ✅ className={`...`} with ${"foo"} inside
273
- // ✅ className={cond ? "a" : "b"}
274
- // ✅ className={cond && "foo"}
275
- // ✅ className={clsx("foo", cond && "bar")}
276
- // ✅ const fooDynamicClassName = "..." | {...} | cond ? ... : ...
277
- // ✅ const BUTTON_CLASS / CARDS_GRID_CLASS = "..."
278
- // ✅ const paginationItemBaseClass / navBtnClass = `...`
325
+ // ✅ className={\`...\`}with${"foo"}inside
326
+ // ✅className={cond ? "a" : "b"}
327
+ // ✅className={cond && "foo"}
328
+ // ✅className={clsx("foo", cond && "bar")}
329
+ // ✅const fooDynamicClassName = "..." | {...} | cond ? ... : ...`
@@ -115,9 +115,9 @@ function prefixQuotedStringsInExpression(expr) {
115
115
 
116
116
  // Handle className
117
117
  function prefixClassesInCode(code) {
118
- // 1) Plain string: className="..." / containerClassName="..." / *ClassName="..."
119
- code = code.replace(/([A-Za-z0-9_]*[Cc]lassName)\s*=\s*["']([^"']*)["']/g, (_, attr, classStr) => {
120
- return `${attr}="${prefixStaticClasses(classStr)}"`
118
+ // 1) Plain string: className="..."
119
+ code = code.replace(/className\s*=\s*["']([^"']*)["']/g, (_, classStr) => {
120
+ return `className="${prefixStaticClasses(classStr)}"`
121
121
  })
122
122
 
123
123
  // code = code.replace(/className\s*=\s*["']([^"']*)["']/g, (_, classStr) => {
@@ -128,8 +128,8 @@ function prefixClassesInCode(code) {
128
128
  // return `className="${prefixStaticClasses(classStr)}"`
129
129
  // })
130
130
 
131
- // 2) Template literal: className={`...`} / *ClassName={`...`}
132
- code = code.replace(/([A-Za-z0-9_]*[Cc]lassName)\s*=\s*{\s*`([\s\S]*?)`\s*}/g, (_, attr, tplBody) => {
131
+ // 2) Template literal: className={`...`}
132
+ code = code.replace(/className\s*=\s*{\s*`([\s\S]*?)`\s*}/g, (_, tplBody) => {
133
133
  const parts = tplBody.split(/(\$\{[\s\S]*?\})/g)
134
134
 
135
135
  // const rebuilt = parts
@@ -161,13 +161,13 @@ function prefixClassesInCode(code) {
161
161
  .join(' ')
162
162
  .replace(/\s+/g, ' ') // collapse multiple spaces
163
163
 
164
- return `${attr}={\`${rebuilt.trim()}\`}`
164
+ return `className={\`${rebuilt.trim()}\`}`
165
165
  })
166
166
 
167
- // 3) Ternary expressions: className={cond ? "..." : "..."} / *ClassName={...}
167
+ // 3) Ternary expressions: className={cond ? "..." : "..."}
168
168
  code = code.replace(
169
- /([A-Za-z0-9_]*[Cc]lassName)\s*=\s*{([^{};]+?\?[^:]+:[^}]+)}/g,
170
- (match, attr, inner) => {
169
+ /className\s*=\s*{([^{};]+?\?[^:]+:[^}]+)}/g,
170
+ (match, inner) => {
171
171
  const ternaryMatch = inner.match(
172
172
  /^(.*?\?)\s*(['"][^'"]*['"])\s*:\s*(['"][^'"]*['"])\s*$/,
173
173
  )
@@ -183,43 +183,42 @@ function prefixClassesInCode(code) {
183
183
  (_, q, cls) => `${q}${prefixStaticClasses(cls)}${q}`,
184
184
  )
185
185
 
186
- return `${attr}={${condition} ${prefixedTrue} : ${prefixedFalse}}`
186
+ return `className={${condition} ${prefixedTrue} : ${prefixedFalse}}`
187
187
  },
188
188
  )
189
189
 
190
- // 4) Variables with "DynamicClassName" or suffix `_CLASS` (quoted; no semicolon required)
190
+ // 4) Variables with "DynamicClassName" (supports TypeScript annotations like `: any`)
191
191
  code = code.replace(
192
- /(const|let|var)\s+([A-Za-z0-9_]*(?:DynamicClassName[A-Za-z0-9_]*|_CLASS))\s*(?::\s*([^=]+))?\s*=\s*(['"`])([\s\S]*?)\4/g,
193
- (match, decl, varName, typeAnnotation, quote, value) => {
194
- const typeSuffix = typeAnnotation ? `: ${typeAnnotation.trim()}` : ''
195
- if (quote === '`') {
196
- return `${decl} ${varName}${typeSuffix} = \`${prefixTemplateLiteralBody(value)}\``
197
- }
198
- return `${decl} ${varName}${typeSuffix} = ${quote}${prefixStaticClasses(value)}${quote}`
199
- },
200
- )
201
-
202
- // 4b) DynamicClassName / _CLASS object or ternary values (semicolon-terminated)
203
- code = code.replace(
204
- /(const|let|var)\s+([A-Za-z0-9_]*(?:DynamicClassName[A-Za-z0-9_]*|_CLASS))\s*(?::\s*([^=]+))?\s*=\s*(\{[\s\S]*?\}|.+\?.+:.+);/g,
192
+ /(const|let|var)\s+([A-Za-z0-9_]*DynamicClassName[A-Za-z0-9_]*)\s*(?::\s*([^=]+))?\s*=\s*([\s\S]*?);/g,
205
193
  (match, decl, varName, typeAnnotation, value) => {
206
194
  const typeSuffix = typeAnnotation ? `: ${typeAnnotation.trim()}` : ''
207
195
  const trimmed = value.trim()
208
- if (/^\{[\s\S]*\}$/.test(trimmed) || /\?.*:/.test(trimmed)) {
196
+ if (/^\{[\s\S]*\}$/.test(trimmed)) {
197
+ return `${decl} ${varName}${typeSuffix} = ${prefixValueStrings(value)};`
198
+ }
199
+ if (/\?.*:/.test(trimmed)) {
209
200
  return `${decl} ${varName}${typeSuffix} = ${prefixValueStrings(value)};`
210
201
  }
202
+ if (/^["'`]/.test(trimmed)) {
203
+ if (trimmed.startsWith('`')) {
204
+ const body = trimmed.slice(1, -1)
205
+ return `${decl} ${varName}${typeSuffix} = \`${prefixTemplateLiteralBody(body)}\`;`
206
+ }
207
+ const cls = trimmed.slice(1, -1)
208
+ return `${decl} ${varName}${typeSuffix} = "${prefixStaticClasses(cls)}";`
209
+ }
211
210
  return match
212
211
  },
213
212
  )
214
213
 
215
- // 5) Handle classNames(...) or clsx(...) on *ClassName props
214
+ // 5) Handle classNames(...) or clsx(...)
216
215
  code = code.replace(
217
- /([A-Za-z0-9_]*[Cc]lassName)\s*=\s*{?\s*(classNames|clsx)\(([\s\S]*?)\)}?/g,
218
- (match, attr, fnName, args) => {
216
+ /className\s*=\s*{?\s*(classNames|clsx)\(([\s\S]*?)\)}?/g,
217
+ (match, fnName, args) => {
219
218
  const transformedArgs = args.replace(/(['"])(.*?)\1/g, (_, q, cls) => {
220
219
  return `${q}${prefixStaticClasses(cls)}${q}`
221
220
  })
222
- return `${attr}={${fnName}(${transformedArgs})}`
221
+ return `className={${fnName}(${transformedArgs})}`
223
222
  },
224
223
  )
225
224
 
@@ -48,8 +48,7 @@ export async function createDevServerViteConfig(options = {}) {
48
48
  ])
49
49
 
50
50
  return defineConfig(async ({ mode, command }) => {
51
- const usePrefix = isPrefix(mode)
52
- const prefixPlugins = usePrefix
51
+ const prefixPlugins = isPrefix(mode)
53
52
  ? [
54
53
  (await import('../tailwind/prefix-plugin.js')).default(),
55
54
  (await import('../tailwind/tailwind-content-plugin.js')).default(),
@@ -62,20 +61,10 @@ export async function createDevServerViteConfig(options = {}) {
62
61
  bootstrapShimResolver({ bootstrapShim, bootstrapGreenfieldShim, kitRoot }),
63
62
  resolveFromProjectRoot(projectRoot, kitRoot),
64
63
  ...(command === 'serve' ? [sdkLogBridge(), moduleConfigStore(projectRoot)] : []),
65
- // Prefix must run before React / compiler so JSX className is still in the source.
66
- ...prefixPlugins,
67
64
  ...(hasTanStackRoutes ? [TanStackRouterVite({ autoCodeSplitting: false })] : []),
68
- viteReact(
69
- usePrefix
70
- ? {
71
- babel: {
72
- // Match production: compiler rewrites className after the prefix pass.
73
- plugins: [],
74
- },
75
- }
76
- : createDevReactPluginOptions(),
77
- ),
65
+ viteReact(createDevReactPluginOptions()),
78
66
  tailwindcss(),
67
+ ...prefixPlugins,
79
68
  ],
80
69
  resolve: {
81
70
  dedupe: [
@@ -135,16 +135,11 @@ function prodDefaultDataStub(mode) {
135
135
 
136
136
  export function portalPlugins(mode, { codeSplitting = false, routeFileIgnorePattern } = {}) {
137
137
  const routeIgnore = routeFileIgnorePattern ?? (isPrefix(mode) ? 'dev\\.' : undefined)
138
- const prefixPlugins = isPrefix(mode)
139
- ? [tailwindPrefixPlugin(), TailwindContentPlugin()]
140
- : []
141
138
  return [
142
139
  prodDevModuleConfigRouteStub(mode),
143
140
  prodDevHubSpotLiveStub(mode),
144
141
  prodSdkTerminalLogSinksStub(mode),
145
142
  prodDefaultDataStub(mode),
146
- // Prefix must run before React / compiler so JSX className is still in the source.
147
- ...prefixPlugins,
148
143
  TanStackRouterVite({
149
144
  autoCodeSplitting: codeSplitting,
150
145
  ...(routeIgnore ? { routeFileIgnorePattern: routeIgnore } : {}),
@@ -157,6 +152,9 @@ export function portalPlugins(mode, { codeSplitting = false, routeFileIgnorePatt
157
152
  },
158
153
  }),
159
154
  tailwindcss(),
155
+ ...(isPrefix(mode)
156
+ ? [tailwindPrefixPlugin(), TailwindContentPlugin()]
157
+ : []),
160
158
  ]
161
159
  }
162
160
 
@@ -16,7 +16,6 @@ export const goldenModuleFixture: ModuleFieldValues = {
16
16
  menu_options: {
17
17
  hubspot_defined_object: true,
18
18
  hubspot_custom_object: false,
19
- custom_menu: true,
20
19
  objects: [
21
20
  {
22
21
  tab_name: 'Contacts',
@@ -27,7 +26,6 @@ export const goldenModuleFixture: ModuleFieldValues = {
27
26
  },
28
27
  ],
29
28
  hubspot_custom_objects: [],
30
- custom_menus: [],
31
29
  },
32
30
  other_options: {
33
31
  show_company_name: true,
@@ -4,7 +4,7 @@ import fieldsSchema from './fixtures/fields.json'
4
4
  import { goldenModuleFixture } from './fixtures/golden-module.fixture'
5
5
  import type { HubSpotFieldDefinition } from '../schema/types'
6
6
  import { HUBSPOT_ONLY_FIELD_NAMES, MODULE_CONFIG_UI_HIDDEN_FIELD_NAMES } from '../schema/types'
7
- import { flattenVisibleFields, isFieldVisible } from '../schema/visibility'
7
+ import { flattenVisibleFields } from '../schema/visibility'
8
8
 
9
9
  const goldenFields = fieldsSchema as Array<HubSpotFieldDefinition>
10
10
 
@@ -105,36 +105,3 @@ describe('flattenVisibleFields', () => {
105
105
  })
106
106
  })
107
107
  })
108
-
109
- describe('isFieldVisible nested repeater items', () => {
110
- const buttonNameField: HubSpotFieldDefinition = {
111
- name: 'button_name',
112
- label: 'Button Name',
113
- type: 'text',
114
- visibility: {
115
- controlling_field_path: 'home.dashboard_cards.properties.property_value_show_as',
116
- controlling_value_regex: 'button',
117
- operator: 'EQUAL',
118
- },
119
- }
120
-
121
- it('shows fields from nested Display Properties item values', () => {
122
- expect(
123
- isFieldVisible(buttonNameField, {
124
- moduleValues: {},
125
- repeaterPath: 'home.dashboard_cards.properties',
126
- itemValues: { property_value_show_as: 'button' },
127
- }),
128
- ).toBe(true)
129
- })
130
-
131
- it('hides nested fields when the controlling item value does not match', () => {
132
- expect(
133
- isFieldVisible(buttonNameField, {
134
- moduleValues: {},
135
- repeaterPath: 'home.dashboard_cards.properties',
136
- itemValues: { property_value_show_as: 'simpleText' },
137
- }),
138
- ).toBe(false)
139
- })
140
- })
@@ -41,33 +41,6 @@ describe('buildHubSpotDataFromFields', () => {
41
41
  expect(built.enableDashboardCards).toBe(true)
42
42
  expect(built.sidebarMenuOptions.length).toBeGreaterThan(0)
43
43
  })
44
-
45
- it('maps enabled custom menu items into sidebarMenuOptions', () => {
46
- const built = buildHubSpotDataFromFields({
47
- ...goldenModuleFixture,
48
- menu_options: {
49
- ...(goldenModuleFixture.menu_options as Record<string, unknown>),
50
- custom_menu: true,
51
- custom_menus: [
52
- {
53
- menu_name: 'Help Center',
54
- menu_url: 'help.example.com',
55
- icon_svg_code: '<svg></svg>',
56
- select_option: 'iframe',
57
- },
58
- ],
59
- },
60
- })
61
-
62
- expect(built.sidebarMenuOptions).toContainEqual(
63
- expect.objectContaining({
64
- label: 'Help Center',
65
- menuUrl: 'https://help.example.com',
66
- selectOption: 'iframe',
67
- objectType: 'CUSTOM_MENU',
68
- }),
69
- )
70
- })
71
44
  })
72
45
 
73
46
  describe('buildCssVarsFromFields', () => {