index.ts 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. import path from "path"
  2. import { $ } from "bun"
  3. import { z } from "zod"
  4. import { NamedError } from "../util/error"
  5. import { Bus } from "../bus"
  6. import { Log } from "../util/log"
  7. declare global {
  8. const OPENCODE_VERSION: string
  9. }
  10. export namespace Installation {
  11. const log = Log.create({ service: "installation" })
  12. export type Method = Awaited<ReturnType<typeof method>>
  13. export const Event = {
  14. Updated: Bus.event(
  15. "installation.updated",
  16. z.object({
  17. version: z.string(),
  18. }),
  19. ),
  20. }
  21. export const Info = z
  22. .object({
  23. version: z.string(),
  24. latest: z.string(),
  25. })
  26. .openapi({
  27. ref: "InstallationInfo",
  28. })
  29. export type Info = z.infer<typeof Info>
  30. export async function info() {
  31. return {
  32. version: VERSION,
  33. latest: await latest(),
  34. }
  35. }
  36. export function isSnapshot() {
  37. return VERSION.startsWith("0.0.0")
  38. }
  39. export function isDev() {
  40. return VERSION === "dev"
  41. }
  42. export async function method() {
  43. if (process.execPath.includes(path.join(".opencode", "bin"))) return "curl"
  44. const exec = process.execPath.toLowerCase()
  45. const checks = [
  46. {
  47. name: "npm" as const,
  48. command: () => $`npm list -g --depth=0`.throws(false).text(),
  49. },
  50. {
  51. name: "yarn" as const,
  52. command: () => $`yarn global list`.throws(false).text(),
  53. },
  54. {
  55. name: "pnpm" as const,
  56. command: () => $`pnpm list -g --depth=0`.throws(false).text(),
  57. },
  58. {
  59. name: "bun" as const,
  60. command: () => $`bun pm ls -g`.throws(false).text(),
  61. },
  62. {
  63. name: "brew" as const,
  64. command: () => $`brew list --formula opencode-ai`.throws(false).text(),
  65. },
  66. ]
  67. checks.sort((a, b) => {
  68. const aMatches = exec.includes(a.name)
  69. const bMatches = exec.includes(b.name)
  70. if (aMatches && !bMatches) return -1
  71. if (!aMatches && bMatches) return 1
  72. return 0
  73. })
  74. for (const check of checks) {
  75. const output = await check.command()
  76. if (output.includes("opencode-ai")) {
  77. return check.name
  78. }
  79. }
  80. return "unknown"
  81. }
  82. export const UpgradeFailedError = NamedError.create(
  83. "UpgradeFailedError",
  84. z.object({
  85. stderr: z.string(),
  86. }),
  87. )
  88. export async function upgrade(method: Method, target: string) {
  89. const cmd = (() => {
  90. switch (method) {
  91. case "curl":
  92. return $`curl -fsSL https://opencode.ai/install | bash`.env({
  93. ...process.env,
  94. VERSION: target,
  95. })
  96. case "npm":
  97. return $`npm install -g opencode-ai@${target}`
  98. case "pnpm":
  99. return $`pnpm install -g opencode-ai@${target}`
  100. case "bun":
  101. return $`bun install -g opencode-ai@${target}`
  102. case "brew":
  103. return $`brew install sst/tap/opencode`.env({
  104. HOMEBREW_NO_AUTO_UPDATE: "1",
  105. })
  106. default:
  107. throw new Error(`Unknown method: ${method}`)
  108. }
  109. })()
  110. const result = await cmd.quiet().throws(false)
  111. log.info("upgraded", {
  112. method,
  113. target,
  114. stdout: result.stdout.toString(),
  115. stderr: result.stderr.toString(),
  116. })
  117. if (result.exitCode !== 0)
  118. throw new UpgradeFailedError({
  119. stderr: result.stderr.toString("utf8"),
  120. })
  121. }
  122. export const VERSION =
  123. typeof OPENCODE_VERSION === "string" ? OPENCODE_VERSION : "dev"
  124. export async function latest() {
  125. return fetch("https://api.github.com/repos/sst/opencode/releases/latest")
  126. .then((res) => res.json())
  127. .then((data) => data.tag_name.slice(1) as string)
  128. }
  129. }