@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
package/package.json
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@woodsportal/hubspot-kit",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "WoodsCLI — HubSpot CMS dev kit by WoodsPortal. wp CLI, Vite presets, module-config overlay, and theme/module build pipelines.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"wp": "./dist/cli.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"scripts",
|
|
14
|
+
"vite",
|
|
15
|
+
"templates",
|
|
16
|
+
"examples",
|
|
17
|
+
"README.md",
|
|
18
|
+
"CHANGELOG.md",
|
|
19
|
+
"SECURITY.md",
|
|
20
|
+
"LICENSE"
|
|
21
|
+
],
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"import": "./dist/index.js"
|
|
26
|
+
},
|
|
27
|
+
"./package.json": "./package.json",
|
|
28
|
+
"./vite": {
|
|
29
|
+
"import": "./dist/vite/index.js"
|
|
30
|
+
},
|
|
31
|
+
"./dev/bootstrap": "./src/dev/bootstrap.tsx",
|
|
32
|
+
"./routing/route-format": "./src/routing/route-format.ts",
|
|
33
|
+
"./routing/build-api-routes": "./src/routing/build-api-routes.ts",
|
|
34
|
+
"./scripts/tailwind/prefix.env.js": "./scripts/tailwind/prefix.env.js",
|
|
35
|
+
"./scripts/*": "./scripts/*",
|
|
36
|
+
"./vite/*": "./vite/*"
|
|
37
|
+
},
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"access": "public",
|
|
40
|
+
"registry": "https://registry.npmjs.org/"
|
|
41
|
+
},
|
|
42
|
+
"repository": {
|
|
43
|
+
"type": "git",
|
|
44
|
+
"url": "https://github.com/Digital-Woods/digitalwoods.io-woodsportal-hubspot-kit.git"
|
|
45
|
+
},
|
|
46
|
+
"scripts": {
|
|
47
|
+
"build": "tsup",
|
|
48
|
+
"dev": "tsup --watch",
|
|
49
|
+
"typecheck": "tsc --noEmit",
|
|
50
|
+
"test": "node --import tsx --test 'src/lib/**/*.test.ts' 'src/schema/**/*.test.ts' 'src/vite/**/*.test.ts' && vitest run src/dev-module-config/__tests__/normalize-repeater-items.test.ts src/dev-module-config/__tests__/module-config-transform.test.ts",
|
|
51
|
+
"test:examples": "node examples/smoke/run-matrix.mjs --tier static",
|
|
52
|
+
"lint": "eslint src",
|
|
53
|
+
"verify": "npm run typecheck && npm run test && npm run test:examples && npm run build",
|
|
54
|
+
"prepublishOnly": "npm run verify"
|
|
55
|
+
},
|
|
56
|
+
"engines": {
|
|
57
|
+
"node": ">=20"
|
|
58
|
+
},
|
|
59
|
+
"dependencies": {
|
|
60
|
+
"@babel/generator": "^7.27.0",
|
|
61
|
+
"@babel/parser": "^7.27.0",
|
|
62
|
+
"@babel/traverse": "^7.27.0",
|
|
63
|
+
"@babel/types": "^7.27.0",
|
|
64
|
+
"@clack/prompts": "^0.10.1",
|
|
65
|
+
"commander": "^13.1.0",
|
|
66
|
+
"picocolors": "^1.1.1",
|
|
67
|
+
"zod": "^3.24.2"
|
|
68
|
+
},
|
|
69
|
+
"devDependencies": {
|
|
70
|
+
"@types/node": "^22.13.4",
|
|
71
|
+
"vitest": "^3.2.4",
|
|
72
|
+
"tsx": "^4.19.3",
|
|
73
|
+
"tsup": "^8.4.0",
|
|
74
|
+
"typescript": "^5.7.3"
|
|
75
|
+
},
|
|
76
|
+
"peerDependencies": {
|
|
77
|
+
"@tailwindcss/vite": "^4.0.0",
|
|
78
|
+
"@tanstack/router-plugin": "^1.0.0",
|
|
79
|
+
"@vitejs/plugin-react": "^4.0.0",
|
|
80
|
+
"babel-plugin-react-compiler": "^1.0.0",
|
|
81
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
82
|
+
"react-dom": "^18.0.0 || ^19.0.0",
|
|
83
|
+
"vite": "^6.0.0"
|
|
84
|
+
},
|
|
85
|
+
"peerDependenciesMeta": {
|
|
86
|
+
"@tailwindcss/vite": { "optional": true },
|
|
87
|
+
"@tanstack/router-plugin": { "optional": true },
|
|
88
|
+
"@vitejs/plugin-react": { "optional": true },
|
|
89
|
+
"babel-plugin-react-compiler": { "optional": true },
|
|
90
|
+
"react": { "optional": true },
|
|
91
|
+
"react-dom": { "optional": true },
|
|
92
|
+
"vite": { "optional": true }
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Audits fields.json leaf paths against supported module-config field types.
|
|
3
|
+
* Run: node scripts/audit-module-config-fields.mjs
|
|
4
|
+
*/
|
|
5
|
+
import fs from 'node:fs/promises'
|
|
6
|
+
import path from 'node:path'
|
|
7
|
+
import { fileURLToPath } from 'node:url'
|
|
8
|
+
|
|
9
|
+
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
|
10
|
+
|
|
11
|
+
const HUBSPOT_ONLY = new Set([
|
|
12
|
+
'portal_cdn_base_url',
|
|
13
|
+
'portal_build_id',
|
|
14
|
+
'portal_vendor_script',
|
|
15
|
+
'portal_app_script',
|
|
16
|
+
'portal_app_css',
|
|
17
|
+
'hubspot_data_source',
|
|
18
|
+
])
|
|
19
|
+
|
|
20
|
+
const SUPPORTED_TYPES = new Set([
|
|
21
|
+
'text',
|
|
22
|
+
'number',
|
|
23
|
+
'boolean',
|
|
24
|
+
'choice',
|
|
25
|
+
'color',
|
|
26
|
+
'richtext',
|
|
27
|
+
'image',
|
|
28
|
+
'link',
|
|
29
|
+
'url',
|
|
30
|
+
'group',
|
|
31
|
+
])
|
|
32
|
+
|
|
33
|
+
/** @typedef {{ name: string, type: string, children?: Field[], occurrence?: unknown, tab?: string }} Field */
|
|
34
|
+
|
|
35
|
+
/** @param {Field[]} fields @param {string} prefix */
|
|
36
|
+
function collectPaths(fields, prefix = '') {
|
|
37
|
+
/** @type {Array<{ path: string, type: string, name: string }>} */
|
|
38
|
+
const paths = []
|
|
39
|
+
for (const field of fields) {
|
|
40
|
+
const fieldPath = prefix ? `${prefix}.${field.name}` : field.name
|
|
41
|
+
const topName = field.name
|
|
42
|
+
|
|
43
|
+
if (HUBSPOT_ONLY.has(topName)) continue
|
|
44
|
+
|
|
45
|
+
if (field.type === 'group') {
|
|
46
|
+
if (field.occurrence) {
|
|
47
|
+
paths.push({ path: fieldPath, type: 'occurrence-group', name: field.name })
|
|
48
|
+
}
|
|
49
|
+
if (field.children?.length) {
|
|
50
|
+
paths.push(...collectPaths(field.children, field.occurrence ? fieldPath : fieldPath))
|
|
51
|
+
}
|
|
52
|
+
continue
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
paths.push({ path: fieldPath, type: field.type, name: field.name })
|
|
56
|
+
}
|
|
57
|
+
return paths
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const fieldsPath = path.join(root, 'public/fields.json')
|
|
61
|
+
const fields = JSON.parse(await fs.readFile(fieldsPath, 'utf8'))
|
|
62
|
+
const paths = collectPaths(fields)
|
|
63
|
+
const unsupported = paths.filter((entry) => !SUPPORTED_TYPES.has(entry.type) && entry.type !== 'occurrence-group')
|
|
64
|
+
|
|
65
|
+
if (unsupported.length > 0) {
|
|
66
|
+
console.error('Unsupported module-config field paths:')
|
|
67
|
+
for (const entry of unsupported) {
|
|
68
|
+
console.error(` - ${entry.path} (${entry.type})`)
|
|
69
|
+
}
|
|
70
|
+
process.exit(1)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
console.log(`OK: ${paths.length} field paths covered by module-config editors`)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Regression: snapshot dist/ layout hashes vs .wp-hs-baseline (optional).
|
|
4
|
+
* Invoked via: wp audit parity
|
|
5
|
+
*/
|
|
6
|
+
import { spawnSync } from 'node:child_process'
|
|
7
|
+
import { createRequire } from 'node:module'
|
|
8
|
+
import { dirname, resolve } from 'node:path'
|
|
9
|
+
import { fileURLToPath } from 'node:url'
|
|
10
|
+
|
|
11
|
+
const require = createRequire(import.meta.url)
|
|
12
|
+
const kitRoot = dirname(require.resolve('@woodsportal/hubspot-kit/package.json', {
|
|
13
|
+
paths: [dirname(fileURLToPath(import.meta.url))],
|
|
14
|
+
}))
|
|
15
|
+
|
|
16
|
+
const cli = resolve(kitRoot, 'dist/cli.js')
|
|
17
|
+
const result = spawnSync(process.execPath, [cli, 'audit', 'parity', ...process.argv.slice(2)], {
|
|
18
|
+
stdio: 'inherit',
|
|
19
|
+
env: process.env,
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
process.exit(result.status ?? 1)
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TDZ audit: build with TanStack route code-splitting + dynamic imports (no inline).
|
|
3
|
+
* Usage: node scripts/audit-tdz-split.mjs
|
|
4
|
+
*/
|
|
5
|
+
import { defineConfig, build } from 'vite'
|
|
6
|
+
import viteReact from '@vitejs/plugin-react'
|
|
7
|
+
import { TanStackRouterVite } from '@tanstack/router-plugin/vite'
|
|
8
|
+
import { resolve, dirname } from 'node:path'
|
|
9
|
+
import { fileURLToPath } from 'node:url'
|
|
10
|
+
import { execSync } from 'node:child_process'
|
|
11
|
+
import { readFileSync, readdirSync, existsSync } from 'node:fs'
|
|
12
|
+
import tailwindcss from '@tailwindcss/vite'
|
|
13
|
+
import tailwindPrefixPlugin from './tailwind/prefix-plugin.js'
|
|
14
|
+
import TailwindContentPlugin from './tailwind/tailwind-content-plugin.js'
|
|
15
|
+
import isPrefix from './tailwind/prefix.env.js'
|
|
16
|
+
|
|
17
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
18
|
+
const root = resolve(__dirname, '..')
|
|
19
|
+
const outDir = resolve(root, 'dist/TdzAudit')
|
|
20
|
+
const mode = 'production'
|
|
21
|
+
|
|
22
|
+
const config = defineConfig({
|
|
23
|
+
plugins: [
|
|
24
|
+
TanStackRouterVite({ autoCodeSplitting: true }),
|
|
25
|
+
viteReact({
|
|
26
|
+
babel: { plugins: ['babel-plugin-react-compiler'] },
|
|
27
|
+
}),
|
|
28
|
+
tailwindcss(),
|
|
29
|
+
...(isPrefix(mode)
|
|
30
|
+
? [tailwindPrefixPlugin(), TailwindContentPlugin()]
|
|
31
|
+
: []),
|
|
32
|
+
],
|
|
33
|
+
resolve: { alias: { '@': resolve(root, './src') } },
|
|
34
|
+
define: {
|
|
35
|
+
'process.env.NODE_ENV': JSON.stringify('production'),
|
|
36
|
+
'process.env': {},
|
|
37
|
+
process: JSON.stringify({ env: {} }),
|
|
38
|
+
},
|
|
39
|
+
build: {
|
|
40
|
+
outDir,
|
|
41
|
+
emptyOutDir: true,
|
|
42
|
+
copyPublicDir: false,
|
|
43
|
+
cssCodeSplit: true,
|
|
44
|
+
lib: {
|
|
45
|
+
entry: resolve(root, 'src/main.tsx'),
|
|
46
|
+
name: 'TdzAuditModule',
|
|
47
|
+
fileName: () => 'module.js',
|
|
48
|
+
formats: ['iife'],
|
|
49
|
+
},
|
|
50
|
+
rollupOptions: {
|
|
51
|
+
external: [],
|
|
52
|
+
output: {
|
|
53
|
+
assetFileNames: 'module.css',
|
|
54
|
+
inlineDynamicImports: false,
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
minify: true,
|
|
58
|
+
sourcemap: true,
|
|
59
|
+
},
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
await build(config)
|
|
64
|
+
|
|
65
|
+
const files = existsSync(outDir) ? readdirSync(outDir) : []
|
|
66
|
+
const jsFiles = files.filter((f) => f.endsWith('.js'))
|
|
67
|
+
let syntaxOk = true
|
|
68
|
+
let syntaxError = null
|
|
69
|
+
for (const f of jsFiles) {
|
|
70
|
+
try {
|
|
71
|
+
execSync(`node --check ${JSON.stringify(resolve(outDir, f))}`, {
|
|
72
|
+
stdio: 'pipe',
|
|
73
|
+
})
|
|
74
|
+
} catch (e) {
|
|
75
|
+
syntaxOk = false
|
|
76
|
+
syntaxError = String(e.stderr ?? e.message)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const mainJs = resolve(outDir, 'module.js')
|
|
81
|
+
const mainSize = existsSync(mainJs) ? readFileSync(mainJs).length : 0
|
|
82
|
+
|
|
83
|
+
console.log(
|
|
84
|
+
JSON.stringify(
|
|
85
|
+
{
|
|
86
|
+
outcome: 'build_succeeded',
|
|
87
|
+
jsFiles,
|
|
88
|
+
chunkCount: jsFiles.length,
|
|
89
|
+
mainModuleBytes: mainSize,
|
|
90
|
+
syntaxCheckPassed: syntaxOk,
|
|
91
|
+
syntaxError: syntaxError?.slice(0, 500) ?? null,
|
|
92
|
+
note:
|
|
93
|
+
'Documented production failure: ReferenceError Cannot access before initialization when route lazy chunks load inside HubSpot single IIFE. Static node --check does not reproduce runtime TDZ.',
|
|
94
|
+
},
|
|
95
|
+
null,
|
|
96
|
+
2,
|
|
97
|
+
),
|
|
98
|
+
)
|
|
99
|
+
} catch (error) {
|
|
100
|
+
console.log(
|
|
101
|
+
JSON.stringify(
|
|
102
|
+
{
|
|
103
|
+
outcome: 'build_failed',
|
|
104
|
+
message: String(error.message ?? error).slice(0, 1200),
|
|
105
|
+
note: 'IIFE lib format + dynamic route imports often conflict at Rollup emit for HubSpot module.js.',
|
|
106
|
+
},
|
|
107
|
+
null,
|
|
108
|
+
2,
|
|
109
|
+
),
|
|
110
|
+
)
|
|
111
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Build CDN vendor + app IIFE chunks into dist/cdn/
|
|
4
|
+
*/
|
|
5
|
+
import { spawnSync } from 'node:child_process'
|
|
6
|
+
import { resolve, dirname } from 'node:path'
|
|
7
|
+
import { fileURLToPath } from 'node:url'
|
|
8
|
+
import { findProjectRoot } from './project-root.mjs'
|
|
9
|
+
|
|
10
|
+
import { envWithPortalBuildId } from './resolve-portal-build-id.mjs'
|
|
11
|
+
import { envForPortalBuild, defaultDatadogFlag } from './portal-vite-env.mjs'
|
|
12
|
+
import { resolvePortalViteMode } from './portal-env-mode.mjs'
|
|
13
|
+
|
|
14
|
+
const root = findProjectRoot()
|
|
15
|
+
|
|
16
|
+
const mode = resolvePortalViteMode()
|
|
17
|
+
|
|
18
|
+
const env = envForPortalBuild(mode, root, {
|
|
19
|
+
VITE_DATADOG_RUM_ENABLED: defaultDatadogFlag(mode),
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
function runWithEnv(config, buildMode) {
|
|
23
|
+
const result = spawnSync(
|
|
24
|
+
'yarn',
|
|
25
|
+
['vite', 'build', '--config', config, '--mode', buildMode],
|
|
26
|
+
{ cwd: root, stdio: 'inherit', env },
|
|
27
|
+
)
|
|
28
|
+
if (result.status !== 0) {
|
|
29
|
+
process.exit(result.status ?? 1)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
runWithEnv('vite.config.cdn-vendor.mjs', mode)
|
|
34
|
+
runWithEnv('vite.config.cdn-app.mjs', mode)
|
|
35
|
+
|
|
36
|
+
console.log('[build-cdn] vendor + app chunks written to dist/cdn/')
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* CI / local gate for HubSpot module and CDN chunk sizes.
|
|
4
|
+
*/
|
|
5
|
+
import { existsSync, readFileSync, readdirSync } from 'node:fs'
|
|
6
|
+
import { resolve, dirname } from 'node:path'
|
|
7
|
+
import { fileURLToPath } from 'node:url'
|
|
8
|
+
import { findProjectRoot } from './project-root.mjs'
|
|
9
|
+
import { gzipSync } from 'node:zlib'
|
|
10
|
+
|
|
11
|
+
const root = findProjectRoot()
|
|
12
|
+
const embedMode = process.env.VITE_PORTAL_EMBED_MODE ?? 'cdn'
|
|
13
|
+
|
|
14
|
+
const HUBSPOT_FILE_LIMIT = Number(process.env.HUBSPOT_MODULE_RAW_LIMIT_BYTES ?? 1.5 * 1024 * 1024)
|
|
15
|
+
const BOOTSTRAP_RAW_LIMIT = Number(process.env.HUBSPOT_BOOTSTRAP_RAW_LIMIT_BYTES ?? 200 * 1024)
|
|
16
|
+
const CDN_CHUNK_RAW_LIMIT = Number(process.env.PORTAL_CDN_CHUNK_RAW_LIMIT_BYTES ?? 1.5 * 1024 * 1024)
|
|
17
|
+
/** Phase 1 app chunk may exceed 1.5 MiB until ESM route splitting (Phase 2). */
|
|
18
|
+
const CDN_APP_RAW_LIMIT = Number(
|
|
19
|
+
process.env.PORTAL_CDN_APP_RAW_LIMIT_BYTES ?? 2.3 * 1024 * 1024,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
function checkFile(label, path, rawLimit) {
|
|
23
|
+
if (!existsSync(path)) {
|
|
24
|
+
console.error(`FAIL: missing ${label} at ${path}`)
|
|
25
|
+
process.exit(1)
|
|
26
|
+
}
|
|
27
|
+
const raw = readFileSync(path)
|
|
28
|
+
const gzip = gzipSync(raw)
|
|
29
|
+
console.log(
|
|
30
|
+
`${label}: ${(raw.length / 1024).toFixed(1)} KiB raw, ${(gzip.length / 1024).toFixed(1)} KiB gzip (limit ${(rawLimit / 1024).toFixed(0)} KiB raw)`,
|
|
31
|
+
)
|
|
32
|
+
if (raw.length > rawLimit) {
|
|
33
|
+
console.error(`FAIL: ${label} exceeds raw limit`)
|
|
34
|
+
process.exit(1)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const moduleDir = resolve(root, 'dist/WoodsPortal.Module')
|
|
39
|
+
const moduleJs = resolve(moduleDir, 'module.js')
|
|
40
|
+
|
|
41
|
+
if (embedMode === 'monolith') {
|
|
42
|
+
checkFile('module.js (monolith)', moduleJs, HUBSPOT_FILE_LIMIT)
|
|
43
|
+
} else {
|
|
44
|
+
checkFile('module.js (bootstrap)', moduleJs, BOOTSTRAP_RAW_LIMIT)
|
|
45
|
+
checkFile('module.css', resolve(moduleDir, 'module.css'), HUBSPOT_FILE_LIMIT)
|
|
46
|
+
|
|
47
|
+
const cdnDir = resolve(root, 'dist/cdn')
|
|
48
|
+
if (!existsSync(cdnDir)) {
|
|
49
|
+
console.error('FAIL: dist/cdn missing — run yarn build:cdn')
|
|
50
|
+
process.exit(1)
|
|
51
|
+
}
|
|
52
|
+
for (const file of readdirSync(cdnDir).filter((f) => f.endsWith('.js'))) {
|
|
53
|
+
const limit = file.startsWith('app.') ? CDN_APP_RAW_LIMIT : CDN_CHUNK_RAW_LIMIT
|
|
54
|
+
checkFile(`cdn/${file}`, resolve(cdnDir, file), limit)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
console.log('OK: HubSpot / CDN bundle size checks passed')
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared project config resolution — `woodscli.json` only.
|
|
3
|
+
*/
|
|
4
|
+
import { existsSync } from 'node:fs'
|
|
5
|
+
import { resolve } from 'node:path'
|
|
6
|
+
|
|
7
|
+
export const WOODSCLI_CONFIG_FILE = 'woodscli.json'
|
|
8
|
+
export const LEGACY_WOODSPORTAL_HS_FILE = 'woodsportal.hs.json'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @param {string} projectRoot
|
|
12
|
+
*/
|
|
13
|
+
export function woodscliConfigPath(projectRoot) {
|
|
14
|
+
return resolve(projectRoot, WOODSCLI_CONFIG_FILE)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @param {string} projectRoot
|
|
19
|
+
*/
|
|
20
|
+
export function legacyConfigPath(projectRoot) {
|
|
21
|
+
return resolve(projectRoot, LEGACY_WOODSPORTAL_HS_FILE)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @param {string} projectRoot
|
|
26
|
+
*/
|
|
27
|
+
export function resolveProjectConfigPath(projectRoot) {
|
|
28
|
+
const fromEnv = process.env.WOODSCLI_CONFIG?.trim() || process.env.WP_HS_CONFIG?.trim() || null
|
|
29
|
+
if (fromEnv) {
|
|
30
|
+
const path = resolve(projectRoot, fromEnv)
|
|
31
|
+
return existsSync(path) ? path : null
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const path = woodscliConfigPath(projectRoot)
|
|
35
|
+
return existsSync(path) ? path : null
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @param {string} [from]
|
|
40
|
+
*/
|
|
41
|
+
export function findProjectRootWithConfig(from = process.cwd()) {
|
|
42
|
+
if (process.env.WP_HS_PROJECT_ROOT?.trim()) {
|
|
43
|
+
return resolve(process.env.WP_HS_PROJECT_ROOT.trim())
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let dir = resolve(from)
|
|
47
|
+
while (true) {
|
|
48
|
+
if (resolveProjectConfigPath(dir)) return dir
|
|
49
|
+
const parent = resolve(dir, '..')
|
|
50
|
+
if (parent === dir) return resolve(from)
|
|
51
|
+
dir = parent
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detect HubSpot CMS project layout (module vs theme, adopted vs existing).
|
|
3
|
+
*/
|
|
4
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
5
|
+
import { join, resolve } from 'node:path'
|
|
6
|
+
|
|
7
|
+
import { resolveProjectConfigPath } from './config-files.mjs'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @param {string} projectRoot
|
|
11
|
+
*/
|
|
12
|
+
export function detectProjectLayout(projectRoot) {
|
|
13
|
+
const root = resolve(projectRoot)
|
|
14
|
+
const configPath = resolveProjectConfigPath(root)
|
|
15
|
+
const hasConfig = Boolean(configPath && existsSync(configPath))
|
|
16
|
+
|
|
17
|
+
let config = null
|
|
18
|
+
if (hasConfig && configPath) {
|
|
19
|
+
try {
|
|
20
|
+
config = JSON.parse(readFileSync(configPath, 'utf8'))
|
|
21
|
+
} catch {
|
|
22
|
+
config = null
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const fieldsJsonPaths = [
|
|
27
|
+
join(root, 'public/fields.json'),
|
|
28
|
+
join(root, config?.dev?.fieldsJson ?? 'public/fields.json'),
|
|
29
|
+
].filter((p, i, arr) => arr.indexOf(p) === i)
|
|
30
|
+
|
|
31
|
+
const moduleHtmlPaths = [join(root, 'public/module.html'), join(root, 'module.html')]
|
|
32
|
+
|
|
33
|
+
const themeJsonPaths = [
|
|
34
|
+
join(root, config?.theme?.themeJson ?? 'src/theme/theme.json'),
|
|
35
|
+
join(root, 'theme.json'),
|
|
36
|
+
join(root, 'src/theme/theme.json'),
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
const hasFieldsJson = fieldsJsonPaths.some((p) => existsSync(p))
|
|
40
|
+
const hasModuleHtml = moduleHtmlPaths.some((p) => existsSync(p))
|
|
41
|
+
const hasThemeJson = themeJsonPaths.some((p) => existsSync(p))
|
|
42
|
+
const hasTemplates =
|
|
43
|
+
existsSync(join(root, 'src/theme/templates')) || existsSync(join(root, 'templates'))
|
|
44
|
+
|
|
45
|
+
const wphsDir = config?.dev?.moduleConfigDir ?? '.wphs'
|
|
46
|
+
const hasWphs = existsSync(join(root, wphsDir))
|
|
47
|
+
|
|
48
|
+
let track = 'unknown'
|
|
49
|
+
if (hasThemeJson || hasTemplates) track = 'theme'
|
|
50
|
+
else if (hasFieldsJson || hasModuleHtml) track = 'module'
|
|
51
|
+
|
|
52
|
+
let setup = 'existing'
|
|
53
|
+
if (hasConfig && hasWphs) setup = 'adopted'
|
|
54
|
+
else if (hasConfig && !hasWphs) setup = 'partial'
|
|
55
|
+
else if (!hasConfig && (hasFieldsJson || hasThemeJson)) setup = 'existing'
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
projectRoot: root,
|
|
59
|
+
track,
|
|
60
|
+
setup,
|
|
61
|
+
hasConfig,
|
|
62
|
+
hasWphs,
|
|
63
|
+
hasFieldsJson,
|
|
64
|
+
hasModuleHtml,
|
|
65
|
+
hasThemeJson,
|
|
66
|
+
hasTemplates,
|
|
67
|
+
projectType: config?.projectType ?? null,
|
|
68
|
+
configPath: hasConfig ? configPath : null,
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Scaffold WoodsCLI `.wphs/` workspace + kit symlinks.
|
|
4
|
+
*/
|
|
5
|
+
import { existsSync, mkdirSync, writeFileSync } from 'node:fs'
|
|
6
|
+
|
|
7
|
+
import { findProjectRoot, getKitRoot } from './project-root.mjs'
|
|
8
|
+
import { wphsPaths, wphsWorkspaceSubdirs } from './wphs-paths.mjs'
|
|
9
|
+
import { ensureDevKitShims } from './vite/ensure-dev-kit-shims.js'
|
|
10
|
+
|
|
11
|
+
const WPHS_README = `# WoodsCLI workspace (.wphs/)
|
|
12
|
+
|
|
13
|
+
Local state for **WoodsCLI** (\`@woodsportal/hubspot-kit\`). Not shipped to HubSpot.
|
|
14
|
+
|
|
15
|
+
| Path | Purpose |
|
|
16
|
+
|------|---------|
|
|
17
|
+
| \`config/module-config.local.json\` | Live Content/Styles values (gitignored) |
|
|
18
|
+
| \`config/module-config.example.json\` | Example generated from \`fields.json\` |
|
|
19
|
+
| \`assets/branding/\` | Dev-tool uploaded images (gitignored) |
|
|
20
|
+
| \`fixtures/\` | Optional preview / hubspot-data overrides |
|
|
21
|
+
| \`cache/\` | CLI caches (gitignored) |
|
|
22
|
+
| \`audit/baseline/\` | Build parity snapshots (gitignored) |
|
|
23
|
+
| \`.kit-module-config\` | Symlink to kit dev-module-config |
|
|
24
|
+
| \`.kit-bootstrap.tsx\` | Symlink to kit dev bootstrap |
|
|
25
|
+
|
|
26
|
+
Run \`wp doctor\` to recreate missing folders.
|
|
27
|
+
`
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @param {string} [projectRoot]
|
|
31
|
+
* @param {string} [kitRoot]
|
|
32
|
+
*/
|
|
33
|
+
export function ensureWphsWorkspace(projectRoot, kitRoot) {
|
|
34
|
+
const root = projectRoot ?? findProjectRoot()
|
|
35
|
+
const kit = kitRoot ?? getKitRoot()
|
|
36
|
+
const paths = wphsPaths(root)
|
|
37
|
+
|
|
38
|
+
for (const dir of wphsWorkspaceSubdirs(root)) {
|
|
39
|
+
mkdirSync(dir, { recursive: true })
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!existsSync(paths.readmePath)) {
|
|
43
|
+
writeFileSync(paths.readmePath, WPHS_README)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
ensureDevKitShims(root, kit)
|
|
47
|
+
return paths
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
51
|
+
const paths = ensureWphsWorkspace()
|
|
52
|
+
console.log(`[wphs] workspace ready at ${paths.workspaceRoot}`)
|
|
53
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Write dist/cdn/manifest.json after vendor + app builds.
|
|
4
|
+
*/
|
|
5
|
+
import { readdirSync, writeFileSync, statSync } from 'node:fs'
|
|
6
|
+
import { resolve, dirname } from 'node:path'
|
|
7
|
+
import { fileURLToPath } from 'node:url'
|
|
8
|
+
import { findProjectRoot } from './project-root.mjs'
|
|
9
|
+
import { gzipSync } from 'node:zlib'
|
|
10
|
+
import { readFileSync } from 'node:fs'
|
|
11
|
+
import { resolveCdnBaseUrl, portalBuildId } from './resolve-cdn-base-url.mjs'
|
|
12
|
+
|
|
13
|
+
const root = findProjectRoot()
|
|
14
|
+
const cdnDir = resolve(root, 'dist/cdn')
|
|
15
|
+
const buildId = portalBuildId()
|
|
16
|
+
|
|
17
|
+
function fileMetrics(file) {
|
|
18
|
+
const path = resolve(cdnDir, file)
|
|
19
|
+
const raw = readFileSync(path)
|
|
20
|
+
return {
|
|
21
|
+
file,
|
|
22
|
+
rawBytes: raw.length,
|
|
23
|
+
gzipBytes: gzipSync(raw).length,
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const jsFiles = readdirSync(cdnDir)
|
|
28
|
+
.filter((f) => f.endsWith('.js'))
|
|
29
|
+
.map((file) => {
|
|
30
|
+
const metrics = fileMetrics(file)
|
|
31
|
+
return {
|
|
32
|
+
...metrics,
|
|
33
|
+
name: file.startsWith('vendor.') ? 'vendor' : file.startsWith('app.') ? 'app' : file,
|
|
34
|
+
order: file.startsWith('vendor.') ? 1 : file.startsWith('app.') ? 2 : 99,
|
|
35
|
+
}
|
|
36
|
+
})
|
|
37
|
+
.sort((a, b) => a.order - b.order)
|
|
38
|
+
|
|
39
|
+
const cssFiles = readdirSync(cdnDir)
|
|
40
|
+
.filter((f) => f.endsWith('.css'))
|
|
41
|
+
.map((file) => {
|
|
42
|
+
const metrics = fileMetrics(file)
|
|
43
|
+
return {
|
|
44
|
+
...metrics,
|
|
45
|
+
name: file.startsWith('app.') ? 'app' : file,
|
|
46
|
+
order: 1,
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
const appCss = cssFiles.find((f) => f.name === 'app') ?? cssFiles[0]
|
|
51
|
+
|
|
52
|
+
if (!jsFiles.some((f) => f.name === 'vendor') || !jsFiles.some((f) => f.name === 'app')) {
|
|
53
|
+
console.error('[cdn-manifest] expected vendor.*.js and app.*.js in dist/cdn/')
|
|
54
|
+
process.exit(1)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (!appCss) {
|
|
58
|
+
console.error('[cdn-manifest] expected app.*.css in dist/cdn/ — component styles load from CDN')
|
|
59
|
+
process.exit(1)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const manifest = {
|
|
63
|
+
generatedAt: new Date().toISOString(),
|
|
64
|
+
buildId,
|
|
65
|
+
baseUrl: resolveCdnBaseUrl(),
|
|
66
|
+
publishMode: process.env.VITE_PORTAL_CDN_PUBLISH_MODE ?? 'jsdelivr',
|
|
67
|
+
scripts: jsFiles.map(({ name, file, order, rawBytes, gzipBytes }) => ({
|
|
68
|
+
name,
|
|
69
|
+
file,
|
|
70
|
+
order,
|
|
71
|
+
rawBytes,
|
|
72
|
+
gzipBytes,
|
|
73
|
+
url: `${resolveCdnBaseUrl()}/${file}`,
|
|
74
|
+
})),
|
|
75
|
+
styles: cssFiles.map(({ name, file, order, rawBytes, gzipBytes }) => ({
|
|
76
|
+
name,
|
|
77
|
+
file,
|
|
78
|
+
order,
|
|
79
|
+
rawBytes,
|
|
80
|
+
gzipBytes,
|
|
81
|
+
url: `${resolveCdnBaseUrl()}/${file}`,
|
|
82
|
+
})),
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
writeFileSync(resolve(cdnDir, 'manifest.json'), JSON.stringify(manifest, null, 2))
|
|
86
|
+
console.log('[cdn-manifest] wrote dist/cdn/manifest.json')
|
|
87
|
+
for (const s of manifest.scripts) {
|
|
88
|
+
console.log(` ${s.file}: ${(s.rawBytes / 1024).toFixed(1)} KiB raw, ${(s.gzipBytes / 1024).toFixed(1)} KiB gzip`)
|
|
89
|
+
}
|
|
90
|
+
console.log(
|
|
91
|
+
` ${appCss.file}: ${(appCss.rawBytes / 1024).toFixed(1)} KiB raw, ${(appCss.gzipBytes / 1024).toFixed(1)} KiB gzip (styles)`,
|
|
92
|
+
)
|