mirror of
https://gitee.com/zhangsken/opencode.git
synced 2026-10-16 10:30:01 +08:00
fix(tui): retry installed plugin dependencies (#49187)
This commit is contained in:
@@ -4,6 +4,7 @@ import path from "node:path"
|
||||
import { fileURLToPath, pathToFileURL } from "node:url"
|
||||
import { Host } from "./host.js"
|
||||
import { localSource } from "./source.js"
|
||||
import { missingPackageTarget } from "./source.package.js"
|
||||
|
||||
let generation = Date.now()
|
||||
|
||||
@@ -37,7 +38,15 @@ export async function prepareSource(entrypoint: string, track: (file: string, di
|
||||
item.path.startsWith("./") || item.path.startsWith("../")
|
||||
? new URL(item.path, pathToFileURL(file))
|
||||
: localSource(item.path, path.dirname(file))
|
||||
if (!local) continue
|
||||
if (!local) {
|
||||
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
|
||||
|
||||
@@ -2,6 +2,7 @@ import { registerHooks } from "node:module"
|
||||
import path from "node:path"
|
||||
import { fileURLToPath } from "node:url"
|
||||
import { localSource } from "./source.js"
|
||||
import { missingPackageTarget } from "./source.package.js"
|
||||
import { Host } from "./host.js"
|
||||
|
||||
let generation = Date.now()
|
||||
@@ -21,7 +22,15 @@ export async function prepareSource(entrypoint: string, track: (file: string, di
|
||||
specifier.startsWith("./") || specifier.startsWith("../")
|
||||
? new URL(specifier, context.parentURL)
|
||||
: localSource(specifier, path.dirname(fileURLToPath(context.parentURL)))
|
||||
if (!local) return nextResolve(specifier, context)
|
||||
if (!local) {
|
||||
try {
|
||||
return nextResolve(specifier, context)
|
||||
} catch (error) {
|
||||
const target = missingPackageTarget(specifier, fileURLToPath(context.parentURL))
|
||||
if (target) track(target, true)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
if (fileURLToPath(local).split(path.sep).includes("node_modules")) return nextResolve(specifier, context)
|
||||
const resolved = (() => {
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import { existsSync } from "node:fs"
|
||||
import path from "node:path"
|
||||
|
||||
export function missingPackageTarget(specifier: string, importer: string) {
|
||||
if (specifier.startsWith("#")) return undefined
|
||||
const parts = specifier.split("/")
|
||||
const name = specifier.startsWith("@") ? parts.slice(0, 2).join("/") : parts[0]
|
||||
const start = path.dirname(importer)
|
||||
for (let directory = start; ; directory = path.dirname(directory)) {
|
||||
if (existsSync(path.join(directory, "package.json"))) return path.join(directory, "node_modules", name)
|
||||
if (path.dirname(directory) === directory) return path.join(start, "node_modules", name)
|
||||
}
|
||||
}
|
||||
@@ -89,6 +89,30 @@ test("renamed exports, failed loads, and new dependencies recover without cached
|
||||
expect((await sources.read(entry.href)).module).toMatchObject({ default: 3 })
|
||||
})
|
||||
|
||||
test("a missing package dependency reloads when it is installed", async () => {
|
||||
let changes = 0
|
||||
const watcher = createSourceWatcher(() => {
|
||||
changes++
|
||||
})
|
||||
using _watcher = { [Symbol.dispose]: watcher.dispose }
|
||||
await using sources = await fixture(watcher.wait)
|
||||
const entry = new URL("tui.ts", sources.url)
|
||||
await Bun.write(entry, 'export { default } from "example"')
|
||||
await expect(sources.read(entry.href)).rejects.toThrow("example")
|
||||
|
||||
const count = changes
|
||||
await Bun.write(new URL("node_modules/example/package.json", sources.url), '{"type":"module","main":"index.js"}')
|
||||
await Bun.write(new URL("node_modules/example/index.js", sources.url), 'export default "installed"')
|
||||
const deadline = Date.now() + 3000
|
||||
while (Date.now() < deadline) {
|
||||
if (changes > count) break
|
||||
await Bun.sleep(10)
|
||||
}
|
||||
|
||||
expect(changes).toBeGreaterThan(count)
|
||||
expect((await sources.read(entry.href)).module).toMatchObject({ default: "installed" })
|
||||
})
|
||||
|
||||
test("shared runtime and ordinary package identities survive plugin generations", async () => {
|
||||
await using sources = await fixture()
|
||||
const entry = new URL("tui.ts", sources.url)
|
||||
@@ -223,6 +247,13 @@ test.each(["", "?mode=plugin", "?mode=plugin#section"])(
|
||||
assert.equal(new URL(updated.source).pathname, helper.pathname)
|
||||
assert.equal(new URL(updated.source).searchParams.get("mode"), suffix ? "plugin" : null)
|
||||
assert.equal(new URL(updated.source).hash, suffix.includes("#") ? "#section" : "")
|
||||
const missing = new URL("./missing.mjs", import.meta.url)
|
||||
await writeFile(missing, 'export { default } from "later"')
|
||||
await assert.rejects(sources.read(missing.href), /later/)
|
||||
await mkdir(new URL("./node_modules/later", import.meta.url), { recursive: true })
|
||||
await writeFile(new URL("./node_modules/later/package.json", import.meta.url), '{"type":"module","main":"index.js"}')
|
||||
await writeFile(new URL("./node_modules/later/index.js", import.meta.url), 'export default "installed"')
|
||||
assert.equal((await sources.read(missing.href)).module.default, "installed")
|
||||
console.log("node graph reload passed")
|
||||
} finally { sources.dispose() }
|
||||
`,
|
||||
|
||||
Reference in New Issue
Block a user