forked from 1b-agent/opencadstudio-upstream
feat(plugins): redesign plugin manager
This commit is contained in:
1 parent
d80637cda7
commit
4e25e88c31
11 files changed
+741
-74
No files matched your search
@@ -30,6 +30,10 @@ pub struct ExternalPlugin {
|
||||
pub name: String,
|
||||
pub version: String,
|
||||
pub description: String,
|
||||
/// GitHub source in `owner/repo` form. New marketplace installs persist
|
||||
/// this beside the manifest so their README remains discoverable even when
|
||||
/// an older `plugin.toml` does not declare `repository`.
|
||||
pub repository: Option<String>,
|
||||
pub api_version: u32,
|
||||
pub ribbon_order: i32,
|
||||
pub command_prefixes: Vec<String>,
|
||||
@@ -125,6 +129,11 @@ pub fn discover() -> Vec<ExternalPlugin> {
|
||||
continue;
|
||||
};
|
||||
if let Some(mut p) = parse_plugin_toml(&text) {
|
||||
if p.repository.is_none() {
|
||||
p.repository = std::fs::read_to_string(dir.join(".source_repo"))
|
||||
.ok()
|
||||
.and_then(|repo| normalize_repository(&repo));
|
||||
}
|
||||
p.lib_present = lib_present_in(&dir);
|
||||
p.dir = dir;
|
||||
found.push(p);
|
||||
@@ -156,6 +165,7 @@ pub(crate) fn parse_plugin_toml(text: &str) -> Option<ExternalPlugin> {
|
||||
let mut name = String::new();
|
||||
let mut version = String::new();
|
||||
let mut description = String::new();
|
||||
let mut repository = None;
|
||||
let mut api_version: u32 = 0;
|
||||
let mut ribbon_order: i32 = 0;
|
||||
let mut command_prefixes: Vec<String> = Vec::new();
|
||||
@@ -175,6 +185,7 @@ pub(crate) fn parse_plugin_toml(text: &str) -> Option<ExternalPlugin> {
|
||||
"name" => name = unquote(value),
|
||||
"version" => version = unquote(value),
|
||||
"description" => description = unquote(value),
|
||||
"repository" => repository = normalize_repository(&unquote(value)),
|
||||
"api_version" => api_version = value.parse().unwrap_or(0),
|
||||
"ribbon_order" => ribbon_order = value.parse().unwrap_or(0),
|
||||
"command_prefixes" => command_prefixes = parse_string_array(value),
|
||||
@@ -187,6 +198,7 @@ pub(crate) fn parse_plugin_toml(text: &str) -> Option<ExternalPlugin> {
|
||||
name,
|
||||
version,
|
||||
description,
|
||||
repository,
|
||||
api_version,
|
||||
ribbon_order,
|
||||
command_prefixes,
|
||||
@@ -195,6 +207,26 @@ pub(crate) fn parse_plugin_toml(text: &str) -> Option<ExternalPlugin> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Convert a plugin source URL or shorthand to the marketplace's canonical
|
||||
/// `owner/repo` form.
|
||||
pub(crate) fn normalize_repository(value: &str) -> Option<String> {
|
||||
let repo = value
|
||||
.trim()
|
||||
.trim_start_matches("https://github.com/")
|
||||
.trim_start_matches("http://github.com/")
|
||||
.trim_end_matches('/')
|
||||
.trim_end_matches(".git");
|
||||
let mut parts = repo.split('/');
|
||||
let (Some(owner), Some(name), None) = (parts.next(), parts.next(), parts.next()) else {
|
||||
return None;
|
||||
};
|
||||
if owner.is_empty() || name.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(format!("{owner}/{name}"))
|
||||
}
|
||||
}
|
||||
|
||||
/// Strip surrounding single or double quotes from a TOML scalar.
|
||||
fn unquote(s: &str) -> String {
|
||||
let s = s.trim();
|
||||
@@ -321,6 +353,7 @@ id = "opencad.my_plugin"
|
||||
name = "My Plugin"
|
||||
version = "0.1.0"
|
||||
description = "Template plugin"
|
||||
repository = "https://github.com/example/opencad-my-plugin.git"
|
||||
|
||||
[opencad]
|
||||
api_version = 2
|
||||
@@ -330,6 +363,7 @@ xdata_apps = ["MYPLUGIN_RECORD"]
|
||||
"#;
|
||||
let p = parse_plugin_toml(toml).expect("parsed");
|
||||
assert_eq!(p.api_version, 2);
|
||||
assert_eq!(p.repository.as_deref(), Some("example/opencad-my-plugin"));
|
||||
assert!(p.command_prefixes.contains(&"MP_".to_string()));
|
||||
assert!(p.api_compatible(), "API v2 plugins must run on API v3 host");
|
||||
}
|
||||
|
||||
@@ -148,6 +148,20 @@ pub fn fetch_releases(repo: &str) -> Result<Vec<Release>, String> {
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
/// Fetch the repository README from its default branch as raw Markdown.
|
||||
pub fn fetch_readme(repo: &str) -> Result<String, String> {
|
||||
let url = format!("https://api.github.com/repos/{repo}/readme");
|
||||
agent()
|
||||
.get(&url)
|
||||
.header("User-Agent", UA)
|
||||
.header("Accept", "application/vnd.github.raw+json")
|
||||
.call()
|
||||
.map_err(|e| e.to_string())?
|
||||
.body_mut()
|
||||
.read_to_string()
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
fn download_string(url: &str) -> Result<String, String> {
|
||||
agent()
|
||||
.get(url)
|
||||
@@ -176,7 +190,7 @@ fn download_bytes(url: &str) -> Result<Vec<u8>, String> {
|
||||
/// Download and install a release's package into the plugins folder. Verifies
|
||||
/// the API version from the package's `plugin.toml` first. Returns the plugin
|
||||
/// id on success.
|
||||
pub fn install(release: &Release) -> Result<String, String> {
|
||||
pub fn install(release: &Release, repository: &str) -> Result<String, String> {
|
||||
let lib = release.lib_asset().ok_or("no library for this platform")?;
|
||||
let toml = release.toml_asset().ok_or("release has no plugin.toml")?;
|
||||
|
||||
@@ -199,6 +213,8 @@ pub fn install(release: &Release) -> Result<String, String> {
|
||||
let bytes = download_bytes(&lib.url)?;
|
||||
replace_library(&dir, &lib.name, external_lib_ext(), &bytes)?;
|
||||
std::fs::write(dir.join("plugin.toml"), toml_text).map_err(|e| e.to_string())?;
|
||||
std::fs::write(dir.join(".source_repo"), format!("{repository}\n"))
|
||||
.map_err(|e| e.to_string())?;
|
||||
Ok(manifest.id)
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user