vite.node.config.ts 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. import path from "node:path"
  2. import { readFile } from "node:fs/promises"
  3. import { createRequire } from "node:module"
  4. import { defineConfig, type Plugin, type UserConfig } from "vite"
  5. import solid from "vite-plugin-solid"
  6. import { nodeExecArgv, nodeTarget, type NodeTarget, photonWasmAsset } from "./src/node/target"
  7. const dir = import.meta.dirname
  8. function rawTextPlugin(): Plugin {
  9. return {
  10. name: "opencode:raw-text",
  11. async load(id) {
  12. if (!id.endsWith(".md")) return
  13. return `export default ${JSON.stringify(await readFile(id, "utf8"))}`
  14. },
  15. }
  16. }
  17. function runtimeRequirePlugin(): Plugin {
  18. return {
  19. name: "opencode:runtime-require",
  20. enforce: "pre",
  21. transform(code, id) {
  22. if (!id.endsWith("turndown/lib/turndown.es.js")) return
  23. const transformed = code.replace(" var domino = require('@mixmark-io/domino');", "")
  24. if (transformed === code) this.error("Failed to rewrite Turndown's Domino require")
  25. return `import domino from "@mixmark-io/domino"\n${transformed}`
  26. },
  27. }
  28. }
  29. const resolve = {
  30. alias: [
  31. { find: /^solid-js\/store$/, replacement: "solid-js/store/dist/store.js" },
  32. { find: /^solid-js$/, replacement: "solid-js/dist/solid.js" },
  33. {
  34. find: /^ws$/,
  35. replacement: path.join(path.dirname(createRequire(import.meta.url).resolve("ws/package.json")), "wrapper.mjs"),
  36. },
  37. ],
  38. conditions: ["node"],
  39. }
  40. const output = (entryFileNames: string, banner?: string) => ({
  41. format: "esm" as const,
  42. entryFileNames,
  43. inlineDynamicImports: true,
  44. banner,
  45. })
  46. function nodePrelude(input: NodeBuildInput) {
  47. const nodePtySpawnHelper =
  48. input.target.platform === "darwin"
  49. ? `${input.target.nodePtyPackage}/prebuilds/darwin-${input.target.arch}/spawn-helper`
  50. : undefined
  51. const promiseModule = `const sdk = globalThis[Symbol.for("opencode.plugin.v2.promise")]
  52. if (!sdk) throw new Error("OpenCode Promise plugin SDK is unavailable")
  53. export const Agent = sdk.Agent
  54. export const Command = sdk.Command
  55. export const Connection = sdk.Connection
  56. export const Credential = sdk.Credential
  57. export const Integration = sdk.Integration
  58. export const Model = sdk.Model
  59. export const Plugin = sdk.Plugin
  60. export const Provider = sdk.Provider
  61. export const Reference = sdk.Reference
  62. export const Skill = sdk.Skill`
  63. const effectModule = promiseModule
  64. .replace("opencode.plugin.v2.promise", "opencode.plugin.v2.effect")
  65. .replace("Promise plugin", "Effect plugin")
  66. const promisePluginModule = `const sdk = globalThis[Symbol.for("opencode.plugin.v2.promise")]
  67. if (!sdk) throw new Error("OpenCode Promise plugin SDK is unavailable")
  68. export const define = sdk.Plugin.define`
  69. const effectPluginModule = promisePluginModule
  70. .replace("opencode.plugin.v2.promise", "opencode.plugin.v2.effect")
  71. .replace("Promise plugin", "Effect plugin")
  72. const effectToolModule = `const sdk = globalThis[Symbol.for("opencode.plugin.v2.effect")]
  73. if (!sdk) throw new Error("OpenCode Effect plugin SDK is unavailable")
  74. export const Tool = sdk.Tool
  75. export const Failure = sdk.Tool.Failure
  76. export const RegistrationError = sdk.Tool.RegistrationError
  77. export const make = sdk.Tool.make
  78. export const validateName = sdk.Tool.validateName
  79. export const registrationEntries = sdk.Tool.registrationEntries
  80. export const withPermission = sdk.Tool.withPermission
  81. export const permission = sdk.Tool.permission
  82. export const definition = sdk.Tool.definition
  83. export const settle = sdk.Tool.settle`
  84. return `#!/usr/bin/env -S node ${nodeExecArgv.join(" ")}
  85. import __cjs_mod__ from "node:module"
  86. import { chmodSync as __ocChmod, existsSync as __ocExists, lstatSync as __ocLstat, mkdirSync as __ocMkdir, renameSync as __ocRename, rmSync as __ocRm, writeFileSync as __ocWrite } from "node:fs"
  87. import { tmpdir as __ocTmpdir } from "node:os"
  88. import __ocPath from "node:path"
  89. import { getAssetKeys as __ocAssetKeys, getRawAsset as __ocRawAsset, isSea as __ocIsSea } from "node:sea"
  90. import { fileURLToPath as __ocFileURLToPath } from "node:url"
  91. const __filename = import.meta.filename
  92. const __dirname = import.meta.dirname
  93. const require = __cjs_mod__.createRequire(import.meta.url)
  94. const __ocPluginModules = ${JSON.stringify({
  95. "@opencode-ai/plugin/v2": "opencode:plugin-v2",
  96. "@opencode-ai/plugin/v2/plugin": "opencode:plugin-v2-plugin",
  97. "@opencode-ai/plugin/v2/effect": "opencode:plugin-v2-effect",
  98. "@opencode-ai/plugin/v2/effect/plugin": "opencode:plugin-v2-effect-plugin",
  99. "@opencode-ai/plugin/v2/effect/tool": "opencode:plugin-v2-effect-tool",
  100. })}
  101. const __ocPluginSources = ${JSON.stringify({
  102. "opencode:plugin-v2": promiseModule,
  103. "opencode:plugin-v2-plugin": promisePluginModule,
  104. "opencode:plugin-v2-effect": effectModule,
  105. "opencode:plugin-v2-effect-plugin": effectPluginModule,
  106. "opencode:plugin-v2-effect-tool": effectToolModule,
  107. })}
  108. __cjs_mod__.registerHooks({
  109. resolve(__ocSpecifier, __ocContext, __ocNextResolve) {
  110. const __ocUrl = __ocPluginModules[__ocSpecifier]
  111. return __ocUrl ? { url: __ocUrl, shortCircuit: true } : __ocNextResolve(__ocSpecifier, __ocContext)
  112. },
  113. load(__ocUrl, __ocContext, __ocNextLoad) {
  114. const __ocSource = __ocPluginSources[__ocUrl]
  115. return __ocSource
  116. ? { format: "module", source: __ocSource, shortCircuit: true }
  117. : __ocNextLoad(__ocUrl, __ocContext)
  118. },
  119. })
  120. const __ocUid = typeof process.getuid === "function" ? process.getuid() : undefined
  121. const __ocCacheRoot = __ocPath.join(__ocTmpdir(), \`opencode-node-\${__ocUid ?? "user"}\`)
  122. if (__ocIsSea()) {
  123. try {
  124. __ocMkdir(__ocCacheRoot, { mode: 0o700 })
  125. } catch (__ocError) {
  126. if (!__ocExists(__ocCacheRoot)) throw __ocError
  127. }
  128. const __ocCacheInfo = __ocLstat(__ocCacheRoot)
  129. if (!__ocCacheInfo.isDirectory() || __ocCacheInfo.isSymbolicLink()) throw new Error("Unsafe Node asset cache path")
  130. if (__ocUid !== undefined && __ocCacheInfo.uid !== __ocUid) throw new Error("Node asset cache is owned by another user")
  131. if (__ocUid !== undefined) __ocChmod(__ocCacheRoot, 0o700)
  132. }
  133. const __ocAssetRoot = __ocIsSea()
  134. ? __ocPath.join(__ocCacheRoot, ${JSON.stringify(`${input.assetHash}-${input.target.platform}-${input.target.arch}`)})
  135. : __ocFileURLToPath(new URL("./assets/", import.meta.url))
  136. if (__ocIsSea()) {
  137. for (const __ocKey of __ocAssetKeys()) {
  138. const __ocTarget = __ocPath.join(__ocAssetRoot, __ocKey)
  139. if (__ocExists(__ocTarget)) continue
  140. __ocMkdir(__ocPath.dirname(__ocTarget), { recursive: true })
  141. const __ocTemporary = \`${"${__ocTarget}"}.${"${process.pid}"}.${"${crypto.randomUUID()}"}.tmp\`
  142. __ocWrite(__ocTemporary, new Uint8Array(__ocRawAsset(__ocKey)))
  143. try {
  144. __ocRename(__ocTemporary, __ocTarget)
  145. } catch (__ocError) {
  146. __ocRm(__ocTemporary, { force: true })
  147. if (!__ocExists(__ocTarget)) throw __ocError
  148. }
  149. }
  150. const __ocPtySpawnHelper = ${JSON.stringify(nodePtySpawnHelper)}
  151. if (__ocPtySpawnHelper) __ocChmod(__ocPath.join(__ocAssetRoot, __ocPtySpawnHelper), 0o755)
  152. }
  153. process.env.OPENCODE_NODE_ASSETS_DIR = __ocAssetRoot
  154. process.env.OTUI_ASSET_ROOT = __ocAssetRoot
  155. process.env.OPENCODE_NODE_PTY_PATH = __ocPath.join(__ocAssetRoot, ${JSON.stringify(input.target.nodePtyEntryAsset)})
  156. process.env.OPENCODE_PARCEL_WATCHER_PATH = __ocPath.join(__ocAssetRoot, ${JSON.stringify(input.target.parcelWatcherAsset)})
  157. process.env.OPENCODE_PHOTON_WASM_PATH = __ocPath.join(__ocAssetRoot, ${JSON.stringify(photonWasmAsset)})
  158. globalThis.__OPENCODE_PHOTON_WASM_PATH = process.env.OPENCODE_PHOTON_WASM_PATH
  159. if (process.platform === "linux") process.env.OPENTUI_LIBC = "glibc"`
  160. }
  161. export type NodeBuildInput = {
  162. readonly version: string
  163. readonly channel: string
  164. readonly models: string
  165. readonly assetHash: string
  166. readonly target: NodeTarget
  167. }
  168. export function mainConfig(input: NodeBuildInput): UserConfig {
  169. return defineConfig({
  170. root: dir,
  171. plugins: [
  172. rawTextPlugin(),
  173. runtimeRequirePlugin(),
  174. solid({
  175. solid: {
  176. generate: "universal",
  177. moduleName: "@opentui/solid",
  178. },
  179. }),
  180. ],
  181. resolve,
  182. esbuild: { jsx: "automatic" },
  183. define: {
  184. OPENCODE_VERSION: JSON.stringify(input.version),
  185. OPENCODE_CLI_NAME: JSON.stringify("opencode2-node"),
  186. OPENCODE_MODELS_DEV: input.models,
  187. OPENCODE_CHANNEL: JSON.stringify(input.channel),
  188. OPENCODE_LIBC: input.target.platform === "linux" ? JSON.stringify("glibc") : "undefined",
  189. FFF_LIBC: input.target.platform === "linux" ? JSON.stringify("gnu") : "undefined",
  190. },
  191. ssr: { noExternal: true },
  192. build: {
  193. ssr: "src/node/index.ts",
  194. target: "node26",
  195. outDir: "dist-node",
  196. emptyOutDir: false,
  197. minify: true,
  198. rollupOptions: {
  199. external: [/^@opencode-ai\/simulation(?:\/|$)/],
  200. output: output("opencode.mjs", nodePrelude(input)),
  201. },
  202. },
  203. })
  204. }
  205. export default mainConfig({
  206. version: process.env.OPENCODE_VERSION ?? "local",
  207. channel: process.env.OPENCODE_CHANNEL ?? "local",
  208. models: "undefined",
  209. assetHash: "local",
  210. target: nodeTarget(process.platform, process.arch),
  211. })