fix(core): skip dependency install in read-only config dirs (#12128)
This commit is contained in:
@@ -24,7 +24,7 @@ import { LSPServer } from "../lsp/server"
|
|||||||
import { BunProc } from "@/bun"
|
import { BunProc } from "@/bun"
|
||||||
import { Installation } from "@/installation"
|
import { Installation } from "@/installation"
|
||||||
import { ConfigMarkdown } from "./markdown"
|
import { ConfigMarkdown } from "./markdown"
|
||||||
import { existsSync } from "fs"
|
import { constants, existsSync } from "fs"
|
||||||
import { Bus } from "@/bus"
|
import { Bus } from "@/bus"
|
||||||
import { GlobalBus } from "@/bus/global"
|
import { GlobalBus } from "@/bus/global"
|
||||||
import { Event } from "../server/event"
|
import { Event } from "../server/event"
|
||||||
@@ -273,7 +273,24 @@ export namespace Config {
|
|||||||
).catch(() => {})
|
).catch(() => {})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function isWritable(dir: string) {
|
||||||
|
try {
|
||||||
|
await fs.access(dir, constants.W_OK)
|
||||||
|
return true
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function needsInstall(dir: string) {
|
async function needsInstall(dir: string) {
|
||||||
|
// Some config dirs may be read-only.
|
||||||
|
// Installing deps there will fail; skip installation in that case.
|
||||||
|
const writable = await isWritable(dir)
|
||||||
|
if (!writable) {
|
||||||
|
log.debug("config dir is not writable, skipping dependency install", { dir })
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
const nodeModules = path.join(dir, "node_modules")
|
const nodeModules = path.join(dir, "node_modules")
|
||||||
if (!existsSync(nodeModules)) return true
|
if (!existsSync(nodeModules)) return true
|
||||||
|
|
||||||
|
|||||||
@@ -566,6 +566,67 @@ test("gets config directories", async () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("does not try to install dependencies in read-only OPENCODE_CONFIG_DIR", async () => {
|
||||||
|
if (process.platform === "win32") return
|
||||||
|
|
||||||
|
await using tmp = await tmpdir<string>({
|
||||||
|
init: async (dir) => {
|
||||||
|
const ro = path.join(dir, "readonly")
|
||||||
|
await fs.mkdir(ro, { recursive: true })
|
||||||
|
await fs.chmod(ro, 0o555)
|
||||||
|
return ro
|
||||||
|
},
|
||||||
|
dispose: async (dir) => {
|
||||||
|
const ro = path.join(dir, "readonly")
|
||||||
|
await fs.chmod(ro, 0o755).catch(() => {})
|
||||||
|
return ro
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const prev = process.env.OPENCODE_CONFIG_DIR
|
||||||
|
process.env.OPENCODE_CONFIG_DIR = tmp.extra
|
||||||
|
|
||||||
|
try {
|
||||||
|
await Instance.provide({
|
||||||
|
directory: tmp.path,
|
||||||
|
fn: async () => {
|
||||||
|
await Config.get()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
if (prev === undefined) delete process.env.OPENCODE_CONFIG_DIR
|
||||||
|
else process.env.OPENCODE_CONFIG_DIR = prev
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test("installs dependencies in writable OPENCODE_CONFIG_DIR", async () => {
|
||||||
|
await using tmp = await tmpdir<string>({
|
||||||
|
init: async (dir) => {
|
||||||
|
const cfg = path.join(dir, "configdir")
|
||||||
|
await fs.mkdir(cfg, { recursive: true })
|
||||||
|
return cfg
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const prev = process.env.OPENCODE_CONFIG_DIR
|
||||||
|
process.env.OPENCODE_CONFIG_DIR = tmp.extra
|
||||||
|
|
||||||
|
try {
|
||||||
|
await Instance.provide({
|
||||||
|
directory: tmp.path,
|
||||||
|
fn: async () => {
|
||||||
|
await Config.get()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(await Bun.file(path.join(tmp.extra, "package.json")).exists()).toBe(true)
|
||||||
|
expect(await Bun.file(path.join(tmp.extra, ".gitignore")).exists()).toBe(true)
|
||||||
|
} finally {
|
||||||
|
if (prev === undefined) delete process.env.OPENCODE_CONFIG_DIR
|
||||||
|
else process.env.OPENCODE_CONFIG_DIR = prev
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
test("resolves scoped npm plugins in config", async () => {
|
test("resolves scoped npm plugins in config", async () => {
|
||||||
await using tmp = await tmpdir({
|
await using tmp = await tmpdir({
|
||||||
init: async (dir) => {
|
init: async (dir) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user