finalize-latest-yml.ts 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. #!/usr/bin/env bun
  2. import { $ } from "bun"
  3. import path from "path"
  4. const dir = process.env.LATEST_YML_DIR!
  5. if (!dir) throw new Error("LATEST_YML_DIR is required")
  6. const repo = process.env.GH_REPO
  7. if (!repo) throw new Error("GH_REPO is required")
  8. const version = process.env.OPENCODE_VERSION
  9. if (!version) throw new Error("OPENCODE_VERSION is required")
  10. type FileEntry = {
  11. url: string
  12. sha512: string
  13. size: number
  14. blockMapSize?: number
  15. }
  16. type LatestYml = {
  17. version: string
  18. files: FileEntry[]
  19. releaseDate: string
  20. }
  21. function parse(content: string): LatestYml {
  22. const lines = content.split("\n")
  23. let version = ""
  24. let releaseDate = ""
  25. const files: FileEntry[] = []
  26. let current: Partial<FileEntry> | undefined
  27. const flush = () => {
  28. if (current?.url && current.sha512 && current.size) files.push(current as FileEntry)
  29. current = undefined
  30. }
  31. for (const line of lines) {
  32. const indented = line.startsWith(" ") || line.startsWith(" -")
  33. if (line.startsWith("version:")) version = line.slice("version:".length).trim()
  34. else if (line.startsWith("releaseDate:"))
  35. releaseDate = line.slice("releaseDate:".length).trim().replace(/^'|'$/g, "")
  36. else if (line.trim().startsWith("- url:")) {
  37. flush()
  38. current = { url: line.trim().slice("- url:".length).trim() }
  39. } else if (indented && current && line.trim().startsWith("sha512:"))
  40. current.sha512 = line.trim().slice("sha512:".length).trim()
  41. else if (indented && current && line.trim().startsWith("size:"))
  42. current.size = Number(line.trim().slice("size:".length).trim())
  43. else if (indented && current && line.trim().startsWith("blockMapSize:"))
  44. current.blockMapSize = Number(line.trim().slice("blockMapSize:".length).trim())
  45. else if (!indented && current) flush()
  46. }
  47. flush()
  48. return { version, files, releaseDate }
  49. }
  50. function serialize(data: LatestYml) {
  51. const lines = [`version: ${data.version}`, "files:"]
  52. for (const file of data.files) {
  53. lines.push(` - url: ${file.url}`)
  54. lines.push(` sha512: ${file.sha512}`)
  55. lines.push(` size: ${file.size}`)
  56. if (file.blockMapSize) lines.push(` blockMapSize: ${file.blockMapSize}`)
  57. }
  58. lines.push(`releaseDate: '${data.releaseDate}'`)
  59. return lines.join("\n") + "\n"
  60. }
  61. async function read(subdir: string, filename: string): Promise<LatestYml | undefined> {
  62. const file = Bun.file(path.join(dir, subdir, filename))
  63. if (!(await file.exists())) return undefined
  64. return parse(await file.text())
  65. }
  66. const output: Record<string, string> = {}
  67. // Windows: single arch, pass through
  68. const win = await read("latest-yml-x86_64-pc-windows-msvc", "latest.yml")
  69. if (win) output["latest.yml"] = serialize(win)
  70. // Linux x64: pass through
  71. const linuxX64 = await read("latest-yml-x86_64-unknown-linux-gnu", "latest-linux.yml")
  72. if (linuxX64) output["latest-linux.yml"] = serialize(linuxX64)
  73. // Linux arm64: pass through
  74. const linuxArm64 = await read("latest-yml-aarch64-unknown-linux-gnu", "latest-linux-arm64.yml")
  75. if (linuxArm64) output["latest-linux-arm64.yml"] = serialize(linuxArm64)
  76. // macOS: merge arm64 + x64 into single file
  77. const macX64 = await read("latest-yml-x86_64-apple-darwin", "latest-mac.yml")
  78. const macArm64 = await read("latest-yml-aarch64-apple-darwin", "latest-mac.yml")
  79. if (macX64 || macArm64) {
  80. const base = macArm64 ?? macX64!
  81. output["latest-mac.yml"] = serialize({
  82. version: base.version,
  83. files: [...(macArm64?.files ?? []), ...(macX64?.files ?? [])],
  84. releaseDate: base.releaseDate,
  85. })
  86. }
  87. // Upload to release
  88. const tag = `v${version}`
  89. const tmp = process.env.RUNNER_TEMP ?? "/tmp"
  90. for (const [filename, content] of Object.entries(output)) {
  91. const filepath = path.join(tmp, filename)
  92. await Bun.write(filepath, content)
  93. await $`gh release upload ${tag} ${filepath} --clobber --repo ${repo}`
  94. console.log(`uploaded ${filename}`)
  95. }
  96. console.log("finalized latest yml files")