@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.
- package/CHANGELOG.md +31 -0
- package/LICENSE +21 -0
- package/README.md +62 -0
- package/SECURITY.md +27 -0
- package/dist/chunk-JDGIAYHX.js +87 -0
- package/dist/chunk-JDGIAYHX.js.map +1 -0
- package/dist/chunk-U4KFGTEW.js +136 -0
- package/dist/chunk-U4KFGTEW.js.map +1 -0
- package/dist/cli.js +1849 -0
- package/dist/cli.js.map +1 -0
- package/dist/exec-K5C6LZP4.js +10 -0
- package/dist/exec-K5C6LZP4.js.map +1 -0
- package/dist/index.d.ts +228 -0
- package/dist/index.js +204 -0
- package/dist/index.js.map +1 -0
- package/dist/vite/index.d.ts +17 -0
- package/dist/vite/index.js +9 -0
- package/dist/vite/index.js.map +1 -0
- package/examples/README.md +12 -0
- package/examples/fixtures/adopted-module-hybrid/public/fields.json +1 -0
- package/examples/fixtures/adopted-module-hybrid/public/module.html +1 -0
- package/examples/fixtures/adopted-module-hybrid/woodscli.json +7 -0
- package/examples/fixtures/adopted-module-monolith/public/fields.json +1 -0
- package/examples/fixtures/adopted-module-monolith/woodscli.json +7 -0
- package/examples/fixtures/adopted-theme-minimal/src/theme/templates/page.html +1 -0
- package/examples/fixtures/adopted-theme-minimal/src/theme/theme.json +1 -0
- package/examples/fixtures/adopted-theme-minimal/woodscli.json +7 -0
- package/examples/fixtures/existing-module-hybrid/public/fields.json +1 -0
- package/examples/fixtures/existing-module-hybrid/public/module.html +1 -0
- package/examples/fixtures/existing-theme-minimal/templates/page.html +1 -0
- package/examples/fixtures/existing-theme-minimal/theme.json +1 -0
- package/examples/greenfield/module-hybrid-cdn/README.md +9 -0
- package/examples/greenfield/module-hybrid-cdn/index.html +1 -0
- package/examples/greenfield/module-hybrid-cdn/package.json +14 -0
- package/examples/greenfield/module-hybrid-cdn/public/fields.json +1 -0
- package/examples/greenfield/module-hybrid-cdn/public/module.html +1 -0
- package/examples/greenfield/module-hybrid-cdn/src/main.tsx +2 -0
- package/examples/greenfield/module-hybrid-cdn/vite.config.dev.mjs +1 -0
- package/examples/greenfield/module-hybrid-cdn/vite.config.hubspot.mjs +1 -0
- package/examples/greenfield/module-hybrid-cdn/woodscli.json +8 -0
- package/examples/greenfield/module-hybrid-cdn/wphs.adapter.ts +7 -0
- package/examples/greenfield/module-monolith/package.json +12 -0
- package/examples/greenfield/module-monolith/public/fields.json +1 -0
- package/examples/greenfield/module-monolith/vite.config.dev.mjs +1 -0
- package/examples/greenfield/module-monolith/vite.config.monolith.mjs +1 -0
- package/examples/greenfield/module-monolith/woodscli.json +25 -0
- package/examples/greenfield/module-monolith/wphs.adapter.ts +7 -0
- package/examples/greenfield/theme-starter/package.json +11 -0
- package/examples/greenfield/theme-starter/src/theme/css/main.css +1 -0
- package/examples/greenfield/theme-starter/src/theme/templates/base.html +1 -0
- package/examples/greenfield/theme-starter/src/theme/templates/page.html +1 -0
- package/examples/greenfield/theme-starter/src/theme/theme.json +1 -0
- package/examples/greenfield/theme-starter/theme.json +6 -0
- package/examples/greenfield/theme-starter/vite.config.dev.mjs +1 -0
- package/examples/greenfield/theme-starter/woodscli.json +7 -0
- package/examples/manifest.json +20 -0
- package/examples/smoke/run-matrix.mjs +100 -0
- package/package.json +94 -0
- package/scripts/audit-module-config-fields.mjs +73 -0
- package/scripts/audit-parity.mjs +22 -0
- package/scripts/audit-tdz-split.mjs +111 -0
- package/scripts/build-cdn.mjs +36 -0
- package/scripts/check-hubspot-bundle-size.mjs +58 -0
- package/scripts/config-files.mjs +53 -0
- package/scripts/detect-layout.mjs +70 -0
- package/scripts/ensure-wphs-workspace.mjs +53 -0
- package/scripts/generate-cdn-manifest.mjs +92 -0
- package/scripts/generate-module-config-example.mjs +57 -0
- package/scripts/hubspot-module-postbuild.mjs +151 -0
- package/scripts/load-portal-cdn-config.mjs +91 -0
- package/scripts/migrate-sdk-3-api.mjs +144 -0
- package/scripts/module-config-dir.mjs +12 -0
- package/scripts/portal-env-mode.mjs +24 -0
- package/scripts/portal-vite-env.mjs +39 -0
- package/scripts/project-root.mjs +30 -0
- package/scripts/publish-cdn-github.mjs +273 -0
- package/scripts/read-woodsportal-config.mjs +40 -0
- package/scripts/resolve-cdn-base-url.mjs +32 -0
- package/scripts/resolve-consumer-dep.mjs +25 -0
- package/scripts/resolve-portal-build-id.mjs +67 -0
- package/scripts/run-dev.mjs +93 -0
- package/scripts/run-hubspot-module-postbuild.mjs +19 -0
- package/scripts/run-vite-build.mjs +33 -0
- package/scripts/tailwind/prefix-plugin.js +291 -0
- package/scripts/tailwind/prefix.env.js +11 -0
- package/scripts/tailwind/tailwind-content-plugin.js +27 -0
- package/scripts/tailwind/tailwind-content.js +267 -0
- package/scripts/verify-cdn-api-endpoint.mjs +68 -0
- package/scripts/vite/ensure-dev-kit-shims.js +49 -0
- package/scripts/vite/ensure-dev-module-config-shim.js +1 -0
- package/scripts/vite/module-config-store.js +242 -0
- package/scripts/vite/plugins.d.ts +9 -0
- package/scripts/vite/portal-build-shared.js +110 -0
- package/scripts/vite/react-dev-plugin-options.js +21 -0
- package/scripts/vite/resolve-from-project-root.js +31 -0
- package/scripts/vite/sdk-log-bridge.js +104 -0
- package/scripts/watch-hubspot.mjs +86 -0
- package/scripts/wphs-paths.mjs +115 -0
- package/scripts/write-audit-artifacts.mjs +56 -0
- package/templates/hubspot.config.yml.example +10 -0
- package/templates/module-hybrid-cdn/README.md +9 -0
- package/templates/module-hybrid-cdn/hubspot.config.yml.example +10 -0
- package/templates/module-hybrid-cdn/index.html +1 -0
- package/templates/module-hybrid-cdn/package.json +14 -0
- package/templates/module-hybrid-cdn/public/fields.json +1 -0
- package/templates/module-hybrid-cdn/public/module.html +1 -0
- package/templates/module-hybrid-cdn/src/main.tsx +2 -0
- package/templates/module-hybrid-cdn/vite.config.dev.mjs +1 -0
- package/templates/module-hybrid-cdn/vite.config.hubspot.mjs +1 -0
- package/templates/module-hybrid-cdn/woodscli.json +8 -0
- package/templates/module-hybrid-cdn/wphs.adapter.ts +7 -0
- package/templates/module-monolith/hubspot.config.yml.example +10 -0
- package/templates/module-monolith/package.json +12 -0
- package/templates/module-monolith/public/fields.json +1 -0
- package/templates/module-monolith/vite.config.dev.mjs +1 -0
- package/templates/module-monolith/vite.config.monolith.mjs +1 -0
- package/templates/module-monolith/woodscli.json +25 -0
- package/templates/module-monolith/wphs.adapter.ts +7 -0
- package/templates/theme-starter/hubspot.config.yml.example +10 -0
- package/templates/theme-starter/package.json +11 -0
- package/templates/theme-starter/src/theme/css/main.css +1 -0
- package/templates/theme-starter/src/theme/templates/base.html +1 -0
- package/templates/theme-starter/src/theme/templates/page.html +1 -0
- package/templates/theme-starter/src/theme/theme.json +1 -0
- package/templates/theme-starter/theme.json +6 -0
- package/templates/theme-starter/vite.config.dev.mjs +1 -0
- package/templates/theme-starter/woodscli.json +7 -0
- package/vite/vite.config.audit.mjs +33 -0
- package/vite/vite.config.cdn-app.mjs +44 -0
- package/vite/vite.config.cdn-esm.mjs +35 -0
- package/vite/vite.config.cdn-vendor.mjs +33 -0
- package/vite/vite.config.dev.mjs +62 -0
- package/vite/vite.config.hubspot.mjs +33 -0
- package/vite/vite.config.monolith.mjs +55 -0
- package/vite/vite.config.tdz-audit.mjs +58 -0
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import { parse } from '@babel/parser'
|
|
2
|
+
import traverse from '@babel/traverse'
|
|
3
|
+
import * as t from '@babel/types'
|
|
4
|
+
import generate from '@babel/generator'
|
|
5
|
+
import { prefix } from './prefix.env.js'
|
|
6
|
+
|
|
7
|
+
const prefix1 = `${prefix}:`
|
|
8
|
+
const prefix2 = `${prefix}\:`
|
|
9
|
+
|
|
10
|
+
export default function TailwindPrefixPlugin() {
|
|
11
|
+
return {
|
|
12
|
+
name: 'vite-tailwind-prefix',
|
|
13
|
+
enforce: 'pre',
|
|
14
|
+
transform(code, id) {
|
|
15
|
+
// Handle CSS files: prefix custom class selectors safely
|
|
16
|
+
if (id.endsWith('.css')) {
|
|
17
|
+
// don't need to add prefix to override.style.css
|
|
18
|
+
if (id.endsWith('override.style.css')) return code;
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
const lines = code.split('\n')
|
|
22
|
+
let braceDepth = 0
|
|
23
|
+
|
|
24
|
+
const prefixedLines = lines.map((line) => {
|
|
25
|
+
// Count { and } to track depth
|
|
26
|
+
const openBraces = (line.match(/{/g) || []).length
|
|
27
|
+
const closeBraces = (line.match(/}/g) || []).length
|
|
28
|
+
|
|
29
|
+
let newLine = line
|
|
30
|
+
if (braceDepth === 0 && line.includes('{')) {
|
|
31
|
+
const braceIndex = line.indexOf('{')
|
|
32
|
+
const selector = line.slice(0, braceIndex)
|
|
33
|
+
const rest = line.slice(braceIndex)
|
|
34
|
+
|
|
35
|
+
const prefixedSelector = selector.replace(
|
|
36
|
+
/(\.)([a-zA-Z0-9_-]+)/g,
|
|
37
|
+
(match, dot, cls) => {
|
|
38
|
+
// ✅ Skip ProseMirror-related, EditorView-related classes
|
|
39
|
+
if (
|
|
40
|
+
cls.startsWith('ProseMirror') ||
|
|
41
|
+
cls.startsWith('EditorView') ||
|
|
42
|
+
cls.startsWith('EmailView') ||
|
|
43
|
+
cls.startsWith('CUSTOM') ||
|
|
44
|
+
cls.startsWith(prefix1) ||
|
|
45
|
+
cls.startsWith(prefix2)
|
|
46
|
+
) {
|
|
47
|
+
return match
|
|
48
|
+
}
|
|
49
|
+
return `${dot}${prefix2}${cls}`
|
|
50
|
+
},
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
newLine = prefixedSelector + rest
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
braceDepth += openBraces - closeBraces
|
|
57
|
+
return newLine
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
return { code: prefixedLines.join('\n'), map: null }
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
if (!id.endsWith('.tsx') && !id.endsWith('.ts')) return null
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const ast = parse(code, {
|
|
68
|
+
sourceType: 'module',
|
|
69
|
+
plugins: ['jsx', 'typescript'],
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
const addImportant = (c) => {
|
|
73
|
+
if (!c) return c
|
|
74
|
+
const parts = c.split(':')
|
|
75
|
+
const utility = parts.pop()
|
|
76
|
+
if (!utility || utility.startsWith('!')) {
|
|
77
|
+
return c
|
|
78
|
+
}
|
|
79
|
+
parts.push(`!${utility}`)
|
|
80
|
+
return parts.join(':')
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const prefixClasses = (str) => {
|
|
84
|
+
return str
|
|
85
|
+
.split(/\s+/)
|
|
86
|
+
.filter(Boolean)
|
|
87
|
+
.map((c) => {
|
|
88
|
+
if (c.startsWith(prefix1) || c.startsWith('CUSTOM') || c.startsWith('ProseMirror') || c.startsWith('EmailView')) return c
|
|
89
|
+
return `${prefix1}${c}`
|
|
90
|
+
})
|
|
91
|
+
.map((c) => {
|
|
92
|
+
if (c.startsWith('CUSTOM') || c.startsWith('ProseMirror') || c.startsWith('EmailView')) return c
|
|
93
|
+
return addImportant(c)
|
|
94
|
+
})
|
|
95
|
+
.join(' ')
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
traverse.default(ast, {
|
|
99
|
+
// ✅ Handle variables whose name includes "DynamicClassName"
|
|
100
|
+
|
|
101
|
+
JSXAttribute(path) {
|
|
102
|
+
if (path.node.name.name !== 'className') return
|
|
103
|
+
|
|
104
|
+
const value = path.node.value
|
|
105
|
+
|
|
106
|
+
// String literal: className="..."
|
|
107
|
+
if (t.isStringLiteral(value)) {
|
|
108
|
+
path.node.value = t.stringLiteral(prefixClasses(value.value))
|
|
109
|
+
}
|
|
110
|
+
// Template literal: className={`...`}
|
|
111
|
+
else if (
|
|
112
|
+
t.isJSXExpressionContainer(value) &&
|
|
113
|
+
t.isTemplateLiteral(value.expression)
|
|
114
|
+
) {
|
|
115
|
+
const quasis = value.expression.quasis.map((quasi, i) => {
|
|
116
|
+
let classes = prefixClasses(quasi.value.raw)
|
|
117
|
+
|
|
118
|
+
if (i < value.expression.expressions.length) classes += ' '
|
|
119
|
+
if (i > 0 && !classes.startsWith(' ')) classes = ' ' + classes
|
|
120
|
+
|
|
121
|
+
return t.templateElement({ raw: classes, cooked: classes })
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
// 🔥 also fix expressions inside ${ ... }
|
|
125
|
+
const expressions = value.expression.expressions.map((expr) => {
|
|
126
|
+
if (t.isStringLiteral(expr)) {
|
|
127
|
+
return t.stringLiteral(prefixClasses(expr.value))
|
|
128
|
+
}
|
|
129
|
+
if (t.isLogicalExpression(expr)) {
|
|
130
|
+
if (t.isStringLiteral(expr.right)) {
|
|
131
|
+
return t.logicalExpression(
|
|
132
|
+
expr.operator,
|
|
133
|
+
expr.left,
|
|
134
|
+
t.stringLiteral(prefixClasses(expr.right.value)),
|
|
135
|
+
)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (t.isConditionalExpression(expr)) {
|
|
139
|
+
const newConsequent = t.isStringLiteral(expr.consequent)
|
|
140
|
+
? t.stringLiteral(prefixClasses(expr.consequent.value))
|
|
141
|
+
: expr.consequent
|
|
142
|
+
const newAlternate = t.isStringLiteral(expr.alternate)
|
|
143
|
+
? t.stringLiteral(prefixClasses(expr.alternate.value))
|
|
144
|
+
: expr.alternate
|
|
145
|
+
return t.conditionalExpression(
|
|
146
|
+
expr.test,
|
|
147
|
+
newConsequent,
|
|
148
|
+
newAlternate,
|
|
149
|
+
)
|
|
150
|
+
}
|
|
151
|
+
return expr
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
path.node.value = t.jsxExpressionContainer(
|
|
155
|
+
t.templateLiteral(quasis, expressions),
|
|
156
|
+
)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// className={condition ? "..." : "..."}
|
|
160
|
+
else if (
|
|
161
|
+
t.isJSXExpressionContainer(value) &&
|
|
162
|
+
t.isConditionalExpression(value.expression)
|
|
163
|
+
) {
|
|
164
|
+
const expr = value.expression
|
|
165
|
+
const newConsequent = t.isStringLiteral(expr.consequent)
|
|
166
|
+
? t.stringLiteral(prefixClasses(expr.consequent.value))
|
|
167
|
+
: expr.consequent
|
|
168
|
+
const newAlternate = t.isStringLiteral(expr.alternate)
|
|
169
|
+
? t.stringLiteral(prefixClasses(expr.alternate.value))
|
|
170
|
+
: expr.alternate
|
|
171
|
+
|
|
172
|
+
path.node.value = t.jsxExpressionContainer(
|
|
173
|
+
t.conditionalExpression(expr.test, newConsequent, newAlternate),
|
|
174
|
+
)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Other expressions (e.g., clsx())
|
|
178
|
+
else if (
|
|
179
|
+
t.isJSXExpressionContainer(value) &&
|
|
180
|
+
t.isCallExpression(value.expression)
|
|
181
|
+
) {
|
|
182
|
+
const args = value.expression.arguments.map((arg) => {
|
|
183
|
+
if (t.isStringLiteral(arg))
|
|
184
|
+
return t.stringLiteral(prefixClasses(arg.value))
|
|
185
|
+
return arg
|
|
186
|
+
})
|
|
187
|
+
path.node.value = t.jsxExpressionContainer(
|
|
188
|
+
t.callExpression(value.expression.callee, args),
|
|
189
|
+
)
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
VariableDeclarator(path) {
|
|
193
|
+
const id = path.node.id
|
|
194
|
+
const init = path.node.init
|
|
195
|
+
if (!t.isIdentifier(id)) return
|
|
196
|
+
|
|
197
|
+
// 🔹 only affect DynamicClassName variables
|
|
198
|
+
if (!id.name.includes('DynamicClassName')) return
|
|
199
|
+
if (!init) return
|
|
200
|
+
|
|
201
|
+
const prefixString = (expr) =>
|
|
202
|
+
t.isStringLiteral(expr)
|
|
203
|
+
? t.stringLiteral(prefixClasses(expr.value))
|
|
204
|
+
: expr
|
|
205
|
+
|
|
206
|
+
const prefixObject = (objExpr) => {
|
|
207
|
+
if (!t.isObjectExpression(objExpr)) return objExpr
|
|
208
|
+
objExpr.properties.forEach((prop) => {
|
|
209
|
+
if (t.isObjectProperty(prop)) {
|
|
210
|
+
if (t.isStringLiteral(prop.value)) {
|
|
211
|
+
prop.value = t.stringLiteral(
|
|
212
|
+
prefixClasses(prop.value.value),
|
|
213
|
+
)
|
|
214
|
+
} else if (t.isTemplateLiteral(prop.value)) {
|
|
215
|
+
const quasis = prop.value.quasis.map((quasi) => {
|
|
216
|
+
const raw = prefixClasses(quasi.value.raw)
|
|
217
|
+
return t.templateElement({ raw, cooked: raw })
|
|
218
|
+
})
|
|
219
|
+
prop.value = t.templateLiteral(
|
|
220
|
+
quasis,
|
|
221
|
+
prop.value.expressions,
|
|
222
|
+
)
|
|
223
|
+
} else if (t.isObjectExpression(prop.value)) {
|
|
224
|
+
prop.value = prefixObject(prop.value) // recurse
|
|
225
|
+
} else if (t.isConditionalExpression(prop.value)) {
|
|
226
|
+
prop.value = t.conditionalExpression(
|
|
227
|
+
prop.value.test,
|
|
228
|
+
prefixString(prop.value.consequent),
|
|
229
|
+
prefixString(prop.value.alternate),
|
|
230
|
+
)
|
|
231
|
+
} else if (t.isLogicalExpression(prop.value)) {
|
|
232
|
+
const right = t.isStringLiteral(prop.value.right)
|
|
233
|
+
? t.stringLiteral(prefixClasses(prop.value.right.value))
|
|
234
|
+
: prop.value.right
|
|
235
|
+
prop.value = t.logicalExpression(
|
|
236
|
+
prop.value.operator,
|
|
237
|
+
prop.value.left,
|
|
238
|
+
right,
|
|
239
|
+
)
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
})
|
|
243
|
+
return objExpr
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (t.isStringLiteral(init)) {
|
|
247
|
+
path.node.init = t.stringLiteral(prefixClasses(init.value))
|
|
248
|
+
} else if (t.isTemplateLiteral(init)) {
|
|
249
|
+
const quasis = init.quasis.map((quasi) => {
|
|
250
|
+
const raw = prefixClasses(quasi.value.raw)
|
|
251
|
+
return t.templateElement({ raw, cooked: raw })
|
|
252
|
+
})
|
|
253
|
+
path.node.init = t.templateLiteral(quasis, init.expressions)
|
|
254
|
+
} else if (t.isObjectExpression(init)) {
|
|
255
|
+
path.node.init = prefixObject(init)
|
|
256
|
+
} else if (t.isConditionalExpression(init)) {
|
|
257
|
+
path.node.init = t.conditionalExpression(
|
|
258
|
+
init.test,
|
|
259
|
+
prefixString(init.consequent),
|
|
260
|
+
prefixString(init.alternate),
|
|
261
|
+
)
|
|
262
|
+
} else if (t.isLogicalExpression(init)) {
|
|
263
|
+
const right = t.isStringLiteral(init.right)
|
|
264
|
+
? t.stringLiteral(prefixClasses(init.right.value))
|
|
265
|
+
: init.right
|
|
266
|
+
path.node.init = t.logicalExpression(
|
|
267
|
+
init.operator,
|
|
268
|
+
init.left,
|
|
269
|
+
right,
|
|
270
|
+
)
|
|
271
|
+
}
|
|
272
|
+
},
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
const output = generate.default(ast, {}, code)
|
|
276
|
+
return { code: output.code, map: output.map }
|
|
277
|
+
} catch (err) {
|
|
278
|
+
console.error(`Error transforming file ${id}:`, err)
|
|
279
|
+
return null
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Handles:
|
|
286
|
+
// ✅ className="..."
|
|
287
|
+
// ✅ className={\`...\`}with${"foo"}inside
|
|
288
|
+
// ✅className={cond ? "a" : "b"}
|
|
289
|
+
// ✅className={cond && "foo"}
|
|
290
|
+
// ✅className={clsx("foo", cond && "bar")}
|
|
291
|
+
// ✅const fooDynamicClassName = "..." | {...} | cond ? ... : ...`
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { isPrefixBuildMode } from '../portal-env-mode.mjs'
|
|
2
|
+
|
|
3
|
+
const forcePrefix = null // true, false, or null to auto-detect
|
|
4
|
+
|
|
5
|
+
export const prefix = 'dw'
|
|
6
|
+
|
|
7
|
+
const isPrefix = (env) => {
|
|
8
|
+
if (forcePrefix != null) return forcePrefix
|
|
9
|
+
return isPrefixBuildMode(env)
|
|
10
|
+
}
|
|
11
|
+
export default isPrefix
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import tailwindContent from './tailwind-content.js'
|
|
2
|
+
|
|
3
|
+
export default function TailwindContentPlugin() {
|
|
4
|
+
return {
|
|
5
|
+
name: 'vite-tailwind-content',
|
|
6
|
+
apply: 'serve',
|
|
7
|
+
|
|
8
|
+
async buildStart() {
|
|
9
|
+
await tailwindContent()
|
|
10
|
+
},
|
|
11
|
+
async handleHotUpdate({ file }) {
|
|
12
|
+
// ❌ ignore changes inside .temp (to avoid infinite loop)
|
|
13
|
+
if (file.includes('.temp')) return
|
|
14
|
+
|
|
15
|
+
if (
|
|
16
|
+
/\.(tsx|jsx|css)$/.test(file) &&
|
|
17
|
+
(file.includes('/src/') ||
|
|
18
|
+
file.includes('/dev-module-config/') ||
|
|
19
|
+
file.includes('/.wphs/') ||
|
|
20
|
+
file.includes('/.kit-module-config/') ||
|
|
21
|
+
file.includes('/.dev/'))
|
|
22
|
+
) {
|
|
23
|
+
await tailwindContent()
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import { prefix } from './prefix.env.js'
|
|
4
|
+
import { findProjectRoot, getKitRoot } from '../project-root.mjs'
|
|
5
|
+
import { moduleConfigPaths } from '../module-config-dir.mjs'
|
|
6
|
+
|
|
7
|
+
const prefix1 = `${prefix}:`
|
|
8
|
+
|
|
9
|
+
const tempDir = path.resolve('./.temp')
|
|
10
|
+
|
|
11
|
+
function resolveContentScanDirs() {
|
|
12
|
+
const dirs = [path.resolve('./src')]
|
|
13
|
+
try {
|
|
14
|
+
const projectRoot = findProjectRoot()
|
|
15
|
+
const { devDir } = moduleConfigPaths(projectRoot)
|
|
16
|
+
const kitModuleConfig = path.join(getKitRoot(), 'src/dev-module-config')
|
|
17
|
+
const shimModuleConfig = path.join(devDir, '.kit-module-config')
|
|
18
|
+
|
|
19
|
+
for (const dir of [kitModuleConfig, shimModuleConfig]) {
|
|
20
|
+
if (fs.existsSync(dir) && !dirs.includes(dir)) {
|
|
21
|
+
dirs.push(dir)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
} catch {
|
|
25
|
+
// cwd may not be a woodsportal project (e.g. kit-only scripts)
|
|
26
|
+
}
|
|
27
|
+
return dirs
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (!fs.existsSync(tempDir)) fs.mkdirSync(tempDir, { recursive: true })
|
|
31
|
+
|
|
32
|
+
function addPrefix(token) {
|
|
33
|
+
if (!token) return token
|
|
34
|
+
if (token.startsWith(prefix1)) return token
|
|
35
|
+
return `${prefix1}${token}`
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function addImportant(token) {
|
|
39
|
+
if (!token) return token
|
|
40
|
+
const parts = token.split(':')
|
|
41
|
+
const utility = parts.pop()
|
|
42
|
+
if (!utility || utility.startsWith('!')) {
|
|
43
|
+
return token
|
|
44
|
+
}
|
|
45
|
+
parts.push(`!${utility}`)
|
|
46
|
+
return parts.join(':')
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// function prefixStaticClasses(str) {
|
|
50
|
+
// // Prefix whitespace-separated class tokens (outside ${ ... })
|
|
51
|
+
// return str.split(/\s+/).filter(Boolean).map(addPrefix).join(' ')
|
|
52
|
+
// }
|
|
53
|
+
|
|
54
|
+
// ✅ Safe prefixing for static classes
|
|
55
|
+
function prefixStaticClasses(str) {
|
|
56
|
+
return str
|
|
57
|
+
.split(/\s+/)
|
|
58
|
+
.filter(Boolean)
|
|
59
|
+
.map((token) => {
|
|
60
|
+
if (!token) return token
|
|
61
|
+
if (token.startsWith(prefix1)) return addImportant(token)
|
|
62
|
+
|
|
63
|
+
// ✅ Handle opening arbitrary value like "z-["
|
|
64
|
+
if (/^[a-z0-9-]+-\[$/i.test(token)) {
|
|
65
|
+
return addImportant(`${prefix1}${token}`)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ✅ Leave closing bracket "]" untouched
|
|
69
|
+
if (token === ']') {
|
|
70
|
+
return token
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ✅ Handle complete arbitrary values like "z-[999]" or "top-[50%]"
|
|
74
|
+
// const match = token.match(/^([a-z0-9-]+(?:\/[a-z0-9-]+)?):?\[(.+)\]$/i)
|
|
75
|
+
// if (match) {
|
|
76
|
+
// const [, key, val] = match
|
|
77
|
+
// return `${prefix1}${key}-[${val}]`
|
|
78
|
+
// }
|
|
79
|
+
|
|
80
|
+
return addImportant(addPrefix(token))
|
|
81
|
+
})
|
|
82
|
+
.join(' ')
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Prefix static segments of a template literal body (leave ${...} untouched)
|
|
86
|
+
function prefixTemplateLiteralBody(body) {
|
|
87
|
+
const parts = body.split(/(\$\{[^}]*\})/g)
|
|
88
|
+
return parts
|
|
89
|
+
.map((part) => {
|
|
90
|
+
if (part.startsWith('${')) return part
|
|
91
|
+
if (!part.trim()) return part
|
|
92
|
+
const leading = part.match(/^\s*/)?.[0] ?? ''
|
|
93
|
+
const trailing = part.match(/\s*$/)?.[0] ?? ''
|
|
94
|
+
const core = part.trim()
|
|
95
|
+
if (!core) return part
|
|
96
|
+
return leading + prefixStaticClasses(core) + trailing
|
|
97
|
+
})
|
|
98
|
+
.join('')
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Prefix class strings inside object literals, quoted strings, and template literals
|
|
102
|
+
function prefixValueStrings(value) {
|
|
103
|
+
let result = value.replace(/"([^"]*)"/g, (_, cls) => `"${prefixStaticClasses(cls)}"`)
|
|
104
|
+
result = result.replace(/`([^`]*)`/g, (_, body) => `\`${prefixTemplateLiteralBody(body)}\``)
|
|
105
|
+
return result
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Prefix classes inside quoted strings ONLY, e.g. "...${cond ? 'lg:w-[...]' : 'lg:w-[...]'}..."
|
|
109
|
+
function prefixQuotedStringsInExpression(expr) {
|
|
110
|
+
// Replace '..."' or '...' with prefixed contents
|
|
111
|
+
return expr.replace(/(['"])((?:\\.|(?!\1).)*)\1/g, (_, quote, inner) => {
|
|
112
|
+
return `${quote}${prefixStaticClasses(inner)}${quote}`
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Handle className
|
|
117
|
+
function prefixClassesInCode(code) {
|
|
118
|
+
// 1) Plain string: className="..."
|
|
119
|
+
code = code.replace(/className\s*=\s*["']([^"']*)["']/g, (_, classStr) => {
|
|
120
|
+
return `className="${prefixStaticClasses(classStr)}"`
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
// code = code.replace(/className\s*=\s*["']([^"']*)["']/g, (_, classStr) => {
|
|
124
|
+
// // 🛑 Skip prefixing if contains "ProseMirror"
|
|
125
|
+
// if (/\bProseMirror\b/.test(classStr)) {
|
|
126
|
+
// return `className="${classStr}"`
|
|
127
|
+
// }
|
|
128
|
+
// return `className="${prefixStaticClasses(classStr)}"`
|
|
129
|
+
// })
|
|
130
|
+
|
|
131
|
+
// 2) Template literal: className={`...`}
|
|
132
|
+
code = code.replace(/className\s*=\s*{\s*`([\s\S]*?)`\s*}/g, (_, tplBody) => {
|
|
133
|
+
const parts = tplBody.split(/(\$\{[\s\S]*?\})/g)
|
|
134
|
+
|
|
135
|
+
// const rebuilt = parts
|
|
136
|
+
// .map((part) => {
|
|
137
|
+
// if (part.startsWith('${')) {
|
|
138
|
+
// return part.replace(
|
|
139
|
+
// /^\$\{([\s\S]*?)\}$/,
|
|
140
|
+
// (_m, inner) => '${' + prefixQuotedStringsInExpression(inner) + '}',
|
|
141
|
+
// )
|
|
142
|
+
// }
|
|
143
|
+
// return prefixStaticClasses(part)
|
|
144
|
+
// })
|
|
145
|
+
// .join('')
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
// return `className={\`${rebuilt}\`}`
|
|
149
|
+
const rebuilt = parts
|
|
150
|
+
.map((part, i) => {
|
|
151
|
+
if (part.startsWith('${')) {
|
|
152
|
+
// normalize: always prefix with one space
|
|
153
|
+
return ' ' + part.replace(
|
|
154
|
+
/^\$\{([\s\S]*?)\}$/,
|
|
155
|
+
(_m, inner) => '${' + prefixQuotedStringsInExpression(inner) + '}',
|
|
156
|
+
)
|
|
157
|
+
}
|
|
158
|
+
// static classes
|
|
159
|
+
return prefixStaticClasses(part).trim()
|
|
160
|
+
})
|
|
161
|
+
.join(' ')
|
|
162
|
+
.replace(/\s+/g, ' ') // collapse multiple spaces
|
|
163
|
+
|
|
164
|
+
return `className={\`${rebuilt.trim()}\`}`
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
// 3) Ternary expressions: className={cond ? "..." : "..."}
|
|
168
|
+
code = code.replace(
|
|
169
|
+
/className\s*=\s*{([^{};]+?\?[^:]+:[^}]+)}/g,
|
|
170
|
+
(match, inner) => {
|
|
171
|
+
const ternaryMatch = inner.match(
|
|
172
|
+
/^(.*?\?)\s*(['"][^'"]*['"])\s*:\s*(['"][^'"]*['"])\s*$/,
|
|
173
|
+
)
|
|
174
|
+
if (!ternaryMatch) return match
|
|
175
|
+
|
|
176
|
+
const [, condition, whenTrue, whenFalse] = ternaryMatch
|
|
177
|
+
const prefixedTrue = whenTrue.replace(
|
|
178
|
+
/(['"])(.*?)\1/,
|
|
179
|
+
(_, q, cls) => `${q}${prefixStaticClasses(cls)}${q}`,
|
|
180
|
+
)
|
|
181
|
+
const prefixedFalse = whenFalse.replace(
|
|
182
|
+
/(['"])(.*?)\1/,
|
|
183
|
+
(_, q, cls) => `${q}${prefixStaticClasses(cls)}${q}`,
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
return `className={${condition} ${prefixedTrue} : ${prefixedFalse}}`
|
|
187
|
+
},
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
// 4) Variables with "DynamicClassName" (supports TypeScript annotations like `: any`)
|
|
191
|
+
code = code.replace(
|
|
192
|
+
/(const|let|var)\s+([A-Za-z0-9_]*DynamicClassName[A-Za-z0-9_]*)\s*(?::\s*([^=]+))?\s*=\s*([\s\S]*?);/g,
|
|
193
|
+
(match, decl, varName, typeAnnotation, value) => {
|
|
194
|
+
const typeSuffix = typeAnnotation ? `: ${typeAnnotation.trim()}` : ''
|
|
195
|
+
const trimmed = value.trim()
|
|
196
|
+
if (/^\{[\s\S]*\}$/.test(trimmed)) {
|
|
197
|
+
return `${decl} ${varName}${typeSuffix} = ${prefixValueStrings(value)};`
|
|
198
|
+
}
|
|
199
|
+
if (/\?.*:/.test(trimmed)) {
|
|
200
|
+
return `${decl} ${varName}${typeSuffix} = ${prefixValueStrings(value)};`
|
|
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
|
+
}
|
|
210
|
+
return match
|
|
211
|
+
},
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
// 5) Handle classNames(...) or clsx(...)
|
|
215
|
+
code = code.replace(
|
|
216
|
+
/className\s*=\s*{?\s*(classNames|clsx)\(([\s\S]*?)\)}?/g,
|
|
217
|
+
(match, fnName, args) => {
|
|
218
|
+
const transformedArgs = args.replace(/(['"])(.*?)\1/g, (_, q, cls) => {
|
|
219
|
+
return `${q}${prefixStaticClasses(cls)}${q}`
|
|
220
|
+
})
|
|
221
|
+
return `className={${fnName}(${transformedArgs})}`
|
|
222
|
+
},
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
return code
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// 🔥 helper: recursively collect all `.tsx` files under src
|
|
229
|
+
function getAllTsxFiles(dir, fileList = []) {
|
|
230
|
+
const files = fs.readdirSync(dir)
|
|
231
|
+
for (const file of files) {
|
|
232
|
+
const fullPath = path.join(dir, file)
|
|
233
|
+
const stat = fs.statSync(fullPath)
|
|
234
|
+
if (stat.isDirectory()) {
|
|
235
|
+
getAllTsxFiles(fullPath, fileList)
|
|
236
|
+
} else if (file.endsWith('.tsx')) {
|
|
237
|
+
fileList.push(fullPath)
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return fileList
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Pre-build static file for Tailwind
|
|
244
|
+
export default async function tailwindContent() {
|
|
245
|
+
const srcFiles = resolveContentScanDirs().flatMap((dir) => getAllTsxFiles(dir))
|
|
246
|
+
let allPrefixedClasses = ''
|
|
247
|
+
|
|
248
|
+
for (const filePath of srcFiles) {
|
|
249
|
+
const code = fs.readFileSync(filePath, 'utf-8')
|
|
250
|
+
const transformed = prefixClassesInCode(code)
|
|
251
|
+
allPrefixedClasses += transformed + '\n'
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const outputPath = path.resolve(tempDir, 'tailwind-classes.tsx')
|
|
255
|
+
fs.mkdirSync(path.dirname(outputPath), { recursive: true })
|
|
256
|
+
fs.writeFileSync(outputPath, allPrefixedClasses, 'utf-8')
|
|
257
|
+
|
|
258
|
+
// Return the file as Tailwind content
|
|
259
|
+
return [
|
|
260
|
+
{
|
|
261
|
+
raw: allPrefixedClasses,
|
|
262
|
+
extension: 'tsx',
|
|
263
|
+
},
|
|
264
|
+
]
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
tailwindContent()
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Fail if dist/cdn/app.*.js does not bake the API URL from .env.stg / .env.prod.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* node scripts/verify-cdn-api-endpoint.mjs --stg
|
|
7
|
+
* node scripts/verify-cdn-api-endpoint.mjs --prod
|
|
8
|
+
*/
|
|
9
|
+
import { existsSync, readFileSync, readdirSync } from 'node:fs'
|
|
10
|
+
import { dirname, resolve } from 'node:path'
|
|
11
|
+
import { fileURLToPath } from 'node:url'
|
|
12
|
+
import { findProjectRoot } from './project-root.mjs'
|
|
13
|
+
import { loadEnv } from 'vite'
|
|
14
|
+
|
|
15
|
+
import { resolvePortalBuildId } from './resolve-portal-build-id.mjs'
|
|
16
|
+
import { resolvePortalViteMode } from './portal-env-mode.mjs'
|
|
17
|
+
|
|
18
|
+
const root = findProjectRoot()
|
|
19
|
+
|
|
20
|
+
const stg = process.argv.includes('--stg') || process.argv.includes('--staging')
|
|
21
|
+
const prod = process.argv.includes('--prod') || process.argv.includes('--production')
|
|
22
|
+
|
|
23
|
+
if (stg === prod) {
|
|
24
|
+
console.error(
|
|
25
|
+
'Usage: node scripts/verify-cdn-api-endpoint.mjs --stg | --prod',
|
|
26
|
+
)
|
|
27
|
+
process.exit(1)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const mode = resolvePortalViteMode()
|
|
31
|
+
const expected = loadEnv(mode, root, '').VITE_PUBLIC_REST_API_ENDPOINT?.replace(/\/$/, '')
|
|
32
|
+
|
|
33
|
+
if (!expected) {
|
|
34
|
+
console.error(`[verify-cdn-api] Missing VITE_PUBLIC_REST_API_ENDPOINT in .env.${mode}`)
|
|
35
|
+
process.exit(1)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const cdnDir = resolve(root, 'dist/cdn')
|
|
39
|
+
if (!existsSync(cdnDir)) {
|
|
40
|
+
console.error('[verify-cdn-api] dist/cdn missing — run yarn build:cdn:dev or yarn build:cdn:prod first')
|
|
41
|
+
process.exit(1)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const buildId = resolvePortalBuildId()
|
|
45
|
+
const appFile = readdirSync(cdnDir).find((f) => f === `app.${buildId}.js`)
|
|
46
|
+
|
|
47
|
+
if (!appFile) {
|
|
48
|
+
console.error(
|
|
49
|
+
`[verify-cdn-api] app.${buildId}.js not found in dist/cdn — rebuild with matching .portal-build-id`,
|
|
50
|
+
)
|
|
51
|
+
process.exit(1)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const source = readFileSync(resolve(cdnDir, appFile), 'utf8')
|
|
55
|
+
const found = [...source.matchAll(/https:\/\/api(?:\.dev)?\.woodsportal\.com/g)].map((m) => m[0])
|
|
56
|
+
const unique = [...new Set(found)]
|
|
57
|
+
|
|
58
|
+
if (!unique.includes(expected)) {
|
|
59
|
+
console.error('[verify-cdn-api] API URL mismatch in dist/cdn — refusing to publish.')
|
|
60
|
+
console.error(` expected (${mode}): ${expected}`)
|
|
61
|
+
console.error(` found in ${appFile}: ${unique.join(', ') || '(none)'}`)
|
|
62
|
+
console.error('')
|
|
63
|
+
console.error(' Dev API on HubSpot: yarn deploy:dev')
|
|
64
|
+
console.error(' Prod API on HubSpot: yarn deploy:prod')
|
|
65
|
+
process.exit(1)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
console.log(`[verify-cdn-api] OK ${appFile} bakes ${expected} (${mode})`)
|