mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
Add marketplace command (#17087)
Added a new top-level `codex marketplace add` command for installing plugin marketplaces into Codex’s local marketplace cache. This change adds source parsing for local directories, GitHub shorthand, and git URLs, supports optional `--ref` and git-only `--sparse` checkout paths, stages the source in a temp directory, validates the marketplace manifest, and installs it under `$CODEX_HOME/marketplaces/<marketplace-name>` Included tests cover local install behavior in the CLI and marketplace discovery from installed roots in core. Scoped formatting and fix passes were run, and targeted CLI/core tests passed.
This commit is contained in:
committed by
GitHub
Unverified
parent
58933237cd
commit
f9a8d1870f
@@ -8,6 +8,7 @@ use crate::config_loader::ConfigRequirementsToml;
|
||||
use crate::plugins::LoadedPlugin;
|
||||
use crate::plugins::MarketplacePluginInstallPolicy;
|
||||
use crate::plugins::PluginLoadOutcome;
|
||||
use crate::plugins::marketplace_install_root;
|
||||
use crate::plugins::test_support::TEST_CURATED_PLUGIN_SHA;
|
||||
use crate::plugins::test_support::write_curated_plugin_sha_with as write_curated_plugin_sha;
|
||||
use crate::plugins::test_support::write_file;
|
||||
@@ -1504,6 +1505,174 @@ plugins = true
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn list_marketplaces_includes_installed_marketplace_roots() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let marketplace_root = marketplace_install_root(tmp.path()).join("debug");
|
||||
let plugin_root = marketplace_root.join("plugins/sample");
|
||||
|
||||
write_file(
|
||||
&tmp.path().join(CONFIG_TOML_FILE),
|
||||
r#"[features]
|
||||
plugins = true
|
||||
|
||||
[marketplaces.debug]
|
||||
last_updated = "2026-04-10T12:34:56Z"
|
||||
source_type = "git"
|
||||
source = "/tmp/debug"
|
||||
"#,
|
||||
);
|
||||
fs::create_dir_all(marketplace_root.join(".agents/plugins")).unwrap();
|
||||
fs::create_dir_all(plugin_root.join(".codex-plugin")).unwrap();
|
||||
fs::write(
|
||||
marketplace_root.join(".agents/plugins/marketplace.json"),
|
||||
r#"{
|
||||
"name": "debug",
|
||||
"plugins": [
|
||||
{
|
||||
"name": "sample",
|
||||
"source": {
|
||||
"source": "local",
|
||||
"path": "./plugins/sample"
|
||||
}
|
||||
}
|
||||
]
|
||||
}"#,
|
||||
)
|
||||
.unwrap();
|
||||
fs::write(
|
||||
plugin_root.join(".codex-plugin/plugin.json"),
|
||||
r#"{"name":"sample"}"#,
|
||||
)
|
||||
.unwrap();
|
||||
let config = load_config(tmp.path(), tmp.path()).await;
|
||||
let marketplaces = PluginsManager::new(tmp.path().to_path_buf())
|
||||
.list_marketplaces_for_config(&config, &[])
|
||||
.unwrap()
|
||||
.marketplaces;
|
||||
|
||||
let marketplace = marketplaces
|
||||
.into_iter()
|
||||
.find(|marketplace| marketplace.name == "debug")
|
||||
.expect("installed marketplace should be listed");
|
||||
|
||||
assert_eq!(
|
||||
marketplace.path,
|
||||
AbsolutePathBuf::try_from(marketplace_root.join(".agents/plugins/marketplace.json"))
|
||||
.unwrap()
|
||||
);
|
||||
assert_eq!(marketplace.plugins.len(), 1);
|
||||
assert_eq!(marketplace.plugins[0].id, "sample@debug");
|
||||
assert_eq!(
|
||||
marketplace.plugins[0].source,
|
||||
MarketplacePluginSource::Local {
|
||||
path: AbsolutePathBuf::try_from(plugin_root).unwrap(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn list_marketplaces_uses_config_when_known_registry_is_malformed() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let marketplace_root = marketplace_install_root(tmp.path()).join("debug");
|
||||
let plugin_root = marketplace_root.join("plugins/sample");
|
||||
let registry_path = tmp.path().join(".tmp/known_marketplaces.json");
|
||||
|
||||
write_file(
|
||||
&tmp.path().join(CONFIG_TOML_FILE),
|
||||
r#"[features]
|
||||
plugins = true
|
||||
|
||||
[marketplaces.debug]
|
||||
last_updated = "2026-04-10T12:34:56Z"
|
||||
source_type = "git"
|
||||
source = "/tmp/debug"
|
||||
"#,
|
||||
);
|
||||
fs::create_dir_all(marketplace_root.join(".agents/plugins")).unwrap();
|
||||
fs::create_dir_all(plugin_root.join(".codex-plugin")).unwrap();
|
||||
fs::write(
|
||||
marketplace_root.join(".agents/plugins/marketplace.json"),
|
||||
r#"{
|
||||
"name": "debug",
|
||||
"plugins": [
|
||||
{
|
||||
"name": "sample",
|
||||
"source": {
|
||||
"source": "local",
|
||||
"path": "./plugins/sample"
|
||||
}
|
||||
}
|
||||
]
|
||||
}"#,
|
||||
)
|
||||
.unwrap();
|
||||
fs::write(
|
||||
plugin_root.join(".codex-plugin/plugin.json"),
|
||||
r#"{"name":"sample"}"#,
|
||||
)
|
||||
.unwrap();
|
||||
fs::create_dir_all(registry_path.parent().unwrap()).unwrap();
|
||||
fs::write(registry_path, "{not valid json").unwrap();
|
||||
|
||||
let config = load_config(tmp.path(), tmp.path()).await;
|
||||
let marketplaces = PluginsManager::new(tmp.path().to_path_buf())
|
||||
.list_marketplaces_for_config(&config, &[])
|
||||
.unwrap()
|
||||
.marketplaces;
|
||||
|
||||
let marketplace = marketplaces
|
||||
.into_iter()
|
||||
.find(|marketplace| marketplace.name == "debug")
|
||||
.expect("configured marketplace should be discovered");
|
||||
|
||||
assert_eq!(marketplace.plugins[0].id, "sample@debug");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn list_marketplaces_ignores_installed_roots_missing_from_config() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let marketplace_root = marketplace_install_root(tmp.path()).join("debug");
|
||||
let plugin_root = marketplace_root.join("plugins/sample");
|
||||
|
||||
write_file(
|
||||
&tmp.path().join(CONFIG_TOML_FILE),
|
||||
r#"[features]
|
||||
plugins = true
|
||||
"#,
|
||||
);
|
||||
fs::create_dir_all(marketplace_root.join(".agents/plugins")).unwrap();
|
||||
fs::create_dir_all(plugin_root.join(".codex-plugin")).unwrap();
|
||||
fs::write(
|
||||
marketplace_root.join(".agents/plugins/marketplace.json"),
|
||||
r#"{
|
||||
"name": "debug",
|
||||
"plugins": [
|
||||
{
|
||||
"name": "sample",
|
||||
"source": {
|
||||
"source": "local",
|
||||
"path": "./plugins/sample"
|
||||
}
|
||||
}
|
||||
]
|
||||
}"#,
|
||||
)
|
||||
.unwrap();
|
||||
fs::write(
|
||||
plugin_root.join(".codex-plugin/plugin.json"),
|
||||
r#"{"name":"sample"}"#,
|
||||
)
|
||||
.unwrap();
|
||||
let config = load_config(tmp.path(), tmp.path()).await;
|
||||
let marketplaces = PluginsManager::new(tmp.path().to_path_buf())
|
||||
.list_marketplaces_for_config(&config, &[])
|
||||
.unwrap()
|
||||
.marketplaces;
|
||||
|
||||
assert!(marketplaces.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn list_marketplaces_uses_first_duplicate_plugin_entry() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
|
||||
Reference in New Issue
Block a user