mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-15 00:08:15 +00:00
Closes #2408 Plugin run targets were static argv wrappers: a plugin couldn't refuse a built-in Run/Test/Debug launch through its target (for example when its container project is stopped), so the wrapper started and could start the environment as a side effect. API 1.5 adds an optional run-target-validation export: after trust, ownership, process-grant and pin checks the host calls validate with the operation, bare program name and working directory; an error refuses the launch with the plugin's (prefixed, bounded) message and keeps the selected target. Plugins without the export behave as before. Calls wake reclaimed instances, use the host call timeout, are cancelled with the launch, and fail closed on trap/timeout; for remote projects validation runs on the server. SDK helpers, plugins/README.md and fixtures updated.
262 lines
8.1 KiB
Rust
262 lines
8.1 KiB
Rust
//! Runs, keyed terminals and plugin-owned content editors (API 1.4).
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
/// ContentAction data exchanged with the IDE.
|
|
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct ContentAction {
|
|
pub id: String,
|
|
pub label: String,
|
|
pub icon: Option<String>,
|
|
pub primary: bool,
|
|
pub save: bool,
|
|
}
|
|
|
|
/// ContentTab data exchanged with the IDE.
|
|
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct ContentTab {
|
|
pub key: String,
|
|
pub title: String,
|
|
pub file_name: Option<String>,
|
|
pub language: Option<String>,
|
|
pub content: String,
|
|
pub read_only: bool,
|
|
pub actions: Vec<ContentAction>,
|
|
}
|
|
|
|
/// ContentDiff data exchanged with the IDE.
|
|
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct ContentDiff {
|
|
pub left: String,
|
|
pub right: String,
|
|
pub left_label: Option<String>,
|
|
pub right_label: Option<String>,
|
|
pub title: Option<String>,
|
|
}
|
|
|
|
/// ContentTabReply data exchanged with the IDE.
|
|
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct ContentTabReply {
|
|
pub content: Option<String>,
|
|
pub saved: Option<bool>,
|
|
pub read_only: Option<bool>,
|
|
pub status: Option<String>,
|
|
pub error: Option<String>,
|
|
pub diff: Option<ContentDiff>,
|
|
}
|
|
|
|
/// PluginRunConfiguration data exchanged with the IDE.
|
|
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct PluginRunConfiguration {
|
|
pub id: String,
|
|
pub name: String,
|
|
pub program: String,
|
|
pub args: Vec<String>,
|
|
pub cwd: Option<String>,
|
|
pub test: bool,
|
|
pub framework: Option<String>,
|
|
}
|
|
|
|
/// RunExited data exchanged with the IDE.
|
|
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct RunExited {
|
|
pub id: String,
|
|
pub config: String,
|
|
pub status: Option<i32>,
|
|
pub killed: bool,
|
|
}
|
|
|
|
/// TerminalExited data exchanged with the IDE.
|
|
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct TerminalExited {
|
|
pub id: String,
|
|
pub key: String,
|
|
pub status: Option<i32>,
|
|
pub closed: bool,
|
|
}
|
|
|
|
/// ContentTabAction data exchanged with the IDE.
|
|
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct ContentTabAction {
|
|
pub request_id: String,
|
|
pub key: String,
|
|
pub action: String,
|
|
pub content: String,
|
|
}
|
|
|
|
/// Incremental test tree data.
|
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct TestStart {
|
|
pub id: String,
|
|
pub parent: Option<String>,
|
|
pub name: String,
|
|
pub location: Option<crate::Location>,
|
|
}
|
|
|
|
/// Incremental test tree data.
|
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct TestPassed {
|
|
pub id: String,
|
|
pub duration_ms: Option<u32>,
|
|
}
|
|
|
|
/// Incremental test tree data.
|
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct TestFailed {
|
|
pub id: String,
|
|
pub duration_ms: Option<u32>,
|
|
pub message: String,
|
|
pub expected: Option<String>,
|
|
pub actual: Option<String>,
|
|
pub location: Option<crate::Location>,
|
|
}
|
|
|
|
/// Incremental test tree data.
|
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct TestSkipped {
|
|
pub id: String,
|
|
pub reason: Option<String>,
|
|
}
|
|
|
|
/// Incremental test tree data.
|
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct TestOutput {
|
|
pub id: String,
|
|
pub text: String,
|
|
}
|
|
|
|
/// A test update, carried by the optional test-results export.
|
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
|
#[serde(tag = "type", rename_all = "camelCase")]
|
|
pub enum PluginTestEvent {
|
|
SuiteStarted(TestStart),
|
|
TestStarted(TestStart),
|
|
Passed(TestPassed),
|
|
Failed(TestFailed),
|
|
Skipped(TestSkipped),
|
|
Output(TestOutput),
|
|
Other { kind: String, data: String },
|
|
}
|
|
|
|
/// Supplies complete project configurations; register with run_configurations.
|
|
pub trait RunConfigurations {
|
|
fn configurations(&mut self, project_root: &str) -> Vec<PluginRunConfiguration>;
|
|
}
|
|
/// Parses bounded test output batches; register with test_results.
|
|
pub trait TestResults {
|
|
fn parse(&mut self, config: &str, chunk: &str) -> Vec<PluginTestEvent>;
|
|
/// Full replacement argv for test-tree name paths, or None when filtering is unsupported.
|
|
fn select_tests(&mut self, _config: &str, _tests: &[Vec<String>]) -> Option<Vec<String>> {
|
|
None
|
|
}
|
|
}
|
|
|
|
impl crate::Event {
|
|
/// Completion of a run started by this plugin, even after idle reclamation.
|
|
pub fn run_exited(&self) -> Option<RunExited> {
|
|
self.execution_event("run-exited")
|
|
}
|
|
/// Exactly one completion for a keyed terminal operation.
|
|
pub fn terminal_exited(&self) -> Option<TerminalExited> {
|
|
self.execution_event("terminal-exited")
|
|
}
|
|
/// A toolbar action or Save from this plugin's wasm content tab.
|
|
pub fn content_tab_action(&self) -> Option<ContentTabAction> {
|
|
self.execution_event("content-tab-action")
|
|
}
|
|
fn execution_event<T: serde::de::DeserializeOwned>(&self, expected: &str) -> Option<T> {
|
|
let Self::Other { kind, data } = self else { return None };
|
|
(kind == expected).then(|| serde_json::from_str(data).ok()).flatten()
|
|
}
|
|
}
|
|
|
|
/// A plugin-owned argv wrapper for project runs (API 1.4).
|
|
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct PluginRunTarget {
|
|
pub id: String,
|
|
pub name: String,
|
|
pub program: String,
|
|
pub args: Vec<String>,
|
|
pub workdir_args: Vec<String>,
|
|
pub container_root: Option<String>,
|
|
pub commands: Vec<(String, Vec<String>)>,
|
|
}
|
|
|
|
/// Per-command argv prefix and optional working-directory arguments (API 1.5).
|
|
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct PluginCommandOverride {
|
|
pub name: String,
|
|
pub args: Vec<String>,
|
|
/// None inherits the target's list; Some(Vec::new()) suppresses it.
|
|
#[serde(default)]
|
|
pub workdir_args: Option<Vec<String>>,
|
|
}
|
|
|
|
/// A project run target with per-command working-directory control (API 1.5).
|
|
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct PluginRunTargetWithOverrides {
|
|
pub id: String,
|
|
pub name: String,
|
|
pub program: String,
|
|
pub args: Vec<String>,
|
|
pub workdir_args: Vec<String>,
|
|
pub container_root: Option<String>,
|
|
pub commands: Vec<PluginCommandOverride>,
|
|
}
|
|
|
|
impl From<PluginRunTarget> for PluginRunTargetWithOverrides {
|
|
fn from(target: PluginRunTarget) -> Self {
|
|
Self {
|
|
id: target.id,
|
|
name: target.name,
|
|
program: target.program,
|
|
args: target.args,
|
|
workdir_args: target.workdir_args,
|
|
container_root: target.container_root,
|
|
commands: target
|
|
.commands
|
|
.into_iter()
|
|
.map(|(name, args)| PluginCommandOverride { name, args, workdir_args: None })
|
|
.collect(),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Operation about to launch through a plugin-owned run target (API 1.5).
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
pub enum RunTargetOperation {
|
|
Run,
|
|
Debug,
|
|
Test,
|
|
}
|
|
|
|
/// Launch metadata available to a target validator; arguments and environment are omitted.
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
pub struct RunTargetLaunch {
|
|
pub operation: RunTargetOperation,
|
|
pub program: String,
|
|
pub cwd: String,
|
|
}
|
|
|
|
/// Optional pre-launch validation; register with `run_target_validation`.
|
|
pub trait RunTargetValidation {
|
|
/// Refuse with a user-facing explanation, or allow with `Ok(())`.
|
|
fn validate(&mut self, project: &str, target_id: &str, launch: &RunTargetLaunch) -> Result<(), String>;
|
|
}
|