refactor: convert config path constants to getter functions for dynamic OPENCODE_CONFIG_DIR support
🤖 Generated with assistance of OhMyOpenCode
This commit is contained in:
@@ -9,7 +9,6 @@ import { loadAvailableModelsFromCache } from "./model-resolution-cache"
|
||||
import { getModelResolutionInfoWithOverrides } from "./model-resolution"
|
||||
import type { OmoConfig } from "./model-resolution-types"
|
||||
|
||||
const USER_CONFIG_DIR = getOpenCodeConfigDir({ binary: "opencode" })
|
||||
const PROJECT_CONFIG_DIR = join(process.cwd(), ".opencode")
|
||||
|
||||
interface ConfigValidationResult {
|
||||
@@ -24,7 +23,8 @@ function findConfigPath(): string | null {
|
||||
const projectConfig = detectPluginConfigFile(PROJECT_CONFIG_DIR)
|
||||
if (projectConfig.format !== "none") return projectConfig.path
|
||||
|
||||
const userConfig = detectPluginConfigFile(USER_CONFIG_DIR)
|
||||
const userConfigDir = getOpenCodeConfigDir({ binary: "opencode" })
|
||||
const userConfig = detectPluginConfigFile(userConfigDir)
|
||||
if (userConfig.format !== "none") return userConfig.path
|
||||
|
||||
return null
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { readFileSync } from "node:fs"
|
||||
import { join } from "node:path"
|
||||
import { detectPluginConfigFile, getOpenCodeConfigPaths, parseJsonc } from "../../../shared"
|
||||
import { detectPluginConfigFile, getOpenCodeConfigDir, parseJsonc } from "../../../shared"
|
||||
import type { OmoConfig } from "./model-resolution-types"
|
||||
|
||||
const USER_CONFIG_DIR = getOpenCodeConfigPaths({ binary: "opencode", version: null }).configDir
|
||||
const PROJECT_CONFIG_DIR = join(process.cwd(), ".opencode")
|
||||
|
||||
export function loadOmoConfig(): OmoConfig | null {
|
||||
@@ -17,7 +16,8 @@ export function loadOmoConfig(): OmoConfig | null {
|
||||
}
|
||||
}
|
||||
|
||||
const userDetected = detectPluginConfigFile(USER_CONFIG_DIR)
|
||||
const userConfigDir = getOpenCodeConfigDir({ binary: "opencode" })
|
||||
const userDetected = detectPluginConfigFile(userConfigDir)
|
||||
if (userDetected.format !== "none") {
|
||||
try {
|
||||
const content = readFileSync(userDetected.path, "utf-8")
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as fs from "node:fs"
|
||||
import * as path from "node:path"
|
||||
import { CACHE_DIR, PACKAGE_NAME, USER_CONFIG_DIR } from "./constants"
|
||||
import { CACHE_DIR, PACKAGE_NAME, getUserConfigDir } from "./constants"
|
||||
import { log } from "../../shared/logger"
|
||||
|
||||
interface BunLockfile {
|
||||
@@ -61,8 +61,9 @@ function removeFromBunLock(packageName: string): boolean {
|
||||
|
||||
export function invalidatePackage(packageName: string = PACKAGE_NAME): boolean {
|
||||
try {
|
||||
const userConfigDir = getUserConfigDir()
|
||||
const pkgDirs = [
|
||||
path.join(USER_CONFIG_DIR, "node_modules", packageName),
|
||||
path.join(userConfigDir, "node_modules", packageName),
|
||||
path.join(CACHE_DIR, "node_modules", packageName),
|
||||
]
|
||||
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
import * as os from "node:os"
|
||||
import * as path from "node:path"
|
||||
import {
|
||||
USER_CONFIG_DIR,
|
||||
USER_OPENCODE_CONFIG,
|
||||
USER_OPENCODE_CONFIG_JSONC,
|
||||
getUserConfigDir,
|
||||
getUserOpencodeConfig,
|
||||
getUserOpencodeConfigJsonc,
|
||||
getWindowsAppdataDir,
|
||||
} from "../constants"
|
||||
|
||||
export function getConfigPaths(directory: string): string[] {
|
||||
const userConfigDir = getUserConfigDir()
|
||||
const paths = [
|
||||
path.join(directory, ".opencode", "opencode.json"),
|
||||
path.join(directory, ".opencode", "opencode.jsonc"),
|
||||
USER_OPENCODE_CONFIG,
|
||||
USER_OPENCODE_CONFIG_JSONC,
|
||||
getUserOpencodeConfig(),
|
||||
getUserOpencodeConfigJsonc(),
|
||||
]
|
||||
|
||||
if (process.platform === "win32") {
|
||||
@@ -20,7 +21,7 @@ export function getConfigPaths(directory: string): string[] {
|
||||
const appdataDir = getWindowsAppdataDir()
|
||||
|
||||
if (appdataDir) {
|
||||
const alternateDir = USER_CONFIG_DIR === crossPlatformDir ? appdataDir : crossPlatformDir
|
||||
const alternateDir = userConfigDir === crossPlatformDir ? appdataDir : crossPlatformDir
|
||||
const alternateConfig = path.join(alternateDir, "opencode", "opencode.json")
|
||||
const alternateConfigJsonc = path.join(alternateDir, "opencode", "opencode.jsonc")
|
||||
|
||||
|
||||
@@ -1,15 +1,51 @@
|
||||
import { afterEach, beforeEach, describe, expect, test } from "bun:test"
|
||||
import { spawnSync } from "node:child_process"
|
||||
import * as fs from "node:fs"
|
||||
import * as os from "node:os"
|
||||
import * as path from "node:path"
|
||||
import { PACKAGE_NAME } from "../constants"
|
||||
import { findPluginEntry } from "./plugin-entry"
|
||||
|
||||
const PACKAGE_NAME = "oh-my-openagent"
|
||||
|
||||
type PluginEntryResult = {
|
||||
entry: string
|
||||
isPinned: boolean
|
||||
pinnedVersion: string | null
|
||||
configPath: string
|
||||
} | null
|
||||
|
||||
function runFindPluginEntry(
|
||||
directory: string,
|
||||
envOverrides: Record<string, string | undefined> = {},
|
||||
): { status: number | null; stdout: string; stderr: string } {
|
||||
const command = [
|
||||
`import { findPluginEntry } from ${JSON.stringify("./src/hooks/auto-update-checker/checker/plugin-entry")};`,
|
||||
`const result = findPluginEntry(${JSON.stringify(directory)});`,
|
||||
"console.log(JSON.stringify(result));",
|
||||
].join("")
|
||||
|
||||
const execution = spawnSync(process.execPath, ["-e", command], {
|
||||
cwd: process.cwd(),
|
||||
env: {
|
||||
...process.env,
|
||||
...envOverrides,
|
||||
},
|
||||
encoding: "utf-8",
|
||||
})
|
||||
|
||||
return {
|
||||
status: execution.status,
|
||||
stdout: execution.stdout,
|
||||
stderr: execution.stderr,
|
||||
}
|
||||
}
|
||||
|
||||
describe("findPluginEntry", () => {
|
||||
let temporaryDirectory: string
|
||||
let configPath: string
|
||||
let originalConfigDir: string | undefined
|
||||
|
||||
beforeEach(() => {
|
||||
originalConfigDir = process.env.OPENCODE_CONFIG_DIR
|
||||
temporaryDirectory = fs.mkdtempSync(path.join(os.tmpdir(), "omo-plugin-entry-test-"))
|
||||
const opencodeDirectory = path.join(temporaryDirectory, ".opencode")
|
||||
fs.mkdirSync(opencodeDirectory, { recursive: true })
|
||||
@@ -17,58 +53,93 @@ describe("findPluginEntry", () => {
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
if (originalConfigDir === undefined) {
|
||||
delete process.env.OPENCODE_CONFIG_DIR
|
||||
} else {
|
||||
process.env.OPENCODE_CONFIG_DIR = originalConfigDir
|
||||
}
|
||||
fs.rmSync(temporaryDirectory, { recursive: true, force: true })
|
||||
})
|
||||
|
||||
test("returns unpinned for bare package name", () => {
|
||||
test("returns unpinned for bare package name", async () => {
|
||||
// #given plugin is configured without a tag
|
||||
fs.writeFileSync(configPath, JSON.stringify({ plugin: [PACKAGE_NAME] }))
|
||||
|
||||
// #when plugin entry is detected
|
||||
const pluginInfo = findPluginEntry(temporaryDirectory)
|
||||
const execution = runFindPluginEntry(temporaryDirectory)
|
||||
|
||||
// #then entry is not pinned
|
||||
expect(execution.status).toBe(0)
|
||||
const pluginInfo = JSON.parse(execution.stdout.trim()) as PluginEntryResult
|
||||
expect(pluginInfo).not.toBeNull()
|
||||
expect(pluginInfo?.isPinned).toBe(false)
|
||||
expect(pluginInfo?.pinnedVersion).toBeNull()
|
||||
})
|
||||
|
||||
test("returns unpinned for latest dist-tag", () => {
|
||||
test("returns unpinned for latest dist-tag", async () => {
|
||||
// #given plugin is configured with latest dist-tag
|
||||
fs.writeFileSync(configPath, JSON.stringify({ plugin: [`${PACKAGE_NAME}@latest`] }))
|
||||
|
||||
// #when plugin entry is detected
|
||||
const pluginInfo = findPluginEntry(temporaryDirectory)
|
||||
const execution = runFindPluginEntry(temporaryDirectory)
|
||||
|
||||
// #then latest is treated as channel, not pin
|
||||
expect(execution.status).toBe(0)
|
||||
const pluginInfo = JSON.parse(execution.stdout.trim()) as PluginEntryResult
|
||||
expect(pluginInfo).not.toBeNull()
|
||||
expect(pluginInfo?.isPinned).toBe(false)
|
||||
expect(pluginInfo?.pinnedVersion).toBe("latest")
|
||||
})
|
||||
|
||||
test("returns unpinned for beta dist-tag", () => {
|
||||
test("returns unpinned for beta dist-tag", async () => {
|
||||
// #given plugin is configured with beta dist-tag
|
||||
fs.writeFileSync(configPath, JSON.stringify({ plugin: [`${PACKAGE_NAME}@beta`] }))
|
||||
|
||||
// #when plugin entry is detected
|
||||
const pluginInfo = findPluginEntry(temporaryDirectory)
|
||||
const execution = runFindPluginEntry(temporaryDirectory)
|
||||
|
||||
// #then beta is treated as channel, not pin
|
||||
expect(execution.status).toBe(0)
|
||||
const pluginInfo = JSON.parse(execution.stdout.trim()) as PluginEntryResult
|
||||
expect(pluginInfo).not.toBeNull()
|
||||
expect(pluginInfo?.isPinned).toBe(false)
|
||||
expect(pluginInfo?.pinnedVersion).toBe("beta")
|
||||
})
|
||||
|
||||
test("returns pinned for explicit semver", () => {
|
||||
test("returns pinned for explicit semver", async () => {
|
||||
// #given plugin is configured with explicit version
|
||||
fs.writeFileSync(configPath, JSON.stringify({ plugin: [`${PACKAGE_NAME}@3.5.2`] }))
|
||||
|
||||
// #when plugin entry is detected
|
||||
const pluginInfo = findPluginEntry(temporaryDirectory)
|
||||
const execution = runFindPluginEntry(temporaryDirectory)
|
||||
|
||||
// #then explicit semver is treated as pin
|
||||
expect(execution.status).toBe(0)
|
||||
const pluginInfo = JSON.parse(execution.stdout.trim()) as PluginEntryResult
|
||||
expect(pluginInfo).not.toBeNull()
|
||||
expect(pluginInfo?.isPinned).toBe(true)
|
||||
expect(pluginInfo?.pinnedVersion).toBe("3.5.2")
|
||||
})
|
||||
|
||||
test("reads user config from profile dir even when OPENCODE_CONFIG_DIR changes after import", async () => {
|
||||
// #given profile-specific user config after module import
|
||||
const profileConfigDir = path.join(temporaryDirectory, "profiles", "today")
|
||||
fs.mkdirSync(profileConfigDir, { recursive: true })
|
||||
fs.writeFileSync(
|
||||
path.join(profileConfigDir, "opencode.json"),
|
||||
JSON.stringify({ plugin: [`${PACKAGE_NAME}@beta`] }),
|
||||
)
|
||||
|
||||
// #when plugin entry is detected
|
||||
const execution = runFindPluginEntry(path.join(temporaryDirectory, "workspace"), {
|
||||
OPENCODE_CONFIG_DIR: profileConfigDir,
|
||||
})
|
||||
|
||||
// #then profile dir is respected
|
||||
expect(execution.status).toBe(0)
|
||||
const pluginInfo = JSON.parse(execution.stdout.trim()) as PluginEntryResult
|
||||
expect(pluginInfo).not.toBeNull()
|
||||
expect(pluginInfo?.configPath).toEndWith("/profiles/today/opencode.json")
|
||||
expect(pluginInfo?.pinnedVersion).toBe("beta")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -16,9 +16,17 @@ export function getWindowsAppdataDir(): string | null {
|
||||
return process.env.APPDATA ?? path.join(os.homedir(), "AppData", "Roaming")
|
||||
}
|
||||
|
||||
export const USER_CONFIG_DIR = getOpenCodeConfigDir({ binary: "opencode" })
|
||||
export const USER_OPENCODE_CONFIG = path.join(USER_CONFIG_DIR, "opencode.json")
|
||||
export const USER_OPENCODE_CONFIG_JSONC = path.join(USER_CONFIG_DIR, "opencode.jsonc")
|
||||
export function getUserConfigDir(): string {
|
||||
return getOpenCodeConfigDir({ binary: "opencode" })
|
||||
}
|
||||
|
||||
export function getUserOpencodeConfig(): string {
|
||||
return path.join(getUserConfigDir(), "opencode.json")
|
||||
}
|
||||
|
||||
export function getUserOpencodeConfigJsonc(): string {
|
||||
return path.join(getUserConfigDir(), "opencode.jsonc")
|
||||
}
|
||||
|
||||
export const INSTALLED_PACKAGE_JSON = path.join(
|
||||
CACHE_DIR,
|
||||
|
||||
@@ -94,6 +94,26 @@ describe("loadPluginExtendedConfig", () => {
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test("#given OPENCODE_CONFIG_DIR points at a profile dir after module import #when loading extended config #then it reads the profile config file", async () => {
|
||||
//#given
|
||||
const profileConfigDir = join(tempDirectory, ".config", "opencode", "profiles", "today")
|
||||
const profileConfigPath = join(profileConfigDir, "opencode-cc-plugin.json")
|
||||
mkdirSync(profileConfigDir, { recursive: true })
|
||||
process.env.OPENCODE_CONFIG_DIR = profileConfigDir
|
||||
writeConfigFile(profileConfigPath, ["profile-stop"])
|
||||
|
||||
//#when
|
||||
clearPluginExtendedConfigCache()
|
||||
const result = await loadPluginExtendedConfig()
|
||||
|
||||
//#then
|
||||
expect(result).toEqual({
|
||||
disabledHooks: {
|
||||
Stop: ["profile-stop"],
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
function writeConfigFile(filePath: string, stopPatterns: string[]): void {
|
||||
|
||||
@@ -23,15 +23,18 @@ interface PluginExtendedConfigCacheEntry {
|
||||
cachedAt: number
|
||||
}
|
||||
|
||||
const USER_CONFIG_PATH = join(getOpenCodeConfigDir({ binary: "opencode" }), "opencode-cc-plugin.json")
|
||||
const configCache = new Map<string, PluginExtendedConfigCacheEntry>()
|
||||
|
||||
function getUserConfigPath(): string {
|
||||
return join(getOpenCodeConfigDir({ binary: "opencode" }), "opencode-cc-plugin.json")
|
||||
}
|
||||
|
||||
function getProjectConfigPath(): string {
|
||||
return join(process.cwd(), ".opencode", "opencode-cc-plugin.json")
|
||||
}
|
||||
|
||||
function getCacheKey(): string {
|
||||
return process.cwd()
|
||||
return `${process.cwd()}::${getUserConfigPath()}`
|
||||
}
|
||||
|
||||
function getCachedConfig(cacheKey: string): PluginExtendedConfig | undefined {
|
||||
@@ -89,7 +92,7 @@ export async function loadPluginExtendedConfig(): Promise<PluginExtendedConfig>
|
||||
return cachedConfig
|
||||
}
|
||||
|
||||
const userConfig = await loadConfigFromPath(USER_CONFIG_PATH)
|
||||
const userConfig = await loadConfigFromPath(getUserConfigPath())
|
||||
const projectConfig = await loadConfigFromPath(getProjectConfigPath())
|
||||
|
||||
const merged: PluginExtendedConfig = {
|
||||
|
||||
@@ -15,14 +15,14 @@ const _realLogger = require("../../shared/logger")
|
||||
async function importFreshCacheModule(): Promise<typeof import("../auto-update-checker/cache")> {
|
||||
mock.module("../auto-update-checker/constants", () => ({
|
||||
CACHE_DIR: TEST_OPENCODE_CACHE_DIR,
|
||||
USER_CONFIG_DIR: TEST_USER_CONFIG_DIR,
|
||||
PACKAGE_NAME: "oh-my-opencode",
|
||||
NPM_REGISTRY_URL: "https://registry.npmjs.org/-/package/oh-my-opencode/dist-tags",
|
||||
NPM_FETCH_TIMEOUT: 5000,
|
||||
VERSION_FILE: join(TEST_OPENCODE_CACHE_DIR, "version"),
|
||||
USER_OPENCODE_CONFIG: join(TEST_USER_CONFIG_DIR, "opencode.json"),
|
||||
USER_OPENCODE_CONFIG_JSONC: join(TEST_USER_CONFIG_DIR, "opencode.jsonc"),
|
||||
INSTALLED_PACKAGE_JSON: join(TEST_OPENCODE_CACHE_DIR, "node_modules", "oh-my-opencode", "package.json"),
|
||||
getUserConfigDir: () => TEST_USER_CONFIG_DIR,
|
||||
getUserOpencodeConfig: () => join(TEST_USER_CONFIG_DIR, "opencode.json"),
|
||||
getUserOpencodeConfigJsonc: () => join(TEST_USER_CONFIG_DIR, "opencode.jsonc"),
|
||||
getWindowsAppdataDir: () => null,
|
||||
}))
|
||||
|
||||
|
||||
@@ -19,10 +19,10 @@ async function importFreshSyncPackageJsonModule(): Promise<typeof import("../aut
|
||||
NPM_REGISTRY_URL: "https://registry.npmjs.org/-/package/oh-my-opencode/dist-tags",
|
||||
NPM_FETCH_TIMEOUT: 5000,
|
||||
VERSION_FILE: join(TEST_CACHE_DIR, "version"),
|
||||
USER_CONFIG_DIR: "/tmp/opencode-config",
|
||||
USER_OPENCODE_CONFIG: "/tmp/opencode-config/opencode.json",
|
||||
USER_OPENCODE_CONFIG_JSONC: "/tmp/opencode-config/opencode.jsonc",
|
||||
INSTALLED_PACKAGE_JSON: join(TEST_CACHE_DIR, "node_modules", "oh-my-opencode", "package.json"),
|
||||
getUserConfigDir: () => "/tmp/opencode-config",
|
||||
getUserOpencodeConfig: () => "/tmp/opencode-config/opencode.json",
|
||||
getUserOpencodeConfigJsonc: () => "/tmp/opencode-config/opencode.jsonc",
|
||||
getWindowsAppdataDir: () => null,
|
||||
}))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user