packages.js (3960B)
1 import { bundleHash } from './bundle.js'; 2 const component = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u; 3 function record(value) { 4 if (!value || typeof value !== 'object' || Array.isArray(value)) 5 throw new Error('Invalid metadata object'); 6 return value; 7 } 8 export function packageIndex(value) { 9 const input = record(value); 10 if (input['schema'] !== 1 || !Array.isArray(input['releases']) || input['releases'].length > 4096) 11 throw new Error('Invalid package index'); 12 const seen = new Set(); 13 return input['releases'].map((raw) => { 14 const r = record(raw); 15 if (typeof r['name'] !== 'string' || !component.test(r['name']) || typeof r['version'] !== 'string' || !component.test(r['version']) || 16 typeof r['title'] !== 'string' || r['title'].length > 256 || (r['demo'] !== undefined && (typeof r['demo'] !== 'string' || !component.test(r['demo'])))) 17 throw new Error('Invalid release'); 18 const key = `${r['name']}@${r['version']}`; 19 if (seen.has(key)) 20 throw new Error('Duplicate package version'); 21 seen.add(key); 22 return { name: r['name'], version: r['version'], title: r['title'], hash: bundleHash(r['hash']), manifest: bundleHash(r['manifest']), 23 ...(r['demo'] === undefined ? {} : { demo: r['demo'] }) }; 24 }); 25 } 26 // SemVer precedence for conventional releases; other accepted labels use a 27 // deterministic numeric-aware ordering below conventional releases. Never infer 28 // recency from index order. 29 function compareLabel(a, b) { 30 const left = a.match(/[0-9]+|[^0-9]+/gu) ?? [], right = b.match(/[0-9]+|[^0-9]+/gu) ?? []; 31 for (let i = 0; i < Math.min(left.length, right.length); i++) { 32 const x = left[i], y = right[i]; 33 const order = /^\d+$/u.test(x) && /^\d+$/u.test(y) 34 ? (BigInt(x) < BigInt(y) ? -1 : BigInt(x) > BigInt(y) ? 1 : 0) 35 : (x < y ? -1 : x > y ? 1 : 0); 36 if (order) 37 return order; 38 } 39 return left.length - right.length; 40 } 41 function compareVersion(a, b) { 42 const semver = /^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$/u; 43 const x = a.match(semver), y = b.match(semver); 44 if (!x || !y) 45 return x ? 1 : y ? -1 : compareLabel(a, b); 46 for (let i = 1; i <= 3; i++) { 47 const order = compareLabel(x[i], y[i]); 48 if (order) 49 return order; 50 } 51 if (x[4] === undefined || y[4] === undefined) 52 return x[4] === y[4] ? 0 : x[4] === undefined ? 1 : -1; 53 const left = x[4].split('.'), right = y[4].split('.'); 54 for (let i = 0; i < Math.min(left.length, right.length); i++) { 55 const a = left[i], b = right[i], an = /^\d+$/u.test(a), bn = /^\d+$/u.test(b); 56 const order = an && bn ? compareLabel(a, b) : an !== bn ? (an ? -1 : 1) : (a < b ? -1 : a > b ? 1 : 0); 57 if (order) 58 return order; 59 } 60 return left.length - right.length; 61 } 62 export function packageReleases(releases, name) { 63 return releases.filter(r => r.name === name).sort((a, b) => compareVersion(b.version, a.version) || (a.version < b.version ? 1 : a.version > b.version ? -1 : 0)); 64 } 65 export function environmentProfile(value, name) { 66 const input = record(value), profiles = record(input['profiles']); 67 if (input['schema'] !== 1 || !Object.hasOwn(profiles, name)) 68 throw new Error('Unknown demo environment'); 69 const compiler = bundleHash(input['compiler']), raw = profiles[name]; 70 if (raw === null) 71 return { compiler, profile: null }; 72 const profile = record(raw), names = profile['names']; 73 if (!Array.isArray(names) || names.length > 128 || names.some(n => typeof n !== 'string' || !/^[A-Za-z_][A-Za-z0-9_?$%'.-]*$/u.test(n) || n.length > 128) || new Set(names).size !== names.length) 74 throw new Error('Invalid environment bindings'); 75 return { compiler, profile: { hash: bundleHash(profile['hash']), names: names } }; 76 }