@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.
Files changed (135) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/LICENSE +21 -0
  3. package/README.md +62 -0
  4. package/SECURITY.md +27 -0
  5. package/dist/chunk-JDGIAYHX.js +87 -0
  6. package/dist/chunk-JDGIAYHX.js.map +1 -0
  7. package/dist/chunk-U4KFGTEW.js +136 -0
  8. package/dist/chunk-U4KFGTEW.js.map +1 -0
  9. package/dist/cli.js +1849 -0
  10. package/dist/cli.js.map +1 -0
  11. package/dist/exec-K5C6LZP4.js +10 -0
  12. package/dist/exec-K5C6LZP4.js.map +1 -0
  13. package/dist/index.d.ts +228 -0
  14. package/dist/index.js +204 -0
  15. package/dist/index.js.map +1 -0
  16. package/dist/vite/index.d.ts +17 -0
  17. package/dist/vite/index.js +9 -0
  18. package/dist/vite/index.js.map +1 -0
  19. package/examples/README.md +12 -0
  20. package/examples/fixtures/adopted-module-hybrid/public/fields.json +1 -0
  21. package/examples/fixtures/adopted-module-hybrid/public/module.html +1 -0
  22. package/examples/fixtures/adopted-module-hybrid/woodscli.json +7 -0
  23. package/examples/fixtures/adopted-module-monolith/public/fields.json +1 -0
  24. package/examples/fixtures/adopted-module-monolith/woodscli.json +7 -0
  25. package/examples/fixtures/adopted-theme-minimal/src/theme/templates/page.html +1 -0
  26. package/examples/fixtures/adopted-theme-minimal/src/theme/theme.json +1 -0
  27. package/examples/fixtures/adopted-theme-minimal/woodscli.json +7 -0
  28. package/examples/fixtures/existing-module-hybrid/public/fields.json +1 -0
  29. package/examples/fixtures/existing-module-hybrid/public/module.html +1 -0
  30. package/examples/fixtures/existing-theme-minimal/templates/page.html +1 -0
  31. package/examples/fixtures/existing-theme-minimal/theme.json +1 -0
  32. package/examples/greenfield/module-hybrid-cdn/README.md +9 -0
  33. package/examples/greenfield/module-hybrid-cdn/index.html +1 -0
  34. package/examples/greenfield/module-hybrid-cdn/package.json +14 -0
  35. package/examples/greenfield/module-hybrid-cdn/public/fields.json +1 -0
  36. package/examples/greenfield/module-hybrid-cdn/public/module.html +1 -0
  37. package/examples/greenfield/module-hybrid-cdn/src/main.tsx +2 -0
  38. package/examples/greenfield/module-hybrid-cdn/vite.config.dev.mjs +1 -0
  39. package/examples/greenfield/module-hybrid-cdn/vite.config.hubspot.mjs +1 -0
  40. package/examples/greenfield/module-hybrid-cdn/woodscli.json +8 -0
  41. package/examples/greenfield/module-hybrid-cdn/wphs.adapter.ts +7 -0
  42. package/examples/greenfield/module-monolith/package.json +12 -0
  43. package/examples/greenfield/module-monolith/public/fields.json +1 -0
  44. package/examples/greenfield/module-monolith/vite.config.dev.mjs +1 -0
  45. package/examples/greenfield/module-monolith/vite.config.monolith.mjs +1 -0
  46. package/examples/greenfield/module-monolith/woodscli.json +25 -0
  47. package/examples/greenfield/module-monolith/wphs.adapter.ts +7 -0
  48. package/examples/greenfield/theme-starter/package.json +11 -0
  49. package/examples/greenfield/theme-starter/src/theme/css/main.css +1 -0
  50. package/examples/greenfield/theme-starter/src/theme/templates/base.html +1 -0
  51. package/examples/greenfield/theme-starter/src/theme/templates/page.html +1 -0
  52. package/examples/greenfield/theme-starter/src/theme/theme.json +1 -0
  53. package/examples/greenfield/theme-starter/theme.json +6 -0
  54. package/examples/greenfield/theme-starter/vite.config.dev.mjs +1 -0
  55. package/examples/greenfield/theme-starter/woodscli.json +7 -0
  56. package/examples/manifest.json +20 -0
  57. package/examples/smoke/run-matrix.mjs +100 -0
  58. package/package.json +94 -0
  59. package/scripts/audit-module-config-fields.mjs +73 -0
  60. package/scripts/audit-parity.mjs +22 -0
  61. package/scripts/audit-tdz-split.mjs +111 -0
  62. package/scripts/build-cdn.mjs +36 -0
  63. package/scripts/check-hubspot-bundle-size.mjs +58 -0
  64. package/scripts/config-files.mjs +53 -0
  65. package/scripts/detect-layout.mjs +70 -0
  66. package/scripts/ensure-wphs-workspace.mjs +53 -0
  67. package/scripts/generate-cdn-manifest.mjs +92 -0
  68. package/scripts/generate-module-config-example.mjs +57 -0
  69. package/scripts/hubspot-module-postbuild.mjs +151 -0
  70. package/scripts/load-portal-cdn-config.mjs +91 -0
  71. package/scripts/migrate-sdk-3-api.mjs +144 -0
  72. package/scripts/module-config-dir.mjs +12 -0
  73. package/scripts/portal-env-mode.mjs +24 -0
  74. package/scripts/portal-vite-env.mjs +39 -0
  75. package/scripts/project-root.mjs +30 -0
  76. package/scripts/publish-cdn-github.mjs +273 -0
  77. package/scripts/read-woodsportal-config.mjs +40 -0
  78. package/scripts/resolve-cdn-base-url.mjs +32 -0
  79. package/scripts/resolve-consumer-dep.mjs +25 -0
  80. package/scripts/resolve-portal-build-id.mjs +67 -0
  81. package/scripts/run-dev.mjs +93 -0
  82. package/scripts/run-hubspot-module-postbuild.mjs +19 -0
  83. package/scripts/run-vite-build.mjs +33 -0
  84. package/scripts/tailwind/prefix-plugin.js +291 -0
  85. package/scripts/tailwind/prefix.env.js +11 -0
  86. package/scripts/tailwind/tailwind-content-plugin.js +27 -0
  87. package/scripts/tailwind/tailwind-content.js +267 -0
  88. package/scripts/verify-cdn-api-endpoint.mjs +68 -0
  89. package/scripts/vite/ensure-dev-kit-shims.js +49 -0
  90. package/scripts/vite/ensure-dev-module-config-shim.js +1 -0
  91. package/scripts/vite/module-config-store.js +242 -0
  92. package/scripts/vite/plugins.d.ts +9 -0
  93. package/scripts/vite/portal-build-shared.js +110 -0
  94. package/scripts/vite/react-dev-plugin-options.js +21 -0
  95. package/scripts/vite/resolve-from-project-root.js +31 -0
  96. package/scripts/vite/sdk-log-bridge.js +104 -0
  97. package/scripts/watch-hubspot.mjs +86 -0
  98. package/scripts/wphs-paths.mjs +115 -0
  99. package/scripts/write-audit-artifacts.mjs +56 -0
  100. package/templates/hubspot.config.yml.example +10 -0
  101. package/templates/module-hybrid-cdn/README.md +9 -0
  102. package/templates/module-hybrid-cdn/hubspot.config.yml.example +10 -0
  103. package/templates/module-hybrid-cdn/index.html +1 -0
  104. package/templates/module-hybrid-cdn/package.json +14 -0
  105. package/templates/module-hybrid-cdn/public/fields.json +1 -0
  106. package/templates/module-hybrid-cdn/public/module.html +1 -0
  107. package/templates/module-hybrid-cdn/src/main.tsx +2 -0
  108. package/templates/module-hybrid-cdn/vite.config.dev.mjs +1 -0
  109. package/templates/module-hybrid-cdn/vite.config.hubspot.mjs +1 -0
  110. package/templates/module-hybrid-cdn/woodscli.json +8 -0
  111. package/templates/module-hybrid-cdn/wphs.adapter.ts +7 -0
  112. package/templates/module-monolith/hubspot.config.yml.example +10 -0
  113. package/templates/module-monolith/package.json +12 -0
  114. package/templates/module-monolith/public/fields.json +1 -0
  115. package/templates/module-monolith/vite.config.dev.mjs +1 -0
  116. package/templates/module-monolith/vite.config.monolith.mjs +1 -0
  117. package/templates/module-monolith/woodscli.json +25 -0
  118. package/templates/module-monolith/wphs.adapter.ts +7 -0
  119. package/templates/theme-starter/hubspot.config.yml.example +10 -0
  120. package/templates/theme-starter/package.json +11 -0
  121. package/templates/theme-starter/src/theme/css/main.css +1 -0
  122. package/templates/theme-starter/src/theme/templates/base.html +1 -0
  123. package/templates/theme-starter/src/theme/templates/page.html +1 -0
  124. package/templates/theme-starter/src/theme/theme.json +1 -0
  125. package/templates/theme-starter/theme.json +6 -0
  126. package/templates/theme-starter/vite.config.dev.mjs +1 -0
  127. package/templates/theme-starter/woodscli.json +7 -0
  128. package/vite/vite.config.audit.mjs +33 -0
  129. package/vite/vite.config.cdn-app.mjs +44 -0
  130. package/vite/vite.config.cdn-esm.mjs +35 -0
  131. package/vite/vite.config.cdn-vendor.mjs +33 -0
  132. package/vite/vite.config.dev.mjs +62 -0
  133. package/vite/vite.config.hubspot.mjs +33 -0
  134. package/vite/vite.config.monolith.mjs +55 -0
  135. 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,12 @@
1
+ /**
2
+ * @deprecated Import from ./wphs-paths.mjs
3
+ */
4
+ export {
5
+ DEFAULT_MODULE_CONFIG_DIR,
6
+ WPHS_WORKSPACE_DIR,
7
+ moduleConfigPaths,
8
+ readModuleConfigDir,
9
+ readWphsWorkspaceDir,
10
+ wphsPaths,
11
+ wphsWorkspaceSubdirs,
12
+ } from './wphs-paths.mjs'
@@ -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'