mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
[plugins] Expose marketplace source in marketplace list JSON (#27009)
## Summary - Follow-up to #26417 and #26631 - Add `marketplaceSource` to `codex plugin marketplace list --json` entries for configured marketplaces - Reuse the existing `marketplaceSource` shape from `codex plugin list --json` - Keep human-readable marketplace list output unchanged - Add CLI coverage for configured local and git marketplace sources Example: ```json { "marketplaces": [ { "name": "debug", "root": "/path/to/.codex/.tmp/marketplaces/debug", "marketplaceSource": { "sourceType": "git", "source": "https://example.com/acme/agent-skills.git" } } ] } ``` ## Validation - `just fmt` - `just fix -p codex-cli` - `just test -p codex-cli marketplace_list` - `just test -p codex-cli`
This commit is contained in:
committed by
GitHub
Unverified
parent
6d8e12ac42
commit
0aa9931aea
@@ -5,7 +5,10 @@ use clap::Parser;
|
||||
use codex_core::config::Config;
|
||||
use codex_core::config::find_codex_home;
|
||||
use codex_core_plugins::PluginMarketplaceUpgradeOutcome;
|
||||
use codex_core_plugins::PluginsConfigInput;
|
||||
use codex_core_plugins::PluginsManager;
|
||||
use codex_core_plugins::installed_marketplaces::marketplace_install_root;
|
||||
use codex_core_plugins::installed_marketplaces::resolve_configured_marketplace_root;
|
||||
use codex_core_plugins::marketplace::marketplace_root_dir;
|
||||
use codex_core_plugins::marketplace_add::MarketplaceAddOutcome;
|
||||
use codex_core_plugins::marketplace_add::MarketplaceAddRequest;
|
||||
@@ -15,9 +18,14 @@ use codex_core_plugins::marketplace_remove::MarketplaceRemoveRequest;
|
||||
use codex_core_plugins::marketplace_remove::remove_marketplace;
|
||||
use codex_utils_cli::CliConfigOverrides;
|
||||
use serde::Serialize;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::plugin_cmd::JsonMarketplaceSource;
|
||||
use crate::plugin_cmd::configured_marketplace_snapshot_issues;
|
||||
use crate::plugin_cmd::configured_marketplace_sources;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(bin_name = "codex plugin marketplace")]
|
||||
@@ -240,7 +248,10 @@ async fn run_list(overrides: Vec<(String, toml::Value)>, args: ListMarketplaceAr
|
||||
}
|
||||
let marketplaces = marketplace_listing.marketplaces;
|
||||
if args.json {
|
||||
let output = JsonMarketplaceListOutput::from_marketplaces(marketplaces);
|
||||
let marketplace_sources =
|
||||
configured_marketplace_sources_by_root(config.codex_home.as_path(), &plugins_input);
|
||||
let output =
|
||||
JsonMarketplaceListOutput::from_marketplaces(marketplaces, &marketplace_sources);
|
||||
println!("{}", serde_json::to_string_pretty(&output)?);
|
||||
return Ok(());
|
||||
}
|
||||
@@ -288,7 +299,10 @@ struct JsonMarketplaceListOutput {
|
||||
}
|
||||
|
||||
impl JsonMarketplaceListOutput {
|
||||
fn from_marketplaces(marketplaces: Vec<codex_core_plugins::marketplace::Marketplace>) -> Self {
|
||||
fn from_marketplaces(
|
||||
marketplaces: Vec<codex_core_plugins::marketplace::Marketplace>,
|
||||
marketplace_sources: &HashMap<PathBuf, JsonMarketplaceSource>,
|
||||
) -> Self {
|
||||
let mut seen_roots = HashSet::new();
|
||||
let marketplaces = marketplaces
|
||||
.into_iter()
|
||||
@@ -298,6 +312,7 @@ impl JsonMarketplaceListOutput {
|
||||
return None;
|
||||
}
|
||||
Some(JsonMarketplaceListEntry {
|
||||
marketplace_source: marketplace_sources.get(root.as_path()).cloned(),
|
||||
name: marketplace.name,
|
||||
root: root.display().to_string(),
|
||||
})
|
||||
@@ -313,6 +328,38 @@ impl JsonMarketplaceListOutput {
|
||||
struct JsonMarketplaceListEntry {
|
||||
name: String,
|
||||
root: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
marketplace_source: Option<JsonMarketplaceSource>,
|
||||
}
|
||||
|
||||
fn configured_marketplace_sources_by_root(
|
||||
codex_home: &Path,
|
||||
plugins_input: &PluginsConfigInput,
|
||||
) -> HashMap<PathBuf, JsonMarketplaceSource> {
|
||||
let marketplace_sources = configured_marketplace_sources(plugins_input);
|
||||
let Some(user_config) = plugins_input.config_layer_stack.effective_user_config() else {
|
||||
return HashMap::new();
|
||||
};
|
||||
let Some(marketplaces) = user_config
|
||||
.get("marketplaces")
|
||||
.and_then(toml::Value::as_table)
|
||||
else {
|
||||
return HashMap::new();
|
||||
};
|
||||
|
||||
let default_install_root = marketplace_install_root(codex_home);
|
||||
marketplaces
|
||||
.iter()
|
||||
.filter_map(|(marketplace_name, marketplace)| {
|
||||
let marketplace_source = marketplace_sources.get(marketplace_name)?;
|
||||
let root = resolve_configured_marketplace_root(
|
||||
marketplace_name,
|
||||
marketplace,
|
||||
&default_install_root,
|
||||
)?;
|
||||
Some((root, marketplace_source.clone()))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
async fn run_upgrade(
|
||||
|
||||
@@ -437,12 +437,12 @@ impl JsonPluginSource {
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct JsonMarketplaceSource {
|
||||
pub(crate) struct JsonMarketplaceSource {
|
||||
source_type: String,
|
||||
source: String,
|
||||
}
|
||||
|
||||
fn configured_marketplace_sources(
|
||||
pub(crate) fn configured_marketplace_sources(
|
||||
plugins_input: &PluginsConfigInput,
|
||||
) -> HashMap<String, JsonMarketplaceSource> {
|
||||
let Some(user_config) = plugins_input.config_layer_stack.effective_user_config() else {
|
||||
|
||||
@@ -341,6 +341,7 @@ async fn marketplace_list_shows_configured_marketplace_names() -> Result<()> {
|
||||
#[tokio::test]
|
||||
async fn marketplace_list_json_prints_configured_marketplaces() -> Result<()> {
|
||||
let (codex_home, source) = setup_local_marketplace()?;
|
||||
let source_path = source.path().display().to_string();
|
||||
|
||||
let assert = codex_command(codex_home.path())?
|
||||
.args(["plugin", "marketplace", "list", "--json"])
|
||||
@@ -355,7 +356,112 @@ async fn marketplace_list_json_prints_configured_marketplaces() -> Result<()> {
|
||||
"marketplaces": [
|
||||
{
|
||||
"name": "debug",
|
||||
"root": source.path().display().to_string(),
|
||||
"root": source_path,
|
||||
"marketplaceSource": {
|
||||
"sourceType": "local",
|
||||
"source": source_path,
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn marketplace_list_json_includes_configured_git_marketplace_source() -> Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
let marketplace_root = codex_home
|
||||
.path()
|
||||
.join(".tmp")
|
||||
.join("marketplaces")
|
||||
.join("debug");
|
||||
write_plugins_enabled_config(codex_home.path())?;
|
||||
write_marketplace_source(&marketplace_root)?;
|
||||
let update = MarketplaceConfigUpdate {
|
||||
last_updated: "2026-06-04T08:39:49Z",
|
||||
last_revision: Some("abc123"),
|
||||
source_type: "git",
|
||||
source: "https://example.com/acme/agent-skills.git",
|
||||
ref_name: None,
|
||||
sparse_paths: &[],
|
||||
};
|
||||
record_user_marketplace(codex_home.path(), "debug", &update)?;
|
||||
let normalized_root = canonicalize_existing_preserving_symlinks(&marketplace_root)?;
|
||||
|
||||
let assert = codex_command(codex_home.path())?
|
||||
.args(["plugin", "marketplace", "list", "--json"])
|
||||
.assert()
|
||||
.success();
|
||||
let stdout = assert.get_output().stdout.as_slice();
|
||||
let actual: serde_json::Value = serde_json::from_slice(stdout)?;
|
||||
|
||||
assert_eq!(
|
||||
actual,
|
||||
json!({
|
||||
"marketplaces": [
|
||||
{
|
||||
"name": "debug",
|
||||
"root": normalized_root.display().to_string(),
|
||||
"marketplaceSource": {
|
||||
"sourceType": "git",
|
||||
"source": "https://example.com/acme/agent-skills.git",
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn marketplace_list_json_keys_configured_source_by_root() -> Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
let home = TempDir::new()?;
|
||||
let marketplace_root = codex_home
|
||||
.path()
|
||||
.join(".tmp")
|
||||
.join("marketplaces")
|
||||
.join("debug");
|
||||
write_plugins_enabled_config(codex_home.path())?;
|
||||
write_marketplace_source(home.path())?;
|
||||
write_marketplace_source(&marketplace_root)?;
|
||||
let update = MarketplaceConfigUpdate {
|
||||
last_updated: "2026-06-04T08:39:49Z",
|
||||
last_revision: Some("abc123"),
|
||||
source_type: "git",
|
||||
source: "https://example.com/acme/agent-skills.git",
|
||||
ref_name: None,
|
||||
sparse_paths: &[],
|
||||
};
|
||||
record_user_marketplace(codex_home.path(), "debug", &update)?;
|
||||
let normalized_root = canonicalize_existing_preserving_symlinks(&marketplace_root)?;
|
||||
|
||||
let assert = codex_command(codex_home.path())?
|
||||
.env("HOME", home.path())
|
||||
.args(["plugin", "marketplace", "list", "--json"])
|
||||
.assert()
|
||||
.success();
|
||||
let stdout = assert.get_output().stdout.as_slice();
|
||||
let actual: serde_json::Value = serde_json::from_slice(stdout)?;
|
||||
|
||||
assert_eq!(
|
||||
actual,
|
||||
json!({
|
||||
"marketplaces": [
|
||||
{
|
||||
"name": "debug",
|
||||
"root": home.path().display().to_string(),
|
||||
},
|
||||
{
|
||||
"name": "debug",
|
||||
"root": normalized_root.display().to_string(),
|
||||
"marketplaceSource": {
|
||||
"sourceType": "git",
|
||||
"source": "https://example.com/acme/agent-skills.git",
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user