openvibers/openvibe-git Commits
An asset's version follows its file, with no restart (#54)
Changes
@@@ -27,12 +27,19 @@ const NAV = [];const API_BASE = 'https://git.openvibe.codes';++// An asset's version is its content hash, kept until the file changes: a static-only deploy (public/ is a no-restart++// path) swaps the file under a running process, and a hash kept for good sent pages on to the old file for as long as++// the process lived (11-12: the new-repository form's fixed radios stayed broken after their deploy).const hashes = new Map();function assetVersion(rel) {-- if (hashes.has(rel)) return hashes.get(rel);++ const file = path.join(PUBLIC_DIR, rel);++ let stamp = null;++ try { const st = fs.statSync(file); stamp = `${st.mtimeMs}:${st.size}:${st.ino}`; } catch { /* missing asset */ }++ const kept = hashes.get(rel);++ if (kept && kept.stamp === stamp) return kept.v;let v = 'dev';-- try { v = crypto.createHash('sha256').update(fs.readFileSync(path.join(PUBLIC_DIR, rel))).digest('hex').slice(0, 10); } catch { /* missing asset */ }-- hashes.set(rel, v);++ try { v = crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex').slice(0, 10); } catch { /* missing asset */ }++ hashes.set(rel, { stamp, v });return v;}const asset = (rel) => `/${rel}?v=${assetVersion(rel)}`;nnew file mode 100644@@@ -0,0 +1,24 @@++'use strict';++// An asset's ?v= follows its file: a static-only deploy swaps public/ under a running process, and the page must point++// at the new file at once (11-12: a hash kept for the life of the process left the fixed radios unseen).++const assert = require('assert');++const fs = require('fs');++const path = require('path');++const { assetVersion, asset } = require('../server/render/layout');++const rel = `css/__asset-version-${process.pid}.css`;++const file = path.join(__dirname, '..', 'public', rel);++try {++ fs.writeFileSync(file, '.a{color:red}');++ const first = assetVersion(rel);++ assert.match(first, /^[0-9a-f]{10}$/);++ assert.strictEqual(assetVersion(rel), first, 'unchanged file, same version');++ fs.writeFileSync(file, '.a{color:blue}');++ const later = new Date(Date.now() + 5000);++ fs.utimesSync(file, later, later);++ const second = assetVersion(rel);++ assert.notStrictEqual(second, first, 'a changed file gets a new version without a restart');++ assert.strictEqual(asset(rel), `/${rel}?v=${second}`);++ fs.rmSync(file);++ assert.strictEqual(assetVersion(rel), 'dev', 'a missing file is dev, not its old hash');++ console.log('asset version: follows the file, no restart needed');++} finally { fs.rmSync(file, { force: true }); }
@@ -27,12 +27,19 @@ const NAV = [
@@ -27,12 +27,19 @@ const NAV = [
];
];
const API_BASE = 'https://git.openvibe.codes';
const API_BASE = 'https://git.openvibe.codes';
+// An asset's version is its content hash, kept until the file changes: a static-only deploy (public/ is a no-restart
+// path) swaps the file under a running process, and a hash kept for good sent pages on to the old file for as long as
+// the process lived (11-12: the new-repository form's fixed radios stayed broken after their deploy).
const hashes = new Map();
const hashes = new Map();
function assetVersion(rel) {
function assetVersion(rel) {
- if (hashes.has(rel)) return hashes.get(rel);
+ const file = path.join(PUBLIC_DIR, rel);
+ let stamp = null;
+ try { const st = fs.statSync(file); stamp = `${st.mtimeMs}:${st.size}:${st.ino}`; } catch { /* missing asset */ }
+ const kept = hashes.get(rel);
+ if (kept && kept.stamp === stamp) return kept.v;
let v = 'dev';
let v = 'dev';
- try { v = crypto.createHash('sha256').update(fs.readFileSync(path.join(PUBLIC_DIR, rel))).digest('hex').slice(0, 10); } catch { /* missing asset */ }
- hashes.set(rel, v);
+ try { v = crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex').slice(0, 10); } catch { /* missing asset */ }
+ hashes.set(rel, { stamp, v });
return v;
return v;
}
}
const asset = (rel) => `/${rel}?v=${assetVersion(rel)}`;
const asset = (rel) => `/${rel}?v=${assetVersion(rel)}`;
new file mode 100644
new file mode 100644
@@ -0,0 +1,24 @@
@@ -0,0 +1,24 @@
+'use strict';
+// An asset's ?v= follows its file: a static-only deploy swaps public/ under a running process, and the page must point
+// at the new file at once (11-12: a hash kept for the life of the process left the fixed radios unseen).
+const assert = require('assert');
+const fs = require('fs');
+const path = require('path');
+const { assetVersion, asset } = require('../server/render/layout');
+const rel = `css/__asset-version-${process.pid}.css`;
+const file = path.join(__dirname, '..', 'public', rel);
+try {
+ fs.writeFileSync(file, '.a{color:red}');
+ const first = assetVersion(rel);
+ assert.match(first, /^[0-9a-f]{10}$/);
+ assert.strictEqual(assetVersion(rel), first, 'unchanged file, same version');
+ fs.writeFileSync(file, '.a{color:blue}');
+ const later = new Date(Date.now() + 5000);
+ fs.utimesSync(file, later, later);
+ const second = assetVersion(rel);
+ assert.notStrictEqual(second, first, 'a changed file gets a new version without a restart');
+ assert.strictEqual(asset(rel), `/${rel}?v=${second}`);
+ fs.rmSync(file);
+ assert.strictEqual(assetVersion(rel), 'dev', 'a missing file is dev, not its old hash');
+ console.log('asset version: follows the file, no restart needed');
+} finally { fs.rmSync(file, { force: true }); }