Compare commits

...

20 Commits

Author SHA1 Message Date
bahdotsh
7970e6ad7d Release 0.7.3
wrkflw@0.7.3
wrkflw-evaluator@0.7.3
wrkflw-executor@0.7.3
wrkflw-github@0.7.3
wrkflw-gitlab@0.7.3
wrkflw-logging@0.7.3
wrkflw-matrix@0.7.3
wrkflw-parser@0.7.3
wrkflw-runtime@0.7.3
wrkflw-secrets@0.7.3
wrkflw-ui@0.7.3
wrkflw-utils@0.7.3
wrkflw-validators@0.7.3

Generated by cargo-workspaces
2025-08-28 12:58:32 +05:30
bahdotsh
51a655f07b version fixes 2025-08-28 12:56:05 +05:30
bahdotsh
7ac18f3715 Release 0.7.2
wrkflw-runtime@0.7.2
wrkflw-utils@0.7.2

Generated by cargo-workspaces
2025-08-28 08:13:02 +05:30
Gokul
1f3fee7373 Merge pull request #56 from bahdotsh/fix/windows-compatibility
fix(utils): add Windows support to fd module
2025-08-28 07:48:37 +05:30
bahdotsh
f49ccd70d9 fix(runtime): remove unnecessary borrow in Windows taskkill command
- Fix clippy needless_borrows_for_generic_args warning
- Change &pid.to_string() to pid.to_string() for taskkill /PID argument
- Ensure clippy passes with -D warnings on Windows builds
2025-08-27 15:45:58 +05:30
bahdotsh
5161882989 fix(utils): remove unused imports to fix Windows clippy warnings
- Remove unused io::self import from common scope
- Remove unused std::fs::OpenOptions and std::io::Write from windows_impl
- Add std::io import to unix_impl to fix io::Error references
- Ensure clippy passes with -D warnings on all platforms
2025-08-27 15:39:52 +05:30
bahdotsh
5e9658c885 ci: add Windows to build matrix and integration tests
- Add windows-latest to OS matrix with x86_64-pc-windows-msvc target
- Add dedicated Windows integration test job
- Verify Windows executable functionality
- Ensure cross-platform compatibility testing

This ensures Windows build issues are caught early in CI/CD pipeline.
2025-08-27 15:37:15 +05:30
bahdotsh
aa9da33b30 docs(utils): update README to document cross-platform fd behavior
- Document Unix vs Windows fd redirection limitations
- Update example to reflect platform-specific behavior
- Clarify that stderr suppression is Unix-only
2025-08-27 15:36:51 +05:30
bahdotsh
dff3697052 fix(utils): add Windows support to fd module
- Add conditional compilation for Unix/Windows platforms
- Move nix dependency to Unix-only target dependency
- Implement Windows-compatible fd redirection API
- Preserve full functionality on Unix systems
- Add comprehensive documentation for platform differences

Resolves Windows build errors:
- E0433: could not find 'sys' in 'nix'
- E0432: unresolved import 'nix::fcntl'
- E0433: could not find 'unix' in 'os'
- E0432: unresolved import 'nix::unistd'

Closes #43
2025-08-27 15:36:23 +05:30
bahdotsh
5051f71b8b Release 0.7.1
wrkflw@0.7.1
wrkflw-evaluator@0.7.1
wrkflw-executor@0.7.1
wrkflw-parser@0.7.1
wrkflw-runtime@0.7.1
wrkflw-secrets@0.7.1
wrkflw-ui@0.7.1

Generated by cargo-workspaces
2025-08-22 13:13:53 +05:30
Gokul
64b980d254 Merge pull request #55 from bahdotsh/fix/ui_logs_for_copy
fix: fix the ui logs from displaying copy logs noise
2025-08-22 12:23:08 +05:30
bahdotsh
2d809388a2 fix: fix the ui logs from displaying copy logs noise 2025-08-22 12:19:16 +05:30
Gokul
03af6cb7c1 Merge pull request #54 from azzamsa/use-rust-tls
build: use `rustls` instead `openssl`
2025-08-22 12:07:37 +05:30
Azzam S.A
ae52779e11 build: use rustls instead openssl
Simplifies local and container builds by removing OpenSSL deps.
2025-08-22 13:25:50 +07:00
Gokul
fe7be3e1ae Merge pull request #53 from bahdotsh/fix/remove-name-field-requirement
fix(evaluator): remove incorrect name field requirement validation
2025-08-21 23:44:17 +05:30
bahdotsh
30f405ccb9 fix(evaluator): remove incorrect name field requirement validation
The 'name' field is optional per GitHub Actions specification. When omitted,
GitHub displays the workflow file path relative to the repository root.

This change removes the validation logic that incorrectly enforced the name
field as required, aligning the validator with the official JSON schema
which only requires 'on' and 'jobs' fields at the root level.

Fixes #50
2025-08-21 22:45:36 +05:30
Gokul
1d56d86ba5 Merge pull request #52 from bahdotsh/fix/ubuntu-container-image-selection
fix: ubuntu container image selection
2025-08-21 22:37:22 +05:30
bahdotsh
f1ca411281 feat(runtime): add dtolnay/rust-toolchain action support
- Add emulation support for dtolnay/rust-toolchain@ actions
- Include Rust and Cargo availability checks for dtolnay toolchain action
- Improve action detection logging for dtolnay Rust toolchain

Related to #49
2025-08-21 22:28:12 +05:30
bahdotsh
797e31e3d3 fix(executor): correct Ubuntu runner image mapping
- Fix get_runner_image() to map ubuntu-latest to ubuntu:latest instead of node:16-buster-slim
- Update ubuntu-22.04, ubuntu-20.04, ubuntu-18.04 to use proper Ubuntu base images
- Fix step execution to use action-specific images instead of always using runner image
- Update Node.js fallback images from node:16-buster-slim to node:20-slim

Fixes #49
2025-08-21 22:27:56 +05:30
Gokul
4e66f65de7 Merge pull request #51 from bahdotsh/feature/gitignore-support
feat: Add .gitignore support for file copying
2025-08-21 15:32:31 +05:30
23 changed files with 1125 additions and 633 deletions

View File

@@ -3,7 +3,7 @@ name: Build
on:
workflow_dispatch:
push:
branches: [ main ]
branches: [main]
pull_request:
jobs:
@@ -12,12 +12,14 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
os: [ubuntu-latest, macos-latest, windows-latest]
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
- os: macos-latest
target: x86_64-apple-darwin
- os: windows-latest
target: x86_64-pc-windows-msvc
steps:
- name: Checkout code
@@ -31,27 +33,27 @@ jobs:
target: ${{ matrix.target }}
override: true
components: clippy, rustfmt
- name: Check formatting
uses: actions-rs/cargo@v1
with:
command: fmt
args: -- --check
- name: Run clippy
uses: actions-rs/cargo@v1
with:
command: clippy
args: -- -D warnings
- name: Build
uses: actions-rs/cargo@v1
with:
command: build
args: --target ${{ matrix.target }}
- name: Run tests
uses: actions-rs/cargo@v1
with:
command: test
args: --target ${{ matrix.target }}
args: --target ${{ matrix.target }}

930
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,9 @@
[workspace]
members = [
"crates/*"
]
members = ["crates/*"]
resolver = "2"
[workspace.package]
version = "0.7.0"
version = "0.7.3"
edition = "2021"
description = "A GitHub Actions workflow validator and executor"
documentation = "https://github.com/bahdotsh/wrkflw"
@@ -16,6 +14,22 @@ categories = ["command-line-utilities"]
license = "MIT"
[workspace.dependencies]
# Internal crate dependencies
wrkflw-models = { path = "crates/models", version = "0.7.2" }
wrkflw-evaluator = { path = "crates/evaluator", version = "0.7.2" }
wrkflw-executor = { path = "crates/executor", version = "0.7.2" }
wrkflw-github = { path = "crates/github", version = "0.7.2" }
wrkflw-gitlab = { path = "crates/gitlab", version = "0.7.2" }
wrkflw-logging = { path = "crates/logging", version = "0.7.2" }
wrkflw-matrix = { path = "crates/matrix", version = "0.7.2" }
wrkflw-parser = { path = "crates/parser", version = "0.7.2" }
wrkflw-runtime = { path = "crates/runtime", version = "0.7.2" }
wrkflw-secrets = { path = "crates/secrets", version = "0.7.2" }
wrkflw-ui = { path = "crates/ui", version = "0.7.2" }
wrkflw-utils = { path = "crates/utils", version = "0.7.2" }
wrkflw-validators = { path = "crates/validators", version = "0.7.2" }
# External dependencies
clap = { version = "4.3", features = ["derive"] }
colored = "2.0"
serde = { version = "1.0", features = ["derive"] }
@@ -44,7 +58,10 @@ rayon = "1.7.0"
num_cpus = "1.16.0"
regex = "1.10"
lazy_static = "1.4"
reqwest = { version = "0.11", features = ["json"] }
reqwest = { version = "0.11", default-features = false, features = [
"rustls-tls",
"json",
] }
libc = "0.2"
nix = { version = "0.27.1", features = ["fs"] }
urlencoding = "2.1.3"

279
VERSION_MANAGEMENT.md Normal file
View File

@@ -0,0 +1,279 @@
# Version Management Guide
This guide explains how to manage versions in the wrkflw workspace, both for the entire workspace and for individual crates.
## Overview
The wrkflw project uses a Cargo workspace with flexible version management that supports:
- **Workspace-wide versioning**: All crates share the same version
- **Individual crate versioning**: Specific crates can have their own versions
- **Automatic dependency management**: Internal dependencies are managed through workspace inheritance
## Current Setup
### Workspace Dependencies
All internal crate dependencies are defined in the root `Cargo.toml` under `[workspace.dependencies]`:
```toml
[workspace.dependencies]
# Internal crate dependencies
wrkflw-models = { path = "crates/models", version = "0.7.2" }
wrkflw-evaluator = { path = "crates/evaluator", version = "0.7.2" }
# ... other crates
```
### Crate Dependencies
Individual crates reference internal dependencies using workspace inheritance:
```toml
[dependencies]
# Internal crates
wrkflw-models.workspace = true
wrkflw-validators.workspace = true
```
This approach means:
- ✅ No hard-coded versions in individual crates
- ✅ Single source of truth for internal crate versions
- ✅ Easy individual crate versioning without manual updates everywhere
## Version Management Strategies
### Strategy 1: Workspace-Wide Versioning (Recommended for most cases)
Use this when changes affect multiple crates or for major releases.
```bash
# Bump all crates to the same version
cargo ws version patch # 0.7.2 → 0.7.3
cargo ws version minor # 0.7.2 → 0.8.0
cargo ws version major # 0.7.2 → 1.0.0
# Or specify exact version
cargo ws version 1.0.0
# Commit and tag
git add .
git commit -m "chore: bump workspace version to $(grep '^version' Cargo.toml | head -1 | sed 's/.*= *"\([^"]*\)".*/\1/')"
git tag v$(grep '^version' Cargo.toml | head -1 | sed 's/.*= *"\([^"]*\)".*/\1/')
git push origin main --tags
```
### Strategy 2: Individual Crate Versioning
Use this when changes are isolated to specific crates.
#### Using the Helper Script
```bash
# Bump a specific crate
./scripts/bump-crate.sh wrkflw-models patch # 0.7.2 → 0.7.3
./scripts/bump-crate.sh wrkflw-models minor # 0.7.2 → 0.8.0
./scripts/bump-crate.sh wrkflw-models 0.8.5 # Specific version
# The script will:
# 1. Update the crate's Cargo.toml to use explicit version
# 2. Update workspace dependencies
# 3. Show you next steps
```
#### Manual Individual Versioning
If you prefer manual control:
1. **Update the crate's Cargo.toml**:
```toml
# Change from:
version.workspace = true
# To:
version = "0.7.3"
```
2. **Update workspace dependencies**:
```toml
[workspace.dependencies]
wrkflw-models = { path = "crates/models", version = "0.7.3" }
```
3. **Test and commit**:
```bash
cargo check
git add .
git commit -m "bump: wrkflw-models to 0.7.3"
git tag v0.7.3-wrkflw-models
git push origin main --tags
```
## Release Workflows
### Full Workspace Release
```bash
# 1. Make your changes
# 2. Bump version
cargo ws version patch --no-git-commit
# 3. Commit and tag
git add .
git commit -m "chore: release version $(grep '^version' Cargo.toml | head -1 | sed 's/.*= *"\([^"]*\)".*/\1/')"
git tag v$(grep '^version' Cargo.toml | head -1 | sed 's/.*= *"\([^"]*\)".*/\1/')
# 4. Push (this triggers GitHub Actions)
git push origin main --tags
```
### Individual Crate Release
```bash
# 1. Use helper script or manual method above
./scripts/bump-crate.sh wrkflw-models patch
# 2. Follow the script's suggestions
git add .
git commit -m "bump: wrkflw-models to X.Y.Z"
git tag vX.Y.Z-wrkflw-models
git push origin main --tags
# 3. Optionally publish to crates.io
cd crates/models
cargo publish
```
## Publishing to crates.io
### Publishing Individual Crates
```bash
# Navigate to the crate
cd crates/models
# Ensure all dependencies are published first
# (or available on crates.io)
cargo publish --dry-run
# Publish
cargo publish
```
### Publishing All Crates
```bash
# Use cargo-workspaces
cargo ws publish --from-git
```
## Integration with GitHub Actions
The existing `.github/workflows/release.yml` works with both strategies:
- **Tag format `v1.2.3`**: Triggers full workspace release
- **Tag format `v1.2.3-crate-name`**: Could be used for individual crate releases (needs workflow modification)
### Modifying for Individual Crate Releases
To support individual crate releases, you could modify the workflow to:
```yaml
on:
push:
tags:
- 'v*' # Full releases: v1.2.3
- 'v*-wrkflw-*' # Individual releases: v1.2.3-wrkflw-models
```
## Best Practices
### When to Use Each Strategy
**Use Workspace-Wide Versioning when:**
- Making breaking changes across multiple crates
- Major feature releases
- Initial development phases
- Simpler release management is preferred
**Use Individual Crate Versioning when:**
- Changes are isolated to specific functionality
- Different crates have different stability levels
- You want to minimize dependency updates for users
- Publishing to crates.io with different release cadences
### Version Numbering
Follow [Semantic Versioning](https://semver.org/):
- **Patch (0.7.2 → 0.7.3)**: Bug fixes, internal improvements
- **Minor (0.7.2 → 0.8.0)**: New features, backward compatible
- **Major (0.7.2 → 1.0.0)**: Breaking changes
### Dependency Management
- Keep internal dependencies using workspace inheritance
- Only specify explicit versions when a crate diverges from workspace version
- Always test with `cargo check` and `cargo test` before releasing
- Use `cargo tree` to verify dependency resolution
## Troubleshooting
### Common Issues
**Issue**: Cargo complains about version mismatches
```bash
# Solution: Check workspace dependencies match crate versions
grep -r "version.*=" crates/*/Cargo.toml
grep "wrkflw-.*version" Cargo.toml
```
**Issue**: Published crate can't find dependencies
```bash
# Solution: Ensure all dependencies are published to crates.io first
# Or use path dependencies only for local development
```
**Issue**: GitHub Actions fails on tag
```bash
# Solution: Ensure tag format matches workflow trigger
git tag -d v1.2.3 # Delete local tag
git push origin :refs/tags/v1.2.3 # Delete remote tag
git tag v1.2.3 # Recreate with correct format
git push origin v1.2.3
```
## Tools and Commands
### Useful Commands
```bash
# List all workspace members with versions
cargo ws list
# Check all crates
cargo check --workspace
# Test all crates
cargo test --workspace
# Show dependency tree
cargo tree
# Show outdated dependencies
cargo outdated
# Verify publishability
cargo publish --dry-run --manifest-path crates/models/Cargo.toml
```
### Recommended Tools
- `cargo-workspaces`: Workspace management
- `cargo-outdated`: Check for outdated dependencies
- `cargo-audit`: Security audit
- `cargo-machete`: Find unused dependencies
## Migration Notes
If you're migrating from the old hard-coded version system:
1. All internal crate versions are now managed in workspace `Cargo.toml`
2. Individual crates use `crate-name.workspace = true` for internal dependencies
3. Use the helper script or manual process above for individual versioning
4. The system is fully backward compatible with existing workflows

View File

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

View File

@@ -21,26 +21,9 @@ pub fn evaluate_workflow_file(path: &Path, verbose: bool) -> Result<ValidationRe
return Ok(result);
}
// Check if name exists
if workflow.get("name").is_none() {
// Check if this might be a reusable workflow caller before reporting missing name
let has_reusable_workflow_job = if let Some(Value::Mapping(jobs)) = workflow.get("jobs") {
jobs.values().any(|job| {
if let Some(job_config) = job.as_mapping() {
job_config.contains_key(Value::String("uses".to_string()))
} else {
false
}
})
} else {
false
};
// Only report missing name if it's not a workflow with reusable workflow jobs
if !has_reusable_workflow_job {
result.add_issue("Workflow is missing a name".to_string());
}
}
// Note: The 'name' field is optional per GitHub Actions specification.
// When omitted, GitHub displays the workflow file path relative to the repository root.
// We do not validate name presence as it's not required by the schema.
// Check if jobs section exists
match workflow.get("jobs") {

View File

@@ -12,13 +12,13 @@ categories.workspace = true
[dependencies]
# Internal crates
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-secrets = { path = "../secrets", version = "0.7.0" }
wrkflw-utils = { path = "../utils", version = "0.7.0" }
wrkflw-models.workspace = true
wrkflw-parser.workspace = true
wrkflw-runtime.workspace = true
wrkflw-logging.workspace = true
wrkflw-matrix.workspace = true
wrkflw-secrets.workspace = true
wrkflw-utils.workspace = true
# External dependencies
async-trait.workspace = true

View File

@@ -570,7 +570,7 @@ async fn prepare_action(
} else {
// It's a JavaScript or composite action
// For simplicity, we'll use node to run it (this would need more work for full support)
return Ok("node:16-buster-slim".to_string());
return Ok("node:20-slim".to_string());
}
}
@@ -628,7 +628,7 @@ fn determine_action_image(repository: &str) -> String {
{
"catthehacker/ubuntu:act-latest".to_string() // Use act runner image for core actions
} else {
"node:16-buster-slim".to_string() // Default for other actions
"node:20-slim".to_string() // Default for other actions
}
}
}
@@ -1160,28 +1160,13 @@ async fn execute_step(ctx: StepExecutionContext<'_>) -> Result<StepResult, Execu
detailed_output
.push_str(&format!(" - Destination: {}\n", ctx.working_dir.display()));
// Add list of top-level files/directories that were copied (limit to 10)
detailed_output.push_str("\nTop-level files/directories copied:\n");
// Add a summary count instead of listing all files
if let Ok(entries) = std::fs::read_dir(&current_dir) {
for (i, entry) in entries.take(10).enumerate() {
if let Ok(entry) = entry {
let file_type = if entry.path().is_dir() {
"directory"
} else {
"file"
};
detailed_output.push_str(&format!(
" - {} ({})\n",
entry.file_name().to_string_lossy(),
file_type
));
}
if i >= 9 {
detailed_output.push_str(" - ... (more items not shown)\n");
break;
}
}
let entry_count = entries.count();
detailed_output.push_str(&format!(
"\nCopied {} top-level items to workspace\n",
entry_count
));
}
detailed_output
@@ -1224,13 +1209,15 @@ async fn execute_step(ctx: StepExecutionContext<'_>) -> Result<StepResult, Execu
let mut owned_strings: Vec<String> = Vec::new(); // Keep strings alive until after we use cmd
// Special handling for Rust actions
if uses.starts_with("actions-rs/") {
if uses.starts_with("actions-rs/") || uses.starts_with("dtolnay/rust-toolchain") {
wrkflw_logging::info(
"🔄 Detected Rust action - using system Rust installation",
);
// For toolchain action, verify Rust is installed
if uses.starts_with("actions-rs/toolchain@") {
if uses.starts_with("actions-rs/toolchain@")
|| uses.starts_with("dtolnay/rust-toolchain@")
{
let rustc_version = Command::new("rustc")
.arg("--version")
.output()
@@ -1556,7 +1543,7 @@ async fn execute_step(ctx: StepExecutionContext<'_>) -> Result<StepResult, Execu
let output = ctx
.runtime
.run_container(
ctx.runner_image,
&image,
&cmd.to_vec(),
&env_vars,
container_workspace,
@@ -1834,6 +1821,13 @@ fn copy_directory_contents_with_gitignore(
gitignore
};
// Log summary of the copy operation
wrkflw_logging::debug(&format!(
"Copying directory contents from {} to {}",
from.display(),
to.display()
));
for entry in std::fs::read_dir(from)
.map_err(|e| ExecutionError::Execution(format!("Failed to read directory: {}", e)))?
{
@@ -1855,7 +1849,7 @@ fn copy_directory_contents_with_gitignore(
}
}
wrkflw_logging::debug(&format!("Copying entry: {path:?} -> {to:?}"));
// Log individual files only in trace mode (removed verbose per-file logging)
// Additional basic filtering for hidden files (but allow .gitignore and .github)
let file_name = match path.file_name() {
@@ -1905,11 +1899,11 @@ fn copy_directory_contents_with_gitignore(
fn get_runner_image(runs_on: &str) -> String {
// Map GitHub runners to Docker images
match runs_on.trim() {
// ubuntu runners - micro images (minimal size)
"ubuntu-latest" => "node:16-buster-slim",
"ubuntu-22.04" => "node:16-bullseye-slim",
"ubuntu-20.04" => "node:16-buster-slim",
"ubuntu-18.04" => "node:16-buster-slim",
// ubuntu runners - using Ubuntu base images for better compatibility
"ubuntu-latest" => "ubuntu:latest",
"ubuntu-22.04" => "ubuntu:22.04",
"ubuntu-20.04" => "ubuntu:20.04",
"ubuntu-18.04" => "ubuntu:18.04",
// ubuntu runners - medium images (with more tools)
"ubuntu-latest-medium" => "catthehacker/ubuntu:act-latest",

View File

@@ -12,7 +12,7 @@ categories.workspace = true
[dependencies]
# Internal crates
wrkflw-models = { path = "../models", version = "0.7.0" }
wrkflw-models.workspace = true
# 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.7.0" }
wrkflw-models.workspace = true
# External dependencies
lazy_static.workspace = true

View File

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

View File

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

View File

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

View File

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

View File

@@ -643,6 +643,15 @@ pub async fn handle_special_action(action: &str) -> Result<(), ContainerError> {
wrkflw_logging::info(&format!("🔄 Detected Rust formatter action: {}", action));
check_command_available("rustfmt", "rustfmt", "rustup component add rustfmt");
} else if action.starts_with("dtolnay/rust-toolchain@") {
// For dtolnay/rust-toolchain action, check for Rust installation
wrkflw_logging::info(&format!(
"🔄 Detected dtolnay Rust toolchain action: {}",
action
));
check_command_available("rustc", "Rust", "https://rustup.rs/");
check_command_available("cargo", "Cargo", "https://rustup.rs/");
} else if action.starts_with("actions/setup-node@") {
// Node.js setup action
wrkflw_logging::info(&format!("🔄 Detected Node.js setup action: {}", action));
@@ -784,7 +793,7 @@ async fn cleanup_processes() {
let _ = Command::new("taskkill")
.arg("/F")
.arg("/PID")
.arg(&pid.to_string())
.arg(pid.to_string())
.output();
}

View File

@@ -1,30 +1,35 @@
[package]
name = "wrkflw-secrets"
version = "0.7.0"
edition = "2021"
authors = ["wrkflw contributors"]
description = "Secrets management for wrkflw workflow execution"
license = "MIT"
keywords = ["secrets", "workflow", "ci-cd", "github-actions"]
categories = ["development-tools"]
version.workspace = true
edition.workspace = true
description = "Secrets management for wrkflw workflow execution engine"
license.workspace = true
documentation.workspace = true
homepage.workspace = true
repository.workspace = true
keywords.workspace = true
categories.workspace = true
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde_yaml = "0.9"
tokio = { version = "1.0", features = ["full"] }
# External dependencies
serde.workspace = true
serde_json.workspace = true
serde_yaml.workspace = true
tokio.workspace = true
thiserror.workspace = true
dirs.workspace = true
regex.workspace = true
lazy_static.workspace = true
chrono = { workspace = true, features = ["serde"] }
async-trait.workspace = true
# Dependencies not in workspace
anyhow = "1.0"
thiserror = "1.0"
base64 = "0.21"
aes-gcm = "0.10"
rand = "0.8"
dirs = "5.0"
tracing = "0.1"
regex = "1.10"
url = "2.4"
async-trait = "0.1"
lazy_static = "1.4"
chrono = { version = "0.4", features = ["serde"] }
pbkdf2 = "0.12"
hmac = "0.12"
sha2 = "0.10"
@@ -46,9 +51,9 @@ file-provider = []
# all-providers = ["vault-provider", "aws-provider", "azure-provider", "gcp-provider"]
[dev-dependencies]
tempfile = "3.8"
tempfile.workspace = true
tokio-test = "0.4"
uuid = { version = "1.6", features = ["v4"] }
uuid.workspace = true
criterion = { version = "0.5", features = ["html_reports"] }
[[bench]]

View File

@@ -12,12 +12,12 @@ categories.workspace = true
[dependencies]
# Internal crates
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" }
wrkflw-models.workspace = true
wrkflw-evaluator.workspace = true
wrkflw-executor.workspace = true
wrkflw-logging.workspace = true
wrkflw-utils.workspace = true
wrkflw-github.workspace = true
# External dependencies
chrono.workspace = true

View File

@@ -12,9 +12,11 @@ categories.workspace = true
[dependencies]
# Internal crates
wrkflw-models = { path = "../models", version = "0.7.0" }
wrkflw-models.workspace = true
# External dependencies
serde.workspace = true
serde_yaml.workspace = true
[target.'cfg(unix)'.dependencies]
nix.workspace = true

View File

@@ -3,7 +3,7 @@
Shared helpers used across crates.
- Workflow file detection (`.github/workflows/*.yml`, `.gitlab-ci.yml`)
- File-descriptor redirection utilities for silencing noisy subprocess output
- File-descriptor redirection utilities for silencing noisy subprocess output (Unix only; Windows support is limited)
### Example
@@ -14,7 +14,7 @@ use wrkflw_utils::{is_workflow_file, fd::with_stderr_to_null};
assert!(is_workflow_file(Path::new(".github/workflows/ci.yml")));
let value = with_stderr_to_null(|| {
eprintln!("this is hidden");
eprintln!("this is hidden on Unix, visible on Windows");
42
}).unwrap();
assert_eq!(value, 42);

View File

@@ -35,78 +35,145 @@ pub fn is_workflow_file(path: &Path) -> bool {
}
/// Module for safely handling file descriptor redirection
///
/// On Unix systems (Linux, macOS), this module provides true file descriptor
/// redirection by duplicating stderr and redirecting it to /dev/null.
///
/// On Windows systems, the redirection functionality is limited due to platform
/// differences in file descriptor handling. The functions will execute without
/// error but stderr may not be fully suppressed.
pub mod fd {
use nix::fcntl::{open, OFlag};
use nix::sys::stat::Mode;
use nix::unistd::{close, dup, dup2};
use std::io::{self, Result};
use std::os::unix::io::RawFd;
use std::path::Path;
/// Standard file descriptors
const STDERR_FILENO: RawFd = 2;
use std::io::Result;
/// Represents a redirected stderr that can be restored
pub struct RedirectedStderr {
original_fd: Option<RawFd>,
null_fd: Option<RawFd>,
#[cfg(unix)]
original_fd: Option<std::os::unix::io::RawFd>,
#[cfg(unix)]
null_fd: Option<std::os::unix::io::RawFd>,
#[cfg(windows)]
_phantom: std::marker::PhantomData<()>,
}
impl RedirectedStderr {
/// Creates a new RedirectedStderr that redirects stderr to /dev/null
pub fn to_null() -> Result<Self> {
// Duplicate the current stderr fd
let stderr_backup = match dup(STDERR_FILENO) {
Ok(fd) => fd,
Err(e) => return Err(io::Error::other(e)),
};
#[cfg(unix)]
mod unix_impl {
use super::*;
use nix::fcntl::{open, OFlag};
use nix::sys::stat::Mode;
use nix::unistd::{close, dup, dup2};
use std::io;
use std::os::unix::io::RawFd;
use std::path::Path;
// Open /dev/null
let null_fd = match open(Path::new("/dev/null"), OFlag::O_WRONLY, Mode::empty()) {
Ok(fd) => fd,
Err(e) => {
/// Standard file descriptors
const STDERR_FILENO: RawFd = 2;
impl RedirectedStderr {
/// Creates a new RedirectedStderr that redirects stderr to /dev/null
pub fn to_null() -> Result<Self> {
// Duplicate the current stderr fd
let stderr_backup = match dup(STDERR_FILENO) {
Ok(fd) => fd,
Err(e) => return Err(io::Error::other(e)),
};
// Open /dev/null
let null_fd = match open(Path::new("/dev/null"), OFlag::O_WRONLY, Mode::empty()) {
Ok(fd) => fd,
Err(e) => {
let _ = close(stderr_backup); // Clean up on error
return Err(io::Error::other(e));
}
};
// Redirect stderr to /dev/null
if let Err(e) = dup2(null_fd, STDERR_FILENO) {
let _ = close(stderr_backup); // Clean up on error
let _ = close(null_fd);
return Err(io::Error::other(e));
}
};
// Redirect stderr to /dev/null
if let Err(e) = dup2(null_fd, STDERR_FILENO) {
let _ = close(stderr_backup); // Clean up on error
let _ = close(null_fd);
return Err(io::Error::other(e));
Ok(RedirectedStderr {
original_fd: Some(stderr_backup),
null_fd: Some(null_fd),
})
}
Ok(RedirectedStderr {
original_fd: Some(stderr_backup),
null_fd: Some(null_fd),
})
}
}
impl Drop for RedirectedStderr {
/// Automatically restores stderr when the RedirectedStderr is dropped
fn drop(&mut self) {
if let Some(orig_fd) = self.original_fd.take() {
// Restore the original stderr
let _ = dup2(orig_fd, STDERR_FILENO);
let _ = close(orig_fd);
}
impl Drop for RedirectedStderr {
/// Automatically restores stderr when the RedirectedStderr is dropped
fn drop(&mut self) {
if let Some(orig_fd) = self.original_fd.take() {
// Restore the original stderr
let _ = dup2(orig_fd, STDERR_FILENO);
let _ = close(orig_fd);
}
// Close the null fd
if let Some(null_fd) = self.null_fd.take() {
let _ = close(null_fd);
// Close the null fd
if let Some(null_fd) = self.null_fd.take() {
let _ = close(null_fd);
}
}
}
}
/// Run a function with stderr redirected to /dev/null, then restore stderr
#[cfg(windows)]
mod windows_impl {
use super::*;
impl RedirectedStderr {
/// Creates a new RedirectedStderr that redirects stderr to NUL on Windows
pub fn to_null() -> Result<Self> {
// On Windows, we can't easily redirect stderr at the file descriptor level
// like we can on Unix systems. This is a simplified implementation that
// doesn't actually redirect but provides the same interface.
// The actual stderr suppression will need to be handled differently on Windows.
Ok(RedirectedStderr {
_phantom: std::marker::PhantomData,
})
}
}
impl Drop for RedirectedStderr {
/// No-op drop implementation for Windows
fn drop(&mut self) {
// Nothing to restore on Windows in this simplified implementation
}
}
}
/// Run a function with stderr redirected to /dev/null (Unix) or suppressed (Windows), then restore stderr
///
/// # Platform Support
/// - **Unix (Linux, macOS)**: Fully supported - stderr is redirected to /dev/null
/// - **Windows**: Limited support - function executes but stderr may be visible
///
/// # Example
/// ```
/// use wrkflw_utils::fd::with_stderr_to_null;
///
/// let result = with_stderr_to_null(|| {
/// eprintln!("This will be hidden on Unix");
/// 42
/// }).unwrap();
/// assert_eq!(result, 42);
/// ```
pub fn with_stderr_to_null<F, T>(f: F) -> Result<T>
where
F: FnOnce() -> T,
{
let _redirected = RedirectedStderr::to_null()?;
Ok(f())
#[cfg(unix)]
{
let _redirected = RedirectedStderr::to_null()?;
Ok(f())
}
#[cfg(windows)]
{
// On Windows, we can't easily redirect stderr at the FD level,
// so we just run the function without redirection.
// This means stderr won't be suppressed on Windows, but the function will work.
Ok(f())
}
}
}
@@ -116,15 +183,16 @@ mod tests {
#[test]
fn test_fd_redirection() {
// This test will write to stderr, which should be redirected
// This test will write to stderr, which should be redirected on Unix
// On Windows, it will just run normally without redirection
let result = fd::with_stderr_to_null(|| {
// This would normally appear in stderr
eprintln!("This should be redirected to /dev/null");
// This would normally appear in stderr (suppressed on Unix, visible on Windows)
eprintln!("This should be redirected to /dev/null on Unix");
// Return a test value to verify the function passes through the result
42
});
// The function should succeed and return our test value
// The function should succeed and return our test value on both platforms
assert!(result.is_ok());
assert_eq!(result.unwrap(), 42);
}

View File

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

View File

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

97
scripts/bump-crate.sh Executable file
View File

@@ -0,0 +1,97 @@
#!/bin/bash
# Script to bump individual crate versions and update workspace dependencies
# Usage: ./scripts/bump-crate.sh <crate-name> <version-type>
# Example: ./scripts/bump-crate.sh wrkflw-models patch
# Example: ./scripts/bump-crate.sh wrkflw-models 0.7.5
set -e
CRATE_NAME="$1"
VERSION_TYPE="$2"
if [[ -z "$CRATE_NAME" || -z "$VERSION_TYPE" ]]; then
echo "Usage: $0 <crate-name> <version-type>"
echo " crate-name: Name of the crate to bump (e.g., wrkflw-models)"
echo " version-type: patch|minor|major or specific version (e.g., 0.7.5)"
echo ""
echo "Available crates:"
ls crates/ | grep -v README.md
exit 1
fi
CRATE_DIR="crates/${CRATE_NAME#wrkflw-}"
if [[ ! -d "$CRATE_DIR" ]]; then
echo "Error: Crate directory '$CRATE_DIR' not found"
echo "Available crates:"
ls crates/ | grep -v README.md
exit 1
fi
echo "Bumping $CRATE_NAME with $VERSION_TYPE..."
# Get current version from the crate's Cargo.toml
CURRENT_VERSION=$(grep "^version" "$CRATE_DIR/Cargo.toml" | head -1 | sed 's/.*= *"\([^"]*\)".*/\1/' | sed 's/.*workspace *= *true.*//')
if [[ "$CURRENT_VERSION" == "" ]]; then
# If using workspace version, get it from workspace Cargo.toml
CURRENT_VERSION=$(grep "^version" Cargo.toml | head -1 | sed 's/.*= *"\([^"]*\)".*/\1/')
echo "Current workspace version: $CURRENT_VERSION"
else
echo "Current crate version: $CURRENT_VERSION"
fi
# Calculate new version
if [[ "$VERSION_TYPE" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
NEW_VERSION="$VERSION_TYPE"
else
# Use semver logic for patch/minor/major
IFS='.' read -ra VERSION_PARTS <<< "$CURRENT_VERSION"
MAJOR="${VERSION_PARTS[0]}"
MINOR="${VERSION_PARTS[1]}"
PATCH="${VERSION_PARTS[2]}"
case "$VERSION_TYPE" in
"patch")
NEW_VERSION="$MAJOR.$MINOR.$((PATCH + 1))"
;;
"minor")
NEW_VERSION="$MAJOR.$((MINOR + 1)).0"
;;
"major")
NEW_VERSION="$((MAJOR + 1)).0.0"
;;
*)
echo "Error: Invalid version type. Use patch|minor|major or specify exact version"
exit 1
;;
esac
fi
echo "New version: $NEW_VERSION"
# Update the crate's Cargo.toml to use explicit version instead of workspace
sed -i.bak "s/version\.workspace = true/version = \"$NEW_VERSION\"/" "$CRATE_DIR/Cargo.toml"
# Update the workspace Cargo.toml with the new version
if grep -q "$CRATE_NAME.*version.*=" Cargo.toml; then
sed -i.bak "s/\($CRATE_NAME.*version = \"\)[^\"]*\"/\1$NEW_VERSION\"/" Cargo.toml
else
echo "Warning: $CRATE_NAME not found in workspace dependencies"
fi
# Clean up backup files
rm -f "$CRATE_DIR/Cargo.toml.bak" Cargo.toml.bak
echo ""
echo "✅ Successfully bumped $CRATE_NAME to version $NEW_VERSION"
echo ""
echo "Next steps:"
echo "1. Review the changes: git diff"
echo "2. Test the build: cargo check"
echo "3. Commit the changes: git add . && git commit -m 'bump: $CRATE_NAME to $NEW_VERSION'"
echo "4. Create a tag: git tag v$NEW_VERSION-$CRATE_NAME"
echo "5. Push: git push origin main --tags"
echo ""
echo "To publish individual crate:"
echo " cd $CRATE_DIR && cargo publish"