Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 23 additions & 13 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,13 @@ on:
- 'docs/**'
- 'mkdocs.yml'
- 'scripts/generate_supported_formats_doc.py'
- 'src/audio/format.rs'
- 'src/graph/format.rs'
- 'src/image/format.rs'
- 'src/input_format.rs'
- 'scripts/generate_tool_registry_doc.py'
- 'crates/renderflow-core/data/tool-registry.yaml'
- 'crates/renderflow-core/src/audio/format.rs'
- 'crates/renderflow-core/src/graph/format.rs'
- 'crates/renderflow-core/src/image/format.rs'
- 'crates/renderflow-core/src/input_format.rs'
- 'crates/renderflow-core/src/toolchain.rs'
push:
branches:
- main
Expand All @@ -23,10 +26,13 @@ on:
- 'docs/**'
- 'mkdocs.yml'
- 'scripts/generate_supported_formats_doc.py'
- 'src/audio/format.rs'
- 'src/graph/format.rs'
- 'src/image/format.rs'
- 'src/input_format.rs'
- 'scripts/generate_tool_registry_doc.py'
- 'crates/renderflow-core/data/tool-registry.yaml'
- 'crates/renderflow-core/src/audio/format.rs'
- 'crates/renderflow-core/src/graph/format.rs'
- 'crates/renderflow-core/src/image/format.rs'
- 'crates/renderflow-core/src/input_format.rs'
- 'crates/renderflow-core/src/toolchain.rs'
workflow_dispatch:

permissions:
Expand Down Expand Up @@ -55,13 +61,15 @@ jobs:
- name: Install MkDocs Material
run: |
python -m pip install --upgrade pip
pip install mkdocs-material mike
pip install mkdocs-material mike pyyaml

- name: Regenerate generated docs
run: python scripts/generate_supported_formats_doc.py
run: |
python scripts/generate_supported_formats_doc.py
python scripts/generate_tool_registry_doc.py

- name: Verify generated docs are committed
run: git diff --exit-code -- docs/user-guide/supported-formats.md
run: git diff --exit-code -- docs/user-guide/supported-formats.md docs/user-guide/tool-registry.md

- name: Build docs
run: mkdocs build --strict
Expand Down Expand Up @@ -91,10 +99,12 @@ jobs:
- name: Install MkDocs Material
run: |
python -m pip install --upgrade pip
pip install mkdocs-material mike
pip install mkdocs-material mike pyyaml

- name: Regenerate generated docs
run: python scripts/generate_supported_formats_doc.py
run: |
python scripts/generate_supported_formats_doc.py
python scripts/generate_tool_registry_doc.py

- name: Configure Pages
uses: actions/configure-pages@v5
Expand Down
161 changes: 161 additions & 0 deletions crates/renderflow-core/data/tool-registry.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
schema: renderflow.tool-registry/v1

tools:
- id: tool.pandoc
name: Pandoc
discovery:
kind: executable
candidates: [pandoc]
version_args: [--version]
version:
min_inclusive: "2.0.0"
operating_systems: [linux, macos, windows]
capabilities:
- document.convert
- document.generate
input_media_types:
- text/markdown
- text/html
- text/x-rst
- application/epub+zip
- application/x-tex
output_media_types:
- text/html
- application/pdf
- application/vnd.openxmlformats-officedocument.wordprocessingml.document
- application/epub+zip
- application/x-tex
determinism: configuration_dependent
locality: local
fidelity: path_dependent
support_tier: required
license_notes: "See the Pandoc upstream license for redistribution terms."
distribution_notes: "Typically installed through the host package manager or official Pandoc release artifacts."

- id: tool.tectonic
name: Tectonic
discovery:
kind: executable
candidates: [tectonic]
version_args: [--version]
operating_systems: [linux, macos, windows]
capabilities:
- pdf.typeset
- latex.compile
input_media_types:
- application/x-tex
- text/x-tex
output_media_types:
- application/pdf
determinism: configuration_dependent
locality: network_optional
fidelity: path_dependent
support_tier: optional
license_notes: "See the Tectonic upstream license for redistribution terms."
distribution_notes: "Optional unless a selected PDF path uses Tectonic."

- id: tool.ffmpeg
name: FFmpeg
discovery:
kind: executable
candidates: [ffmpeg]
version_args: [--version]
version:
min_inclusive: "4.0.0"
operating_systems: [linux, macos, windows]
capabilities:
- media.convert
- audio.convert
- image.convert
- video.convert
input_media_types:
- audio/*
- image/*
- video/*
output_media_types:
- audio/*
- image/*
- video/*
determinism: configuration_dependent
locality: local
fidelity: path_dependent
support_tier: optional
license_notes: "FFmpeg licensing is build-dependent; consult the exact distributed build."
distribution_notes: "Used by Renderflow audio, image, and future video adapters."

- id: tool.wkhtmltopdf
name: wkhtmltopdf
discovery:
kind: executable
candidates: [wkhtmltopdf]
version_args: [--version]
operating_systems: [linux, macos, windows]
capabilities:
- html.render.pdf
input_media_types: [text/html]
output_media_types: [application/pdf]
determinism: configuration_dependent
locality: local
fidelity: partial_loss
support_tier: experimental
license_notes: "Consult upstream wkhtmltopdf licensing and bundled Qt terms."
distribution_notes: "Alternative HTML-to-PDF provider; not required by the default document pipeline."

- id: tool.zip
name: Info-ZIP compatible zip
discovery:
kind: executable
candidates: [zip]
version_args: [--version]
operating_systems: [linux, macos, windows]
capabilities:
- archive.zip.create
- comic.cbz.create
output_media_types:
- application/zip
- application/vnd.comicbook+zip
determinism: configuration_dependent
locality: local
fidelity: lossless
support_tier: experimental
license_notes: "License depends on the compatible zip implementation selected on PATH."
distribution_notes: "Used by command-backed CBZ aggregation when configured."

- id: tool.img2pdf
name: img2pdf
discovery:
kind: executable
candidates: [img2pdf]
version_args: [--version]
operating_systems: [linux, macos, windows]
capabilities:
- image.aggregate.pdf
input_media_types: [image/*]
output_media_types: [application/pdf]
determinism: deterministic
locality: local
fidelity: lossless
support_tier: experimental
license_notes: "Consult the img2pdf upstream license for redistribution terms."
distribution_notes: "Used by lossless image-to-PDF aggregation when configured."

- id: tool.ghostscript
name: Ghostscript
discovery:
kind: executable
candidates: [gs]
version_args: [--version]
operating_systems: [linux, macos, windows]
capabilities:
- pdf.process
- tiff.aggregate.press_pdf
input_media_types:
- image/tiff
- application/pdf
output_media_types: [application/pdf]
determinism: configuration_dependent
locality: local
fidelity: path_dependent
support_tier: experimental
license_notes: "Ghostscript is available under AGPL/commercial licensing; review distribution obligations."
distribution_notes: "Used by press-oriented TIFF/PDF command adapters when configured."
6 changes: 5 additions & 1 deletion crates/renderflow-core/src/adapters/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,11 @@ pub fn run_command(program: &str, args: &[&str]) -> Result<()> {
}

result.ensure_success()?;
info!(program = program, duration_ms = result.duration_ms(), "Command completed successfully");
info!(
program = program,
duration_ms = result.duration_ms(),
"Command completed successfully"
);
Ok(())
}

Expand Down
15 changes: 14 additions & 1 deletion crates/renderflow-core/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use anyhow::{bail, Result};
use clap::Parser;
use tracing::info;

use crate::cli::{AiCommands, Cli, Commands, GraphCommands, PluginCommands};
use crate::cli::{AiCommands, Cli, Commands, GraphCommands, PluginCommands, ToolCommands};
use crate::{commands, transforms};

/// Initialize logging for a Renderflow CLI run.
Expand Down Expand Up @@ -133,6 +133,19 @@ pub fn run_cli(cli: Cli) -> Result<()> {
optimization,
} => commands::graph::run_stats(&config, target.as_deref(), optimization)?,
},
Some(Commands::Tools { subcommand }) => match subcommand {
ToolCommands::List { format, transforms } => {
commands::tools::run_list(transforms.as_deref(), &format)?
}
ToolCommands::Inspect {
id,
format,
transforms,
} => commands::tools::run_inspect(&id, transforms.as_deref(), &format)?,
},
Some(Commands::Capabilities { format, transforms }) => {
commands::tools::run_capabilities(transforms.as_deref(), &format)?
}
Some(Commands::Version) => commands::system::run_version(),
Some(Commands::Env) => commands::system::run_env(),
Some(Commands::Doctor { strict }) => commands::system::run_doctor(strict)?,
Expand Down
11 changes: 9 additions & 2 deletions crates/renderflow-core/src/artifact/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,15 +96,22 @@ pub(crate) fn save_artifact_cache(cache: &ArtifactCache, path: &Path) -> Result<
let mut temporary = tempfile::NamedTempFile::new_in(parent)
.context("Failed to create artifact cache temporary file")?;
serde_json::to_writer(&mut temporary, cache).context("Failed to serialize artifact cache")?;
temporary.flush().context("Failed to flush artifact cache")?;
temporary
.flush()
.context("Failed to flush artifact cache")?;
temporary
.as_file()
.sync_all()
.context("Failed to sync artifact cache")?;
temporary
.persist(path)
.map_err(|error| error.error)
.with_context(|| format!("Failed to atomically save artifact cache '{}'", path.display()))?;
.with_context(|| {
format!(
"Failed to atomically save artifact cache '{}'",
path.display()
)
})?;
Ok(())
}

Expand Down
9 changes: 5 additions & 4 deletions crates/renderflow-core/src/artifact/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,9 @@ impl ArtifactStore {
artifact.id()
)
})?;
temporary.flush().context("Failed to flush final artifact")?;
temporary
.flush()
.context("Failed to flush final artifact")?;
temporary
.as_file()
.sync_all()
Expand Down Expand Up @@ -293,9 +295,8 @@ mod tests {
fn identical_payloads_share_content_storage() {
let directory = tempfile::tempdir().unwrap();
let store = ArtifactStore::new(directory.path()).unwrap();
let descriptor = || {
ArtifactDescriptor::for_format(Format::Png, ArtifactStorageClass::Intermediate)
};
let descriptor =
|| ArtifactDescriptor::for_format(Format::Png, ArtifactStorageClass::Intermediate);
let first = store.put_bytes(b"\x89PNG\x00", descriptor()).unwrap();
let second = store.put_bytes(b"\x89PNG\x00", descriptor()).unwrap();
assert_eq!(first.id(), second.id());
Expand Down
46 changes: 46 additions & 0 deletions crates/renderflow-core/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ use crate::optimization::OptimizationMode;
renderflow version Print the installed version\n \
renderflow env Print installation environment details\n \
renderflow doctor Run installation diagnostics\n \
renderflow tools list List runtime tool providers\n \
renderflow tools inspect tool.ffmpeg Inspect one runtime provider\n \
renderflow capabilities List provider capability IDs\n \
renderflow my-project.yaml Shorthand: run build on the given config"
)]
pub struct Cli {
Expand Down Expand Up @@ -198,6 +201,23 @@ pub enum Commands {
subcommand: GraphCommands,
},

/// Inspect the runtime external-tool/provider registry.
#[command(subcommand_required = true, arg_required_else_help = true)]
Tools {
#[command(subcommand)]
subcommand: ToolCommands,
},

/// List stable provider capability IDs and their implementations.
Capabilities {
/// Output format: text (default), json, or yaml.
#[arg(long, default_value = "text", value_name = "FORMAT")]
format: String,
/// Optional transform YAML whose dynamic providers should be included.
#[arg(long, value_name = "FILE")]
transforms: Option<String>,
},

/// Print the installed Renderflow version
Version,

Expand Down Expand Up @@ -238,6 +258,32 @@ pub enum PluginCommands {
Doctor,
}

/// Subcommands for `renderflow tools`.
#[derive(Subcommand)]
pub enum ToolCommands {
/// List registered providers and live availability/version state.
List {
/// Output format: text (default), json, or yaml.
#[arg(long, default_value = "text", value_name = "FORMAT")]
format: String,
/// Optional transform YAML whose dynamic providers should be included.
#[arg(long, value_name = "FILE")]
transforms: Option<String>,
},

/// Inspect one stable provider ID.
Inspect {
/// Stable provider/tool identifier, for example `tool.ffmpeg`.
id: String,
/// Output format: text (default), json, or yaml.
#[arg(long, default_value = "text", value_name = "FORMAT")]
format: String,
/// Optional transform YAML whose dynamic providers should be included.
#[arg(long, value_name = "FILE")]
transforms: Option<String>,
},
}

/// Subcommands for `renderflow ai`.
#[derive(Subcommand)]
pub enum AiCommands {
Expand Down
Loading
Loading