openvibers/openvibe.shared Commits

pin-drift: one copy of openvibe-shared per package root (WS-C task 8, ADR-008 amendment): after install, every installed copy is found (npm nesting, pnpm store, symlinks resolved) and a second one fails with each copy's version and path; every repository has one today

OP OpenVibers committed 25 Sept 2026 · 94575ed63b · 2 files, +86 −4


Changes

ddiff --git a/scripts/pin-drift.js b/scripts/pin-drift.js
iindex 31245e3..532bddc 100644
---- a/scripts/pin-drift.js
++++ b/scripts/pin-drift.js
@@@ -17,7 +17,12 @@
* Checks openvibe-contracts, openvibe-shared, openvibe-sdk and openvibe-publishing in dependencies,
* devDependencies, optionalDependencies and peerDependencies; ranges that are not tag URLs (a peer range
* like ">=1.5.0") are not pins and are skipped. Tags come from `git ls-remote --tags` (no token needed).
-- * Exit 0 = no drift, 1 = drift, 2 = could not check (network).
++ * One copy (WS-C task 8, ADR-008 amendment): openvibe-publishing takes openvibe-shared as an optional peer, so a
++ * product must install openvibe-shared exactly once. For every package root with a node_modules tree (after
++ * the install step), every installed openvibe-shared is found (npm nesting and pnpm's .pnpm store, symlinks
++ * resolved); more than one copy fails and names each one's version and path.
++ *
++ * Exit 0 = no drift, 1 = drift or a second copy, 2 = could not check (network).
*/
const fs = require('fs');
const path = require('path');
@@@ -96,7 +101,50 @@ function main(dir = '.', { tagsOf, datesOf, now = Date.now() } = {}) {
return results;
}
--module.exports = { judge, pinsIn, main, releaseDates, LIBS, GRACE_DAYS };
++/** Every installed copy of `name` under root/node_modules (nested and pnpm), deduplicated by real path. → [{ dir, version }] */
++function installedCopies(root, name = 'openvibe-shared', maxDepth = 8) {
++ const found = new Map();
++ const seen = new Set();
++ const walk = (nm, depth) => {
++ let real;
++ try { real = fs.realpathSync(nm); } catch { return; }
++ if (seen.has(real) || depth > maxDepth) return;
++ seen.add(real);
++ let entries = [];
++ try { entries = fs.readdirSync(nm); } catch { return; }
++ for (const e of entries) {
++ if (e === '.bin' || e === '.cache') continue;
++ const p = path.join(nm, e);
++ if (e === '.pnpm') { for (const v of fs.readdirSync(p)) walk(path.join(p, v, 'node_modules'), depth + 1); continue; }
++ const pkgs = e.startsWith('@') ? (() => { try { return fs.readdirSync(p).map((x) => path.join(p, x)); } catch { return []; } })() : [p];
++ for (const dir of pkgs) {
++ if (path.basename(dir) === name && fs.existsSync(path.join(dir, 'package.json'))) {
++ try {
++ const r = fs.realpathSync(dir);
++ if (!found.has(r)) found.set(r, { dir: path.relative(root, dir), version: JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf8')).version });
++ } catch { /* unreadable */ }
++ }
++ walk(path.join(dir, 'node_modules'), depth + 1);
++ }
++ }
++ };
++ walk(path.join(root, 'node_modules'), 0);
++ return [...found.values()];
++}
++
++/** For each package root under dir that has node_modules: its openvibe-shared copies. → [{ root, copies, ok }] */
++function sharedCopies(dir = '.') {
++ const out = [];
++ for (const file of packageFiles(dir)) {
++ const root = path.dirname(file);
++ if (!fs.existsSync(path.join(root, 'node_modules'))) continue;
++ const copies = installedCopies(root);
++ if (copies.length) out.push({ root, copies, ok: copies.length === 1 });
++ }
++ return out;
++}
++
++module.exports = { judge, pinsIn, main, releaseDates, installedCopies, sharedCopies, LIBS, GRACE_DAYS };
if (require.main === module || !module.parent) {
let results;
@@@ -104,5 +152,9 @@ if (require.main === module || !module.parent) {
for (const r of results) console.log(`${r.ok ? 'ok ' : 'FAIL'} ${path.relative(process.cwd(), r.file) || r.file} ${r.name} v${r.pin.join('.')}: ${r.reason}`);
const bad = results.filter((r) => !r.ok);
console.log(bad.length ? `pin-drift: ${bad.length} of ${results.length} pin(s) drifted` : `pin-drift: ${results.length} pin(s), no drift`);
-- process.exit(bad.length ? 1 : 0);
++ const copies = sharedCopies(process.argv[2] || '.');
++ for (const c of copies) console.log(`${c.ok ? 'ok ' : 'FAIL'} ${path.relative(process.cwd(), c.root) || '.'}: openvibe-shared installed ${c.copies.length === 1 ? 'once' : `${c.copies.length} times`} (${c.copies.map((x) => `v${x.version} at ${x.dir}`).join('; ')})`);
++ const twice = copies.filter((c) => !c.ok);
++ if (twice.length) console.log(`pin-drift: ${twice.length} package root(s) install openvibe-shared more than once (one copy: ADR-008 amendment)`);
++ process.exit(bad.length || twice.length ? 1 : 0);
}
ddiff --git a/test/pin-drift.test.js b/test/pin-drift.test.js
iindex 734b0c8..9415e8b 100644
---- a/test/pin-drift.test.js
++++ b/test/pin-drift.test.js
@@@ -4,7 +4,7 @@ const assert = require('assert');
const fs = require('fs');
const os = require('os');
const path = require('path');
--const { judge, main, releaseDates } = require('../scripts/pin-drift');
++const { judge, main, releaseDates, sharedCopies } = require('../scripts/pin-drift');
const tags = ['v0.40.0', 'v0.41.0', 'v0.41.1', 'v0.42.0'];
assert.strictEqual(judge([0, 42, 0], tags).ok, true);
@@@ -28,3 +28,33 @@ const out = main(dir, { tagsOf: (repo) => (repo === 'OpenVibe.Contracts' ? tags
assert.deepStrictEqual(out.map((r) => [r.name, r.ok]), [['openvibe-contracts', false], ['openvibe-sdk', true]], 'ranges are not pins; other packages are ignored');
fs.rmSync(dir, { recursive: true, force: true });
console.log('pin-drift: all checks passed');
++
++// One copy of openvibe-shared per package root (WS-C task 8): npm nesting and pnpm's store, symlinks resolved.
++{
++ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ov-one-shared-'));
++ const put = (rel, version) => { fs.mkdirSync(path.join(root, rel), { recursive: true }); fs.writeFileSync(path.join(root, rel, 'package.json'), JSON.stringify({ name: path.basename(rel), version })); };
++ fs.writeFileSync(path.join(root, 'package.json'), '{}');
++ put('node_modules/openvibe-shared', '1.13.0');
++ put('node_modules/openvibe-publishing', '0.4.0');
++ let r = sharedCopies(root);
++ assert.deepStrictEqual(r.map((x) => [x.ok, x.copies.length]), [[true, 1]], 'one hoisted copy');
++ put('node_modules/openvibe-publishing/node_modules/openvibe-shared', '1.12.0');
++ r = sharedCopies(root);
++ assert.strictEqual(r[0].ok, false, 'a nested second copy fails');
++ assert.deepStrictEqual(r[0].copies.map((c) => c.version).sort(), ['1.12.0', '1.13.0']);
++ fs.rmSync(root, { recursive: true, force: true });
++
++ // pnpm: the store holds the package once; node_modules/openvibe-shared and a dependant's link point at it.
++ const pn = fs.mkdtempSync(path.join(os.tmpdir(), 'ov-one-shared-pnpm-'));
++ fs.writeFileSync(path.join(pn, 'package.json'), '{}');
++ const store = path.join(pn, 'node_modules/.pnpm/[email protected]/node_modules/openvibe-shared');
++ fs.mkdirSync(store, { recursive: true });
++ fs.writeFileSync(path.join(store, 'package.json'), JSON.stringify({ name: 'openvibe-shared', version: '1.13.0' }));
++ fs.symlinkSync(store, path.join(pn, 'node_modules/openvibe-shared'), 'dir');
++ const pubNm = path.join(pn, 'node_modules/.pnpm/[email protected]/node_modules');
++ fs.mkdirSync(path.join(pubNm, 'openvibe-publishing'), { recursive: true });
++ fs.symlinkSync(store, path.join(pubNm, 'openvibe-shared'), 'dir');
++ assert.deepStrictEqual(sharedCopies(pn).map((x) => [x.ok, x.copies.length]), [[true, 1]], 'links to one store entry are one copy');
++ fs.rmSync(pn, { recursive: true, force: true });
++}
++console.log('pin-drift: one copy of openvibe-shared checked');