mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
fix(plugins): support root local marketplace plugins (#28771)
## Summary - allow local marketplace `source.path: "."` and `source.path: "./"` to resolve to the marketplace root - keep `""` invalid and preserve rejection of non-root paths without `./` plus non-normal/traversal paths - add focused regression coverage for repo-root plugin layouts and rejected local paths ## Tests - `RUSTUP_TOOLCHAIN=stable just fmt` - `RUSTUP_TOOLCHAIN=stable just test -p codex-core-plugins` - `RUSTUP_TOOLCHAIN=stable just fix -p codex-core-plugins` Note: plain pinned-toolchain `just fmt` was blocked locally by a rustup `clippy` component conflict, so validation used the working stable 1.95 toolchain fallback.
This commit is contained in:
committed by
GitHub
Unverified
parent
a0586ad12d
commit
a760b63f83
@@ -544,20 +544,26 @@ fn resolve_local_plugin_source_path(
|
||||
marketplace_path: &AbsolutePathBuf,
|
||||
path: &str,
|
||||
) -> Result<AbsolutePathBuf, MarketplaceError> {
|
||||
let Some(path) = path.strip_prefix("./") else {
|
||||
match path {
|
||||
"" => {
|
||||
return Err(MarketplaceError::InvalidMarketplaceFile {
|
||||
path: marketplace_path.to_path_buf(),
|
||||
message: "local plugin source path must not be empty".to_string(),
|
||||
});
|
||||
}
|
||||
"." | "./" => return marketplace_root_dir(marketplace_path),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// Non-root local sources must keep the explicit `./` prefix and remain normalized.
|
||||
let Some(relative_path) = path.strip_prefix("./") else {
|
||||
return Err(MarketplaceError::InvalidMarketplaceFile {
|
||||
path: marketplace_path.to_path_buf(),
|
||||
message: "local plugin source path must start with `./`".to_string(),
|
||||
});
|
||||
};
|
||||
if path.is_empty() {
|
||||
return Err(MarketplaceError::InvalidMarketplaceFile {
|
||||
path: marketplace_path.to_path_buf(),
|
||||
message: "local plugin source path must not be empty".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
let relative_source_path = Path::new(path);
|
||||
let relative_source_path = Path::new(relative_path);
|
||||
if relative_source_path
|
||||
.components()
|
||||
.any(|component| !matches!(component, Component::Normal(_)))
|
||||
|
||||
@@ -420,6 +420,92 @@ fn list_marketplaces_supports_alternate_manifest_layout() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_marketplaces_supports_repo_root_local_plugin_sources() {
|
||||
for path in [".", "./"] {
|
||||
let tmp = tempdir().unwrap();
|
||||
let repo_root = tmp.path().join("repo");
|
||||
|
||||
fs::create_dir_all(repo_root.join(".git")).unwrap();
|
||||
fs::create_dir_all(repo_root.join(".agents/plugins")).unwrap();
|
||||
fs::create_dir_all(repo_root.join(".codex-plugin")).unwrap();
|
||||
fs::write(
|
||||
repo_root.join(".agents/plugins/marketplace.json"),
|
||||
format!(
|
||||
r#"{{
|
||||
"name": "repo-root-marketplace",
|
||||
"plugins": [
|
||||
{{
|
||||
"name": "repo-root-plugin",
|
||||
"source": {{
|
||||
"source": "local",
|
||||
"path": "{path}"
|
||||
}}
|
||||
}}
|
||||
]
|
||||
}}"#
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
fs::write(
|
||||
repo_root.join(".codex-plugin/plugin.json"),
|
||||
r#"{
|
||||
"name":"repo-root-plugin",
|
||||
"interface": {
|
||||
"displayName": "Repo Root Plugin"
|
||||
}
|
||||
}"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let marketplaces = list_marketplaces_with_home(
|
||||
&[AbsolutePathBuf::try_from(repo_root.clone()).unwrap()],
|
||||
/*home_dir*/ None,
|
||||
)
|
||||
.unwrap()
|
||||
.marketplaces;
|
||||
|
||||
assert_eq!(
|
||||
marketplaces,
|
||||
vec![Marketplace {
|
||||
name: "repo-root-marketplace".to_string(),
|
||||
path: AbsolutePathBuf::try_from(repo_root.join(".agents/plugins/marketplace.json"))
|
||||
.unwrap(),
|
||||
interface: None,
|
||||
plugins: vec![MarketplacePlugin {
|
||||
name: "repo-root-plugin".to_string(),
|
||||
local_version: None,
|
||||
source: MarketplacePluginSource::Local {
|
||||
path: AbsolutePathBuf::try_from(repo_root).unwrap(),
|
||||
},
|
||||
policy: MarketplacePluginPolicy {
|
||||
installation: MarketplacePluginInstallPolicy::Available,
|
||||
authentication: MarketplacePluginAuthPolicy::OnInstall,
|
||||
products: None,
|
||||
},
|
||||
interface: Some(PluginManifestInterface {
|
||||
display_name: Some("Repo Root Plugin".to_string()),
|
||||
short_description: None,
|
||||
long_description: None,
|
||||
developer_name: None,
|
||||
category: None,
|
||||
capabilities: Vec::new(),
|
||||
website_url: None,
|
||||
privacy_policy_url: None,
|
||||
terms_of_service_url: None,
|
||||
default_prompt: None,
|
||||
brand_color: None,
|
||||
composer_icon: None,
|
||||
logo: None,
|
||||
screenshots: Vec::new(),
|
||||
}),
|
||||
keywords: Vec::new(),
|
||||
}],
|
||||
}]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_marketplaces_includes_plugins_without_discoverable_manifest() {
|
||||
let tmp = tempdir().unwrap();
|
||||
@@ -1423,37 +1509,41 @@ fn list_marketplaces_ignores_plugin_interface_assets_without_dot_slash() {
|
||||
|
||||
#[test]
|
||||
fn find_marketplace_plugin_skips_invalid_local_paths() {
|
||||
let tmp = tempdir().unwrap();
|
||||
let repo_root = tmp.path().join("repo");
|
||||
fs::create_dir_all(repo_root.join(".git")).unwrap();
|
||||
fs::create_dir_all(repo_root.join(".agents/plugins")).unwrap();
|
||||
fs::write(
|
||||
repo_root.join(".agents/plugins/marketplace.json"),
|
||||
r#"{
|
||||
for path in ["", "plugin-1", "././", "./plugins/../", "../plugin-1"] {
|
||||
let tmp = tempdir().unwrap();
|
||||
let repo_root = tmp.path().join("repo");
|
||||
fs::create_dir_all(repo_root.join(".git")).unwrap();
|
||||
fs::create_dir_all(repo_root.join(".agents/plugins")).unwrap();
|
||||
fs::write(
|
||||
repo_root.join(".agents/plugins/marketplace.json"),
|
||||
format!(
|
||||
r#"{{
|
||||
"name": "codex-curated",
|
||||
"plugins": [
|
||||
{
|
||||
{{
|
||||
"name": "local-plugin",
|
||||
"source": {
|
||||
"source": {{
|
||||
"source": "local",
|
||||
"path": "../plugin-1"
|
||||
}
|
||||
}
|
||||
"path": "{path}"
|
||||
}}
|
||||
}}
|
||||
]
|
||||
}"#,
|
||||
)
|
||||
.unwrap();
|
||||
}}"#
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let err = find_marketplace_plugin(
|
||||
&AbsolutePathBuf::try_from(repo_root.join(".agents/plugins/marketplace.json")).unwrap(),
|
||||
"local-plugin",
|
||||
)
|
||||
.unwrap_err();
|
||||
let err = find_marketplace_plugin(
|
||||
&AbsolutePathBuf::try_from(repo_root.join(".agents/plugins/marketplace.json")).unwrap(),
|
||||
"local-plugin",
|
||||
)
|
||||
.unwrap_err();
|
||||
|
||||
assert_eq!(
|
||||
err.to_string(),
|
||||
"plugin `local-plugin` was not found in marketplace `codex-curated`"
|
||||
);
|
||||
assert_eq!(
|
||||
err.to_string(),
|
||||
"plugin `local-plugin` was not found in marketplace `codex-curated`"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user