fix: version the complete HiCAD asset graph

This commit is contained in:
134 2026-07-13 18:43:51 +08:00
parent beb6ff4f04
commit 08d8a1d4af
3 changed files with 58 additions and 26 deletions

View File

@ -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;

View File

@ -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}`);
}

View File

@ -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)) {