mirror of
https://github.com/openai/codex.git
synced 2026-10-25 18:28:38 +00:00
Add cross-repo plugin sources to marketplace manifests
This commit is contained in:
@@ -22,6 +22,7 @@ use codex_core_plugins::loader::load_plugin_mcp_servers;
|
||||
use codex_core_plugins::loader::load_plugin_skills;
|
||||
use codex_core_plugins::loader::load_plugins_from_layer_stack;
|
||||
use codex_core_plugins::loader::log_plugin_load_errors;
|
||||
use codex_core_plugins::loader::materialize_marketplace_plugin_source;
|
||||
use codex_core_plugins::loader::plugin_telemetry_metadata_from_root;
|
||||
use codex_core_plugins::loader::refresh_curated_plugin_cache;
|
||||
use codex_core_plugins::loader::refresh_non_curated_plugin_cache;
|
||||
@@ -560,11 +561,16 @@ impl PluginsManager {
|
||||
None
|
||||
};
|
||||
let store = self.store.clone();
|
||||
let codex_home = self.codex_home.clone();
|
||||
let result: StorePluginInstallResult = tokio::task::spawn_blocking(move || {
|
||||
let materialized =
|
||||
materialize_marketplace_plugin_source(codex_home.as_path(), &resolved.source)
|
||||
.map_err(PluginStoreError::Invalid)?;
|
||||
let source_path = materialized.path;
|
||||
if let Some(plugin_version) = plugin_version {
|
||||
store.install_with_version(resolved.source_path, resolved.plugin_id, plugin_version)
|
||||
store.install_with_version(source_path, resolved.plugin_id, plugin_version)
|
||||
} else {
|
||||
store.install(resolved.source_path, resolved.plugin_id)
|
||||
store.install(source_path, resolved.plugin_id)
|
||||
}
|
||||
})
|
||||
.await
|
||||
@@ -725,6 +731,14 @@ impl PluginsManager {
|
||||
let plugin_key = plugin_id.as_key();
|
||||
let source_path = match plugin.source {
|
||||
MarketplacePluginSource::Local { path } => path,
|
||||
MarketplacePluginSource::Git { .. } => {
|
||||
warn!(
|
||||
plugin = plugin_name,
|
||||
marketplace = %marketplace_name,
|
||||
"skipping remote plugin source during remote sync"
|
||||
);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let current_enabled = configured_plugins
|
||||
.get(&plugin_key)
|
||||
@@ -1018,9 +1032,17 @@ impl PluginsManager {
|
||||
}
|
||||
})?;
|
||||
let plugin_key = plugin_id.as_key();
|
||||
let source_path = match &plugin.source {
|
||||
MarketplacePluginSource::Local { path } => path.clone(),
|
||||
};
|
||||
let codex_home = self.codex_home.clone();
|
||||
let source = plugin.source.clone();
|
||||
let materialized = tokio::task::spawn_blocking(move || {
|
||||
materialize_marketplace_plugin_source(codex_home.as_path(), &source)
|
||||
})
|
||||
.await
|
||||
.map_err(|err| {
|
||||
MarketplaceError::InvalidPlugin(format!("failed to materialize plugin source: {err}"))
|
||||
})?
|
||||
.map_err(MarketplaceError::InvalidPlugin)?;
|
||||
let source_path = materialized.path.clone();
|
||||
if !source_path.as_path().is_dir() {
|
||||
return Err(MarketplaceError::InvalidPlugin(
|
||||
"path does not exist or is not a directory".to_string(),
|
||||
|
||||
@@ -64,6 +64,31 @@ fn write_plugin(root: &Path, dir_name: &str, manifest_name: &str) {
|
||||
);
|
||||
}
|
||||
|
||||
fn init_git_repo(repo: &Path) {
|
||||
run_git(repo, &["init"]);
|
||||
run_git(repo, &["config", "user.email", "codex-test@example.com"]);
|
||||
run_git(repo, &["config", "user.name", "Codex Test"]);
|
||||
run_git(repo, &["add", "."]);
|
||||
run_git(repo, &["commit", "-m", "initial"]);
|
||||
}
|
||||
|
||||
fn run_git(repo: &Path, args: &[&str]) {
|
||||
let output = std::process::Command::new("git")
|
||||
.arg("-C")
|
||||
.arg(repo)
|
||||
.args(args)
|
||||
.output()
|
||||
.unwrap_or_else(|err| panic!("git should run: {err}"));
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"git -C {} {} failed\nstdout:\n{}\nstderr:\n{}",
|
||||
repo.display(),
|
||||
args.join(" "),
|
||||
String::from_utf8_lossy(&output.stdout),
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
}
|
||||
|
||||
fn plugin_config_toml(enabled: bool, plugins_feature_enabled: bool) -> String {
|
||||
let mut root = toml::map::Map::new();
|
||||
|
||||
@@ -1040,6 +1065,60 @@ async fn install_plugin_uses_manifest_version_for_non_curated_plugins() {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn install_plugin_supports_git_subdir_marketplace_sources() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let repo_root = tmp.path().join("marketplace");
|
||||
let remote_repo = tmp.path().join("remote-plugin-repo");
|
||||
fs::create_dir_all(repo_root.join(".git")).unwrap();
|
||||
fs::create_dir_all(repo_root.join(".agents/plugins")).unwrap();
|
||||
write_plugin(&remote_repo, "plugins/toolkit", "toolkit");
|
||||
init_git_repo(&remote_repo);
|
||||
fs::write(
|
||||
repo_root.join(".agents/plugins/marketplace.json"),
|
||||
format!(
|
||||
r#"{{
|
||||
"name": "debug",
|
||||
"plugins": [
|
||||
{{
|
||||
"name": "toolkit",
|
||||
"source": {{
|
||||
"source": "git-subdir",
|
||||
"url": "{}",
|
||||
"path": "plugins/toolkit"
|
||||
}}
|
||||
}}
|
||||
]
|
||||
}}"#,
|
||||
remote_repo.display()
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let result = PluginsManager::new(tmp.path().to_path_buf())
|
||||
.install_plugin(PluginInstallRequest {
|
||||
plugin_name: "toolkit".to_string(),
|
||||
marketplace_path: AbsolutePathBuf::try_from(
|
||||
repo_root.join(".agents/plugins/marketplace.json"),
|
||||
)
|
||||
.unwrap(),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let installed_path = tmp.path().join("plugins/cache/debug/toolkit/local");
|
||||
assert_eq!(
|
||||
result,
|
||||
PluginInstallOutcome {
|
||||
plugin_id: PluginId::new("toolkit".to_string(), "debug".to_string()).unwrap(),
|
||||
plugin_version: "local".to_string(),
|
||||
installed_path: AbsolutePathBuf::try_from(installed_path.clone()).unwrap(),
|
||||
auth_policy: MarketplacePluginAuthPolicy::OnInstall,
|
||||
}
|
||||
);
|
||||
assert!(installed_path.join(".codex-plugin/plugin.json").is_file());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn uninstall_plugin_removes_cache_and_config_entry() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
|
||||
Reference in New Issue
Block a user