diff --git a/deploy/nginx-hicad-zoo-ws.conf.template b/deploy/nginx-hicad-zoo-ws.conf.template index 2d10cda7b..0b4147cb5 100644 --- a/deploy/nginx-hicad-zoo-ws.conf.template +++ b/deploy/nginx-hicad-zoo-ws.conf.template @@ -1,7 +1,7 @@ # Include inside the authenticated HiCAD HTTPS server block. # Replace __AI_CAD_WS_PROXY_SECRET__ during deployment and chmod the rendered file 600. -location ^~ /vendor/zoo-modeling-app/assets/ { +location ^~ /vendor/zoo-modeling-app/assets { root /var/www/ai-chatbot/public; try_files $uri =404; gzip_static on; diff --git a/scripts/cache-bust-zoo-modeling-app-assets.mjs b/scripts/cache-bust-zoo-modeling-app-assets.mjs index 98dbe158b..d950add27 100644 --- a/scripts/cache-bust-zoo-modeling-app-assets.mjs +++ b/scripts/cache-bust-zoo-modeling-app-assets.mjs @@ -1,44 +1,77 @@ #!/usr/bin/env node import { createHash } from "node:crypto"; -import { readFileSync, writeFileSync } from "node:fs"; +import { + cpSync, + existsSync, + readFileSync, + renameSync, + rmSync, + writeFileSync, +} from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; const assetReferencePattern = - /(\b(?:src|href)=")(\.\/assets\/[^"?]+\.(?:css|js))(?:\?v=[^"]+)?(")/g; + /(\b(?:src|href)=")(\.\/assets\/([^"?]+\.(?:css|js)))(?:\?v=[^"]+)?(")/g; -export function cacheBustZooModelingAppAssets(outputDir) { +export function cacheBustZooModelingAppAssets( + outputDir, + { retainOriginalAssets = false } = {} +) { const indexPath = path.join(outputDir, "index.html"); const source = readFileSync(indexPath, "utf8"); - let versionedAssetCount = 0; + const assetReferences = [...source.matchAll(assetReferencePattern)]; + + if (assetReferences.length === 0) { + const currentVersion = source.match(/\.\/assets-([a-f0-9]{12})\//)?.[1]; + if (currentVersion) { + return { assetCount: 0, version: currentVersion }; + } + throw new Error(`No HiCAD entry assets found in ${indexPath}`); + } + + const assetsDir = path.join(outputDir, "assets"); + const contentHash = createHash("sha256"); + for (const reference of assetReferences.sort((a, b) => + a[3].localeCompare(b[3]) + )) { + contentHash.update(reference[3]); + contentHash.update(readFileSync(path.join(assetsDir, reference[3]))); + } + + const version = contentHash.digest("hex").slice(0, 12); + const versionedAssetsName = `assets-${version}`; + const versionedAssetsDir = path.join(outputDir, versionedAssetsName); + rmSync(versionedAssetsDir, { force: true, recursive: true }); + if (retainOriginalAssets) { + cpSync(assetsDir, versionedAssetsDir, { recursive: true }); + } else { + renameSync(assetsDir, versionedAssetsDir); + } const versionedSource = source.replace( assetReferencePattern, - (_match, prefix, assetReference, suffix) => { - const assetPath = path.join(outputDir, assetReference.slice(2)); - const version = createHash("sha256") - .update(readFileSync(assetPath)) - .digest("hex") - .slice(0, 12); - versionedAssetCount += 1; - return `${prefix}${assetReference}?v=${version}${suffix}`; - } + (_match, prefix, _assetReference, assetPath, suffix) => + `${prefix}./${versionedAssetsName}/${assetPath}${suffix}` ); - - if (versionedAssetCount === 0) { - throw new Error(`No versioned HiCAD assets found in ${indexPath}`); - } - writeFileSync(indexPath, versionedSource); - return versionedAssetCount; + return { assetCount: assetReferences.length, version }; } const invokedPath = process.argv[1] ? path.resolve(process.argv[1]) : ""; if (invokedPath === fileURLToPath(import.meta.url)) { const outputDir = path.resolve( - process.argv[2] || "public/vendor/zoo-modeling-app" + process.argv.slice(2).find((argument) => !argument.startsWith("--")) || + "public/vendor/zoo-modeling-app" + ); + if (!existsSync(outputDir)) { + throw new Error(`HiCAD output directory does not exist: ${outputDir}`); + } + const { assetCount, version } = cacheBustZooModelingAppAssets(outputDir, { + retainOriginalAssets: process.argv.includes("--retain-original"), + }); + console.log( + `Versioned ${assetCount} HiCAD entry assets as assets-${version} in ${outputDir}` ); - const assetCount = cacheBustZooModelingAppAssets(outputDir); - console.log(`Versioned ${assetCount} HiCAD entry assets in ${outputDir}`); } diff --git a/scripts/vendor-zoo-modeling-app.mjs b/scripts/vendor-zoo-modeling-app.mjs index dfeddebe8..10b412827 100644 --- a/scripts/vendor-zoo-modeling-app.mjs +++ b/scripts/vendor-zoo-modeling-app.mjs @@ -301,8 +301,7 @@ function ensureWasm() { copyFileSync(wasmBinary, path.join(sourceDir, "public/kcl_wasm_lib_bg.wasm")); } -function precompressVersionedAssets() { - const assetsDir = path.join(outputDir, "assets"); +function precompressVersionedAssets(assetsDir) { for (const entry of readdirSync(assetsDir, { withFileTypes: true })) { if (!entry.isFile() || !/\.(?:css|js)$/.test(entry.name)) { continue; @@ -339,12 +338,12 @@ async function main() { if (!existsSync(path.join(buildDir, "index.html"))) { throw new Error("Zoo Design Studio build did not produce index.html"); } + precompressVersionedAssets(path.join(buildDir, "assets")); cacheBustZooModelingAppAssets(buildDir); rmSync(outputDir, { force: true, recursive: true }); mkdirSync(path.dirname(outputDir), { recursive: true }); await cp(buildDir, outputDir, { recursive: true }); - precompressVersionedAssets(); const license = path.join(sourceDir, "LICENSE"); if (existsSync(license)) {