| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195 |
- #!/usr/bin/env bun
- import path from "node:path"
- const args = process.argv.slice(2)
- const usage = "Usage: bun run script/upgrade-opentui.ts [--snapshot] <version>"
- if (args.includes("--help") || args.includes("-h")) {
- console.log(usage)
- process.exit(0)
- }
- const snapshotArg = args.find((arg) => arg.startsWith("--snapshot="))
- const snapshot = args.includes("--snapshot") || snapshotArg !== undefined
- const unknown = args.find((arg) => arg.startsWith("-") && arg !== "--snapshot" && !arg.startsWith("--snapshot="))
- if (unknown) {
- console.error(`Unknown option: ${unknown}`)
- console.error(usage)
- process.exit(1)
- }
- const positional = args.filter((arg) => arg !== "--snapshot" && !arg.startsWith("--snapshot="))
- const raw = snapshotArg?.slice("--snapshot=".length) || positional[0]
- if (!raw || positional.length > (snapshotArg ? 0 : 1)) {
- console.error(usage)
- process.exit(1)
- }
- if (snapshotArg === "--snapshot=") {
- console.error("Missing snapshot version")
- console.error(usage)
- process.exit(1)
- }
- const ver = raw.replace(/^v/, "")
- const root = path.resolve(import.meta.dir, "..")
- const lockfile = path.join(root, "bun.lock")
- const skip = new Set([".git", ".opencode", ".turbo", "dist", "node_modules"])
- const keys = ["@opentui/core", "@opentui/keymap", "@opentui/solid"] as const
- const files = (await Array.fromAsync(new Bun.Glob("**/package.json").scan({ cwd: root }))).filter(
- (file) => !file.split("/").some((part) => skip.has(part)),
- )
- const setVersion = (cur: string, kind: "dep" | "peer") => {
- if (cur === "catalog:" || cur.startsWith("workspace:")) return cur
- if (snapshot) return ver
- if (kind === "peer") return `>=${ver}`
- if (cur.startsWith(">=")) return `>=${ver}`
- if (cur.startsWith("^")) return `^${ver}`
- if (cur.startsWith("~")) return `~${ver}`
- return ver
- }
- const editDeps = (obj: unknown, kind: "dep" | "peer") => {
- if (!obj || typeof obj !== "object") return false
- const map = obj as Record<string, unknown>
- return keys
- .map((key) => {
- const cur = map[key]
- if (typeof cur !== "string") return false
- const next = setVersion(cur, kind)
- if (next === cur) return false
- map[key] = next
- return true
- })
- .some(Boolean)
- }
- const editCatalog = (obj: unknown) => {
- if (!obj || typeof obj !== "object") return false
- const map = obj as Record<string, unknown>
- return keys
- .map((key) => {
- const cur = map[key]
- if (typeof cur !== "string" || cur === ver) return false
- map[key] = ver
- return true
- })
- .some(Boolean)
- }
- const editOverrides = (obj: unknown) => {
- if (!obj || typeof obj !== "object") return false
- const map = obj as Record<string, unknown>
- return keys
- .map((key) => {
- const cur = map[key]
- if (typeof cur !== "string") return false
- const next = snapshot ? ver : "catalog:"
- if (next === cur) return false
- map[key] = next
- return true
- })
- .some(Boolean)
- }
- const out = (
- await Promise.all(
- files.map(async (rel) => {
- const file = path.join(root, rel)
- const txt = await Bun.file(file).text()
- const json = JSON.parse(txt)
- const hit = [
- editCatalog(json.workspaces?.catalog),
- editOverrides(json.overrides),
- editDeps(json.dependencies, "dep"),
- editDeps(json.devDependencies, "dep"),
- editDeps(json.peerDependencies, "peer"),
- ].some(Boolean)
- if (!hit) return null
- await Bun.write(file, `${JSON.stringify(json, null, 2)}\n`)
- return rel
- }),
- )
- ).filter((item): item is string => item !== null)
- if (out.length === 0) {
- console.log(`No opentui manifest updates needed for ${ver}`)
- }
- if (out.length > 0) {
- console.log(`Updated opentui${snapshot ? " snapshot" : ""} to ${ver} in:`)
- for (const file of out) {
- console.log(`- ${file}`)
- }
- }
- console.log("Running bun install to update bun.lock...")
- const install = Bun.spawn([process.execPath, "install"], {
- cwd: root,
- stdout: "inherit",
- stderr: "inherit",
- })
- const installCode = await install.exited
- if (installCode !== 0) process.exit(installCode)
- const fixed = await fixKnownLockfileIssues()
- if (fixed.length > 0) {
- console.log("Removed stale opentui-spinner peer lockfile entries:")
- for (const item of fixed) {
- console.log(`- ${item}`)
- }
- }
- const stale = await findStaleLockfileEntries()
- if (stale.length > 0) {
- console.error(`bun.lock still contains stale opentui versions after upgrading to ${ver}:`)
- for (const item of stale) {
- console.error(`- ${item.entry}: ${item.pkg}@${item.version}`)
- }
- process.exit(1)
- }
- console.log("bun.lock opentui versions are consistent")
- async function fixKnownLockfileIssues() {
- const txt = await Bun.file(lockfile).text()
- const stale = findStaleLockfileEntriesInText(txt)
- if (stale.length === 0) return []
- if (stale.some((item) => !item.entry.startsWith("opentui-spinner/@opentui/"))) return []
- const lines = txt.split("\n")
- const spinnerEntry = /^ "(opentui-spinner\/@opentui\/[^"]+)": \[.*\],\r?$/
- const removed = lines
- .map((line) => line.match(spinnerEntry)?.[1])
- .filter((item): item is string => item !== undefined)
- if (removed.length === 0) return []
- // Bun separates package records with a blank line, so remove each record's separator with it.
- await Bun.write(
- lockfile,
- lines
- .filter(
- (line, index) => !spinnerEntry.test(line) && !(line.trim() === "" && spinnerEntry.test(lines[index - 1] ?? "")),
- )
- .join("\n"),
- )
- return removed
- }
- async function findStaleLockfileEntries() {
- return findStaleLockfileEntriesInText(await Bun.file(lockfile).text())
- }
- function findStaleLockfileEntriesInText(txt: string) {
- return Array.from(txt.matchAll(/^ "([^"]+)": \["(@opentui\/(?:core(?:-[^@"]+)?|keymap|solid))@([^"]+)"/gm))
- .map((match) => ({
- entry: match[1]!,
- pkg: match[2]!,
- version: match[3]!,
- }))
- .filter((item) => item.version !== ver)
- }
|