@woodsportal/hubspot-kit 5.0.3-beta.2 → 5.0.3-beta.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@woodsportal/hubspot-kit",
3
- "version": "5.0.3-beta.2",
3
+ "version": "5.0.3-beta.3",
4
4
  "packageManager": "pnpm@10.12.1",
5
5
  "description": "WoodsCLI — HubSpot CMS dev kit by WoodsPortal. wp CLI, Vite presets, module-config overlay, and theme/module build pipelines.",
6
6
  "type": "module",
@@ -52,7 +52,7 @@
52
52
  "build": "tsup",
53
53
  "dev": "tsup --watch",
54
54
  "typecheck": "tsc --noEmit",
55
- "test": "node --import tsx --test 'src/lib/**/*.test.ts' 'src/cli/**/*.test.ts' 'src/schema/**/*.test.ts' 'src/vite/**/*.test.ts' 'scripts/portal-build-id.test.mjs' 'scripts/resolve-consumer-dep.test.mjs' 'scripts/wp-cli-smoke.test.mjs' 'scripts/vite/ensure-dev-kit-shims.test.mjs' 'scripts/vite/consumer-react-aliases.test.mjs' 'scripts/vite/resolve-from-project-root.test.mjs' 'scripts/tailwind/prefix-plugin.test.mjs' && vitest run src/dev-module-config/__tests__/normalize-repeater-items.test.ts src/dev-module-config/__tests__/module-config-transform.test.ts src/dev-module-config/__tests__/flatten-visible-fields.test.ts src/dev-module-config/__tests__/drill-down-panels.test.ts",
55
+ "test": "node --import tsx --test 'src/lib/**/*.test.ts' 'src/cli/**/*.test.ts' 'src/schema/**/*.test.ts' 'src/vite/**/*.test.ts' 'scripts/portal-build-id.test.mjs' 'scripts/cdn-release-artifacts.test.mjs' 'scripts/hubspot-module-postbuild.test.mjs' 'scripts/resolve-consumer-dep.test.mjs' 'scripts/wp-cli-smoke.test.mjs' 'scripts/vite/ensure-dev-kit-shims.test.mjs' 'scripts/vite/consumer-react-aliases.test.mjs' 'scripts/vite/resolve-from-project-root.test.mjs' 'scripts/tailwind/prefix-plugin.test.mjs' && vitest run src/dev-module-config/__tests__/normalize-repeater-items.test.ts src/dev-module-config/__tests__/module-config-transform.test.ts src/dev-module-config/__tests__/flatten-visible-fields.test.ts src/dev-module-config/__tests__/drill-down-panels.test.ts",
56
56
  "test:examples": "node examples/smoke/run-matrix.mjs --tier static",
57
57
  "test:dist-pack": "node --test scripts/dist-cli-pack-smoke.test.mjs",
58
58
  "lint": "eslint src",
@@ -2,7 +2,7 @@
2
2
  /**
3
3
  * Write dist/cdn/manifest.json after vendor + app builds.
4
4
  */
5
- import { readdirSync, readFileSync, writeFileSync } from 'node:fs'
5
+ import { readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
6
6
  import { resolve } from 'node:path'
7
7
  import { gzipSync } from 'node:zlib'
8
8
 
@@ -52,8 +52,11 @@ const allJsRelPaths = readdirSync(cdnDir, { recursive: true })
52
52
  const staleJs = allJsRelPaths.filter((file) => !cdnArtifactMatchesBuildId(buildId, file))
53
53
  if (staleJs.length > 0) {
54
54
  console.warn(
55
- `[cdn-manifest] ignoring ${staleJs.length} stale .js artifact(s) for buildId ${buildId}: ${staleJs.join(', ')}`,
55
+ `[cdn-manifest] removing ${staleJs.length} stale .js artifact(s) for buildId ${buildId}: ${staleJs.join(', ')}`,
56
56
  )
57
+ for (const file of staleJs) {
58
+ rmSync(resolve(cdnDir, file), { force: true })
59
+ }
57
60
  }
58
61
 
59
62
  const jsFiles = allJsRelPaths
@@ -72,8 +75,11 @@ const allCssFiles = readdirSync(cdnDir).filter((f) => f.endsWith('.css'))
72
75
  const staleCss = allCssFiles.filter((file) => !cdnArtifactMatchesBuildId(buildId, file))
73
76
  if (staleCss.length > 0) {
74
77
  console.warn(
75
- `[cdn-manifest] ignoring ${staleCss.length} stale .css artifact(s) for buildId ${buildId}: ${staleCss.join(', ')}`,
78
+ `[cdn-manifest] removing ${staleCss.length} stale .css artifact(s) for buildId ${buildId}: ${staleCss.join(', ')}`,
76
79
  )
80
+ for (const file of staleCss) {
81
+ rmSync(resolve(cdnDir, file), { force: true })
82
+ }
77
83
  }
78
84
 
79
85
  const cssFiles = allCssFiles
@@ -55,14 +55,70 @@ export function assertValidJavaScriptSyntax(jsPath) {
55
55
  execSync(`node --check ${JSON.stringify(jsPath)}`, { stdio: 'pipe' })
56
56
  }
57
57
 
58
+ /**
59
+ * CodeBuild sets MODULE_BUILD_ID; never let a leftover manifest.buildId win.
60
+ * @param {{ buildId?: string }} manifest
61
+ * @param {NodeJS.ProcessEnv} [env]
62
+ */
63
+ export function resolveInjectBuildId(manifest, env = process.env) {
64
+ const envId = env.MODULE_BUILD_ID?.trim() || env.PORTAL_BUILD_ID?.trim()
65
+ const manifestId = manifest.buildId?.trim()
66
+ const buildId = envId || manifestId
67
+ if (!buildId) {
68
+ throw new Error('module.html inject needs manifest.buildId or MODULE_BUILD_ID')
69
+ }
70
+ return buildId
71
+ }
72
+
73
+ /**
74
+ * Pin HubL/JS CDN fields even when a stale worker already baked an older build id.
75
+ * @param {string} html
76
+ * @param {{ baseUrl: string, buildId: string, vendorFile: string, appFile: string, appCssFile: string }} pins
77
+ */
78
+ export function pinModuleHtmlCdnUrls(html, pins) {
79
+ const { baseUrl, buildId, vendorFile, appFile, appCssFile } = pins
80
+ let out = html
81
+ .replaceAll('__PORTAL_CDN_BASE__', baseUrl)
82
+ .replaceAll('__PORTAL_BUILD_ID__', buildId)
83
+ .replaceAll('__PORTAL_VENDOR_FILE__', vendorFile)
84
+ .replaceAll('__PORTAL_APP_FILE__', appFile)
85
+ .replaceAll('__PORTAL_APP_CSS_FILE__', appCssFile)
86
+
87
+ out = out.replace(/assetBaseUrl:\s*"[^"]*"/g, `assetBaseUrl: "${baseUrl}"`)
88
+ out = out.replace(/buildId:\s*"[^"]*"/g, `buildId: "${buildId}"`)
89
+ out = out.replace(
90
+ /\{% set portal_cdn_base = "[^"]*" %\}/g,
91
+ `{% set portal_cdn_base = "${baseUrl}" %}`,
92
+ )
93
+ out = out.replace(
94
+ /\{% set portal_vendor_script = "[^"]*" %\}/g,
95
+ `{% set portal_vendor_script = "${vendorFile}" %}`,
96
+ )
97
+ out = out.replace(
98
+ /\{% set portal_app_script = "[^"]*" %\}/g,
99
+ `{% set portal_app_script = "${appFile}" %}`,
100
+ )
101
+
102
+ if (!out.includes(buildId) || !out.includes(vendorFile) || !out.includes(appFile)) {
103
+ throw new Error(
104
+ `module.html inject failed: expected ${vendorFile} and ${appFile} for buildId ${buildId}`,
105
+ )
106
+ }
107
+ if (out.includes('__PORTAL_')) {
108
+ throw new Error('module.html still has CDN placeholders after inject')
109
+ }
110
+ return out
111
+ }
112
+
58
113
  /**
59
114
  * Replace CDN placeholders in module.html from dist/cdn/manifest.json.
60
115
  * @param {string} html
61
116
  * @param {string} manifestPath
117
+ * @param {NodeJS.ProcessEnv} [env]
62
118
  */
63
- export function injectCdnManifestIntoModuleHtml(html, manifestPath) {
119
+ export function injectCdnManifestIntoModuleHtml(html, manifestPath, env = process.env) {
64
120
  if (!existsSync(manifestPath)) {
65
- if (html.includes('__PORTAL_CDN_BASE__')) {
121
+ if (html.includes('__PORTAL_CDN_BASE__') || /vendor\.\d/.test(html) || html.includes('portal_vendor_script')) {
66
122
  throw new Error(
67
123
  `Missing ${manifestPath} — run yarn build:cdn:dev or yarn build:cdn:prod before hubspot post-build`,
68
124
  )
@@ -71,14 +127,16 @@ export function injectCdnManifestIntoModuleHtml(html, manifestPath) {
71
127
  }
72
128
 
73
129
  const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'))
74
- const { buildId, vendorFile, appFile, appCssFile } = resolveCdnPrimaryScripts(manifest)
75
-
76
- return html
77
- .replaceAll('__PORTAL_CDN_BASE__', manifest.baseUrl)
78
- .replaceAll('__PORTAL_BUILD_ID__', buildId)
79
- .replaceAll('__PORTAL_VENDOR_FILE__', vendorFile)
80
- .replaceAll('__PORTAL_APP_FILE__', appFile)
81
- .replaceAll('__PORTAL_APP_CSS_FILE__', appCssFile)
130
+ const buildId = resolveInjectBuildId(manifest, env)
131
+ const { vendorFile, appFile, appCssFile } = resolveCdnPrimaryScripts({ ...manifest, buildId })
132
+
133
+ return pinModuleHtmlCdnUrls(html, {
134
+ baseUrl: manifest.baseUrl,
135
+ buildId,
136
+ vendorFile,
137
+ appFile,
138
+ appCssFile,
139
+ })
82
140
  }
83
141
 
84
142
  /**
@@ -91,7 +149,8 @@ export function mergeCdnAppStylesIntoModuleCss(moduleDir, manifestPath) {
91
149
  }
92
150
 
93
151
  const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'))
94
- const { appCssFile } = resolveCdnPrimaryScripts(manifest)
152
+ const buildId = resolveInjectBuildId(manifest)
153
+ const { appCssFile } = resolveCdnPrimaryScripts({ ...manifest, buildId })
95
154
 
96
155
  const cdnCssPath = resolve(moduleDir, '../cdn', appCssFile)
97
156
  if (!existsSync(cdnCssPath)) {
@@ -0,0 +1,86 @@
1
+ import assert from 'node:assert/strict'
2
+ import { mkdtempSync, writeFileSync } from 'node:fs'
3
+ import { tmpdir } from 'node:os'
4
+ import { join } from 'node:path'
5
+ import { describe, it } from 'node:test'
6
+
7
+ import {
8
+ injectCdnManifestIntoModuleHtml,
9
+ pinModuleHtmlCdnUrls,
10
+ resolveInjectBuildId,
11
+ } from './hubspot-module-postbuild.mjs'
12
+
13
+ const placeholders = ` assetBaseUrl: "__PORTAL_CDN_BASE__",
14
+ buildId: "__PORTAL_BUILD_ID__",
15
+ {% set portal_cdn_base = "__PORTAL_CDN_BASE__" %}
16
+ {% set portal_vendor_script = "__PORTAL_VENDOR_FILE__" %}
17
+ {% set portal_app_script = "__PORTAL_APP_FILE__" %}
18
+ `
19
+
20
+ const baked800 = ` assetBaseUrl: "https://cdn.example/cdn",
21
+ buildId: "5.0.2-beta.800",
22
+ {% set portal_cdn_base = "https://cdn.example/cdn" %}
23
+ {% set portal_vendor_script = "vendor.5.0.2-beta.800.js" %}
24
+ {% set portal_app_script = "app.5.0.2-beta.800.js" %}
25
+ `
26
+
27
+ describe('resolveInjectBuildId', () => {
28
+ it('prefers MODULE_BUILD_ID over a stale manifest.buildId', () => {
29
+ assert.equal(
30
+ resolveInjectBuildId({ buildId: '5.0.2-beta.800' }, { MODULE_BUILD_ID: '5.0.2-beta.804' }),
31
+ '5.0.2-beta.804',
32
+ )
33
+ })
34
+ })
35
+
36
+ describe('pinModuleHtmlCdnUrls', () => {
37
+ it('rewrites leftover baked 800 pins to the current build id', () => {
38
+ const out = pinModuleHtmlCdnUrls(baked800, {
39
+ baseUrl: 'https://cdn.example/cdn',
40
+ buildId: '5.0.2-beta.804',
41
+ vendorFile: 'vendor.5.0.2-beta.804.js',
42
+ appFile: 'app.5.0.2-beta.804.js',
43
+ appCssFile: 'app.5.0.2-beta.804.css',
44
+ })
45
+ assert.match(out, /buildId: "5\.0\.2-beta\.804"/)
46
+ assert.match(out, /vendor\.5\.0\.2-beta\.804\.js/)
47
+ assert.match(out, /app\.5\.0\.2-beta\.804\.js/)
48
+ assert.doesNotMatch(out, /5\.0\.2-beta\.800/)
49
+ })
50
+ })
51
+
52
+ describe('injectCdnManifestIntoModuleHtml', () => {
53
+ it('pins vendor/app from MODULE_BUILD_ID even when the manifest still lists 800 first', () => {
54
+ const dir = mkdtempSync(join(tmpdir(), 'wp-mod-html-'))
55
+ const manifestPath = join(dir, 'manifest.json')
56
+ writeFileSync(
57
+ manifestPath,
58
+ JSON.stringify({
59
+ baseUrl: 'https://cdn.example/cdn',
60
+ buildId: '5.0.2-beta.800',
61
+ scripts: [
62
+ { name: 'vendor', file: 'vendor.5.0.2-beta.800.js' },
63
+ { name: 'vendor', file: 'vendor.5.0.2-beta.804.js' },
64
+ { name: 'app', file: 'app.5.0.2-beta.800.js' },
65
+ { name: 'app', file: 'app.5.0.2-beta.804.js' },
66
+ ],
67
+ styles: [
68
+ { name: 'app', file: 'app.5.0.2-beta.800.css' },
69
+ { name: 'app', file: 'app.5.0.2-beta.804.css' },
70
+ ],
71
+ }),
72
+ )
73
+
74
+ const fromPlaceholders = injectCdnManifestIntoModuleHtml(placeholders, manifestPath, {
75
+ MODULE_BUILD_ID: '5.0.2-beta.804',
76
+ })
77
+ assert.match(fromPlaceholders, /vendor\.5\.0\.2-beta\.804\.js/)
78
+ assert.doesNotMatch(fromPlaceholders, /5\.0\.2-beta\.800/)
79
+
80
+ const fromBaked = injectCdnManifestIntoModuleHtml(baked800, manifestPath, {
81
+ MODULE_BUILD_ID: '5.0.2-beta.804',
82
+ })
83
+ assert.match(fromBaked, /vendor\.5\.0\.2-beta\.804\.js/)
84
+ assert.doesNotMatch(fromBaked, /5\.0\.2-beta\.800/)
85
+ })
86
+ })