Compare commits

...

8 Commits

Author SHA1 Message Date
bahdotsh
8fc6dcaa6c Fix PHP workflow execution issues
- Add automatic Docker image pulling in run_container_inner
- Implement smart container image selection for GitHub actions
- Fix shell command parsing to use bash -c for proper quote/pipe handling
- Map shivammathur/setup-php to composer:latest container
- Support complex commands with quotes, pipes, and substitutions

Fixes issues where:
1. Docker images required manual pulling
2. PHP actions used wrong Node.js containers
3. Commands like 'echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT' failed
2025-08-14 14:22:34 +05:30
bahdotsh
3f7bd30cca workflow update 2025-08-13 18:15:34 +05:30
bahdotsh
960f7486a2 Release 0.7.0
wrkflw@0.7.0
wrkflw-evaluator@0.7.0
wrkflw-executor@0.7.0
wrkflw-github@0.7.0
wrkflw-gitlab@0.7.0
wrkflw-logging@0.7.0
wrkflw-matrix@0.7.0
wrkflw-models@0.7.0
wrkflw-parser@0.7.0
wrkflw-runtime@0.7.0
wrkflw-ui@0.7.0
wrkflw-utils@0.7.0
wrkflw-validators@0.7.0

Generated by cargo-workspaces
2025-08-13 18:07:11 +05:30
bahdotsh
cb936cd1af updated publish script 2025-08-13 17:57:44 +05:30
Gokul
625b8111f1 Merge pull request #38 from bahdotsh/improve-tui-help-tab
feat(ui): enhance TUI help tab with comprehensive documentation and s…
2025-08-13 15:29:22 +05:30
bahdotsh
b2b6e9e08d formatted 2025-08-13 15:26:08 +05:30
bahdotsh
86660ae573 feat(ui): enhance TUI help tab with comprehensive documentation and scrolling
- Add comprehensive keyboard shortcut documentation organized in sections
- Implement two-column layout with color-coded sections and emoji icons
- Add scrollable help content with ↑/↓ and k/j key support
- Enhance help overlay with larger modal size and scroll support
- Include detailed explanations of all tabs, runtime modes, and features
- Update status bar with context-aware help instructions
- Add help scroll state management to app state
- Document workflow management, search functionality, and best practices

The help tab now provides a complete guide covering:
- Navigation controls and tab switching
- Workflow selection, execution, and triggering
- Runtime modes (Docker, Podman, Emulation, Secure Emulation)
- Log search and filtering capabilities
- Tab-specific functionality and tips
- Quick actions and keyboard shortcuts
2025-08-13 14:52:10 +05:30
Gokul
886c415fa7 Merge pull request #37 from bahdotsh/feature/secure-emulation-sandboxing
feat: Add comprehensive sandboxing for secure emulation mode
2025-08-13 14:36:02 +05:30
24 changed files with 764 additions and 141 deletions

View File

@@ -42,7 +42,30 @@ jobs:
cargo install git-cliff --force
- name: Generate Changelog
run: git-cliff --latest --output CHANGELOG.md
run: |
# Debug: Show current state
echo "Current ref: ${{ github.ref_name }}"
echo "Input version: ${{ github.event.inputs.version }}"
echo "All tags:"
git tag --sort=-version:refname | head -10
# Generate changelog from the current tag to the previous version tag
CURRENT_TAG="${{ github.event.inputs.version || github.ref_name }}"
PREVIOUS_TAG=$(git tag --sort=-version:refname | grep "^v" | head -2 | tail -1)
echo "Current tag: $CURRENT_TAG"
echo "Previous tag: $PREVIOUS_TAG"
if [ -n "$PREVIOUS_TAG" ] && [ "$PREVIOUS_TAG" != "$CURRENT_TAG" ]; then
echo "Generating changelog for range: $PREVIOUS_TAG..$CURRENT_TAG"
git-cliff --tag "$CURRENT_TAG" "$PREVIOUS_TAG..$CURRENT_TAG" --output CHANGELOG.md
else
echo "Generating latest changelog for tag: $CURRENT_TAG"
git-cliff --tag "$CURRENT_TAG" --latest --output CHANGELOG.md
fi
echo "Generated changelog:"
cat CHANGELOG.md
- name: Create Release
id: create_release

26
Cargo.lock generated
View File

@@ -2545,7 +2545,7 @@ checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
[[package]]
name = "wrkflw"
version = "0.6.0"
version = "0.7.0"
dependencies = [
"bollard",
"chrono",
@@ -2592,7 +2592,7 @@ dependencies = [
[[package]]
name = "wrkflw-evaluator"
version = "0.6.0"
version = "0.7.0"
dependencies = [
"colored",
"serde_yaml",
@@ -2602,7 +2602,7 @@ dependencies = [
[[package]]
name = "wrkflw-executor"
version = "0.6.0"
version = "0.7.0"
dependencies = [
"async-trait",
"bollard",
@@ -2632,7 +2632,7 @@ dependencies = [
[[package]]
name = "wrkflw-github"
version = "0.6.0"
version = "0.7.0"
dependencies = [
"lazy_static",
"regex",
@@ -2646,7 +2646,7 @@ dependencies = [
[[package]]
name = "wrkflw-gitlab"
version = "0.6.0"
version = "0.7.0"
dependencies = [
"lazy_static",
"regex",
@@ -2661,7 +2661,7 @@ dependencies = [
[[package]]
name = "wrkflw-logging"
version = "0.6.0"
version = "0.7.0"
dependencies = [
"chrono",
"once_cell",
@@ -2672,7 +2672,7 @@ dependencies = [
[[package]]
name = "wrkflw-matrix"
version = "0.6.0"
version = "0.7.0"
dependencies = [
"indexmap 2.8.0",
"serde",
@@ -2683,7 +2683,7 @@ dependencies = [
[[package]]
name = "wrkflw-models"
version = "0.6.0"
version = "0.7.0"
dependencies = [
"serde",
"serde_json",
@@ -2693,7 +2693,7 @@ dependencies = [
[[package]]
name = "wrkflw-parser"
version = "0.6.0"
version = "0.7.0"
dependencies = [
"jsonschema",
"serde",
@@ -2707,7 +2707,7 @@ dependencies = [
[[package]]
name = "wrkflw-runtime"
version = "0.6.0"
version = "0.7.0"
dependencies = [
"async-trait",
"futures",
@@ -2726,7 +2726,7 @@ dependencies = [
[[package]]
name = "wrkflw-ui"
version = "0.6.0"
version = "0.7.0"
dependencies = [
"chrono",
"crossterm 0.26.1",
@@ -2748,7 +2748,7 @@ dependencies = [
[[package]]
name = "wrkflw-utils"
version = "0.6.0"
version = "0.7.0"
dependencies = [
"nix",
"serde",
@@ -2758,7 +2758,7 @@ dependencies = [
[[package]]
name = "wrkflw-validators"
version = "0.6.0"
version = "0.7.0"
dependencies = [
"serde",
"serde_yaml",

View File

@@ -5,7 +5,7 @@ members = [
resolver = "2"
[workspace.package]
version = "0.6.0"
version = "0.7.0"
edition = "2021"
description = "A GitHub Actions workflow validator and executor"
documentation = "https://github.com/bahdotsh/wrkflw"

View File

@@ -12,9 +12,9 @@ categories.workspace = true
[dependencies]
# Internal crates
wrkflw-models = { path = "../models", version = "0.6.0" }
wrkflw-validators = { path = "../validators", version = "0.6.0" }
wrkflw-models = { path = "../models", version = "0.7.0" }
wrkflw-validators = { path = "../validators", version = "0.7.0" }
# External dependencies
colored.workspace = true
serde_yaml.workspace = true
serde_yaml.workspace = true

View File

@@ -12,12 +12,12 @@ categories.workspace = true
[dependencies]
# Internal crates
wrkflw-models = { path = "../models", version = "0.6.0" }
wrkflw-parser = { path = "../parser", version = "0.6.0" }
wrkflw-runtime = { path = "../runtime", version = "0.6.0" }
wrkflw-logging = { path = "../logging", version = "0.6.0" }
wrkflw-matrix = { path = "../matrix", version = "0.6.0" }
wrkflw-utils = { path = "../utils", version = "0.6.0" }
wrkflw-models = { path = "../models", version = "0.7.0" }
wrkflw-parser = { path = "../parser", version = "0.7.0" }
wrkflw-runtime = { path = "../runtime", version = "0.7.0" }
wrkflw-logging = { path = "../logging", version = "0.7.0" }
wrkflw-matrix = { path = "../matrix", version = "0.7.0" }
wrkflw-utils = { path = "../utils", version = "0.7.0" }
# External dependencies
async-trait.workspace = true

View File

@@ -840,6 +840,14 @@ impl DockerRuntime {
working_dir: &Path,
volumes: &[(&Path, &Path)],
) -> Result<ContainerOutput, ContainerError> {
// First, try to pull the image if it's not available locally
if let Err(e) = self.pull_image_inner(image).await {
wrkflw_logging::warning(&format!(
"Failed to pull image {}: {}. Attempting to continue with existing image.",
image, e
));
}
// Collect environment variables
let mut env: Vec<String> = env_vars
.iter()

View File

@@ -526,9 +526,64 @@ async fn prepare_action(
}
}
// GitHub action: use standard runner image
// In a real implementation, you'd need to clone the repo at the specified version
Ok("node:16-buster-slim".to_string())
// GitHub action: determine appropriate image based on action type
let image = determine_action_image(&action.repository);
Ok(image)
}
/// Determine the appropriate Docker image for a GitHub action
fn determine_action_image(repository: &str) -> String {
// Handle specific well-known actions
match repository {
// PHP setup actions
repo if repo.starts_with("shivammathur/setup-php") => {
"composer:latest".to_string() // Use composer image which includes PHP and composer
}
// Python setup actions
repo if repo.starts_with("actions/setup-python") => "python:3.11-slim".to_string(),
// Node.js setup actions
repo if repo.starts_with("actions/setup-node") => "node:20-slim".to_string(),
// Java setup actions
repo if repo.starts_with("actions/setup-java") => "eclipse-temurin:17-jdk".to_string(),
// Go setup actions
repo if repo.starts_with("actions/setup-go") => "golang:1.21-slim".to_string(),
// .NET setup actions
repo if repo.starts_with("actions/setup-dotnet") => {
"mcr.microsoft.com/dotnet/sdk:7.0".to_string()
}
// Rust setup actions
repo if repo.starts_with("actions-rs/toolchain")
|| repo.starts_with("dtolnay/rust-toolchain") =>
{
"rust:latest".to_string()
}
// Docker/container actions
repo if repo.starts_with("docker/") => "docker:latest".to_string(),
// AWS actions
repo if repo.starts_with("aws-actions/") => "amazon/aws-cli:latest".to_string(),
// Default to Node.js for most GitHub actions (checkout, upload-artifact, etc.)
_ => {
// Check if it's a common core GitHub action that should use a more complete environment
if repository.starts_with("actions/checkout")
|| repository.starts_with("actions/upload-artifact")
|| repository.starts_with("actions/download-artifact")
|| repository.starts_with("actions/cache")
{
"catthehacker/ubuntu:act-latest".to_string() // Use act runner image for core actions
} else {
"node:16-buster-slim".to_string() // Default for other actions
}
}
}
}
async fn execute_job_batch(
@@ -1536,8 +1591,9 @@ async fn execute_step(ctx: StepExecutionContext<'_>) -> Result<StepResult, Execu
// Check if this is a cargo command
let is_cargo_cmd = run.trim().starts_with("cargo");
// Convert command string to array of string slices
let cmd_parts: Vec<&str> = run.split_whitespace().collect();
// For complex shell commands, use bash to execute them properly
// This handles quotes, pipes, redirections, and command substitutions correctly
let cmd_parts = vec!["bash", "-c", run];
// Convert environment variables to the required format
let env_vars: Vec<(&str, &str)> = step_env

View File

@@ -12,7 +12,7 @@ categories.workspace = true
[dependencies]
# Internal crates
wrkflw-models = { path = "../models", version = "0.6.0" }
wrkflw-models = { path = "../models", version = "0.7.0" }
# External dependencies from workspace
serde.workspace = true

View File

@@ -12,7 +12,7 @@ categories.workspace = true
[dependencies]
# Internal crates
wrkflw-models = { path = "../models", version = "0.6.0" }
wrkflw-models = { path = "../models", version = "0.7.0" }
# External dependencies
lazy_static.workspace = true

View File

@@ -12,7 +12,7 @@ categories.workspace = true
[dependencies]
# Internal crates
wrkflw-models = { path = "../models", version = "0.6.0" }
wrkflw-models = { path = "../models", version = "0.7.0" }
# External dependencies
chrono.workspace = true

View File

@@ -12,7 +12,7 @@ categories.workspace = true
[dependencies]
# Internal crates
wrkflw-models = { path = "../models", version = "0.6.0" }
wrkflw-models = { path = "../models", version = "0.7.0" }
# External dependencies
indexmap.workspace = true

View File

@@ -14,4 +14,4 @@ categories.workspace = true
serde.workspace = true
serde_yaml.workspace = true
serde_json.workspace = true
thiserror.workspace = true
thiserror.workspace = true

View File

@@ -12,8 +12,8 @@ categories.workspace = true
[dependencies]
# Internal crates
wrkflw-models = { path = "../models", version = "0.6.0" }
wrkflw-matrix = { path = "../matrix", version = "0.6.0" }
wrkflw-models = { path = "../models", version = "0.7.0" }
wrkflw-matrix = { path = "../matrix", version = "0.7.0" }
# External dependencies
jsonschema.workspace = true

View File

@@ -12,8 +12,8 @@ categories.workspace = true
[dependencies]
# Internal crates
wrkflw-models = { path = "../models", version = "0.6.0" }
wrkflw-logging = { path = "../logging", version = "0.6.0" }
wrkflw-models = { path = "../models", version = "0.7.0" }
wrkflw-logging = { path = "../logging", version = "0.7.0" }
# External dependencies
async-trait.workspace = true
@@ -23,7 +23,7 @@ serde_yaml.workspace = true
tempfile = "3.9"
tokio.workspace = true
futures = "0.3"
wrkflw-utils = { path = "../utils", version = "0.6.0" }
wrkflw-utils = { path = "../utils", version = "0.7.0" }
which = "4.4"
regex = "1.10"
thiserror = "1.0"

View File

@@ -12,12 +12,12 @@ categories.workspace = true
[dependencies]
# Internal crates
wrkflw-models = { path = "../models", version = "0.6.0" }
wrkflw-evaluator = { path = "../evaluator", version = "0.6.0" }
wrkflw-executor = { path = "../executor", version = "0.6.0" }
wrkflw-logging = { path = "../logging", version = "0.6.0" }
wrkflw-utils = { path = "../utils", version = "0.6.0" }
wrkflw-github = { path = "../github", version = "0.6.0" }
wrkflw-models = { path = "../models", version = "0.7.0" }
wrkflw-evaluator = { path = "../evaluator", version = "0.7.0" }
wrkflw-executor = { path = "../executor", version = "0.7.0" }
wrkflw-logging = { path = "../logging", version = "0.7.0" }
wrkflw-utils = { path = "../utils", version = "0.7.0" }
wrkflw-github = { path = "../github", version = "0.7.0" }
# External dependencies
chrono.workspace = true

View File

@@ -189,6 +189,25 @@ fn run_tui_event_loop(
continue;
}
// Handle help overlay scrolling
if app.show_help {
match key.code {
KeyCode::Up | KeyCode::Char('k') => {
app.scroll_help_up();
continue;
}
KeyCode::Down | KeyCode::Char('j') => {
app.scroll_help_down();
continue;
}
KeyCode::Esc | KeyCode::Char('?') => {
app.show_help = false;
continue;
}
_ => {}
}
}
match key.code {
KeyCode::Char('q') => {
// Exit and clean up
@@ -223,6 +242,8 @@ fn run_tui_event_loop(
} else {
app.scroll_logs_up();
}
} else if app.selected_tab == 3 {
app.scroll_help_up();
} else if app.selected_tab == 0 {
app.previous_workflow();
} else if app.selected_tab == 1 {
@@ -240,6 +261,8 @@ fn run_tui_event_loop(
} else {
app.scroll_logs_down();
}
} else if app.selected_tab == 3 {
app.scroll_help_down();
} else if app.selected_tab == 0 {
app.next_workflow();
} else if app.selected_tab == 1 {

View File

@@ -42,6 +42,9 @@ pub struct App {
pub log_search_matches: Vec<usize>, // Indices of logs that match the search
pub log_search_match_idx: usize, // Current match index for navigation
// Help tab scrolling
pub help_scroll: usize, // Scrolling position for help content
// Background log processing
pub log_processor: LogProcessor,
pub processed_logs: Vec<ProcessedLogEntry>,
@@ -207,6 +210,7 @@ impl App {
log_filter_level: Some(LogFilterLevel::All),
log_search_matches: Vec::new(),
log_search_match_idx: 0,
help_scroll: 0,
// Background log processing
log_processor: LogProcessor::new(),
@@ -807,6 +811,18 @@ impl App {
}
}
// Scroll help content up
pub fn scroll_help_up(&mut self) {
self.help_scroll = self.help_scroll.saturating_sub(1);
}
// Scroll help content down
pub fn scroll_help_down(&mut self) {
// The help content has a fixed number of lines, so we set a reasonable max
const MAX_HELP_SCROLL: usize = 30; // Adjust based on help content length
self.help_scroll = (self.help_scroll + 1).min(MAX_HELP_SCROLL);
}
// Update progress for running workflows
pub fn update_running_workflow_progress(&mut self) {
if let Some(idx) = self.current_execution {

View File

@@ -1,7 +1,7 @@
// Help overlay rendering
use ratatui::{
backend::CrosstermBackend,
layout::Rect,
layout::{Constraint, Direction, Layout, Rect},
style::{Color, Modifier, Style},
text::{Line, Span},
widgets::{Block, BorderType, Borders, Paragraph, Wrap},
@@ -9,11 +9,22 @@ use ratatui::{
};
use std::io;
// Render the help tab
pub fn render_help_tab(f: &mut Frame<CrosstermBackend<io::Stdout>>, area: Rect) {
let help_text = vec![
// Render the help tab with scroll support
pub fn render_help_content(
f: &mut Frame<CrosstermBackend<io::Stdout>>,
area: Rect,
scroll_offset: usize,
) {
// Split the area into columns for better organization
let chunks = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
.split(area);
// Left column content
let left_help_text = vec![
Line::from(Span::styled(
"Keyboard Controls",
"🗂 NAVIGATION",
Style::default()
.fg(Color::Cyan)
.add_modifier(Modifier::BOLD),
@@ -21,35 +32,391 @@ pub fn render_help_tab(f: &mut Frame<CrosstermBackend<io::Stdout>>, area: Rect)
Line::from(""),
Line::from(vec![
Span::styled(
"Tab",
"Tab / Shift+Tab",
Style::default()
.fg(Color::Yellow)
.add_modifier(Modifier::BOLD),
),
Span::raw(" - Switch between tabs"),
]),
// More help text would follow...
Line::from(vec![
Span::styled(
"1-4 / w,x,l,h",
Style::default()
.fg(Color::Yellow)
.add_modifier(Modifier::BOLD),
),
Span::raw(" - Jump to specific tab"),
]),
Line::from(vec![
Span::styled(
"↑/↓ or k/j",
Style::default()
.fg(Color::Yellow)
.add_modifier(Modifier::BOLD),
),
Span::raw(" - Navigate lists"),
]),
Line::from(vec![
Span::styled(
"Enter",
Style::default()
.fg(Color::Yellow)
.add_modifier(Modifier::BOLD),
),
Span::raw(" - Select/View details"),
]),
Line::from(vec![
Span::styled(
"Esc",
Style::default()
.fg(Color::Yellow)
.add_modifier(Modifier::BOLD),
),
Span::raw(" - Back/Exit help"),
]),
Line::from(""),
Line::from(Span::styled(
"🚀 WORKFLOW MANAGEMENT",
Style::default()
.fg(Color::Green)
.add_modifier(Modifier::BOLD),
)),
Line::from(""),
Line::from(vec![
Span::styled(
"Space",
Style::default()
.fg(Color::Yellow)
.add_modifier(Modifier::BOLD),
),
Span::raw(" - Toggle workflow selection"),
]),
Line::from(vec![
Span::styled(
"r",
Style::default()
.fg(Color::Yellow)
.add_modifier(Modifier::BOLD),
),
Span::raw(" - Run selected workflows"),
]),
Line::from(vec![
Span::styled(
"a",
Style::default()
.fg(Color::Yellow)
.add_modifier(Modifier::BOLD),
),
Span::raw(" - Select all workflows"),
]),
Line::from(vec![
Span::styled(
"n",
Style::default()
.fg(Color::Yellow)
.add_modifier(Modifier::BOLD),
),
Span::raw(" - Deselect all workflows"),
]),
Line::from(vec![
Span::styled(
"Shift+R",
Style::default()
.fg(Color::Yellow)
.add_modifier(Modifier::BOLD),
),
Span::raw(" - Reset workflow status"),
]),
Line::from(vec![
Span::styled(
"t",
Style::default()
.fg(Color::Yellow)
.add_modifier(Modifier::BOLD),
),
Span::raw(" - Trigger remote workflow"),
]),
Line::from(""),
Line::from(Span::styled(
"🔧 EXECUTION MODES",
Style::default()
.fg(Color::Magenta)
.add_modifier(Modifier::BOLD),
)),
Line::from(""),
Line::from(vec![
Span::styled(
"e",
Style::default()
.fg(Color::Yellow)
.add_modifier(Modifier::BOLD),
),
Span::raw(" - Toggle emulation mode"),
]),
Line::from(vec![
Span::styled(
"v",
Style::default()
.fg(Color::Yellow)
.add_modifier(Modifier::BOLD),
),
Span::raw(" - Toggle validation mode"),
]),
Line::from(""),
Line::from(vec![Span::styled(
"Runtime Modes:",
Style::default()
.fg(Color::White)
.add_modifier(Modifier::BOLD),
)]),
Line::from(vec![
Span::raw(""),
Span::styled("Docker", Style::default().fg(Color::Blue)),
Span::raw(" - Container isolation (default)"),
]),
Line::from(vec![
Span::raw(""),
Span::styled("Podman", Style::default().fg(Color::Blue)),
Span::raw(" - Rootless containers"),
]),
Line::from(vec![
Span::raw(""),
Span::styled("Emulation", Style::default().fg(Color::Red)),
Span::raw(" - Process mode (UNSAFE)"),
]),
Line::from(vec![
Span::raw(""),
Span::styled("Secure Emulation", Style::default().fg(Color::Yellow)),
Span::raw(" - Sandboxed processes"),
]),
];
let help_widget = Paragraph::new(help_text)
// Right column content
let right_help_text = vec![
Line::from(Span::styled(
"📄 LOGS & SEARCH",
Style::default()
.fg(Color::Blue)
.add_modifier(Modifier::BOLD),
)),
Line::from(""),
Line::from(vec![
Span::styled(
"s",
Style::default()
.fg(Color::Yellow)
.add_modifier(Modifier::BOLD),
),
Span::raw(" - Toggle log search"),
]),
Line::from(vec![
Span::styled(
"f",
Style::default()
.fg(Color::Yellow)
.add_modifier(Modifier::BOLD),
),
Span::raw(" - Toggle log filter"),
]),
Line::from(vec![
Span::styled(
"c",
Style::default()
.fg(Color::Yellow)
.add_modifier(Modifier::BOLD),
),
Span::raw(" - Clear search & filter"),
]),
Line::from(vec![
Span::styled(
"n",
Style::default()
.fg(Color::Yellow)
.add_modifier(Modifier::BOLD),
),
Span::raw(" - Next search match"),
]),
Line::from(vec![
Span::styled(
"↑/↓",
Style::default()
.fg(Color::Yellow)
.add_modifier(Modifier::BOLD),
),
Span::raw(" - Scroll logs/Navigate"),
]),
Line::from(""),
Line::from(Span::styled(
" TAB OVERVIEW",
Style::default()
.fg(Color::White)
.add_modifier(Modifier::BOLD),
)),
Line::from(""),
Line::from(vec![
Span::styled(
"1. Workflows",
Style::default()
.fg(Color::Cyan)
.add_modifier(Modifier::BOLD),
),
Span::raw(" - Browse & select workflows"),
]),
Line::from(vec![Span::raw(" • View workflow files")]),
Line::from(vec![Span::raw(" • Select multiple for batch execution")]),
Line::from(vec![Span::raw(" • Trigger remote workflows")]),
Line::from(""),
Line::from(vec![
Span::styled(
"2. Execution",
Style::default()
.fg(Color::Green)
.add_modifier(Modifier::BOLD),
),
Span::raw(" - Monitor job progress"),
]),
Line::from(vec![Span::raw(" • View job status and details")]),
Line::from(vec![Span::raw(" • Enter job details with Enter")]),
Line::from(vec![Span::raw(" • Navigate step execution")]),
Line::from(""),
Line::from(vec![
Span::styled(
"3. Logs",
Style::default()
.fg(Color::Blue)
.add_modifier(Modifier::BOLD),
),
Span::raw(" - View execution logs"),
]),
Line::from(vec![Span::raw(" • Search and filter logs")]),
Line::from(vec![Span::raw(" • Real-time log streaming")]),
Line::from(vec![Span::raw(" • Navigate search results")]),
Line::from(""),
Line::from(vec![
Span::styled(
"4. Help",
Style::default()
.fg(Color::Yellow)
.add_modifier(Modifier::BOLD),
),
Span::raw(" - This comprehensive guide"),
]),
Line::from(""),
Line::from(Span::styled(
"🎯 QUICK ACTIONS",
Style::default().fg(Color::Red).add_modifier(Modifier::BOLD),
)),
Line::from(""),
Line::from(vec![
Span::styled(
"?",
Style::default()
.fg(Color::Yellow)
.add_modifier(Modifier::BOLD),
),
Span::raw(" - Toggle help overlay"),
]),
Line::from(vec![
Span::styled(
"q",
Style::default()
.fg(Color::Yellow)
.add_modifier(Modifier::BOLD),
),
Span::raw(" - Quit application"),
]),
Line::from(""),
Line::from(Span::styled(
"💡 TIPS",
Style::default()
.fg(Color::Yellow)
.add_modifier(Modifier::BOLD),
)),
Line::from(""),
Line::from(vec![
Span::raw("• Use "),
Span::styled("emulation mode", Style::default().fg(Color::Red)),
Span::raw(" when containers"),
]),
Line::from(vec![Span::raw(" are unavailable or for quick testing")]),
Line::from(""),
Line::from(vec![
Span::raw(""),
Span::styled("Secure emulation", Style::default().fg(Color::Yellow)),
Span::raw(" provides sandboxing"),
]),
Line::from(vec![Span::raw(" for untrusted workflows")]),
Line::from(""),
Line::from(vec![
Span::raw("• Use "),
Span::styled("validation mode", Style::default().fg(Color::Green)),
Span::raw(" to check"),
]),
Line::from(vec![Span::raw(" workflows without execution")]),
Line::from(""),
Line::from(vec![
Span::raw(""),
Span::styled("Preserve containers", Style::default().fg(Color::Blue)),
Span::raw(" on failure"),
]),
Line::from(vec![Span::raw(" for debugging (Docker/Podman only)")]),
];
// Apply scroll offset to the content
let left_help_text = if scroll_offset < left_help_text.len() {
left_help_text.into_iter().skip(scroll_offset).collect()
} else {
vec![Line::from("")]
};
let right_help_text = if scroll_offset < right_help_text.len() {
right_help_text.into_iter().skip(scroll_offset).collect()
} else {
vec![Line::from("")]
};
// Render left column
let left_widget = Paragraph::new(left_help_text)
.block(
Block::default()
.borders(Borders::ALL)
.border_type(BorderType::Rounded)
.title(Span::styled(" Help ", Style::default().fg(Color::Yellow))),
.title(Span::styled(
" WRKFLW Help - Controls & Features ",
Style::default()
.fg(Color::Yellow)
.add_modifier(Modifier::BOLD),
)),
)
.wrap(Wrap { trim: true });
f.render_widget(help_widget, area);
// Render right column
let right_widget = Paragraph::new(right_help_text)
.block(
Block::default()
.borders(Borders::ALL)
.border_type(BorderType::Rounded)
.title(Span::styled(
" Interface Guide & Tips ",
Style::default()
.fg(Color::Cyan)
.add_modifier(Modifier::BOLD),
)),
)
.wrap(Wrap { trim: true });
f.render_widget(left_widget, chunks[0]);
f.render_widget(right_widget, chunks[1]);
}
// Render a help overlay
pub fn render_help_overlay(f: &mut Frame<CrosstermBackend<io::Stdout>>) {
pub fn render_help_overlay(f: &mut Frame<CrosstermBackend<io::Stdout>>, scroll_offset: usize) {
let size = f.size();
// Create a slightly smaller centered modal
let width = size.width.min(60);
let height = size.height.min(20);
// Create a larger centered modal to accommodate comprehensive help content
let width = (size.width * 9 / 10).min(120); // Use 90% of width, max 120 chars
let height = (size.height * 9 / 10).min(40); // Use 90% of height, max 40 lines
let x = (size.width - width) / 2;
let y = (size.height - height) / 2;
@@ -60,10 +427,32 @@ pub fn render_help_overlay(f: &mut Frame<CrosstermBackend<io::Stdout>>) {
height,
};
// Create a clear background
// Create a semi-transparent dark background for better visibility
let clear = Block::default().style(Style::default().bg(Color::Black));
f.render_widget(clear, size);
// Render the help content
render_help_tab(f, help_area);
// Add a border around the entire overlay for better visual separation
let overlay_block = Block::default()
.borders(Borders::ALL)
.border_type(BorderType::Double)
.style(Style::default().bg(Color::Black).fg(Color::White))
.title(Span::styled(
" Press ? or Esc to close help ",
Style::default()
.fg(Color::Gray)
.add_modifier(Modifier::ITALIC),
));
f.render_widget(overlay_block, help_area);
// Create inner area for content
let inner_area = Rect {
x: help_area.x + 1,
y: help_area.y + 1,
width: help_area.width.saturating_sub(2),
height: help_area.height.saturating_sub(2),
};
// Render the help content with scroll support
render_help_content(f, inner_area, scroll_offset);
}

View File

@@ -15,7 +15,7 @@ use std::io;
pub fn render_ui(f: &mut Frame<CrosstermBackend<io::Stdout>>, app: &mut App) {
// Check if help should be shown as an overlay
if app.show_help {
help_overlay::render_help_overlay(f);
help_overlay::render_help_overlay(f, app.help_scroll);
return;
}
@@ -48,7 +48,7 @@ pub fn render_ui(f: &mut Frame<CrosstermBackend<io::Stdout>>, app: &mut App) {
}
}
2 => logs_tab::render_logs_tab(f, app, main_chunks[1]),
3 => help_overlay::render_help_tab(f, main_chunks[1]),
3 => help_overlay::render_help_content(f, main_chunks[1], app.help_scroll),
_ => {}
}

View File

@@ -181,7 +181,7 @@ pub fn render_status_bar(f: &mut Frame<CrosstermBackend<io::Stdout>>, app: &App,
"[No logs to display]"
}
}
3 => "[?] Toggle help overlay",
3 => "[↑/↓] Scroll help [?] Toggle help overlay",
_ => "",
};
status_items.push(Span::styled(

View File

@@ -12,7 +12,7 @@ categories.workspace = true
[dependencies]
# Internal crates
wrkflw-models = { path = "../models", version = "0.6.0" }
wrkflw-models = { path = "../models", version = "0.7.0" }
# External dependencies
serde.workspace = true

View File

@@ -12,8 +12,8 @@ categories.workspace = true
[dependencies]
# Internal crates
wrkflw-models = { path = "../models", version = "0.6.0" }
wrkflw-matrix = { path = "../matrix", version = "0.6.0" }
wrkflw-models = { path = "../models", version = "0.7.0" }
wrkflw-matrix = { path = "../matrix", version = "0.7.0" }
# External dependencies
serde.workspace = true

View File

@@ -12,18 +12,18 @@ license.workspace = true
[dependencies]
# Workspace crates
wrkflw-models = { path = "../models", version = "0.6.0" }
wrkflw-executor = { path = "../executor", version = "0.6.0" }
wrkflw-github = { path = "../github", version = "0.6.0" }
wrkflw-gitlab = { path = "../gitlab", version = "0.6.0" }
wrkflw-logging = { path = "../logging", version = "0.6.0" }
wrkflw-matrix = { path = "../matrix", version = "0.6.0" }
wrkflw-parser = { path = "../parser", version = "0.6.0" }
wrkflw-runtime = { path = "../runtime", version = "0.6.0" }
wrkflw-ui = { path = "../ui", version = "0.6.0" }
wrkflw-utils = { path = "../utils", version = "0.6.0" }
wrkflw-validators = { path = "../validators", version = "0.6.0" }
wrkflw-evaluator = { path = "../evaluator", version = "0.6.0" }
wrkflw-models = { path = "../models", version = "0.7.0" }
wrkflw-executor = { path = "../executor", version = "0.7.0" }
wrkflw-github = { path = "../github", version = "0.7.0" }
wrkflw-gitlab = { path = "../gitlab", version = "0.7.0" }
wrkflw-logging = { path = "../logging", version = "0.7.0" }
wrkflw-matrix = { path = "../matrix", version = "0.7.0" }
wrkflw-parser = { path = "../parser", version = "0.7.0" }
wrkflw-runtime = { path = "../runtime", version = "0.7.0" }
wrkflw-ui = { path = "../ui", version = "0.7.0" }
wrkflw-utils = { path = "../utils", version = "0.7.0" }
wrkflw-validators = { path = "../validators", version = "0.7.0" }
wrkflw-evaluator = { path = "../evaluator", version = "0.7.0" }
# External dependencies
clap.workspace = true
@@ -62,4 +62,4 @@ path = "src/lib.rs"
[[bin]]
name = "wrkflw"
path = "src/main.rs"
path = "src/main.rs"

View File

@@ -1,71 +1,179 @@
#!/bin/bash
# Simple script to publish all wrkflw crates to crates.io in dependency order
# Enhanced script to manage versions and publish all wrkflw crates using cargo-workspaces
set -e
DRY_RUN=${1:-""}
# Parse command line arguments
COMMAND=${1:-""}
VERSION_TYPE=${2:-""}
DRY_RUN=""
if [[ "$DRY_RUN" == "--dry-run" ]]; then
echo "🧪 DRY RUN: Testing wrkflw crates publication"
else
echo "🚀 Publishing wrkflw crates to crates.io"
fi
show_help() {
echo "Usage: $0 <command> [options]"
echo ""
echo "Commands:"
echo " version <type> Update versions across workspace"
echo " Types: patch, minor, major"
echo " publish Publish all crates to crates.io"
echo " release <type> Update versions and publish (combines version + publish)"
echo " help Show this help message"
echo ""
echo "Options:"
echo " --dry-run Test without making changes (for publish/release)"
echo ""
echo "Examples:"
echo " $0 version minor # Bump to 0.7.0"
echo " $0 publish --dry-run # Test publishing"
echo " $0 release minor --dry-run # Test version bump + publish"
echo " $0 release patch # Release patch version"
}
# Check if we're logged in to crates.io
if [ ! -f ~/.cargo/credentials.toml ] && [ ! -f ~/.cargo/credentials ]; then
echo "❌ Not logged in to crates.io. Please run: cargo login <your-token>"
exit 1
fi
# Publication order (respecting dependencies)
CRATES=(
"models"
"logging"
"utils"
"matrix"
"validators"
"github"
"gitlab"
"parser"
"runtime"
"evaluator"
"executor"
"ui"
"wrkflw"
)
echo "📦 Publishing crates in dependency order..."
for crate in "${CRATES[@]}"; do
if [[ "$DRY_RUN" == "--dry-run" ]]; then
echo "Testing $crate..."
cd "crates/$crate"
cargo publish --dry-run --allow-dirty
echo "$crate dry-run successful"
else
echo "Publishing $crate..."
cd "crates/$crate"
cargo publish --allow-dirty
echo "✅ Published $crate"
fi
cd - > /dev/null
# Small delay to avoid rate limiting (except for the last crate and in dry-run)
if [[ "$crate" != "wrkflw" ]] && [[ "$DRY_RUN" != "--dry-run" ]]; then
echo " Waiting 10 seconds to avoid rate limits..."
sleep 10
# Parse dry-run flag from any position
for arg in "$@"; do
if [[ "$arg" == "--dry-run" ]]; then
DRY_RUN="--dry-run"
fi
done
if [[ "$DRY_RUN" == "--dry-run" ]]; then
echo "🎉 All crates passed dry-run tests!"
echo ""
echo "To actually publish, run:"
echo " ./publish_crates.sh"
else
echo "🎉 All crates published successfully!"
echo ""
echo "Users can now install wrkflw with:"
echo " cargo install wrkflw"
case "$COMMAND" in
"help"|"-h"|"--help"|"")
show_help
exit 0
;;
"version")
if [[ -z "$VERSION_TYPE" ]]; then
echo "❌ Error: Version type required (patch, minor, major)"
echo ""
show_help
exit 1
fi
;;
"publish")
# publish command doesn't need version type
;;
"release")
if [[ -z "$VERSION_TYPE" ]]; then
echo "❌ Error: Version type required for release (patch, minor, major)"
echo ""
show_help
exit 1
fi
;;
*)
echo "❌ Error: Unknown command '$COMMAND'"
echo ""
show_help
exit 1
;;
esac
# Check if cargo-workspaces is installed
if ! command -v cargo-workspaces &> /dev/null; then
echo "❌ cargo-workspaces not found. Installing..."
cargo install cargo-workspaces
fi
# Check if we're logged in to crates.io (only for publish operations)
if [[ "$COMMAND" == "publish" ]] || [[ "$COMMAND" == "release" ]]; then
if [ ! -f ~/.cargo/credentials.toml ] && [ ! -f ~/.cargo/credentials ]; then
echo "❌ Not logged in to crates.io. Please run: cargo login <your-token>"
exit 1
fi
fi
# Function to update versions
update_versions() {
local version_type=$1
echo "🔄 Updating workspace versions ($version_type)..."
if [[ "$DRY_RUN" == "--dry-run" ]]; then
echo "🧪 DRY RUN: Simulating version update"
echo ""
echo "Current workspace version: $(grep '^version =' Cargo.toml | cut -d'"' -f2)"
echo "Would execute: cargo workspaces version $version_type"
echo ""
echo "This would update all crates and their internal dependencies."
echo "✅ Version update simulation completed (no changes made)"
else
cargo workspaces version "$version_type"
echo "✅ Versions updated successfully"
fi
}
# Function to test build
test_build() {
echo "🔨 Testing workspace build..."
if cargo build --workspace; then
echo "✅ Workspace builds successfully"
else
echo "❌ Build failed. Please fix errors before publishing."
exit 1
fi
}
# Function to publish crates
publish_crates() {
echo "📦 Publishing crates to crates.io..."
if [[ "$DRY_RUN" == "--dry-run" ]]; then
echo "🧪 DRY RUN: Testing publication"
cargo workspaces publish --dry-run
echo "✅ All crates passed dry-run tests!"
echo ""
echo "To actually publish, run:"
echo " $0 publish"
else
cargo workspaces publish
echo "🎉 All crates published successfully!"
echo ""
echo "Users can now install wrkflw with:"
echo " cargo install wrkflw"
fi
}
# Function to show changelog info
show_changelog_info() {
echo "📝 Changelog will be generated automatically by GitHub Actions workflow"
}
# Execute commands based on the operation
case "$COMMAND" in
"version")
update_versions "$VERSION_TYPE"
show_changelog_info
;;
"publish")
test_build
publish_crates
;;
"release")
echo "🚀 Starting release process..."
echo ""
# Step 1: Update versions
update_versions "$VERSION_TYPE"
# Step 2: Test build
test_build
# Step 3: Show changelog info
show_changelog_info
# Step 4: Publish (if not dry-run)
if [[ "$DRY_RUN" != "--dry-run" ]]; then
echo ""
read -p "🤔 Continue with publishing? (y/N): " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
publish_crates
else
echo "⏸️ Publishing cancelled. To publish later, run:"
echo " $0 publish"
fi
else
echo ""
publish_crates
fi
;;
esac