build.ts 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. #!/usr/bin/env bun
  2. import { fileURLToPath } from "url"
  3. const dir = fileURLToPath(new URL("..", import.meta.url))
  4. process.chdir(dir)
  5. import { $ } from "bun"
  6. import path from "path"
  7. import { createClient } from "@hey-api/openapi-ts"
  8. const opencode = path.resolve(dir, "../../opencode")
  9. await $`bun dev generate > ${dir}/openapi.json`.cwd(opencode)
  10. const document = (await Bun.file("./openapi.json").json()) as {
  11. components?: { schemas?: Record<string, unknown> }
  12. [key: string]: unknown
  13. }
  14. const schemas = document.components?.schemas
  15. if (schemas) {
  16. const reachable = new Set<string>()
  17. const visit = (value: unknown) => {
  18. if (Array.isArray(value)) {
  19. value.forEach(visit)
  20. return
  21. }
  22. if (typeof value !== "object" || value === null) return
  23. for (const [key, child] of Object.entries(value)) {
  24. if (key === "$ref" && typeof child === "string" && child.startsWith("#/components/schemas/")) {
  25. const name = child.slice("#/components/schemas/".length)
  26. if (reachable.has(name)) continue
  27. reachable.add(name)
  28. visit(schemas[name])
  29. } else {
  30. visit(child)
  31. }
  32. }
  33. }
  34. visit({ ...document, components: { ...document.components, schemas: undefined } })
  35. for (const name of Object.keys(schemas)) {
  36. if (/^SessionNext\w+1$/.test(name) && !reachable.has(name)) delete schemas[name]
  37. }
  38. await Bun.write("./openapi.json", JSON.stringify(document))
  39. }
  40. await createClient({
  41. input: "./openapi.json",
  42. output: {
  43. path: "./src/v2/gen",
  44. tsConfigPath: path.join(dir, "tsconfig.json"),
  45. clean: true,
  46. },
  47. plugins: [
  48. {
  49. name: "@hey-api/typescript",
  50. exportFromIndex: false,
  51. },
  52. {
  53. name: "@hey-api/sdk",
  54. instance: "OpencodeClient",
  55. exportFromIndex: false,
  56. auth: false,
  57. paramsStructure: "flat",
  58. },
  59. {
  60. name: "@hey-api/client-fetch",
  61. exportFromIndex: false,
  62. baseUrl: "http://localhost:4096",
  63. },
  64. ],
  65. })
  66. const generatedTypes = await Bun.file("./src/v2/gen/types.gen.ts").text()
  67. if (/export type SessionNext\w+1 =/.test(generatedTypes)) {
  68. throw new Error("Session history generated duplicate Session event variants")
  69. }
  70. const historyTypesPatched = generatedTypes.replace(
  71. /(export type V2SessionHistoryData = \{[\s\S]*?query\?: \{\s*limit\?: )string([;,]\s*after\?: )string/,
  72. "$1number$2number",
  73. )
  74. if (historyTypesPatched === generatedTypes) {
  75. throw new Error("Session history numeric query patch did not apply")
  76. }
  77. await Bun.write("./src/v2/gen/types.gen.ts", historyTypesPatched)
  78. const generatedSdk = await Bun.file("./src/v2/gen/sdk.gen.ts").text()
  79. const historySdkPatched = generatedSdk.replace(
  80. /(Get session history[\s\S]*?parameters: \{\s*sessionID: string[;,]\s*limit\?: )string([;,]\s*after\?: )string/,
  81. "$1number$2number",
  82. )
  83. if (historySdkPatched === generatedSdk) {
  84. throw new Error("Session history numeric SDK patch did not apply")
  85. }
  86. await Bun.write("./src/v2/gen/sdk.gen.ts", historySdkPatched)
  87. // Patch a @hey-api/openapi-ts codegen bug: SseFn incorrectly passes the
  88. // endpoint's TError into the second generic of ServerSentEventsResult, which
  89. // is the AsyncGenerator's TReturn slot. Iterator return values have nothing
  90. // to do with HTTP errors, and any consumer that calls `.return()` or returns
  91. // from a mock generator gets type-checked against the wrong shape. Drop the
  92. // arg so TReturn defaults to void.
  93. const sseTypesPath = "./src/v2/gen/client/types.gen.ts"
  94. const sseTypesFile = Bun.file(sseTypesPath)
  95. const sseTypesSource = await sseTypesFile.text()
  96. const sseTypesPatched = sseTypesSource.replace(
  97. "=> Promise<ServerSentEventsResult<TData, TError>>",
  98. "=> Promise<ServerSentEventsResult<TData>>",
  99. )
  100. if (sseTypesPatched === sseTypesSource) {
  101. throw new Error(`SseFn patch did not apply; @hey-api/openapi-ts output may have changed (${sseTypesPath})`)
  102. }
  103. await Bun.write(sseTypesPath, sseTypesPatched)
  104. await $`bun prettier --write src/gen`
  105. await $`bun prettier --write src/v2`
  106. await $`rm -rf dist`
  107. await $`bun tsc`
  108. await $`rm openapi.json`