@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,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates {dev.moduleConfigDir}/module-config.example.json from public/fields.json defaults.
|
|
3
|
+
* Run: node scripts/generate-module-config-example.mjs
|
|
4
|
+
*/
|
|
5
|
+
import fs from 'node:fs/promises'
|
|
6
|
+
import path from 'node:path'
|
|
7
|
+
|
|
8
|
+
import { findProjectRoot } from './project-root.mjs'
|
|
9
|
+
import { moduleConfigPaths } from './module-config-dir.mjs'
|
|
10
|
+
|
|
11
|
+
const root = findProjectRoot()
|
|
12
|
+
|
|
13
|
+
function isFieldGroup(field) {
|
|
14
|
+
return field.type === 'group' && Array.isArray(field.children)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function defaultForField(field) {
|
|
18
|
+
if (field.default !== undefined) return structuredClone(field.default)
|
|
19
|
+
if (isFieldGroup(field)) return buildDefaultsFromFields(field.children ?? [])
|
|
20
|
+
if (field.type === 'boolean') return false
|
|
21
|
+
if (field.type === 'number') return 0
|
|
22
|
+
if (field.type === 'color') return { color: '', opacity: 100 }
|
|
23
|
+
if (field.type === 'image') return { src: '', alt: '' }
|
|
24
|
+
if (field.type === 'url' || field.type === 'link') {
|
|
25
|
+
return {
|
|
26
|
+
url: { href: '', type: 'EXTERNAL' },
|
|
27
|
+
open_in_new_tab: false,
|
|
28
|
+
no_follow: false,
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return ''
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function buildDefaultsFromFields(fields) {
|
|
35
|
+
const result = {}
|
|
36
|
+
for (const field of fields) {
|
|
37
|
+
result[field.name] = defaultForField(field)
|
|
38
|
+
}
|
|
39
|
+
return result
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const fieldsPath = path.join(root, 'public/fields.json')
|
|
43
|
+
const { exampleConfigPath: outPath } = moduleConfigPaths(root)
|
|
44
|
+
|
|
45
|
+
const fields = JSON.parse(await fs.readFile(fieldsPath, 'utf8'))
|
|
46
|
+
const moduleValues = buildDefaultsFromFields(fields)
|
|
47
|
+
|
|
48
|
+
const record = {
|
|
49
|
+
version: 1,
|
|
50
|
+
module: moduleValues,
|
|
51
|
+
updatedAt: new Date().toISOString(),
|
|
52
|
+
source: 'example',
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
await fs.mkdir(path.dirname(outPath), { recursive: true })
|
|
56
|
+
await fs.writeFile(outPath, JSON.stringify(record, null, 2), 'utf8')
|
|
57
|
+
console.log(`Wrote ${outPath}`)
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HubSpot custom module post-build: escape HubL tokens in module.js, validate, sync public assets.
|
|
3
|
+
* Invoked after `vite build` via scripts/run-hubspot-module-postbuild.mjs (build:dev / build:prod).
|
|
4
|
+
*/
|
|
5
|
+
import { execSync } from 'node:child_process'
|
|
6
|
+
import {
|
|
7
|
+
cpSync,
|
|
8
|
+
existsSync,
|
|
9
|
+
mkdirSync,
|
|
10
|
+
readdirSync,
|
|
11
|
+
readFileSync,
|
|
12
|
+
rmSync,
|
|
13
|
+
writeFileSync,
|
|
14
|
+
} from 'node:fs'
|
|
15
|
+
import { resolve } from 'node:path'
|
|
16
|
+
|
|
17
|
+
/** Substrings that break when HubSpot tokenizes module.js (Jinja/HubL). */
|
|
18
|
+
export const FORBIDDEN_HUBL_IN_MODULE_JS = ['{{', '{%', '<%', '%>']
|
|
19
|
+
|
|
20
|
+
const ALLOWED_DIST_FILES = new Set([
|
|
21
|
+
'module.html',
|
|
22
|
+
'module.js',
|
|
23
|
+
'module.css',
|
|
24
|
+
'fields.json',
|
|
25
|
+
])
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* react-html-parser ships mustache/erb detector regexes; escape so HubSpot does not parse them as HubL.
|
|
29
|
+
*/
|
|
30
|
+
export function escapeHubSpotModuleJs(js) {
|
|
31
|
+
let out = js
|
|
32
|
+
if (out.includes('{{')) out = out.replaceAll('{{', '\\u007b\\u007b')
|
|
33
|
+
if (out.includes('{%')) out = out.replaceAll('{%', '\\u007b%')
|
|
34
|
+
if (out.includes('<%')) out = out.replaceAll('<%', '\\u003c%')
|
|
35
|
+
if (out.includes('%>')) out = out.replaceAll('%>', '%\\u003e')
|
|
36
|
+
if (out.includes('|^[\\w\\W]*}}')) {
|
|
37
|
+
out = out.replaceAll('|^[\\w\\W]*}}', '|^[\\w\\W]*\\u007d\\u007d')
|
|
38
|
+
}
|
|
39
|
+
return out
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function assertNoForbiddenHubLTokens(js, label = 'module.js') {
|
|
43
|
+
for (const token of FORBIDDEN_HUBL_IN_MODULE_JS) {
|
|
44
|
+
if (js.includes(token)) {
|
|
45
|
+
throw new Error(
|
|
46
|
+
`${label}: HubL-sensitive token ${JSON.stringify(token)} remains after escape pass`,
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function assertValidJavaScriptSyntax(jsPath) {
|
|
53
|
+
execSync(`node --check ${JSON.stringify(jsPath)}`, { stdio: 'pipe' })
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Replace CDN placeholders in module.html from dist/cdn/manifest.json.
|
|
58
|
+
* @param {string} html
|
|
59
|
+
* @param {string} manifestPath
|
|
60
|
+
*/
|
|
61
|
+
export function injectCdnManifestIntoModuleHtml(html, manifestPath) {
|
|
62
|
+
if (!existsSync(manifestPath)) {
|
|
63
|
+
if (html.includes('__PORTAL_CDN_BASE__')) {
|
|
64
|
+
throw new Error(
|
|
65
|
+
`Missing ${manifestPath} — run yarn build:cdn:dev or yarn build:cdn:prod before hubspot post-build`,
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
return html
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'))
|
|
72
|
+
const vendor = manifest.scripts?.find((s) => s.name === 'vendor')
|
|
73
|
+
const app = manifest.scripts?.find((s) => s.name === 'app')
|
|
74
|
+
const appCss = manifest.styles?.find((s) => s.name === 'app') ?? manifest.styles?.[0]
|
|
75
|
+
|
|
76
|
+
if (!vendor?.file || !app?.file) {
|
|
77
|
+
throw new Error('manifest.json must include vendor and app scripts')
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!appCss?.file) {
|
|
81
|
+
throw new Error('manifest.json must include app styles (app.*.css from yarn build:cdn:dev or build:cdn:prod)')
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return html
|
|
85
|
+
.replaceAll('__PORTAL_CDN_BASE__', manifest.baseUrl)
|
|
86
|
+
.replaceAll('__PORTAL_BUILD_ID__', manifest.buildId)
|
|
87
|
+
.replaceAll('__PORTAL_VENDOR_FILE__', vendor.file)
|
|
88
|
+
.replaceAll('__PORTAL_APP_FILE__', app.file)
|
|
89
|
+
.replaceAll('__PORTAL_APP_CSS_FILE__', appCss.file)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* @param {string} moduleDir absolute path to dist/WoodsPortal.Module
|
|
94
|
+
* @param {string} publicDir absolute path to public/
|
|
95
|
+
* @param {{ manifestPath?: string, bootstrapRawLimit?: number }} [options]
|
|
96
|
+
*/
|
|
97
|
+
export function finalizeHubSpotModuleDist(moduleDir, publicDir, options = {}) {
|
|
98
|
+
const { bootstrapRawLimit = 200 * 1024 } = options
|
|
99
|
+
const manifestPath =
|
|
100
|
+
options.manifestPath ?? resolve(moduleDir, '../cdn/manifest.json')
|
|
101
|
+
mkdirSync(moduleDir, { recursive: true })
|
|
102
|
+
|
|
103
|
+
const jsPath = resolve(moduleDir, 'module.js')
|
|
104
|
+
if (!existsSync(jsPath)) {
|
|
105
|
+
const entries = existsSync(moduleDir) ? readdirSync(moduleDir) : []
|
|
106
|
+
throw new Error(
|
|
107
|
+
`Missing ${jsPath} — Vite lib build must emit module.js first. Found in ${moduleDir}: ${entries.join(', ') || '(empty)'}`,
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const cssPath = resolve(moduleDir, 'module.css')
|
|
112
|
+
if (!existsSync(cssPath)) {
|
|
113
|
+
const entries = existsSync(moduleDir) ? readdirSync(moduleDir) : []
|
|
114
|
+
throw new Error(
|
|
115
|
+
`Missing ${cssPath} — Vite lib build must emit module.css first. Found in ${moduleDir}: ${entries.join(', ') || '(empty)'}`,
|
|
116
|
+
)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
for (const file of ['module.html', 'fields.json']) {
|
|
120
|
+
const src = resolve(publicDir, file)
|
|
121
|
+
if (!existsSync(src)) {
|
|
122
|
+
throw new Error(`Missing public/${file} (required for HubSpot module upload)`)
|
|
123
|
+
}
|
|
124
|
+
if (file === 'module.html') {
|
|
125
|
+
let html = readFileSync(src, 'utf8')
|
|
126
|
+
html = injectCdnManifestIntoModuleHtml(html, manifestPath)
|
|
127
|
+
writeFileSync(resolve(moduleDir, file), html, 'utf8')
|
|
128
|
+
} else {
|
|
129
|
+
cpSync(src, resolve(moduleDir, file), { force: true })
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const escaped = escapeHubSpotModuleJs(readFileSync(jsPath, 'utf8'))
|
|
134
|
+
assertNoForbiddenHubLTokens(escaped, jsPath)
|
|
135
|
+
writeFileSync(jsPath, escaped, 'utf8')
|
|
136
|
+
assertValidJavaScriptSyntax(jsPath)
|
|
137
|
+
|
|
138
|
+
if (escaped.length > bootstrapRawLimit) {
|
|
139
|
+
throw new Error(
|
|
140
|
+
`module.js bootstrap ${escaped.length} bytes exceeds ${bootstrapRawLimit} byte limit`,
|
|
141
|
+
)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
for (const entry of readdirSync(moduleDir)) {
|
|
145
|
+
if (!ALLOWED_DIST_FILES.has(entry)) {
|
|
146
|
+
rmSync(resolve(moduleDir, entry), { recursive: true, force: true })
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return { jsBytes: escaped.length, cssBytes: readFileSync(cssPath).length }
|
|
151
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Load CDN publish settings from woodscli.json (cdn.mode, cdn.mirror) with portal.cdn.json fallback.
|
|
3
|
+
* Env overrides: VITE_PORTAL_CDN_PUBLISH_MODE, VITE_PORTAL_CDN_GITHUB, PORTAL_CDN_GIT_REMOTE, VITE_PORTAL_CDN_BASE_URL
|
|
4
|
+
*/
|
|
5
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
6
|
+
import { resolve } from 'node:path'
|
|
7
|
+
|
|
8
|
+
import { findProjectRoot } from './project-root.mjs'
|
|
9
|
+
import { resolveProjectConfigPath } from './config-files.mjs'
|
|
10
|
+
|
|
11
|
+
const DEFAULTS = {
|
|
12
|
+
publishMode: 'jsdelivr',
|
|
13
|
+
github: 'Digital-Woods/woodsportal-client-frontend-cdn',
|
|
14
|
+
gitRemote: 'https://github.com/Digital-Woods/woodsportal-client-frontend-cdn.git',
|
|
15
|
+
cdnPathInRepo: 'dist/cdn',
|
|
16
|
+
defaultBranch: 'main',
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function readWoodscliConfig(projectRoot) {
|
|
20
|
+
const configPath = resolveProjectConfigPath(projectRoot)
|
|
21
|
+
if (!configPath) {
|
|
22
|
+
return null
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
return JSON.parse(readFileSync(configPath, 'utf8'))
|
|
26
|
+
} catch {
|
|
27
|
+
return null
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function resolveMirrorPath(projectRoot, hsConfig) {
|
|
32
|
+
const mirror = hsConfig?.cdn?.mirror?.trim() || 'portal.cdn.json'
|
|
33
|
+
return resolve(projectRoot, mirror)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @param {NodeJS.ProcessEnv} [env]
|
|
38
|
+
* @param {string} [projectRoot]
|
|
39
|
+
*/
|
|
40
|
+
export function loadPortalCdnConfig(env = process.env, projectRoot) {
|
|
41
|
+
const root = projectRoot ?? findProjectRoot()
|
|
42
|
+
const hsConfig = readWoodscliConfig(root)
|
|
43
|
+
const cdnMode = hsConfig?.cdn?.mode ?? DEFAULTS.publishMode
|
|
44
|
+
const mirrorPath = resolveMirrorPath(root, hsConfig)
|
|
45
|
+
const examplePath = resolve(root, 'portal.cdn.example.json')
|
|
46
|
+
|
|
47
|
+
let fileConfig = {}
|
|
48
|
+
let loadedFrom = null
|
|
49
|
+
|
|
50
|
+
if (existsSync(mirrorPath)) {
|
|
51
|
+
fileConfig = JSON.parse(readFileSync(mirrorPath, 'utf8'))
|
|
52
|
+
loadedFrom = mirrorPath
|
|
53
|
+
} else if (existsSync(examplePath)) {
|
|
54
|
+
fileConfig = JSON.parse(readFileSync(examplePath, 'utf8'))
|
|
55
|
+
loadedFrom = examplePath
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const publishMode =
|
|
59
|
+
env.VITE_PORTAL_CDN_PUBLISH_MODE?.trim() ||
|
|
60
|
+
cdnMode ||
|
|
61
|
+
fileConfig.publishMode ||
|
|
62
|
+
DEFAULTS.publishMode
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
publishMode,
|
|
66
|
+
cdnMode,
|
|
67
|
+
github: env.VITE_PORTAL_CDN_GITHUB?.trim() || fileConfig.github || DEFAULTS.github,
|
|
68
|
+
gitRemote: env.PORTAL_CDN_GIT_REMOTE?.trim() || fileConfig.gitRemote || DEFAULTS.gitRemote,
|
|
69
|
+
cdnPathInRepo: fileConfig.cdnPathInRepo || DEFAULTS.cdnPathInRepo,
|
|
70
|
+
defaultBranch: fileConfig.defaultBranch || DEFAULTS.defaultBranch,
|
|
71
|
+
configPath: loadedFrom,
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Merge CDN config into env for Vite / child processes. */
|
|
76
|
+
export function envWithPortalCdnConfig(baseEnv = process.env, projectRoot) {
|
|
77
|
+
const config = loadPortalCdnConfig(baseEnv, projectRoot)
|
|
78
|
+
return {
|
|
79
|
+
...baseEnv,
|
|
80
|
+
VITE_PORTAL_CDN_PUBLISH_MODE: config.publishMode,
|
|
81
|
+
VITE_PORTAL_CDN_GITHUB: config.github,
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** jsDelivr base URL for a tagged release. */
|
|
86
|
+
export function jsDelivrBaseUrl(github, buildId, cdnPathInRepo = DEFAULTS.cdnPathInRepo) {
|
|
87
|
+
const path = cdnPathInRepo.replace(/^\//, '').replace(/\/$/, '')
|
|
88
|
+
return `https://cdn.jsdelivr.net/gh/${github}@${buildId}/${path}`
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export { DEFAULTS }
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* One-time codemod: woodsportal-client-sdk 2.x flat api → 3.0 nested api.
|
|
4
|
+
*/
|
|
5
|
+
import { readFileSync, writeFileSync, readdirSync, statSync } from 'node:fs'
|
|
6
|
+
import { join, extname } from 'node:path'
|
|
7
|
+
import { fileURLToPath } from 'node:url'
|
|
8
|
+
|
|
9
|
+
const root = join(fileURLToPath(new URL('..', import.meta.url)), 'src')
|
|
10
|
+
|
|
11
|
+
function walk(dir) {
|
|
12
|
+
const out = []
|
|
13
|
+
for (const name of readdirSync(dir)) {
|
|
14
|
+
const p = join(dir, name)
|
|
15
|
+
if (statSync(p).isDirectory()) out.push(...walk(p))
|
|
16
|
+
else if (['.ts', '.tsx'].includes(extname(name))) out.push(p)
|
|
17
|
+
}
|
|
18
|
+
return out
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const factoryReplacements = [
|
|
22
|
+
['api.sideBarObjects', 'api.crm.objects.listSidebar'],
|
|
23
|
+
['api.objectFormOptions', 'api.crm.objects.getFormOptions'],
|
|
24
|
+
['api.updateObjectsDetails', 'api.crm.objects.updateDetails'],
|
|
25
|
+
['api.createExistingObject', 'api.crm.objects.createExisting'],
|
|
26
|
+
['api.removeExistingObject', 'api.crm.objects.removeExisting'],
|
|
27
|
+
['api.objectsDetails', 'api.crm.objects.getDetails'],
|
|
28
|
+
['api.objectsForm', 'api.crm.objects.getForm'],
|
|
29
|
+
['api.createObject', 'api.crm.objects.create'],
|
|
30
|
+
['api.fileDownload', 'api.crm.files.download'],
|
|
31
|
+
['api.attachmentUpload', 'api.crm.uploads.uploadAttachment'],
|
|
32
|
+
['api.imageUpload', 'api.crm.uploads.uploadImage'],
|
|
33
|
+
['api.cachePurgeStatus', 'api.crm.cache.getPurgeStatus'],
|
|
34
|
+
['api.purgeCache', 'api.crm.cache.purge'],
|
|
35
|
+
['api.createEmail', 'api.crm.emails.create'],
|
|
36
|
+
['api.updateEmail', 'api.crm.emails.update'],
|
|
37
|
+
['api.createNote', 'api.crm.notes.create'],
|
|
38
|
+
['api.updateNote', 'api.crm.notes.update'],
|
|
39
|
+
['api.resetPasswordVerifyToken', 'api.auth.resetPasswordVerifyToken'],
|
|
40
|
+
['api.registerExistingUser', 'api.auth.registerExistingUser'],
|
|
41
|
+
['api.verifyEmailResend', 'api.auth.verifyEmailResend'],
|
|
42
|
+
['api.forgetPassword', 'api.auth.forgetPassword'],
|
|
43
|
+
['api.changePassword', 'api.auth.changePassword'],
|
|
44
|
+
['api.getSsoDetails', 'api.auth.sso.getDetails'],
|
|
45
|
+
['api.generateSsoUrl', 'api.auth.sso.generateUrl'],
|
|
46
|
+
['api.ssoCallback', 'api.auth.sso.callback'],
|
|
47
|
+
['api.profileUpdate', 'api.auth.updateProfile'],
|
|
48
|
+
['api.preLogin', 'api.auth.preLogin'],
|
|
49
|
+
['api.verifyEmail', 'api.auth.verifyEmail'],
|
|
50
|
+
['api.resetPassword', 'api.auth.resetPassword'],
|
|
51
|
+
['api.resendEmail', 'api.auth.resendEmail'],
|
|
52
|
+
['api.pipelines', 'api.crm.pipelines.list'],
|
|
53
|
+
['api.addFolder', 'api.crm.files.addFolder'],
|
|
54
|
+
['api.addFile', 'api.crm.files.addFile'],
|
|
55
|
+
['api.stages', 'api.crm.stages.list'],
|
|
56
|
+
['api.objects', 'api.crm.objects.list'],
|
|
57
|
+
['api.notes', 'api.crm.notes.list'],
|
|
58
|
+
['api.emails', 'api.crm.emails.list'],
|
|
59
|
+
['api.files', 'api.crm.files.list'],
|
|
60
|
+
['api.file', 'api.crm.files.getDetails'],
|
|
61
|
+
['api.login', 'api.auth.login'],
|
|
62
|
+
['api.logout', 'api.auth.logout'],
|
|
63
|
+
['api.profile', 'api.auth.profile'],
|
|
64
|
+
['api.me', 'api.auth.me']
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
const sessionReplacements = [
|
|
68
|
+
['api.getRefreshToken', 'api.auth.session.getRefreshToken'],
|
|
69
|
+
['api.getAuthRefreshToken', 'api.auth.session.refreshAccessToken'],
|
|
70
|
+
['api.getAccessToken', 'api.auth.session.getAccessToken'],
|
|
71
|
+
['api.isAuthenticateApp', 'api.auth.session.isAuthenticated'],
|
|
72
|
+
['api.isExpiresAccessToken', 'api.auth.session.isAccessTokenExpired'],
|
|
73
|
+
['api.isCookieExpired', 'api.auth.session.isCookieExpired']
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
const destructureReplacements = [
|
|
77
|
+
['getSideBarObjects', 'listSidebar'],
|
|
78
|
+
['getObjectsDetails', 'getDetails'],
|
|
79
|
+
['getObjectsForm', 'getForm'],
|
|
80
|
+
['updateObjectsDetails', 'updateDetails'],
|
|
81
|
+
['createExistingObject', 'createExisting'],
|
|
82
|
+
['removeExistingObject', 'removeExisting'],
|
|
83
|
+
['createObject', 'create'],
|
|
84
|
+
['createNote', 'create'],
|
|
85
|
+
['updateNote', 'update'],
|
|
86
|
+
['createEmail', 'create'],
|
|
87
|
+
['updateEmail', 'update'],
|
|
88
|
+
['downloadFile', 'download'],
|
|
89
|
+
['getObjects', 'list'],
|
|
90
|
+
['getPipelines', 'list'],
|
|
91
|
+
['getStages', 'list'],
|
|
92
|
+
['getNotes', 'list'],
|
|
93
|
+
['getEmails', 'list'],
|
|
94
|
+
['getFiles', 'list'],
|
|
95
|
+
['getFile', 'getDetails'],
|
|
96
|
+
['objectFormOptions', 'getFormOptions'],
|
|
97
|
+
['imageUpload', 'uploadImage'],
|
|
98
|
+
['attachmentUpload', 'uploadAttachment']
|
|
99
|
+
]
|
|
100
|
+
|
|
101
|
+
let changed = 0
|
|
102
|
+
for (const file of walk(root)) {
|
|
103
|
+
let content = readFileSync(file, 'utf8')
|
|
104
|
+
const original = content
|
|
105
|
+
|
|
106
|
+
for (const [from, to] of factoryReplacements) content = content.split(from).join(to)
|
|
107
|
+
for (const [from, to] of sessionReplacements) content = content.split(from).join(to)
|
|
108
|
+
for (const [from, to] of destructureReplacements) {
|
|
109
|
+
content = content.replace(new RegExp(`\\b${from}\\b`, 'g'), to)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (content.includes('woodsportal-client-sdk')) {
|
|
113
|
+
content = content.replace(
|
|
114
|
+
/import \{([^}]*)\burl\b([^}]*)\} from ['"]woodsportal-client-sdk['"]/g,
|
|
115
|
+
(_match, before, after) => {
|
|
116
|
+
const parts = `${before}${after}`.split(',').map((s) => s.trim()).filter(Boolean)
|
|
117
|
+
const withoutUrl = parts.filter((p) => p !== 'url' && !p.startsWith('url '))
|
|
118
|
+
if (withoutUrl.length === 0) return "import { api } from 'woodsportal-client-sdk'"
|
|
119
|
+
const hasApi = withoutUrl.some((p) => p === 'api' || p.startsWith('api '))
|
|
120
|
+
return hasApi
|
|
121
|
+
? `import { ${withoutUrl.join(', ')} } from 'woodsportal-client-sdk'`
|
|
122
|
+
: `import { ${withoutUrl.join(', ')}, api } from 'woodsportal-client-sdk'`
|
|
123
|
+
}
|
|
124
|
+
)
|
|
125
|
+
content = content.replace(/\burl\.makeLink\b/g, 'api.navigation.url.makeLink')
|
|
126
|
+
content = content.replace(/\burl\.updateLink\b/g, 'api.navigation.url.updateLink')
|
|
127
|
+
content = content.replace(
|
|
128
|
+
/const \{ makeLink: createMakeLink \} = url/g,
|
|
129
|
+
'const { makeLink: createMakeLink } = api.navigation.url'
|
|
130
|
+
)
|
|
131
|
+
content = content.replace(
|
|
132
|
+
/const \{ updateLink: createUpdateLink \} = url/g,
|
|
133
|
+
'const { updateLink: createUpdateLink } = api.navigation.url'
|
|
134
|
+
)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (content !== original) {
|
|
138
|
+
writeFileSync(file, content)
|
|
139
|
+
changed++
|
|
140
|
+
console.log('updated', file.replace(root, 'src'))
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
console.log(`Done. ${changed} files updated.`)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Portal Vite modes — align with `.env.dev` | `.env.stg` | `.env.prod`.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/** @typedef {'dev' | 'stg' | 'prod'} PortalViteMode */
|
|
6
|
+
|
|
7
|
+
/** @param {string[]} [argv] */
|
|
8
|
+
export function resolvePortalViteMode(argv = process.argv) {
|
|
9
|
+
if (argv.includes('--stg') || argv.includes('--staging')) return /** @type {PortalViteMode} */ ('stg')
|
|
10
|
+
if (argv.includes('--dev') || argv.includes('--development')) return /** @type {PortalViteMode} */ ('dev')
|
|
11
|
+
return /** @type {PortalViteMode} */ ('prod')
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** React / bundler NODE_ENV (not the same as portal tier). */
|
|
15
|
+
/** @param {PortalViteMode | string} mode */
|
|
16
|
+
export function nodeEnvForPortalMode(mode) {
|
|
17
|
+
return mode === 'dev' ? 'development' : 'production'
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Tailwind class prefix applies on CDN / HubSpot builds, not local dev. */
|
|
21
|
+
/** @param {PortalViteMode | string} mode */
|
|
22
|
+
export function isPrefixBuildMode(mode) {
|
|
23
|
+
return mode === 'stg' || mode === 'prod' || mode === 'staging' || mode === 'production'
|
|
24
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vite env for HubSpot/CDN builds — .env.[mode] wins over stale shell VITE_* vars.
|
|
3
|
+
*/
|
|
4
|
+
import { loadEnv } from 'vite'
|
|
5
|
+
import { envWithPortalBuildId } from './resolve-portal-build-id.mjs'
|
|
6
|
+
import { envWithPortalCdnConfig } from './load-portal-cdn-config.mjs'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @param {'stg' | 'prod' | 'dev'} mode
|
|
10
|
+
* @param {string} root
|
|
11
|
+
* @param {NodeJS.ProcessEnv} [extra]
|
|
12
|
+
*/
|
|
13
|
+
export function envForPortalBuild(mode, root, extra = {}) {
|
|
14
|
+
const fileEnv = loadEnv(mode, root, '')
|
|
15
|
+
const base = { ...process.env }
|
|
16
|
+
|
|
17
|
+
for (const key of Object.keys(base)) {
|
|
18
|
+
if (key.startsWith('VITE_')) {
|
|
19
|
+
delete base[key]
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
...base,
|
|
25
|
+
...fileEnv,
|
|
26
|
+
...envWithPortalCdnConfig(envWithPortalBuildId({ ...process.env, ...fileEnv, ...extra })),
|
|
27
|
+
...extra,
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @param {'stg' | 'prod' | 'dev'} mode
|
|
33
|
+
*/
|
|
34
|
+
export function defaultDatadogFlag(mode) {
|
|
35
|
+
if (process.env.VITE_DATADOG_RUM_ENABLED != null) {
|
|
36
|
+
return process.env.VITE_DATADOG_RUM_ENABLED
|
|
37
|
+
}
|
|
38
|
+
return mode === 'prod' ? 'false' : 'false'
|
|
39
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve consumer project root (woodscli.json) vs kit package root.
|
|
3
|
+
*/
|
|
4
|
+
import { existsSync } from 'node:fs'
|
|
5
|
+
import { dirname, resolve } from 'node:path'
|
|
6
|
+
import { fileURLToPath } from 'node:url'
|
|
7
|
+
|
|
8
|
+
import { findProjectRootWithConfig } from './config-files.mjs'
|
|
9
|
+
|
|
10
|
+
const kitRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @param {string} [from]
|
|
14
|
+
*/
|
|
15
|
+
export function findProjectRoot(from = process.cwd()) {
|
|
16
|
+
return findProjectRootWithConfig(from)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function getKitRoot() {
|
|
20
|
+
return kitRoot
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @param {...string} segments
|
|
25
|
+
*/
|
|
26
|
+
export function kitPath(...segments) {
|
|
27
|
+
return resolve(kitRoot, ...segments)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export { resolveProjectConfigPath, WOODSCLI_CONFIG_FILE, LEGACY_WOODSPORTAL_HS_FILE } from './config-files.mjs'
|