#!/usr/bin/env node import { createHash } from "node:crypto"; 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; export function cacheBustZooModelingAppAssets( outputDir, { retainOriginalAssets = false } = {} ) { const indexPath = path.join(outputDir, "index.html"); const source = readFileSync(indexPath, "utf8"); 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, assetPath, suffix) => `${prefix}./${versionedAssetsName}/${assetPath}${suffix}` ); writeFileSync(indexPath, versionedSource); 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.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}` ); }