feat: support opencode.jsonc configs

This commit is contained in:
Omar McAdam
2026-01-22 13:07:13 -08:00
parent a92363d22e
commit f299e304d7
5 changed files with 117 additions and 13 deletions

View File

@@ -1818,6 +1818,17 @@ dependencies = [
"thiserror 1.0.69",
]
[[package]]
name = "json5"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1"
dependencies = [
"pest",
"pest_derive",
"serde",
]
[[package]]
name = "jsonptr"
version = "0.6.3"
@@ -2370,8 +2381,9 @@ dependencies = [
[[package]]
name = "openwork"
version = "0.3.0"
version = "0.3.2"
dependencies = [
"json5",
"serde",
"serde_json",
"tauri",
@@ -2478,6 +2490,49 @@ version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
[[package]]
name = "pest"
version = "2.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c9eb05c21a464ea704b53158d358a31e6425db2f63a1a7312268b05fe2b75f7"
dependencies = [
"memchr",
"ucd-trie",
]
[[package]]
name = "pest_derive"
version = "2.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68f9dbced329c441fa79d80472764b1a2c7e57123553b8519b36663a2fb234ed"
dependencies = [
"pest",
"pest_generator",
]
[[package]]
name = "pest_generator"
version = "2.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3bb96d5051a78f44f43c8f712d8e810adb0ebf923fc9ed2655a7f66f63ba8ee5"
dependencies = [
"pest",
"pest_meta",
"proc-macro2",
"quote",
"syn 2.0.114",
]
[[package]]
name = "pest_meta"
version = "2.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "602113b5b5e8621770cfd490cfd90b9f84ab29bd2b0e49ad83eb6d186cef2365"
dependencies = [
"pest",
"sha2",
]
[[package]]
name = "phf"
version = "0.8.0"
@@ -4467,6 +4522,12 @@ version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
[[package]]
name = "ucd-trie"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
[[package]]
name = "uds_windows"
version = "1.1.0"

View File

@@ -9,6 +9,7 @@ edition = "2021"
tauri-build = { version = "2", features = [] }
[dependencies]
json5 = "0.4"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tauri = { version = "2", features = [] }

View File

@@ -4,13 +4,14 @@ use std::path::PathBuf;
use crate::types::{ExecResult, OpencodeConfigFile};
pub fn resolve_opencode_config_path(scope: &str, project_dir: &str) -> Result<PathBuf, String> {
fn opencode_config_candidates(scope: &str, project_dir: &str) -> Result<(PathBuf, PathBuf), String> {
match scope {
"project" => {
if project_dir.trim().is_empty() {
return Err("projectDir is required".to_string());
}
Ok(PathBuf::from(project_dir).join("opencode.json"))
let root = PathBuf::from(project_dir);
Ok((root.join("opencode.jsonc"), root.join("opencode.json")))
}
"global" => {
let base = if let Ok(dir) = env::var("XDG_CONFIG_HOME") {
@@ -21,12 +22,27 @@ pub fn resolve_opencode_config_path(scope: &str, project_dir: &str) -> Result<Pa
return Err("Unable to resolve config directory".to_string());
};
Ok(base.join("opencode").join("opencode.json"))
let root = base.join("opencode");
Ok((root.join("opencode.jsonc"), root.join("opencode.json")))
}
_ => Err("scope must be 'project' or 'global'".to_string()),
}
}
pub fn resolve_opencode_config_path(scope: &str, project_dir: &str) -> Result<PathBuf, String> {
let (jsonc_path, json_path) = opencode_config_candidates(scope, project_dir)?;
if jsonc_path.exists() {
return Ok(jsonc_path);
}
if json_path.exists() {
return Ok(json_path);
}
Ok(jsonc_path)
}
pub fn read_opencode_config(scope: &str, project_dir: &str) -> Result<OpencodeConfigFile, String> {
let path = resolve_opencode_config_path(scope.trim(), project_dir)?;
let exists = path.exists();

View File

@@ -38,11 +38,20 @@ pub fn build_engine_command(program: &Path, hostname: &str, port: u16, project_d
command.env("XDG_DATA_HOME", xdg_data_home);
}
if let Some(xdg_config_home) = maybe_infer_xdg_home(
let xdg_config_home = maybe_infer_xdg_home(
"XDG_CONFIG_HOME",
candidate_xdg_config_dirs(),
Path::new("opencode/opencode.json"),
) {
Path::new("opencode/opencode.jsonc"),
)
.or_else(|| {
maybe_infer_xdg_home(
"XDG_CONFIG_HOME",
candidate_xdg_config_dirs(),
Path::new("opencode/opencode.json"),
)
});
if let Some(xdg_config_home) = xdg_config_home {
command.env("XDG_CONFIG_HOME", xdg_config_home);
}

View File

@@ -158,11 +158,22 @@ pub fn ensure_workspace_files(workspace_path: &str, preset: &str) -> Result<(),
.map_err(|e| format!("Failed to create .openwork/templates: {e}"))?;
seed_templates(&templates_dir)?;
let config_path = root.join("opencode.json");
let mut config: serde_json::Value = if config_path.exists() {
let config_path_jsonc = root.join("opencode.jsonc");
let config_path_json = root.join("opencode.json");
let config_path = if config_path_jsonc.exists() {
config_path_jsonc
} else if config_path_json.exists() {
config_path_json
} else {
config_path_jsonc
};
let config_exists = config_path.exists();
let mut config_changed = !config_exists;
let mut config: serde_json::Value = if config_exists {
let raw = fs::read_to_string(&config_path)
.map_err(|e| format!("Failed to read {}: {e}", config_path.display()))?;
serde_json::from_str(&raw).unwrap_or_else(|_| serde_json::json!({}))
json5::from_str(&raw).unwrap_or_else(|_| serde_json::json!({}))
} else {
serde_json::json!({
"$schema": "https://opencode.ai/config.json"
@@ -173,6 +184,7 @@ pub fn ensure_workspace_files(workspace_path: &str, preset: &str) -> Result<(),
config = serde_json::json!({
"$schema": "https://opencode.ai/config.json"
});
config_changed = true;
}
let required_plugins: Vec<&str> = match preset {
@@ -196,7 +208,10 @@ pub fn ensure_workspace_files(workspace_path: &str, preset: &str) -> Result<(),
_ => vec![],
};
let merged = merge_plugins(existing_plugins, &required_plugins);
let merged = merge_plugins(existing_plugins.clone(), &required_plugins);
if merged != existing_plugins {
config_changed = true;
}
if let Some(obj) = config.as_object_mut() {
obj.insert(
"plugin".to_string(),
@@ -205,8 +220,10 @@ pub fn ensure_workspace_files(workspace_path: &str, preset: &str) -> Result<(),
}
}
fs::write(&config_path, serde_json::to_string_pretty(&config).map_err(|e| e.to_string())?)
.map_err(|e| format!("Failed to write {}: {e}", config_path.display()))?;
if config_changed {
fs::write(&config_path, serde_json::to_string_pretty(&config).map_err(|e| e.to_string())?)
.map_err(|e| format!("Failed to write {}: {e}", config_path.display()))?;
}
let openwork_path = root.join(".opencode").join("openwork.json");
if !openwork_path.exists() {