Files
opencode/packages/plugin/src/source.bun.ts
2026-10-07 16:57:24 -07:00

72 lines
2.7 KiB
TypeScript

import { createRequire } from "node:module"
import { readFileSync } from "node:fs"
import path from "node:path"
import { fileURLToPath, pathToFileURL } from "node:url"
import { Host } from "./host.js"
import { provides } from "./runtime.js"
import { localSource } from "./source.js"
import { missingPackageTarget } from "./source.package.js"
let generation = Date.now()
export async function prepareSource(entrypoint: string, track: (file: string, directory?: boolean) => void) {
const root = fileURLToPath(entrypoint)
const files = new Set<string>()
const visit = (file: string, search = "") => {
if (file !== root && file.split(path.sep).includes("node_modules")) return
if (search) delete require.cache[file + search]
if (files.has(file)) return
files.add(file)
// Bun exposes ESM here too. Delete known keys even when absent: rejected
// evaluations are not enumerable, but deletion still invalidates them.
delete require.cache[file]
track(file)
if (!/\.[cm]?[jt]sx?$/.test(file)) return
// Scan dependencies only; the normal runtime loader still owns compilation,
// package resolution, import attributes, and error reporting.
const imports = (() => {
try {
return new Bun.Transpiler({
loader: file.endsWith("tsx") ? "tsx" : file.endsWith("jsx") ? "jsx" : /\.[cm]?ts$/.test(file) ? "ts" : "js",
target: "bun",
}).scan(readFileSync(file, "utf8")).imports
} catch {
return []
}
})()
for (const item of imports) {
const local =
item.path.startsWith("./") || item.path.startsWith("../")
? new URL(item.path, pathToFileURL(file))
: localSource(item.path, path.dirname(file))
if (!local) {
if (provides(item.path)) continue
try {
Bun.resolveSync(item.path, path.dirname(file))
} catch {
const target = missingPackageTarget(item.path, file)
if (target) track(target, true)
}
continue
}
const requested = fileURLToPath(local)
// Resolving a workspace symlink can erase its node_modules boundary.
if (requested.split(path.sep).includes("node_modules")) continue
try {
visit(
item.kind === "require-call"
? createRequire(file).resolve(requested)
: Bun.resolveSync(requested, path.dirname(file)),
local.search,
)
} catch {
// A missing local dependency may appear on the next save. Leave its
// actual failure (or optional fallback) to the native loader.
track(path.dirname(requested), true)
}
}
}
visit(root)
return { version: String(++generation), load: () => Host.load(entrypoint) }
}