| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268 |
- import path from "node:path"
- import { readFile } from "node:fs/promises"
- import { createRequire } from "node:module"
- import { defineConfig, type Plugin, type UserConfig } from "vite"
- import solid from "vite-plugin-solid"
- import {
- nodeExecArgv,
- nodeTarget,
- type NodeTarget,
- photonWasmAsset,
- shellParserWasmAssets,
- } from "./src/node/target"
- const dir = import.meta.dirname
- function rawTextPlugin(): Plugin {
- return {
- name: "opencode:raw-text",
- async load(id) {
- if (!id.endsWith(".md")) return
- return `export default ${JSON.stringify(await readFile(id, "utf8"))}`
- },
- }
- }
- function runtimeRequirePlugin(): Plugin {
- return {
- name: "opencode:runtime-require",
- enforce: "pre",
- transform(code, id) {
- if (!id.endsWith("turndown/lib/turndown.es.js")) return
- const transformed = code.replace(" var domino = require('@mixmark-io/domino');", "")
- if (transformed === code) this.error("Failed to rewrite Turndown's Domino require")
- return `import domino from "@mixmark-io/domino"\n${transformed}`
- },
- }
- }
- function fffNodePlugin(): Plugin {
- return {
- name: "opencode:fff-node",
- enforce: "pre",
- transform(code, id) {
- const normalized = id.replaceAll("\\", "/")
- if (normalized.endsWith("/ffi-rs/index.js")) {
- const start = code.indexOf("if (!nativeBinding) {")
- if (start === -1) this.error("Failed to rewrite ffi-rs native binding loader")
- return `const unavailable = () => { throw new Error("ffi-rs native binding unavailable") }
- const nativeBinding = globalThis.__OPENCODE_FFF_FFI ?? {
- DataType: new Proxy({}, { get: (target, key) => target[key] ?? key }),
- PointerType: {},
- FFITypeTag: {},
- open: unavailable,
- close: unavailable,
- load: unavailable,
- isNullPointer: unavailable,
- createPointer: unavailable,
- restorePointer: unavailable,
- unwrapPointer: unavailable,
- wrapPointer: unavailable,
- freePointer: unavailable,
- }
- const loadError = undefined
- ${code.slice(start)}`
- }
- if (!normalized.endsWith("/fff-node/dist/src/binary.js")) return
- const transformed = code.replace(
- "export function findBinary() {",
- "export function findBinary() { if (process.env.FFF_BINARY_PATH) return process.env.FFF_BINARY_PATH;",
- )
- if (transformed === code) this.error("Failed to rewrite FFF binary loader")
- return transformed
- },
- }
- }
- const resolve = {
- alias: [
- { find: /^solid-js\/store$/, replacement: "solid-js/store/dist/store.js" },
- { find: /^solid-js$/, replacement: "solid-js/dist/solid.js" },
- {
- find: /^ws$/,
- replacement: path.join(path.dirname(createRequire(import.meta.url).resolve("ws/package.json")), "wrapper.mjs"),
- },
- ],
- conditions: ["node"],
- }
- const output = (entryFileNames: string, banner?: string) => ({
- format: "esm" as const,
- entryFileNames,
- inlineDynamicImports: true,
- banner,
- })
- function nodePrelude(input: NodeBuildInput) {
- const nodePtySpawnHelper =
- input.target.platform === "darwin"
- ? `${input.target.nodePtyPackage}/prebuilds/darwin-${input.target.arch}/spawn-helper`
- : undefined
- const promiseModule = `const sdk = globalThis[Symbol.for("opencode.plugin.v2.promise")]
- if (!sdk) throw new Error("OpenCode Promise plugin SDK is unavailable")
- export const Agent = sdk.Agent
- export const Command = sdk.Command
- export const Connection = sdk.Connection
- export const Credential = sdk.Credential
- export const Integration = sdk.Integration
- export const Model = sdk.Model
- export const Plugin = sdk.Plugin
- export const Provider = sdk.Provider
- export const Reference = sdk.Reference
- export const Skill = sdk.Skill`
- const effectModule = promiseModule
- .replace("opencode.plugin.v2.promise", "opencode.plugin.v2.effect")
- .replace("Promise plugin", "Effect plugin")
- const promisePluginModule = `const sdk = globalThis[Symbol.for("opencode.plugin.v2.promise")]
- if (!sdk) throw new Error("OpenCode Promise plugin SDK is unavailable")
- export const define = sdk.Plugin.define`
- const effectPluginModule = promisePluginModule
- .replace("opencode.plugin.v2.promise", "opencode.plugin.v2.effect")
- .replace("Promise plugin", "Effect plugin")
- const promiseToolModule = `export {}`
- const effectToolModule = `const sdk = globalThis[Symbol.for("opencode.plugin.v2.effect")]
- if (!sdk) throw new Error("OpenCode Effect plugin SDK is unavailable")
- export const Error = sdk.Tool.Error
- `
- return `#!/usr/bin/env -S node ${nodeExecArgv.join(" ")}
- import __cjs_mod__ from "node:module"
- 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"
- import { tmpdir as __ocTmpdir } from "node:os"
- import __ocPath from "node:path"
- import { getAssetKeys as __ocAssetKeys, getRawAsset as __ocRawAsset, isSea as __ocIsSea } from "node:sea"
- import { fileURLToPath as __ocFileURLToPath } from "node:url"
- const __filename = import.meta.filename
- const __dirname = import.meta.dirname
- const require = __cjs_mod__.createRequire(import.meta.url)
- const __ocPluginModules = ${JSON.stringify({
- "@opencode-ai/plugin": "opencode:plugin-v2",
- "@opencode-ai/plugin/promise/plugin": "opencode:plugin-promise-plugin",
- "@opencode-ai/plugin/promise/tool": "opencode:plugin-promise-tool",
- "@opencode-ai/plugin/effect": "opencode:plugin-v2-effect",
- "@opencode-ai/plugin/effect/plugin": "opencode:plugin-v2-effect-plugin",
- "@opencode-ai/plugin/effect/tool": "opencode:plugin-v2-effect-tool",
- })}
- const __ocPluginSources = ${JSON.stringify({
- "opencode:plugin-v2": promiseModule,
- "opencode:plugin-promise-plugin": promisePluginModule,
- "opencode:plugin-promise-tool": promiseToolModule,
- "opencode:plugin-v2-effect": effectModule,
- "opencode:plugin-v2-effect-plugin": effectPluginModule,
- "opencode:plugin-v2-effect-tool": effectToolModule,
- })}
- __cjs_mod__.registerHooks({
- resolve(__ocSpecifier, __ocContext, __ocNextResolve) {
- const __ocUrl = __ocPluginModules[__ocSpecifier]
- return __ocUrl ? { url: __ocUrl, shortCircuit: true } : __ocNextResolve(__ocSpecifier, __ocContext)
- },
- load(__ocUrl, __ocContext, __ocNextLoad) {
- const __ocSource = __ocPluginSources[__ocUrl]
- return __ocSource
- ? { format: "module", source: __ocSource, shortCircuit: true }
- : __ocNextLoad(__ocUrl, __ocContext)
- },
- })
- const __ocUid = typeof process.getuid === "function" ? process.getuid() : undefined
- const __ocCacheRoot = __ocPath.join(__ocTmpdir(), \`opencode-node-\${__ocUid ?? "user"}\`)
- if (__ocIsSea()) {
- try {
- __ocMkdir(__ocCacheRoot, { mode: 0o700 })
- } catch (__ocError) {
- if (!__ocExists(__ocCacheRoot)) throw __ocError
- }
- const __ocCacheInfo = __ocLstat(__ocCacheRoot)
- if (!__ocCacheInfo.isDirectory() || __ocCacheInfo.isSymbolicLink()) throw new Error("Unsafe Node asset cache path")
- if (__ocUid !== undefined && __ocCacheInfo.uid !== __ocUid) throw new Error("Node asset cache is owned by another user")
- if (__ocUid !== undefined) __ocChmod(__ocCacheRoot, 0o700)
- }
- const __ocAssetRoot = __ocIsSea()
- ? __ocPath.join(__ocCacheRoot, ${JSON.stringify(`${input.assetHash}-${input.target.platform}-${input.target.arch}`)})
- : __ocFileURLToPath(new URL("./assets/", import.meta.url))
- if (__ocIsSea()) {
- for (const __ocKey of __ocAssetKeys()) {
- const __ocTarget = __ocPath.join(__ocAssetRoot, __ocKey)
- if (__ocExists(__ocTarget)) continue
- __ocMkdir(__ocPath.dirname(__ocTarget), { recursive: true })
- const __ocTemporary = \`${"${__ocTarget}"}.${"${process.pid}"}.${"${crypto.randomUUID()}"}.tmp\`
- __ocWrite(__ocTemporary, new Uint8Array(__ocRawAsset(__ocKey)))
- try {
- __ocRename(__ocTemporary, __ocTarget)
- } catch (__ocError) {
- __ocRm(__ocTemporary, { force: true })
- if (!__ocExists(__ocTarget)) throw __ocError
- }
- }
- const __ocPtySpawnHelper = ${JSON.stringify(nodePtySpawnHelper)}
- if (__ocPtySpawnHelper) __ocChmod(__ocPath.join(__ocAssetRoot, __ocPtySpawnHelper), 0o755)
- }
- process.env.OPENCODE_NODE_ASSETS_DIR = __ocAssetRoot
- process.env.OTUI_ASSET_ROOT = __ocAssetRoot
- process.env.OPENCODE_NODE_PTY_PATH = __ocPath.join(__ocAssetRoot, ${JSON.stringify(input.target.nodePtyEntryAsset)})
- process.env.OPENCODE_PARCEL_WATCHER_PATH = __ocPath.join(__ocAssetRoot, ${JSON.stringify(input.target.parcelWatcherAsset)})
- process.env.OPENCODE_PHOTON_WASM_PATH = __ocPath.join(__ocAssetRoot, ${JSON.stringify(photonWasmAsset)})
- process.env.OPENCODE_TREE_SITTER_WASM_PATH = __ocPath.join(__ocAssetRoot, ${JSON.stringify(shellParserWasmAssets.runtime)})
- process.env.OPENCODE_TREE_SITTER_BASH_WASM_PATH = __ocPath.join(__ocAssetRoot, ${JSON.stringify(shellParserWasmAssets.bash)})
- process.env.OPENCODE_TREE_SITTER_POWERSHELL_WASM_PATH = __ocPath.join(__ocAssetRoot, ${JSON.stringify(shellParserWasmAssets.powershell)})
- process.env.FFF_BINARY_PATH = __ocPath.join(__ocAssetRoot, ${JSON.stringify(input.target.fffAsset)})
- process.env.OPENCODE_FFF_FFI_PATH = __ocPath.join(__ocAssetRoot, ${JSON.stringify(input.target.fffFfiAsset)})
- try {
- globalThis.__OPENCODE_FFF_FFI = require(process.env.OPENCODE_FFF_FFI_PATH)
- } catch {}
- globalThis.__OPENCODE_PHOTON_WASM_PATH = process.env.OPENCODE_PHOTON_WASM_PATH
- if (process.platform === "linux") process.env.OPENTUI_LIBC = "glibc"`
- }
- export type NodeBuildInput = {
- readonly version: string
- readonly channel: string
- readonly models: string
- readonly assetHash: string
- readonly target: NodeTarget
- }
- export function mainConfig(input: NodeBuildInput): UserConfig {
- return defineConfig({
- root: dir,
- plugins: [
- rawTextPlugin(),
- runtimeRequirePlugin(),
- fffNodePlugin(),
- solid({
- solid: {
- generate: "universal",
- moduleName: "@opentui/solid",
- },
- }),
- ],
- resolve,
- esbuild: { jsx: "automatic" },
- define: {
- OPENCODE_VERSION: JSON.stringify(input.version),
- OPENCODE_CLI_NAME: JSON.stringify("opencode2-node"),
- OPENCODE_MODELS_DEV: input.models,
- OPENCODE_CHANNEL: JSON.stringify(input.channel),
- OPENCODE_LIBC: input.target.platform === "linux" ? JSON.stringify("glibc") : "undefined",
- FFF_LIBC: input.target.platform === "linux" ? JSON.stringify("gnu") : "undefined",
- },
- ssr: { noExternal: true },
- build: {
- ssr: "src/node/index.ts",
- target: "node26",
- outDir: "dist-node",
- emptyOutDir: false,
- minify: true,
- rollupOptions: {
- external: [/^@opencode-ai\/simulation(?:\/|$)/],
- output: output("opencode.mjs", nodePrelude(input)),
- },
- },
- })
- }
- export default mainConfig({
- version: process.env.OPENCODE_VERSION ?? "local",
- channel: process.env.OPENCODE_CHANNEL ?? "local",
- models: "undefined",
- assetHash: "local",
- target: nodeTarget(process.platform, process.arch),
- })
|