index.ts 2.7 KB

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