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
Changes
@@@ -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);}@@@ -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' ? tagsassert.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');
@@ -17,7 +17,12 @@
@@ -17,7 +17,12 @@
* Checks openvibe-contracts, openvibe-shared, openvibe-sdk and openvibe-publishing in dependencies,
* 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
* 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).
* 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 fs = require('fs');
const path = require('path');
const path = require('path');
@@ -96,7 +101,50 @@ function main(dir = '.', { tagsOf, datesOf, now = Date.now() } = {}) {
@@ -96,7 +101,50 @@ function main(dir = '.', { tagsOf, datesOf, now = Date.now() } = {}) {
return results;
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) {
if (require.main === module || !module.parent) {
let results;
let results;
@@ -104,5 +152,9 @@ if (require.main === module || !module.parent) {
@@ -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}`);
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);
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`);
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);
}
}
@@ -4,7 +4,7 @@ const assert = require('assert');
@@ -4,7 +4,7 @@ const assert = require('assert');
const fs = require('fs');
const fs = require('fs');
const os = require('os');
const os = require('os');
const path = require('path');
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'];
const tags = ['v0.40.0', 'v0.41.0', 'v0.41.1', 'v0.42.0'];
assert.strictEqual(judge([0, 42, 0], tags).ok, true);
assert.strictEqual(judge([0, 42, 0], tags).ok, true);
@@ -28,3 +28,33 @@ const out = main(dir, { tagsOf: (repo) => (repo === 'OpenVibe.Contracts' ? tags
@@ -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');
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 });
fs.rmSync(dir, { recursive: true, force: true });
console.log('pin-drift: all checks passed');
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');