tricu

An interpreted language for exploring Tree Calculus
Log | Files | Refs | README | LICENSE

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 }