upgradeWithin

tcmts.cli.upgrade.upgradeWithin · #76zfmgb5 · term · runs on the server

uses process

Move the lock to the newest versions within the ranges name is used at.

import type { Codebase } from "#codebase/tcmts/cli/codebase";
import { bold, dim, green, red, yellow } from "#codebase/tcmts/cli/style";
import { builtAgainst, typesFor, usersOf } from "#codebase/tcmts/cli/upgrade";
import type { Move } from "#codebase/tcmts/cli/upgrade";
import { compareVersions, satisfies as satisfies_ } from "#codebase/tcmts/core/semver";
import { ensureExternals, versionsIn } from "#codebase/tcmts/loader/packages";

export function upgradeWithin(cb: Codebase, name: string, force: boolean): boolean {
  const moves: Move[] = [];
  const locked = cb.store.lockedPackages();
  for (const l of locked.filter((l) => l.name === name)) {
    const newest = versionsIn(name, l.range)[0]?.version;
    if (newest && compareVersions(newest, l.version) > 0)
      moves.push({ name, range: l.range, from: l.version, to: newest });
  }
  if (!locked.some((l) => l.name === name)) {
    console.log(dim(`nothing in the codebase uses ${name}`));
    return true;
  }
  // what's built against it moves along, to versions that accept the new one
  const next = (n: string, r: string) =>
    moves.find((m) => m.name === n && m.range === r)?.to ?? cb.store.lockedVersion(n, r);
  for (const q of builtAgainst(cb, name)) {
    const fits = versionsIn(q.name, q.range).find((c) => {
      const peer = c.peers[name];
      return (
        !peer ||
        cb.store
          .lockedPackages()
          .filter((l) => l.name === name)
          .some((l) => satisfies_(next(name, l.range)!, peer))
      );
    });
    if (fits && compareVersions(fits.version, q.version) > 0)
      moves.push({ name: q.name, range: q.range, from: q.version, to: fits.version });
  }
  // (a copy: the loop adds to it)
  for (const m of moves.slice()) {
    const t = typesFor(m.name);
    const was = cb.store.lockedVersion(t, m.range);
    const newest = was && versionsIn(t, m.range)[0]?.version;
    if (was && newest && compareVersions(newest, was) > 0)
      moves.push({ name: t, range: m.range, from: was, to: newest });
  }
  if (!moves.length) {
    console.log(
      green(`${name} is up to date`) +
        dim(
          ` (${locked
            .filter((l) => l.name === name)
            .map((l) => `${l.range}: ${l.version}`)
            .join(", ")})`,
        ),
    );
    return true;
  }
  const apply = (version: (m: Move) => string) => {
    for (const m of moves) cb.store.lockVersion(m.name, m.range, version(m));
    ensureExternals(
      cb.lay,
      moves
        .filter((m) => !m.name.startsWith("@types/"))
        .map((m) => ({ module: `npm:${m.name}@${m.range}`, subpath: "", export: "*" })),
    );
  };
  apply((m) => m.to);
  for (const m of moves)
    console.log(`  ${bold(m.name)} ${dim(m.range)}  ${m.from} → ${green(m.to)}`);
  // nothing's hash changes; check that everything using them still typechecks
  const users = new Set(moves.flatMap((m) => [...usersOf(cb, m.name, m.range).keys()]));
  const problems = cb.recheck(users);
  if (problems.length && !force) {
    apply((m) => m.from);
    for (const p of problems.slice(0, 5)) process.stdout.write(p);
    console.log(
      red(
        `\n${problems.length} problem(s) in the ${users.size} definition(s) using them; the lock is back where it was. --force upgrades anyway.`,
      ),
    );
    return false;
  }
  if (problems.length)
    console.log(yellow(`  upgraded with ${problems.length} type problem(s) (--force)`));
  console.log(dim(`  ${users.size} definition(s) re-checked; no hashes changed`));
  return true;
}

uses

used by