mizan-webview-transport + webview-channels: VSCode webview as Mizan frontend
Two new frontend packages let a VSCode webview consume a Mizan backend
through its postMessage channel — peer transports to `mizan-tauri-transport`
and the default `httpTransport()`.
- `@mizan/webview-transport` implements `MizanTransport` (call/fetch)
over postMessage with correlation-id pairing. Drop-in for `configure({
transport: webviewTransport() })`; codegen output and React adapter
are unchanged.
- `@mizan/webview-channels` mirrors mizan-react's WebSocket-based
ChannelConnection — RPC + subscribe over the same postMessage channel
for long-running ops where short request/reply isn't enough.
Both expect an extension-host-side dispatcher that reads envelopes via
`webview.onDidReceiveMessage` and routes them through mizan-ts's
`handleMutationCall` / `handleContextFetch`. First consumer is the
holomorphic VSCode extension.
mizan-codegen: new `[source.script]` generic source. Spawns an arbitrary
command and reads stdout as KDL IR. Keeps mizan-codegen out of the
business of knowing every possible backend language while preserving
the "subprocess emits KDL" contract every other source already follows.
Holomorphic uses it to invoke `python -m holomorphic.emit_ir` against
the mizan_core registry.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -69,6 +69,16 @@ pub struct SourceConfig {
|
||||
/// usage (no Pydantic) just omits the sub-block.
|
||||
#[serde(default)]
|
||||
pub rust: Option<RustSource>,
|
||||
|
||||
/// `[source.script]` — generic source. Spawn an arbitrary command and
|
||||
/// read its stdout as KDL IR. Use when none of the language-specific
|
||||
/// sources fit — e.g. a Python module that walks `mizan_core.registry`
|
||||
/// for a non-Django/non-FastAPI consumer, or a custom IR emitter.
|
||||
/// Keeps mizan-codegen out of the business of knowing every possible
|
||||
/// backend language while preserving the "subprocess emits KDL"
|
||||
/// contract every other source already follows.
|
||||
#[serde(default)]
|
||||
pub script: Option<ScriptSource>,
|
||||
}
|
||||
|
||||
|
||||
@@ -206,6 +216,35 @@ fn default_pydantic_derives() -> Vec<String> {
|
||||
}
|
||||
|
||||
|
||||
/// `[source.script]` — generic stdout-of-arbitrary-command source.
|
||||
///
|
||||
/// Spawns `command` with `args`, reads its stdout, and parses it as KDL
|
||||
/// Mizan IR. The same contract every other source follows; this one just
|
||||
/// doesn't bake in any language-specific assumptions.
|
||||
///
|
||||
/// Example:
|
||||
///
|
||||
/// ```toml
|
||||
/// [source.script]
|
||||
/// command = ["uv", "run", "python", "-m", "holomorphic.emit_ir"]
|
||||
/// ```
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct ScriptSource {
|
||||
/// Full command vector. First entry is the program; rest are argv.
|
||||
/// Must be non-empty.
|
||||
pub command: Vec<String>,
|
||||
|
||||
/// Working directory for the subprocess, relative to the codegen
|
||||
/// config directory. Defaults to the config directory itself.
|
||||
#[serde(default)]
|
||||
pub cwd: Option<PathBuf>,
|
||||
|
||||
/// Environment overrides.
|
||||
#[serde(default)]
|
||||
pub env: BTreeMap<String, String>,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
#[serde(untagged)]
|
||||
pub enum RustKernelSpec {
|
||||
|
||||
@@ -21,7 +21,7 @@ use std::process::{Command, Stdio};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use serde_json::json;
|
||||
|
||||
use crate::config::{Config, DjangoSource, FastapiSource, PydanticPreStep, RustSource};
|
||||
use crate::config::{Config, DjangoSource, FastapiSource, PydanticPreStep, RustSource, ScriptSource};
|
||||
use crate::ir::{parse_ir, MizanIR};
|
||||
|
||||
|
||||
@@ -43,9 +43,11 @@ pub fn fetch_schema(config: &Config, config_dir: &Path) -> Result<MizanIR> {
|
||||
run_fastapi(fa, config_dir)?
|
||||
} else if let Some(dj) = &config.source.django {
|
||||
run_django(dj, config_dir)?
|
||||
} else if let Some(sc) = &config.source.script {
|
||||
run_script(sc, config_dir)?
|
||||
} else {
|
||||
return Err(anyhow!(
|
||||
"config.source must declare one of [source.rust], [source.fastapi], or [source.django]"
|
||||
"config.source must declare one of [source.rust], [source.fastapi], [source.django], or [source.script]"
|
||||
));
|
||||
};
|
||||
|
||||
@@ -70,6 +72,18 @@ fn run_fastapi(src: &FastapiSource, config_dir: &Path) -> Result<String> {
|
||||
}
|
||||
|
||||
|
||||
fn run_script(src: &ScriptSource, config_dir: &Path) -> Result<String> {
|
||||
let cwd = match &src.cwd {
|
||||
Some(rel) => config_dir.join(rel),
|
||||
None => config_dir.to_path_buf(),
|
||||
};
|
||||
let (program, args) = src.command.split_first().ok_or_else(|| {
|
||||
anyhow!("[source.script]: command must be non-empty")
|
||||
})?;
|
||||
run_subprocess(program, args, &cwd, &src.env, "script IR export")
|
||||
}
|
||||
|
||||
|
||||
fn run_django(src: &DjangoSource, config_dir: &Path) -> Result<String> {
|
||||
let manage_path = config_dir.join(&src.manage_path);
|
||||
let manage_dir = manage_path
|
||||
|
||||
Reference in New Issue
Block a user