refactor: streamline dynamic import hash updates for public JS files

This commit is contained in:
Azgaar 2026-03-07 17:32:14 +01:00
parent 9a92a481d8
commit 8b9849aeed

View file

@ -180,99 +180,70 @@ function updateIndexHtmlHashes(changedFiles, newVersion, dry) {
console.log(` src/index.html hashes updated for:\n - ${updated.join("\n - ")}`);
} else {
console.log(
const publicRoot = path.join(repoRoot, "public");
function walk(dir) {
for (const entry of fs.readdirSync(dir, {withFileTypes: true})) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) {
// Skip third-party vendor bundles under public/libs/**
const relFromPublic = path
.relative(publicRoot, full)
.replace(/\\/g, "/");
if (relFromPublic === "libs" || relFromPublic.startsWith("libs/")) {
continue;
}
walk(full);
} else if (entry.isFile() && entry.name.endsWith(".js")) {
results.push(path.relative(repoRoot, full).replace(/\\/g, "/"));
}
}
}
walk(publicRoot);
` src/index.html (changed files have no ?v= entry: ${changedFiles.map(f => f.replace("public/", "")).join(", ")}`
);
}
}
/** Returns all .js file paths (relative to repo root, with forward slashes) under public/. */
function getAllPublicJsFiles() {
const results = [];
function walk(dir) {
for (const entry of fs.readdirSync(dir, {withFileTypes: true})) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) {
walk(full);
} else if (entry.isFile() && entry.name.endsWith(".js")) {
results.push(path.relative(repoRoot, full).replace(/\\/g, "/"));
}
}
}
walk(path.join(repoRoot, "public"));
return results;
}
/**
* Scans all public/**\/*.js files for relative dynamic-import ?v= references
* (e.g. import("../dynamic/supporters.js?v=1.97.14")) and updates any that
* point to one of the changed files.
* For each changed public JS file, scans ALL other public JS files for
* dynamic import() calls that reference it via a relative ?v= path, and
* updates the hash to newVersion.
*
* Example: public/modules/dynamic/installation.js changed
* main.js: import("./modules/dynamic/installation.js?v=1.89.19")
* import("./modules/dynamic/installation.js?v=1.113.4")
*/
function updatePublicJsDynamicImportHashes(changedFiles, newVersion, dry) {
if (changedFiles.length === 0) {
console.log(" public/**/*.js (no changed public/*.js files detected)");
console.log(" public/**/*.js (no changed files, skipping dynamic import hashes)");
return;
const replacement = `${quote}${relImportPath}?v=${newVersion}${quote}`;
// Only record and apply an update if the version actually changes
if (match === replacement) {
return match;
}
if (!updatedMap[relJsFile]) updatedMap[relJsFile] = [];
updatedMap[relJsFile].push(relImportPath);
return replacement;
const publicJsFiles = getAllPublicJsFiles();
}
// Absolute paths of every changed file for O(1) lookup
const changedAbsPaths = new Set(changedFiles.map(f => path.join(repoRoot, f)));
// Collect all public JS files, skipping public/libs (third-party)
const publicRoot = path.join(repoRoot, "public");
const allJsFiles = [];
(function walk(dir) {
for (const entry of fs.readdirSync(dir, {withFileTypes: true})) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) {
if (path.relative(publicRoot, full).replace(/\\/g, "/") === "libs") continue;
walk(full);
} else if (entry.isFile() && entry.name.endsWith(".js")) {
allJsFiles.push(full);
}
}
})(publicRoot);
const updatedMap = {};
for (const relJsFile of publicJsFiles) {
const absJsFile = path.join(repoRoot, relJsFile);
for (const absJsFile of allJsFiles) {
const content = readFile(absJsFile);
const dir = path.dirname(absJsFile);
const pattern = /(['"])(\.\.?\/[^'"]*)\?v=[0-9.]+\1/g;
// Matches: import("../path/file.js?v=1.2.3") or import('../path/file.js?v=1.2.3')
const pattern = /(['"])(\.{1,2}\/[^'"?]+)\?v=[0-9.]+\1/g;
let anyChanged = false;
const newContent = content.replace(pattern, (match, quote, relImportPath) => {
// Strip any query string defensively before resolving the path
const cleanImportPath = relImportPath.split("?")[0];
const absImport = path.resolve(dir, cleanImportPath);
const repoRelImport = path.relative(repoRoot, absImport).replace(/\\/g, "/");
if (changedSet.has(repoRelImport)) {
if (!updatedMap[relJsFile]) updatedMap[relJsFile] = [];
updatedMap[relJsFile].push(relImportPath);
return `${quote}${relImportPath}?v=${newVersion}${quote}`;
}
return match;
const absImport = path.resolve(path.dirname(absJsFile), relImportPath);
if (!changedAbsPaths.has(absImport)) return match;
const repoRelFile = path.relative(repoRoot, absJsFile).replace(/\\/g, "/");
if (!updatedMap[repoRelFile]) updatedMap[repoRelFile] = [];
updatedMap[repoRelFile].push(relImportPath);
anyChanged = true;
return `${quote}${relImportPath}?v=${newVersion}${quote}`;
});
if (updatedMap[relJsFile] && !dry) {
writeFile(absJsFile, newContent);
}
if (anyChanged && !dry) writeFile(absJsFile, newContent);
}
if (Object.keys(updatedMap).length > 0) {
const lines = Object.entries(updatedMap)
.map(([file, refs]) => ` ${file}:\n - ${refs.join("\n - ")}`)
.join("\n");
console.log(` public/**/*.js hashes updated:\n${lines}`);
console.log(` public/**/*.js dynamic import hashes updated:\n${lines}`);
} else {
console.log(" public/**/*.js (no dynamic import ?v= hashes needed updating)");
console.log(" public/**/*.js (no dynamic import ?v= hashes to update)");
}
}