finalize-latest-yml.ts 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  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: merge arm64 + x64 into single file
  68. const winX64 = await read("latest-yml-x86_64-pc-windows-msvc", "latest.yml")
  69. const winArm64 = await read("latest-yml-aarch64-pc-windows-msvc", "latest.yml")
  70. if (winX64 || winArm64) {
  71. const base = winArm64 ?? winX64!
  72. output["latest.yml"] = serialize({
  73. version: base.version,
  74. files: [...(winArm64?.files ?? []), ...(winX64?.files ?? [])],
  75. releaseDate: base.releaseDate,
  76. })
  77. }
  78. // Linux x64: pass through
  79. const linuxX64 = await read("latest-yml-x86_64-unknown-linux-gnu", "latest-linux.yml")
  80. if (linuxX64) output["latest-linux.yml"] = serialize(linuxX64)
  81. // Linux arm64: pass through
  82. const linuxArm64 = await read("latest-yml-aarch64-unknown-linux-gnu", "latest-linux-arm64.yml")
  83. if (linuxArm64) output["latest-linux-arm64.yml"] = serialize(linuxArm64)
  84. // macOS: merge arm64 + x64 into single file
  85. const macX64 = await read("latest-yml-x86_64-apple-darwin", "latest-mac.yml")
  86. const macArm64 = await read("latest-yml-aarch64-apple-darwin", "latest-mac.yml")
  87. if (macX64 || macArm64) {
  88. const base = macArm64 ?? macX64!
  89. output["latest-mac.yml"] = serialize({
  90. version: base.version,
  91. files: [...(macArm64?.files ?? []), ...(macX64?.files ?? [])],
  92. releaseDate: base.releaseDate,
  93. })
  94. }
  95. // Upload to release
  96. const tag = `v${version}`
  97. const tmp = process.env.RUNNER_TEMP ?? "/tmp"
  98. for (const [filename, content] of Object.entries(output)) {
  99. const filepath = path.join(tmp, filename)
  100. await Bun.write(filepath, content)
  101. await $`gh release upload ${tag} ${filepath} --clobber --repo ${repo}`
  102. console.log(`uploaded ${filename}`)
  103. }
  104. console.log("finalized latest yml files")