File size: 5,750 Bytes
251a574 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 | #!/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 removed = txt
.split("\n")
.map((line) => line.match(/^ "(opentui-spinner\/@opentui\/[^\"]+)": /)?.[1])
.filter((item): item is string => item !== undefined)
if (removed.length === 0) return []
await Bun.write(
lockfile,
txt
.split("\n")
.filter((line) => !line.match(/^ "opentui-spinner\/@opentui\//))
.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)
}
|