@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,49 @@
|
|
|
1
|
+
import { existsSync, lstatSync, mkdirSync, rmSync, symlinkSync } from 'node:fs'
|
|
2
|
+
import { resolve } from 'node:path'
|
|
3
|
+
|
|
4
|
+
import { moduleConfigPaths } from '../module-config-dir.mjs'
|
|
5
|
+
|
|
6
|
+
function ensureSymlink(shimPath, target, { directory }) {
|
|
7
|
+
try {
|
|
8
|
+
if (existsSync(shimPath)) {
|
|
9
|
+
const stat = lstatSync(shimPath)
|
|
10
|
+
if (stat.isSymbolicLink()) {
|
|
11
|
+
return shimPath
|
|
12
|
+
}
|
|
13
|
+
rmSync(shimPath, { recursive: !directory, force: true })
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
mkdirSync(resolve(shimPath, '..'), { recursive: true })
|
|
17
|
+
symlinkSync(target, shimPath, directory ? 'dir' : 'file')
|
|
18
|
+
return shimPath
|
|
19
|
+
} catch {
|
|
20
|
+
return target
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Symlink kit dev sources into the consumer repo so Vite serves them in-root
|
|
26
|
+
* (avoids @fs/ + broken react/jsx-dev-runtime / react-dom CJS interop).
|
|
27
|
+
*/
|
|
28
|
+
export function ensureDevKitShims(projectRoot, kitRoot) {
|
|
29
|
+
const { devDir } = moduleConfigPaths(projectRoot)
|
|
30
|
+
|
|
31
|
+
const moduleConfigRoot = ensureSymlink(
|
|
32
|
+
resolve(devDir, '.kit-module-config'),
|
|
33
|
+
resolve(kitRoot, 'src/dev-module-config'),
|
|
34
|
+
{ directory: true },
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
const bootstrapShim = ensureSymlink(
|
|
38
|
+
resolve(devDir, '.kit-bootstrap.tsx'),
|
|
39
|
+
resolve(kitRoot, 'src/dev/bootstrap.tsx'),
|
|
40
|
+
{ directory: false },
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
return { moduleConfigRoot, bootstrapShim }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** @deprecated use ensureDevKitShims */
|
|
47
|
+
export function ensureDevModuleConfigShim(projectRoot, kitRoot) {
|
|
48
|
+
return ensureDevKitShims(projectRoot, kitRoot).moduleConfigRoot
|
|
49
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ensureDevKitShims, ensureDevModuleConfigShim } from './ensure-dev-kit-shims.js'
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/** @typedef {import('vite').Plugin} Plugin */
|
|
2
|
+
|
|
3
|
+
import fs from 'node:fs'
|
|
4
|
+
import fsp from 'node:fs/promises'
|
|
5
|
+
import path from 'node:path'
|
|
6
|
+
import { fileURLToPath } from 'node:url'
|
|
7
|
+
|
|
8
|
+
import { moduleConfigPaths } from '../module-config-dir.mjs'
|
|
9
|
+
|
|
10
|
+
const CONFIG_API = '/__dev/module-config'
|
|
11
|
+
const BRANDING_API = '/__dev/branding-asset'
|
|
12
|
+
const MAX_BODY_BYTES = 5 * 1024 * 1024
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Dev-only Vite middleware: persist module config + branding assets under dev.moduleConfigDir.
|
|
16
|
+
*
|
|
17
|
+
* @param {string} [projectRoot]
|
|
18
|
+
* @returns {Plugin}
|
|
19
|
+
*/
|
|
20
|
+
export default function moduleConfigStore(projectRoot) {
|
|
21
|
+
const root = projectRoot ?? path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..')
|
|
22
|
+
const {
|
|
23
|
+
moduleConfigDir,
|
|
24
|
+
devDir,
|
|
25
|
+
configDir,
|
|
26
|
+
localConfigPath,
|
|
27
|
+
exampleConfigPath,
|
|
28
|
+
assetsDir,
|
|
29
|
+
brandingAssetsUrlPrefix,
|
|
30
|
+
legacyBrandingAssetsUrlPrefixes = [],
|
|
31
|
+
} = moduleConfigPaths(root)
|
|
32
|
+
|
|
33
|
+
const legacyPrefixes = Array.isArray(legacyBrandingAssetsUrlPrefixes)
|
|
34
|
+
? legacyBrandingAssetsUrlPrefixes
|
|
35
|
+
: []
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
name: 'woodsportal-module-config-store',
|
|
39
|
+
apply: 'serve',
|
|
40
|
+
configureServer(server) {
|
|
41
|
+
ensureDirSync(devDir)
|
|
42
|
+
ensureDirSync(configDir)
|
|
43
|
+
ensureDirSync(assetsDir)
|
|
44
|
+
|
|
45
|
+
server.middlewares.use((req, res, next) => {
|
|
46
|
+
const url = req.url ?? ''
|
|
47
|
+
const assetsPrefix = [brandingAssetsUrlPrefix, ...legacyPrefixes].find((prefix) =>
|
|
48
|
+
url.startsWith(prefix),
|
|
49
|
+
)
|
|
50
|
+
if (!assetsPrefix) {
|
|
51
|
+
next()
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const relative = decodeURIComponent(url.slice(assetsPrefix.length).split('?')[0] ?? '')
|
|
56
|
+
if (!relative || relative.includes('..')) {
|
|
57
|
+
res.statusCode = 400
|
|
58
|
+
res.end('Invalid path')
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const filePath = path.join(assetsDir, relative)
|
|
63
|
+
if (!filePath.startsWith(assetsDir) || !fs.existsSync(filePath)) {
|
|
64
|
+
res.statusCode = 404
|
|
65
|
+
res.end('Not found')
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const ext = path.extname(filePath).toLowerCase()
|
|
70
|
+
const types = {
|
|
71
|
+
'.png': 'image/png',
|
|
72
|
+
'.jpg': 'image/jpeg',
|
|
73
|
+
'.jpeg': 'image/jpeg',
|
|
74
|
+
'.gif': 'image/gif',
|
|
75
|
+
'.webp': 'image/webp',
|
|
76
|
+
'.svg': 'image/svg+xml',
|
|
77
|
+
}
|
|
78
|
+
res.setHeader('Content-Type', types[ext] ?? 'application/octet-stream')
|
|
79
|
+
fs.createReadStream(filePath).pipe(res)
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
server.middlewares.use(BRANDING_API, (req, res, next) => {
|
|
83
|
+
if (req.method !== 'POST') {
|
|
84
|
+
next()
|
|
85
|
+
return
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
readBody(req, res, async (body) => {
|
|
89
|
+
try {
|
|
90
|
+
const payload = JSON.parse(body)
|
|
91
|
+
const filename = sanitizeFilename(String(payload.filename ?? 'upload.bin'))
|
|
92
|
+
const data = String(payload.data ?? '')
|
|
93
|
+
const buffer = Buffer.from(data, 'base64')
|
|
94
|
+
if (buffer.length > MAX_BODY_BYTES) {
|
|
95
|
+
res.statusCode = 413
|
|
96
|
+
res.end('Payload too large')
|
|
97
|
+
return
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const filePath = path.join(assetsDir, filename)
|
|
101
|
+
const tmpPath = `${filePath}.tmp`
|
|
102
|
+
await fsp.writeFile(tmpPath, buffer)
|
|
103
|
+
await fsp.rename(tmpPath, filePath)
|
|
104
|
+
|
|
105
|
+
writeJson(res, 200, {
|
|
106
|
+
src: `${brandingAssetsUrlPrefix}${encodeURIComponent(filename)}`,
|
|
107
|
+
filename,
|
|
108
|
+
})
|
|
109
|
+
} catch {
|
|
110
|
+
res.statusCode = 400
|
|
111
|
+
res.end('Invalid JSON')
|
|
112
|
+
}
|
|
113
|
+
})
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
server.middlewares.use(CONFIG_API, (req, res, next) => {
|
|
117
|
+
if (req.method === 'GET') {
|
|
118
|
+
void (async () => {
|
|
119
|
+
const local = await readConfigRecord(localConfigPath, 'local')
|
|
120
|
+
if (local) {
|
|
121
|
+
writeJson(res, 200, local)
|
|
122
|
+
return
|
|
123
|
+
}
|
|
124
|
+
const example = await readConfigRecord(exampleConfigPath, 'example')
|
|
125
|
+
if (example) {
|
|
126
|
+
writeJson(res, 200, example)
|
|
127
|
+
return
|
|
128
|
+
}
|
|
129
|
+
writeJson(res, 200, { version: 1, module: {}, updatedAt: null, source: 'empty' })
|
|
130
|
+
})()
|
|
131
|
+
return
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (req.method === 'PUT') {
|
|
135
|
+
readBody(req, res, (body) => {
|
|
136
|
+
void (async () => {
|
|
137
|
+
try {
|
|
138
|
+
const parsed = JSON.parse(body)
|
|
139
|
+
const record = {
|
|
140
|
+
version: Number(parsed.version ?? 1),
|
|
141
|
+
module: parsed.module ?? {},
|
|
142
|
+
updatedAt: new Date().toISOString(),
|
|
143
|
+
source: 'local',
|
|
144
|
+
}
|
|
145
|
+
await writeJsonAtomic(localConfigPath, record)
|
|
146
|
+
writeJson(res, 200, record)
|
|
147
|
+
} catch {
|
|
148
|
+
res.statusCode = 400
|
|
149
|
+
res.end('Invalid JSON')
|
|
150
|
+
}
|
|
151
|
+
})()
|
|
152
|
+
})
|
|
153
|
+
return
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (req.method === 'DELETE') {
|
|
157
|
+
void (async () => {
|
|
158
|
+
try {
|
|
159
|
+
if (fs.existsSync(localConfigPath)) {
|
|
160
|
+
await fsp.unlink(localConfigPath)
|
|
161
|
+
}
|
|
162
|
+
res.statusCode = 204
|
|
163
|
+
res.end()
|
|
164
|
+
} catch {
|
|
165
|
+
res.statusCode = 500
|
|
166
|
+
res.end('Delete failed')
|
|
167
|
+
}
|
|
168
|
+
})()
|
|
169
|
+
return
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
next()
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
console.log(
|
|
176
|
+
`\n\x1b[90m[module-config-store]\x1b[0m ${moduleConfigDir}/config/module-config.local.json + ${moduleConfigDir}/assets/branding/\n`,
|
|
177
|
+
)
|
|
178
|
+
},
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function writeJson(res, status, payload) {
|
|
183
|
+
res.statusCode = status
|
|
184
|
+
res.setHeader('Content-Type', 'application/json')
|
|
185
|
+
res.end(JSON.stringify(payload))
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function ensureDirSync(dir) {
|
|
189
|
+
if (!fs.existsSync(dir)) {
|
|
190
|
+
fs.mkdirSync(dir, { recursive: true })
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function sanitizeFilename(name) {
|
|
195
|
+
const base = path.basename(name).replace(/[^a-zA-Z0-9._-]/g, '-')
|
|
196
|
+
return base || 'upload.bin'
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function readBody(req, res, onEnd) {
|
|
200
|
+
const chunks = []
|
|
201
|
+
let size = 0
|
|
202
|
+
req.on('data', (chunk) => {
|
|
203
|
+
size += chunk.length
|
|
204
|
+
if (size > MAX_BODY_BYTES) {
|
|
205
|
+
res.statusCode = 413
|
|
206
|
+
res.end('Payload too large')
|
|
207
|
+
req.destroy()
|
|
208
|
+
return
|
|
209
|
+
}
|
|
210
|
+
chunks.push(chunk)
|
|
211
|
+
})
|
|
212
|
+
req.on('end', () => {
|
|
213
|
+
onEnd(Buffer.concat(chunks).toString('utf8'))
|
|
214
|
+
})
|
|
215
|
+
req.on('error', () => {
|
|
216
|
+
res.statusCode = 500
|
|
217
|
+
res.end('Request error')
|
|
218
|
+
})
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async function readConfigRecord(filePath, source) {
|
|
222
|
+
try {
|
|
223
|
+
const raw = await fsp.readFile(filePath, 'utf8')
|
|
224
|
+
const parsed = JSON.parse(raw)
|
|
225
|
+
return {
|
|
226
|
+
version: Number(parsed.version ?? 1),
|
|
227
|
+
module: parsed.module ?? {},
|
|
228
|
+
updatedAt: parsed.updatedAt ?? null,
|
|
229
|
+
source,
|
|
230
|
+
}
|
|
231
|
+
} catch {
|
|
232
|
+
return null
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async function writeJsonAtomic(filePath, record) {
|
|
237
|
+
const dir = path.dirname(filePath)
|
|
238
|
+
ensureDirSync(dir)
|
|
239
|
+
const tmpPath = `${filePath}.tmp`
|
|
240
|
+
await fsp.writeFile(tmpPath, JSON.stringify(record, null, 2), 'utf8')
|
|
241
|
+
await fsp.rename(tmpPath, filePath)
|
|
242
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
declare module './scripts/vite/sdk-log-bridge.js' {
|
|
2
|
+
import type { Plugin } from 'vite'
|
|
3
|
+
export default function sdkLogBridge(): Plugin
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
declare module './scripts/vite/module-config-store.js' {
|
|
7
|
+
import type { Plugin } from 'vite'
|
|
8
|
+
export default function moduleConfigStore(projectRoot?: string): Plugin
|
|
9
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Vite options for HubSpot portal builds (CDN + module).
|
|
3
|
+
*/
|
|
4
|
+
import { dirname, resolve } from 'node:path'
|
|
5
|
+
import { fileURLToPath } from 'node:url'
|
|
6
|
+
import tailwindPrefixPlugin from '../tailwind/prefix-plugin.js'
|
|
7
|
+
import TailwindContentPlugin from '../tailwind/tailwind-content-plugin.js'
|
|
8
|
+
import isPrefix from '../tailwind/prefix.env.js'
|
|
9
|
+
import { portalBuildId as resolveBuildId } from '../resolve-portal-build-id.mjs'
|
|
10
|
+
import { nodeEnvForPortalMode } from '../portal-env-mode.mjs'
|
|
11
|
+
import { findProjectRoot } from '../project-root.mjs'
|
|
12
|
+
import { importConsumerDep } from '../resolve-consumer-dep.mjs'
|
|
13
|
+
|
|
14
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
15
|
+
const root = findProjectRoot()
|
|
16
|
+
|
|
17
|
+
const [{ default: viteReact }, { TanStackRouterVite }, { default: tailwindcss }] =
|
|
18
|
+
await Promise.all([
|
|
19
|
+
importConsumerDep('@vitejs/plugin-react'),
|
|
20
|
+
importConsumerDep('@tanstack/router-plugin/vite'),
|
|
21
|
+
importConsumerDep('@tailwindcss/vite'),
|
|
22
|
+
])
|
|
23
|
+
|
|
24
|
+
export function portalBuildId() {
|
|
25
|
+
return resolveBuildId()
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function portalDefine(mode) {
|
|
29
|
+
return {
|
|
30
|
+
'process.env.NODE_ENV': JSON.stringify(nodeEnvForPortalMode(mode)),
|
|
31
|
+
'process.env': {},
|
|
32
|
+
process: JSON.stringify({ env: {} }),
|
|
33
|
+
__PORTAL_BUILD_ID__: JSON.stringify(portalBuildId()),
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function prodDevModuleConfigRouteStub(mode) {
|
|
38
|
+
if (!isPrefix(mode)) {
|
|
39
|
+
return null
|
|
40
|
+
}
|
|
41
|
+
const stub = resolve(root, 'src/routes/dev.module-config.prod-stub.tsx')
|
|
42
|
+
return {
|
|
43
|
+
name: 'woodsportal-prod-dev-route-stub',
|
|
44
|
+
enforce: 'pre',
|
|
45
|
+
resolveId(source) {
|
|
46
|
+
if (
|
|
47
|
+
source === './routes/dev.module-config' ||
|
|
48
|
+
source.endsWith('/routes/dev.module-config') ||
|
|
49
|
+
source.endsWith('/routes/dev.module-config.tsx')
|
|
50
|
+
) {
|
|
51
|
+
return stub
|
|
52
|
+
}
|
|
53
|
+
return null
|
|
54
|
+
},
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function portalPlugins(mode, { codeSplitting = false, routeFileIgnorePattern } = {}) {
|
|
59
|
+
const routeIgnore = routeFileIgnorePattern ?? (isPrefix(mode) ? 'dev\\.' : undefined)
|
|
60
|
+
return [
|
|
61
|
+
prodDevModuleConfigRouteStub(mode),
|
|
62
|
+
TanStackRouterVite({
|
|
63
|
+
autoCodeSplitting: codeSplitting,
|
|
64
|
+
...(routeIgnore ? { routeFileIgnorePattern: routeIgnore } : {}),
|
|
65
|
+
}),
|
|
66
|
+
viteReact({
|
|
67
|
+
babel: {
|
|
68
|
+
plugins: ['babel-plugin-react-compiler'],
|
|
69
|
+
},
|
|
70
|
+
}),
|
|
71
|
+
tailwindcss(),
|
|
72
|
+
...(isPrefix(mode)
|
|
73
|
+
? [tailwindPrefixPlugin(), TailwindContentPlugin()]
|
|
74
|
+
: []),
|
|
75
|
+
]
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function portalResolve(mode) {
|
|
79
|
+
const kitRoot = resolve(dirname(fileURLToPath(import.meta.url)), '../..')
|
|
80
|
+
/** @type {import('vite').AliasOptions} */
|
|
81
|
+
const alias = [
|
|
82
|
+
{
|
|
83
|
+
find: '@wphs/adapter',
|
|
84
|
+
replacement: resolve(root, './wphs.adapter.ts'),
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
find: '@/dev/module-config',
|
|
88
|
+
replacement: resolve(kitRoot, 'src/dev-module-config'),
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
find: '@',
|
|
92
|
+
replacement: resolve(root, './src'),
|
|
93
|
+
},
|
|
94
|
+
]
|
|
95
|
+
|
|
96
|
+
const rumEnabled =
|
|
97
|
+
process.env.VITE_DATADOG_RUM_ENABLED === 'true' ||
|
|
98
|
+
process.env.VITE_DATADOG_RUM_ENABLED === '1'
|
|
99
|
+
|
|
100
|
+
if (!rumEnabled) {
|
|
101
|
+
alias.push({
|
|
102
|
+
find: '@datadog/browser-rum',
|
|
103
|
+
replacement: resolve(root, 'src/integrations/datadog-browser-rum-stub.ts'),
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return { alias }
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export { root, __dirname }
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared @vitejs/plugin-react options for local dev.
|
|
3
|
+
* Kit dev-module-config must skip react-compiler (CJS/ESM interop when loaded from kit path)
|
|
4
|
+
* but still use the React plugin's Babel JSX pipeline — do not use plugin `exclude`.
|
|
5
|
+
*/
|
|
6
|
+
const DEV_KIT_UI_RE = /[/\\]dev-module-config[/\\]|[/\\]\.kit-bootstrap\.tsx$|[/\\]dev[/\\]bootstrap\.tsx$/
|
|
7
|
+
|
|
8
|
+
export function createDevReactPluginOptions() {
|
|
9
|
+
return {
|
|
10
|
+
babel: {
|
|
11
|
+
plugins: [
|
|
12
|
+
[
|
|
13
|
+
'babel-plugin-react-compiler',
|
|
14
|
+
{
|
|
15
|
+
sources: (filename) => !DEV_KIT_UI_RE.test(filename),
|
|
16
|
+
},
|
|
17
|
+
],
|
|
18
|
+
],
|
|
19
|
+
},
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { createRequire } from 'node:module'
|
|
2
|
+
import { resolve } from 'node:path'
|
|
3
|
+
import { pathToFileURL } from 'node:url'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* When Vite transforms files under @woodsportal/hubspot-kit, bare imports must
|
|
7
|
+
* resolve from the consumer project's node_modules (not the kit package root).
|
|
8
|
+
*/
|
|
9
|
+
export default function resolveFromProjectRoot(projectRoot, kitRoot) {
|
|
10
|
+
const require = createRequire(pathToFileURL(resolve(projectRoot, 'package.json')))
|
|
11
|
+
const kitRootNorm = resolve(kitRoot)
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
name: 'wp-resolve-from-project-root',
|
|
15
|
+
enforce: 'pre',
|
|
16
|
+
resolveId(source, importer) {
|
|
17
|
+
if (!importer) return null
|
|
18
|
+
if (source.startsWith('.') || source.startsWith('/') || source.startsWith('\0')) {
|
|
19
|
+
return null
|
|
20
|
+
}
|
|
21
|
+
if (!resolve(importer).startsWith(kitRootNorm)) {
|
|
22
|
+
return null
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
return require.resolve(source)
|
|
26
|
+
} catch {
|
|
27
|
+
return null
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/** @typedef {import('vite').Plugin} Plugin */
|
|
2
|
+
|
|
3
|
+
const SDK_LOG_PATH = '/__sdk-log'
|
|
4
|
+
|
|
5
|
+
const LEVEL_STYLE = {
|
|
6
|
+
debug: { color: '\x1b[90m', label: 'DEBUG' },
|
|
7
|
+
info: { color: '\x1b[36m', label: 'INFO ' },
|
|
8
|
+
warn: { color: '\x1b[33m', label: 'WARN ' },
|
|
9
|
+
error: { color: '\x1b[31m', label: 'ERROR' },
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Dev-only Vite middleware: browser SDK logs → Vite terminal.
|
|
14
|
+
* Active when running `yarn dev` / `vite serve` in development mode.
|
|
15
|
+
*
|
|
16
|
+
* @returns {Plugin}
|
|
17
|
+
*/
|
|
18
|
+
export default function sdkLogBridge() {
|
|
19
|
+
return {
|
|
20
|
+
name: 'woodsportal-sdk-log-bridge',
|
|
21
|
+
apply: 'serve',
|
|
22
|
+
configureServer(server) {
|
|
23
|
+
server.middlewares.use(SDK_LOG_PATH, (req, res, next) => {
|
|
24
|
+
if (req.method !== 'POST') {
|
|
25
|
+
if (req.method === 'GET' || req.method === 'HEAD') {
|
|
26
|
+
res.statusCode = 405
|
|
27
|
+
res.setHeader('Allow', 'POST')
|
|
28
|
+
res.end('Method Not Allowed')
|
|
29
|
+
return
|
|
30
|
+
}
|
|
31
|
+
next()
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const chunks = []
|
|
36
|
+
req.on('data', (chunk) => chunks.push(chunk))
|
|
37
|
+
req.on('end', () => {
|
|
38
|
+
try {
|
|
39
|
+
const body = Buffer.concat(chunks).toString('utf8')
|
|
40
|
+
const entry = JSON.parse(body)
|
|
41
|
+
printSdkLog(entry)
|
|
42
|
+
res.statusCode = 204
|
|
43
|
+
res.end()
|
|
44
|
+
} catch {
|
|
45
|
+
res.statusCode = 400
|
|
46
|
+
res.end('Invalid JSON')
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
req.on('error', () => {
|
|
50
|
+
res.statusCode = 500
|
|
51
|
+
res.end('Request error')
|
|
52
|
+
})
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
console.log(
|
|
56
|
+
'\n\x1b[90m[sdk-log-bridge]\x1b[0m WoodsPortal SDK runtime logs → this terminal\n',
|
|
57
|
+
)
|
|
58
|
+
},
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* @param {{ level?: string, context?: string, message?: string, meta?: unknown, error?: unknown }} entry
|
|
64
|
+
*/
|
|
65
|
+
function printSdkLog(entry) {
|
|
66
|
+
const level = entry.level ?? 'info'
|
|
67
|
+
const style = LEVEL_STYLE[level] ?? LEVEL_STYLE.info
|
|
68
|
+
const context = entry.context ?? 'sdk'
|
|
69
|
+
const reset = '\x1b[0m'
|
|
70
|
+
const dim = '\x1b[90m'
|
|
71
|
+
const head = `${style.color}${style.label}${reset} ${dim}[woodsportal-sdk]${reset} ${context}`
|
|
72
|
+
|
|
73
|
+
if (level === 'error') {
|
|
74
|
+
const parts = [head]
|
|
75
|
+
if (entry.error != null) {
|
|
76
|
+
parts.push(formatPayload(entry.error))
|
|
77
|
+
}
|
|
78
|
+
if (entry.meta != null && Object.keys(entry.meta).length > 0) {
|
|
79
|
+
parts.push(formatPayload(entry.meta))
|
|
80
|
+
}
|
|
81
|
+
console.error(parts.join(' '))
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const line = [head]
|
|
86
|
+
if (entry.message) {
|
|
87
|
+
line.push(entry.message)
|
|
88
|
+
}
|
|
89
|
+
if (entry.meta != null && Object.keys(entry.meta).length > 0) {
|
|
90
|
+
line.push(formatPayload(entry.meta))
|
|
91
|
+
}
|
|
92
|
+
console.log(line.join(' '))
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function formatPayload(value) {
|
|
96
|
+
if (typeof value === 'string') {
|
|
97
|
+
return value
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
return JSON.stringify(value)
|
|
101
|
+
} catch {
|
|
102
|
+
return String(value)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* HubSpot CMS watch — uses woodscli.json upload paths.
|
|
4
|
+
*/
|
|
5
|
+
import { spawn, spawnSync } from 'node:child_process'
|
|
6
|
+
import { watch } from 'node:fs'
|
|
7
|
+
import { resolve } from 'node:path'
|
|
8
|
+
|
|
9
|
+
import { moduleOutDir, moduleUploadPath, readWoodsportalConfig, themeUploadPath } from './read-woodsportal-config.mjs'
|
|
10
|
+
|
|
11
|
+
const { root, config } = readWoodsportalConfig()
|
|
12
|
+
const rebuild = process.argv.includes('--rebuild')
|
|
13
|
+
const isProd = process.argv.includes('--prod')
|
|
14
|
+
const buildScript = isProd ? 'build:prod' : 'build:dev'
|
|
15
|
+
|
|
16
|
+
const isTheme = config?.projectType === 'theme-starter'
|
|
17
|
+
const hubspotModulePath = moduleOutDir(root)
|
|
18
|
+
const hubspotUploadTarget = moduleUploadPath(root)
|
|
19
|
+
const theme = themeUploadPath(root)
|
|
20
|
+
|
|
21
|
+
const watchRoots = isTheme
|
|
22
|
+
? [theme.srcDir]
|
|
23
|
+
: ['src', 'public'].map((dir) => resolve(root, dir))
|
|
24
|
+
|
|
25
|
+
function log(msg) {
|
|
26
|
+
console.log(`[watch] ${msg}`)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function runYarn(script) {
|
|
30
|
+
const result = spawnSync('yarn', [script], { cwd: root, stdio: 'inherit' })
|
|
31
|
+
if (result.status !== 0) process.exit(result.status ?? 1)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function runBuild() {
|
|
35
|
+
if (isTheme) {
|
|
36
|
+
log('Theme project — no module build step')
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
log(`Running ${buildScript} …`)
|
|
40
|
+
runYarn(buildScript)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function startCmsWatch() {
|
|
44
|
+
const src = isTheme ? theme.srcDir : hubspotModulePath
|
|
45
|
+
const target = isTheme ? theme.uploadPath : hubspotUploadTarget
|
|
46
|
+
log(`Starting hs cms watch ${src} → ${target}`)
|
|
47
|
+
return spawn('hs', ['cms', 'watch', src, target], { cwd: root, stdio: 'inherit' })
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function startRebuildWatcher() {
|
|
51
|
+
let debounceTimer = null
|
|
52
|
+
let building = false
|
|
53
|
+
let pending = false
|
|
54
|
+
|
|
55
|
+
const triggerBuild = () => {
|
|
56
|
+
if (building) {
|
|
57
|
+
pending = true
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
building = true
|
|
61
|
+
try {
|
|
62
|
+
runBuild()
|
|
63
|
+
} finally {
|
|
64
|
+
building = false
|
|
65
|
+
if (pending) {
|
|
66
|
+
pending = false
|
|
67
|
+
triggerBuild()
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const scheduleBuild = () => {
|
|
73
|
+
if (debounceTimer) clearTimeout(debounceTimer)
|
|
74
|
+
debounceTimer = setTimeout(triggerBuild, 500)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
for (const watchRoot of watchRoots) {
|
|
78
|
+
watch(watchRoot, { recursive: true }, () => scheduleBuild())
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (!isTheme) runBuild()
|
|
83
|
+
const cms = startCmsWatch()
|
|
84
|
+
if (rebuild && !isTheme) startRebuildWatcher()
|
|
85
|
+
|
|
86
|
+
cms.on('exit', (code) => process.exit(code ?? 0))
|