mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-21 14:20:06 +00:00
Bind the existing qualified security override to its exact consumer and serializer pair instead of letting npm overrides silently carry it into a newer Solid runtime. Resolve nested lock edges, retain the strict complete audit and security floors, and cover the two stale dependencies actually present in PR 2759 with positive, partial-repair and nested-resolution controls. Change-source: pulse-maintainer
99 lines
4.5 KiB
JavaScript
99 lines
4.5 KiB
JavaScript
#!/usr/bin/env node
|
|
// A security override is not blanket compatibility approval for a new Solid.
|
|
// Check the locked runtime edges before npm ci can hide them behind overrides.
|
|
import { readFileSync } from 'node:fs';
|
|
import { resolve } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const defaultLock = new URL('../frontend-modern/package-lock.json', import.meta.url);
|
|
const stableVersion = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
|
|
|
|
function versionParts(value) {
|
|
const match = typeof value === 'string' && stableVersion.exec(value);
|
|
const parts = match ? match.slice(1).map(Number) : null;
|
|
return parts?.every(Number.isSafeInteger) ? parts : null;
|
|
}
|
|
|
|
function resolvedDependency(packages, consumerPath, dependency) {
|
|
let parent = consumerPath;
|
|
while (true) {
|
|
const nested = `${parent}/node_modules/${dependency}`;
|
|
if (Object.hasOwn(packages, nested)) return { path: nested, entry: packages[nested] };
|
|
const boundary = parent.lastIndexOf('/node_modules/');
|
|
if (boundary < 0) break;
|
|
parent = parent.slice(0, boundary);
|
|
}
|
|
const root = `node_modules/${dependency}`;
|
|
return Object.hasOwn(packages, root) ? { path: root, entry: packages[root] } : null;
|
|
}
|
|
|
|
export function serializationGraphErrors(lock) {
|
|
const packages = lock?.packages;
|
|
if (lock?.lockfileVersion !== 3 || !packages || typeof packages !== 'object' || Array.isArray(packages)) {
|
|
return ['Expected an npm v3 lock with a complete packages map.'];
|
|
}
|
|
const consumers = Object.entries(packages).filter(([path]) =>
|
|
path === 'node_modules/solid-js' || path.endsWith('/node_modules/solid-js'),
|
|
);
|
|
if (!consumers.some(([path]) => path === 'node_modules/solid-js')) {
|
|
return ['The frontend lock must contain its direct Solid runtime.'];
|
|
}
|
|
const errors = [];
|
|
for (const [path, consumer] of consumers) {
|
|
if (!versionParts(consumer?.version)) {
|
|
errors.push('Solid must have an exact stable locked version.');
|
|
continue;
|
|
}
|
|
for (const dependency of ['seroval', 'seroval-plugins']) {
|
|
const range = consumer.dependencies?.[dependency];
|
|
// These runtime edges currently use full stable tilde ranges. A changed
|
|
// range format needs review rather than an invented semver interpretation.
|
|
const floor = typeof range === 'string' && range.startsWith('~') && versionParts(range.slice(1));
|
|
if (!floor) {
|
|
errors.push(`Solid ${consumer.version}: review the new ${dependency} requirement (expected ~major.minor.patch).`);
|
|
continue;
|
|
}
|
|
const resolved = resolvedDependency(packages, path, dependency);
|
|
const installed = versionParts(resolved?.entry?.version);
|
|
if (!installed) {
|
|
errors.push(`Solid ${consumer.version}: ${dependency} must resolve to an exact stable locked version.`);
|
|
continue;
|
|
}
|
|
const compatible = installed[0] === floor[0] && installed[1] === floor[1] && installed[2] >= floor[2];
|
|
// 795e534517 qualified this one security override with actual cyclic,
|
|
// typed-array and plugin consumers. Preserve it, but do not apply that
|
|
// old proof to a new consumer, dependency, declared range or nested copy.
|
|
const qualifiedOverride = path === 'node_modules/solid-js' &&
|
|
consumer.version === '1.9.15' && dependency === 'seroval' &&
|
|
range === '~1.5.4' && resolved.path === 'node_modules/seroval' &&
|
|
resolved.entry.version === '1.6.3';
|
|
if (!compatible && !qualifiedOverride) {
|
|
errors.push(`Solid ${consumer.version} requires ${dependency} ${range}, but resolves ${resolved.entry.version}. Update the coupled graph and retain consumer compatibility proof; an npm override is not that proof.`);
|
|
}
|
|
}
|
|
}
|
|
return errors;
|
|
}
|
|
|
|
export function main(args = process.argv.slice(2)) {
|
|
if (args.length !== 0 && (args.length !== 2 || args[0] !== '--lockfile')) {
|
|
console.error('Usage: node scripts/check-frontend-serialization.mjs [--lockfile FILE]');
|
|
return 2;
|
|
}
|
|
let lock;
|
|
try {
|
|
lock = JSON.parse(readFileSync(args.length ? args[1] : defaultLock, 'utf8'));
|
|
} catch {
|
|
console.error('Frontend serialization graph: lock read/JSON parse failed.');
|
|
return 2;
|
|
}
|
|
const errors = serializationGraphErrors(lock);
|
|
for (const error of errors) console.error(`Frontend serialization graph: ${error}`);
|
|
if (errors.length) return 1;
|
|
console.log('Frontend serialization graph: Solid runtime edges match, or use the exact qualified security override.');
|
|
return 0;
|
|
}
|
|
|
|
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
process.exitCode = main();
|
|
}
|