Skip to content

Commit 9a92a48

Browse files
CopilotAzgaarCopilot
authored
Extend bump-version.js to update ?v= cache-busting hashes in public/**/*.js dynamic imports (#1347)
* Initial plan * feat: extend bump-version.js to update ?v= hashes in public/**/*.js dynamic imports Co-authored-by: Azgaar <26469650+Azgaar@users.noreply.github.com> * chore: merge base branch changes (package-lock.json sync, RELEASE_BOT_TOKEN, node 24.x, comment fix) Co-authored-by: Azgaar <26469650+Azgaar@users.noreply.github.com> * Update scripts/bump-version.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update scripts/bump-version.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Azgaar <26469650+Azgaar@users.noreply.github.com> Co-authored-by: Azgaar <maxganiev@yandex.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent ab445d9 commit 9a92a48

1 file changed

Lines changed: 100 additions & 6 deletions

File tree

scripts/bump-version.js

Lines changed: 100 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
* - package.json — "version" field
1010
* - package-lock.json — top-level "version" and packages[""].version fields
1111
* - src/index.html — ?v= cache-busting hashes for changed public/*.js files
12+
* - public/**\/*.js — ?v= cache-busting hashes in dynamic import() calls
1213
*
1314
* Usage:
1415
* node scripts/bump-version.js # interactive prompt
@@ -156,9 +157,7 @@ function updatePackageLockJson(newVersion, dry) {
156157
console.log(` package-lock.json ${oldVersion}${newVersion}`);
157158
}
158159

159-
function updateIndexHtmlHashes(newVersion, dry) {
160-
const changedFiles = getChangedPublicJsFiles();
161-
160+
function updateIndexHtmlHashes(changedFiles, newVersion, dry) {
162161
if (changedFiles.length === 0) {
163162
console.log(" src/index.html (no changed public/*.js files detected)");
164163
return;
@@ -181,11 +180,102 @@ function updateIndexHtmlHashes(newVersion, dry) {
181180
console.log(` src/index.html hashes updated for:\n - ${updated.join("\n - ")}`);
182181
} else {
183182
console.log(
184-
` src/index.html (changed files not referenced: ${changedFiles.map(f => f.replace("public/", "")).join(", ")})`
183+
const publicRoot = path.join(repoRoot, "public");
184+
185+
function walk(dir) {
186+
for (const entry of fs.readdirSync(dir, {withFileTypes: true})) {
187+
const full = path.join(dir, entry.name);
188+
if (entry.isDirectory()) {
189+
// Skip third-party vendor bundles under public/libs/**
190+
const relFromPublic = path
191+
.relative(publicRoot, full)
192+
.replace(/\\/g, "/");
193+
if (relFromPublic === "libs" || relFromPublic.startsWith("libs/")) {
194+
continue;
195+
}
196+
walk(full);
197+
} else if (entry.isFile() && entry.name.endsWith(".js")) {
198+
results.push(path.relative(repoRoot, full).replace(/\\/g, "/"));
199+
}
200+
}
201+
}
202+
203+
walk(publicRoot);
185204
);
186205
}
187206
}
188207

208+
/** Returns all .js file paths (relative to repo root, with forward slashes) under public/. */
209+
function getAllPublicJsFiles() {
210+
const results = [];
211+
function walk(dir) {
212+
for (const entry of fs.readdirSync(dir, {withFileTypes: true})) {
213+
const full = path.join(dir, entry.name);
214+
if (entry.isDirectory()) {
215+
walk(full);
216+
} else if (entry.isFile() && entry.name.endsWith(".js")) {
217+
results.push(path.relative(repoRoot, full).replace(/\\/g, "/"));
218+
}
219+
}
220+
}
221+
walk(path.join(repoRoot, "public"));
222+
return results;
223+
}
224+
225+
/**
226+
* Scans all public/**\/*.js files for relative dynamic-import ?v= references
227+
* (e.g. import("../dynamic/supporters.js?v=1.97.14")) and updates any that
228+
* point to one of the changed files.
229+
*/
230+
function updatePublicJsDynamicImportHashes(changedFiles, newVersion, dry) {
231+
if (changedFiles.length === 0) {
232+
console.log(" public/**/*.js (no changed public/*.js files detected)");
233+
return;
234+
const replacement = `${quote}${relImportPath}?v=${newVersion}${quote}`;
235+
// Only record and apply an update if the version actually changes
236+
if (match === replacement) {
237+
return match;
238+
}
239+
if (!updatedMap[relJsFile]) updatedMap[relJsFile] = [];
240+
updatedMap[relJsFile].push(relImportPath);
241+
return replacement;
242+
const publicJsFiles = getAllPublicJsFiles();
243+
const updatedMap = {};
244+
245+
for (const relJsFile of publicJsFiles) {
246+
const absJsFile = path.join(repoRoot, relJsFile);
247+
const content = readFile(absJsFile);
248+
const dir = path.dirname(absJsFile);
249+
250+
const pattern = /(['"])(\.\.?\/[^'"]*)\?v=[0-9.]+\1/g;
251+
const newContent = content.replace(pattern, (match, quote, relImportPath) => {
252+
// Strip any query string defensively before resolving the path
253+
const cleanImportPath = relImportPath.split("?")[0];
254+
const absImport = path.resolve(dir, cleanImportPath);
255+
const repoRelImport = path.relative(repoRoot, absImport).replace(/\\/g, "/");
256+
if (changedSet.has(repoRelImport)) {
257+
if (!updatedMap[relJsFile]) updatedMap[relJsFile] = [];
258+
updatedMap[relJsFile].push(relImportPath);
259+
return `${quote}${relImportPath}?v=${newVersion}${quote}`;
260+
}
261+
return match;
262+
});
263+
264+
if (updatedMap[relJsFile] && !dry) {
265+
writeFile(absJsFile, newContent);
266+
}
267+
}
268+
269+
if (Object.keys(updatedMap).length > 0) {
270+
const lines = Object.entries(updatedMap)
271+
.map(([file, refs]) => ` ${file}:\n - ${refs.join("\n - ")}`)
272+
.join("\n");
273+
console.log(` public/**/*.js hashes updated:\n${lines}`);
274+
} else {
275+
console.log(" public/**/*.js (no dynamic import ?v= hashes needed updating)");
276+
}
277+
}
278+
189279
// ---------------------------------------------------------------------------
190280
// Prompt
191281
// ---------------------------------------------------------------------------
@@ -230,7 +320,9 @@ async function main() {
230320
`\n[bump-version] Version already updated manually: ${baseVersion}${currentVersion} (base was ${baseVersion})\n`
231321
);
232322
console.log(" Skipping version increment — updating ?v= hashes only.\n");
233-
updateIndexHtmlHashes(currentVersion, dry);
323+
const changedFiles = getChangedPublicJsFiles();
324+
updateIndexHtmlHashes(changedFiles, currentVersion, dry);
325+
updatePublicJsDynamicImportHashes(changedFiles, currentVersion, dry);
234326
console.log(`\n[bump-version] ${dry ? "(dry run) " : ""}done.\n`);
235327
return;
236328
}
@@ -247,10 +339,12 @@ async function main() {
247339

248340
console.log(`\n[bump-version] ${bumpType}: ${currentVersion}${newVersion}\n`);
249341

342+
const changedFiles = getChangedPublicJsFiles();
250343
updateVersioningJs(newVersion, dry);
251344
updatePackageJson(newVersion, dry);
252345
updatePackageLockJson(newVersion, dry);
253-
updateIndexHtmlHashes(newVersion, dry);
346+
updateIndexHtmlHashes(changedFiles, newVersion, dry);
347+
updatePublicJsDynamicImportHashes(changedFiles, newVersion, dry);
254348

255349
console.log(`\n[bump-version] ${dry ? "(dry run) " : ""}done.\n`);
256350
}

0 commit comments

Comments
 (0)