upgradeTo
uses tcmts.loader.packages.newestVersion, which uses tcmts.loader.packages.registryView, which uses node:child_process
Re-pin the definitions using name (in namespace, or all) to range target (a range, a version, or
"latest"), and the packages built against it to ranges that accept it; re-hash them and their dependents.
import type { Codebase } from "#codebase/tcmts/cli/codebase"; import { TOOLS } from "#codebase/tcmts/cli/lint"; import { bold, dim, green, yellow } from "#codebase/tcmts/cli/style"; import { builtAgainst, newestFitting, usersOf } from "#codebase/tcmts/cli/upgrade"; import { shortHash } from "#codebase/tcmts/core/hash"; import { compatRange, satisfies as satisfies_ } from "#codebase/tcmts/core/semver"; import { ensureExternals, newestVersion, parseModule, versionsIn } from "#codebase/tcmts/loader/packages"; export function upgradeTo(cb: Codebase, name: string, target: string, namespace: string | undefined): boolean { const version = target === "latest" ? newestVersion(name) : (versionsIn(name, target)[0]?.version ?? (versionsIn(name, compatRange(target))[0]?.version)); if (!version) throw new Error(`can't find ${name}@${target} (is the registry reachable?)`); const range = compatRange(version); // the range a package built against `name` needs, to accept the new version const peerRanges = new Map<string, string>(); for (const q of builtAgainst(cb, name)) { const fit = newestFitting(q.name, (c) => !c.peers[name] || satisfies_(version, c.peers[name]!)); if (fit) peerRanges.set(q.name, compatRange(fit)); } const module = (m: string): string => { if (!m.startsWith("npm:")) return m; const p = parseModule(m); if (p.name === name) return `npm:${name}@${range}`; const r = peerRanges.get(p.name); return r ? `npm:${p.name}@${r}` : m; }; const hashes = new Set<string>(); for (const n of [name, ...peerRanges.keys()]) { for (const [h, r] of usersOf(cb, n, undefined, namespace)) if (module(`npm:${n}@${r}`) !== `npm:${n}@${r}`) hashes.add(h); } const where = namespace ? ` in ${namespace}` : ""; // a tool (oxlint, oxfmt, vitest): no definition pins it; the codebase records which range it runs if (TOOLS.has(name) && !usersOf(cb, name).size && !namespace) { ensureExternals(cb.lay, [{ module: `npm:${name}@${range}`, subpath: "", export: "*" }]); cb.store.setSetting(`tool.${name}`, range); console.log(`${bold(name)} now runs at ${range} (${cb.store.lockedVersion(name, range)})`); return true; } if (!hashes.size) { console.log(dim(`nothing${where} uses ${name} other than at ${range}`)); return true; } console.log(`re-pinning ${hashes.size} definition(s)${where} to ${bold(`${name} ${range}`)}${peerRanges.size ? dim(`, with ${[...peerRanges].map(([n, r]) => `${n} ${r}`).join(", ")}`) : ""}`); const r = cb.propagate(new Map(), { hashes, module, command: `upgrade ${name}@${range}` }); for (const p of r.updated) console.log(` ${green("~")} ${p}`); if (r.failed.length) { console.log(yellow(`\n${r.failed.length} definition(s) didn't typecheck on ${name} ${range}; they keep their old versions and are in \`tcm todo\`:`)); for (const h of r.failed.slice(0, 10)) console.log(` ${cb.displayRef(`#${h}`)} ${dim(shortHash(h))}: ${(cb.store.todos().find((t) => t.hash === h)?.message ?? "").split("\n")[0]}`); } return !r.failed.length; }
uses
- tcmts.cli.codebase.Codebase
- tcmts.cli.lint.TOOLS
- tcmts.cli.style.bold
- tcmts.cli.style.dim
- tcmts.cli.style.green
- tcmts.cli.style.yellow
- tcmts.cli.upgrade.builtAgainst
- tcmts.cli.upgrade.newestFitting
- tcmts.cli.upgrade.usersOf
- tcmts.core.hash.shortHash
- tcmts.core.semver.compatRange
- tcmts.core.semver.satisfies
- tcmts.loader.packages.ensureExternals
- tcmts.loader.packages.newestVersion
- tcmts.loader.packages.parseModule
- tcmts.loader.packages.versionsIn