upgradeWithin
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
- tcmts.cli.codebase.Codebase
- tcmts.cli.style.bold
- tcmts.cli.style.dim
- tcmts.cli.style.green
- tcmts.cli.style.red
- tcmts.cli.style.yellow
- tcmts.cli.upgrade.Move
- tcmts.cli.upgrade.builtAgainst
- tcmts.cli.upgrade.typesFor
- tcmts.cli.upgrade.usersOf
- tcmts.core.semver.compareVersions
- tcmts.core.semver.satisfies
- tcmts.loader.packages.ensureExternals
- tcmts.loader.packages.versionsIn