tricu

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

packages.ts (4212B)


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