feat: update plugin service authoring templates
This commit is contained in:
@@ -727,7 +727,7 @@ fn parse_plugin_pack_args(
|
||||
}
|
||||
|
||||
fn plugin_usage() -> &'static str {
|
||||
"usage: yoi plugin new rust-component-tool <path-or-name> [--json]\n yoi plugin check <path-or-package> [--json]\n yoi plugin pack <path> [--output <file>] [--json]\n yoi plugin list [--workspace PATH] [--profile REF] [--json]\n yoi plugin show <ref> [--workspace PATH] [--profile REF] [--json]"
|
||||
"usage: yoi plugin new <rust-component-tool|rust-component-service> <path-or-name> [--json]\n yoi plugin check <path-or-package> [--json]\n yoi plugin pack <path> [--output <file>] [--json]\n yoi plugin list [--workspace PATH] [--profile REF] [--json]\n yoi plugin show <ref> [--workspace PATH] [--profile REF] [--json]"
|
||||
}
|
||||
|
||||
fn parse_mcp_args(args: &[String]) -> Result<mcp_cli::McpCliCommand, ParseError> {
|
||||
@@ -901,7 +901,7 @@ fn parse_session_id(value: &str) -> Result<SegmentId, ParseError> {
|
||||
|
||||
fn print_help() {
|
||||
println!(
|
||||
"yoi\n\nUsage:\n yoi [OPTIONS]\n yoi resume [--workspace <PATH>] [--all]\n yoi panel [--workspace <PATH>]\n yoi keys\n yoi setup-model\n yoi pod [POD_OPTIONS]\n yoi pod delete <NAME> [--force] [--dry-run]\n yoi pod prune --older-than <DURATION> [--force] [--dry-run]\n yoi objective <COMMAND> [OPTIONS]\n yoi session analyze <SESSION_JSONL_PATH> --json\n yoi session prune --unreferenced [--older-than <DURATION>] [--force] [--dry-run]\n yoi ticket <COMMAND> [OPTIONS]\n yoi workspace serve [OPTIONS]\n yoi plugin new rust-component-tool <PATH> [--json]\n yoi plugin check <PATH_OR_PACKAGE> [--json]\n yoi plugin pack <PATH> [--output <FILE>] [--json]\n yoi plugin list [--workspace <PATH>] [--profile <REF>] [--json]\n yoi plugin show <REF> [--workspace <PATH>] [--profile <REF>] [--json]\n yoi mcp list [--workspace <PATH>] [--profile <REF>] [--json]\n yoi mcp show <SERVER> [--workspace <PATH>] [--profile <REF>] [--json]\n yoi mcp tools|resources|prompts [SERVER] [--workspace <PATH>] [--profile <REF>] [--json]\n yoi memory lint [OPTIONS]\n\nSurfaces:\n Console Single-Pod chat/client surface (default, --pod, yoi resume)\n Dashboard Workspace cockpit/action surface (yoi panel)\n TUI Terminal UI implementation umbrella for Console and Dashboard\n\nOptions:\n --workspace <PATH> Runtime workspace root for default Console/--pod (defaults to cwd)\n --pod <NAME> Open the Pod Console by name (attach/restore/create)\n --socket <PATH> Attach a Pod Console to a specific socket with --pod\n --session <UUID> Resume a specific session segment in the Pod Console\n --profile <REF> Select a reusable Profile recipe\n -h, --help Print help\n"
|
||||
"yoi\n\nUsage:\n yoi [OPTIONS]\n yoi resume [--workspace <PATH>] [--all]\n yoi panel [--workspace <PATH>]\n yoi keys\n yoi setup-model\n yoi pod [POD_OPTIONS]\n yoi pod delete <NAME> [--force] [--dry-run]\n yoi pod prune --older-than <DURATION> [--force] [--dry-run]\n yoi objective <COMMAND> [OPTIONS]\n yoi session analyze <SESSION_JSONL_PATH> --json\n yoi session prune --unreferenced [--older-than <DURATION>] [--force] [--dry-run]\n yoi ticket <COMMAND> [OPTIONS]\n yoi workspace serve [OPTIONS]\n yoi plugin new <rust-component-tool|rust-component-service> <PATH> [--json]\n yoi plugin check <PATH_OR_PACKAGE> [--json]\n yoi plugin pack <PATH> [--output <FILE>] [--json]\n yoi plugin list [--workspace <PATH>] [--profile <REF>] [--json]\n yoi plugin show <REF> [--workspace <PATH>] [--profile <REF>] [--json]\n yoi mcp list [--workspace <PATH>] [--profile <REF>] [--json]\n yoi mcp show <SERVER> [--workspace <PATH>] [--profile <REF>] [--json]\n yoi mcp tools|resources|prompts [SERVER] [--workspace <PATH>] [--profile <REF>] [--json]\n yoi memory lint [OPTIONS]\n\nSurfaces:\n Console Single-Pod chat/client surface (default, --pod, yoi resume)\n Dashboard Workspace cockpit/action surface (yoi panel)\n TUI Terminal UI implementation umbrella for Console and Dashboard\n\nOptions:\n --workspace <PATH> Runtime workspace root for default Console/--pod (defaults to cwd)\n --pod <NAME> Open the Pod Console by name (attach/restore/create)\n --socket <PATH> Attach a Pod Console to a specific socket with --pod\n --session <UUID> Resume a specific session segment in the Pod Console\n --profile <REF> Select a reusable Profile recipe\n -h, --help Print help\n"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,10 +9,10 @@ use manifest::plugin::{
|
||||
MaterializedPluginPackage, PluginConfig, PluginDiagnostic, PluginDiagnosticKind,
|
||||
PluginDiagnosticPhase, PluginDiscoveryLimits, PluginDiscoveryOptions, PluginDiscoveryReport,
|
||||
PluginExactVersion, PluginGrantConfig, PluginPackageManifest, PluginPermission,
|
||||
PluginResolution, PluginSourceKind, PluginSurface, RUST_COMPONENT_TOOL_TEMPLATE,
|
||||
ResolvedPlugin, ResolvedPluginRecord, SourceQualifiedPluginId, discover_plugins,
|
||||
read_plugin_directory, read_plugin_package_file, resolve_enabled_plugins,
|
||||
write_plugin_package_file,
|
||||
PluginResolution, PluginSourceKind, PluginSurface, PluginTemplateResource,
|
||||
RUST_COMPONENT_INSTANCE_TEMPLATE, RUST_COMPONENT_TOOL_TEMPLATE, ResolvedPlugin,
|
||||
ResolvedPluginRecord, SourceQualifiedPluginId, discover_plugins, read_plugin_directory,
|
||||
read_plugin_package_file, resolve_enabled_plugins, write_plugin_package_file,
|
||||
};
|
||||
use manifest::{ProfileResolveOptions, ProfileResolver, ProfileSelector, paths};
|
||||
use pod::feature::plugin::{PluginStaticInspection, inspect_resolved_plugin_static};
|
||||
@@ -85,27 +85,29 @@ pub(crate) fn run(command: PluginCliCommand) -> Result<()> {
|
||||
}
|
||||
|
||||
fn render_new(template: &str, destination: &Path, args: &PluginCliArgs) -> Result<String> {
|
||||
if template != "rust-component-tool" {
|
||||
return Err(format!(
|
||||
"unsupported plugin template `{template}` (supported: rust-component-tool)"
|
||||
)
|
||||
.into());
|
||||
let (template_name, resources) = embedded_template_resources(template)?;
|
||||
materialize_template(destination, resources)?;
|
||||
let mut next_steps = vec![
|
||||
"Review plugin.toml and generated Rust source.".to_string(),
|
||||
"Replace the placeholder plugin.component.wasm with a real built component before enabling or execution.".to_string(),
|
||||
"Run `yoi plugin check <path>` and then `yoi plugin pack <path>`.".to_string(),
|
||||
];
|
||||
if template == "rust-component-service" {
|
||||
next_steps.insert(
|
||||
1,
|
||||
"Implement Service ingress logic in handle_ingress and return ServiceOutput output_commands for host-owned WebSocket sends.".to_string(),
|
||||
);
|
||||
}
|
||||
materialize_template(destination)?;
|
||||
let report = NewReport {
|
||||
command: "new",
|
||||
template: "rust-component-tool",
|
||||
template: template_name,
|
||||
destination: destination.display().to_string(),
|
||||
files: RUST_COMPONENT_TOOL_TEMPLATE
|
||||
files: resources
|
||||
.iter()
|
||||
.map(|resource| resource.path.to_string())
|
||||
.collect(),
|
||||
safety: AuthoringSafetyReport::default(),
|
||||
next_steps: vec![
|
||||
"Review plugin.toml and generated Rust source.".to_string(),
|
||||
"Replace the placeholder plugin.component.wasm with a real built component before enabling or execution.".to_string(),
|
||||
"Run `yoi plugin check <path>` and then `yoi plugin pack <path>`.".to_string(),
|
||||
],
|
||||
next_steps,
|
||||
};
|
||||
if args.json {
|
||||
return Ok(format!("{}\n", serde_json::to_string_pretty(&report)?));
|
||||
@@ -113,7 +115,23 @@ fn render_new(template: &str, destination: &Path, args: &PluginCliArgs) -> Resul
|
||||
render_new_human(&report)
|
||||
}
|
||||
|
||||
fn materialize_template(destination: &Path) -> Result<()> {
|
||||
fn embedded_template_resources(
|
||||
template: &str,
|
||||
) -> Result<(&'static str, &'static [PluginTemplateResource])> {
|
||||
match template {
|
||||
"rust-component-tool" => Ok(("rust-component-tool", RUST_COMPONENT_TOOL_TEMPLATE)),
|
||||
"rust-component-service" => Ok(("rust-component-service", RUST_COMPONENT_INSTANCE_TEMPLATE)),
|
||||
_ => Err(format!(
|
||||
"unsupported plugin template `{template}` (supported: rust-component-tool, rust-component-service)"
|
||||
)
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn materialize_template(
|
||||
destination: &Path,
|
||||
resources: &'static [PluginTemplateResource],
|
||||
) -> Result<()> {
|
||||
match fs::symlink_metadata(destination) {
|
||||
Ok(metadata) => {
|
||||
if metadata.file_type().is_symlink() {
|
||||
@@ -144,7 +162,7 @@ fn materialize_template(destination: &Path) -> Result<()> {
|
||||
Err(error) => return Err(error.into()),
|
||||
}
|
||||
|
||||
for resource in RUST_COMPONENT_TOOL_TEMPLATE {
|
||||
for resource in resources {
|
||||
let relative = safe_template_relative_path(resource.path)?;
|
||||
let path = destination.join(relative);
|
||||
if let Some(parent) = path.parent() {
|
||||
@@ -2136,6 +2154,61 @@ mod tests {
|
||||
let human_check = render_check(&destination, &PluginCliArgs::default()).unwrap();
|
||||
assert!(human_check.contains("[partial]"));
|
||||
assert!(human_check.contains("not ready to enable"));
|
||||
|
||||
let service_destination = dir.path().join("my-service-plugin");
|
||||
let service_json = render_new(
|
||||
"rust-component-service",
|
||||
&service_destination,
|
||||
&PluginCliArgs {
|
||||
json: true,
|
||||
..PluginCliArgs::default()
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let service_value: serde_json::Value = serde_json::from_str(&service_json).unwrap();
|
||||
assert_eq!(service_value["template"], "rust-component-service");
|
||||
assert!(
|
||||
service_value["next_steps"]
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.any(|step| step
|
||||
.as_str()
|
||||
.unwrap_or_default()
|
||||
.contains("Service ingress"))
|
||||
);
|
||||
for resource in RUST_COMPONENT_INSTANCE_TEMPLATE {
|
||||
assert!(
|
||||
service_destination.join(resource.path).is_file(),
|
||||
"missing service {}",
|
||||
resource.path
|
||||
);
|
||||
}
|
||||
let manifest = fs::read_to_string(service_destination.join("plugin.toml")).unwrap();
|
||||
assert!(manifest.contains("kind = \"wasm-component\""));
|
||||
assert!(manifest.contains("[[services]]"));
|
||||
assert!(manifest.contains("[[ingresses]]"));
|
||||
let source = fs::read_to_string(service_destination.join("src/lib.rs")).unwrap();
|
||||
assert!(source.contains("ServiceOutput::websocket_send"));
|
||||
assert!(!source.contains("recv(timeout"));
|
||||
let service_check = render_check(&service_destination, &PluginCliArgs::default()).unwrap();
|
||||
assert!(service_check.contains("plugin check:"));
|
||||
assert!(service_check.contains("service"));
|
||||
let service_package = dir.path().join("my-service-plugin.yoi-plugin");
|
||||
let service_pack_json = render_pack(
|
||||
&service_destination,
|
||||
Some(&service_package),
|
||||
&PluginCliArgs {
|
||||
json: true,
|
||||
..PluginCliArgs::default()
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let service_pack_value: serde_json::Value =
|
||||
serde_json::from_str(&service_pack_json).unwrap();
|
||||
assert_eq!(service_pack_value["status"], "packed");
|
||||
assert!(service_package.is_file());
|
||||
|
||||
let error = render_new(
|
||||
"rust-component-tool",
|
||||
&destination,
|
||||
|
||||
Reference in New Issue
Block a user