feat(cli): add luca run command for pipeline execution#81
Merged
Conversation
…Running protocol Pipeline tasks need to run in specific directories and may be interactive (e.g. TTY-aware tools that read stdin). Extend the protocol with these two parameters and provide backward-compatible convenience overloads so existing callers are unchanged.
Define the YAML-decodable DSL models for the pipeline executor. PipelineTask maps continue-on-error and working-directory CodingKeys; Pipeline carries the top-level task list, shared env, and optional working-directory default.
…to FileManaging PipelineValidator needs to check executable availability on disk. Follow the existing fine-grained FileManaging protocol pattern: new narrow protocol, wired into the umbrella FileManaging composition, and implemented in FileManagerWrapper and FileManagerWrapperMock.
YAML-based pipeline loader following the SpecLoader pattern. Reads a file URL, decodes it into Pipeline via YAMLDecoder, and surfaces missingPipeline and invalidPipeline errors with clean, human-readable descriptions (no raw NSUnderlyingError leakage).
Pre-flight tool availability check before any task runs. Uses the explicit tools: list when provided; falls back to the first whitespace-delimited token of the command string. Absolute-path tools are checked directly; bare names are searched across PATH directories via isExecutableFile. toolCheckResults(for:) returns per-task [[ToolCheckResult]] for dry-run display.
Sequential task executor. Wraps each command in /bin/bash -c "set -eo pipefail && <cmd>" with stdin inherited so TTY-aware tools work. Merges env in layers (pipeline-level then task-level). Resolves relative working-directory paths against the invocation directory. continue-on-error tasks log a warning and proceed; any other non-zero exit throws taskFailed and halts the pipeline.
New RunCommand under an "Execution" group. Accepts a pipeline name (resolved via convention: ./name.yml, ./name, ./pipelines/name.yml, ./pipelines/name) or an explicit --file path. --dry-run runs pre-flight validation and prints per-task tool availability (✓/✗) without executing anything. Adds the pipelinesFolder constant to LucaCore.
…PipelineRunner PipelineLoaderTests: valid/missing/malformed YAML, error description readability. PipelineValidatorTests: explicit tools list, first-word fallback, absolute paths, toolCheckResults, error descriptions. PipelineRunnerTests: single/multi-task execution, stdin inheritance, env merging (pipeline vs task precedence), working-directory resolution (relative, absolute, task vs pipeline default), failure propagation, continue-on-error, error descriptions.
- PipelineLoaderError: add Equatable conformance; fold Error into a pre-formatted String so synthesis is automatic and round-trip equality is predictable (matches SpecLoaderError intent) - PipelineLoader: add // MARK: - PipelineLoading divider - PipelineRunner: replace hardcoded /bin/bash with /usr/bin/env bash for portability across Linux layouts; align summary line padding constant with header width (60) - PipelineValidating: add toolCheckResults(for:) to the protocol so RunCommand depends on the abstraction, not the concrete type - RunCommand: accept PipelineValidating in printDryRun; add Equatable to RunCommandError - Pipeline/PipelineTask: add explicit public memberwise inits so public visibility is unambiguous and external callers don't rely on the synthesized internal init - Tests: fix arguments index for new bash/env invocation shape; add test for empty command (validator passes silently) and empty task list (runner completes without subprocess calls)
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
4ddd38e to
18ef81c
Compare
…ner, and SubprocessRunner Cover previously missing lines: dataCorrupted and keyNotFound DecodingError paths in PipelineLoader, the public init in PipelineRunner, and the workingDirectory branch in SubprocessRunner.
Add fixtures and tests to exercise the non-DecodingError guard path (invalid UTF-8 data) and the valueNotFound path (null task element in array), reducing missing lines in PipelineLoader from 3 to 1 (the unreachable @unknown default).
…r path Use a single 0x80 byte which fails both UTF-8 and UTF-16 decoding, causing Yams to throw a YamlError (not wrapped in DecodingError). This covers the guard-else branch in formattedParseError that was previously unreachable with the old fixture.
Move formattedParseError to an internal PipelineLoader extension so tests can call it directly. Add test for the .valueNotFound DecodingError case (line 83) which is unreachable via YAML loading because Yams converts null scalars to strings. Collapse the @unknown default body to a single-line break to minimise unreachable code surface.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Adds
luca run— a new command that loads a YAML pipeline file and executes its tasks sequentially in a shell. This turns Luca into both a tool manager and a pipeline runner, so the same tool that pins your toolchain can also orchestrate the workflows that use it.Each task runs under
/usr/bin/env bash -c "set -eo pipefail && <command>", inheriting stdout, stderr, and stdin from the parent process. Tools used by the pipelines are validated againstPATHbefore execution begins.Pipeline File Format
A pipeline file is a YAML file with a
tasks:list. Environment variables and a default working directory can be set at the pipeline or task level.Task fields
namecommandtoolscommandenvenv)working-directorycontinue-on-errortrue, a non-zero exit logs a warning and execution continuesPipeline fields
tasksenvworking-directoryUsage
Convention-based lookup
Searches for the pipeline file in order:
./<name>.yml./<name>./pipelines/<name>.yml./pipelines/<name>Explicit file path
Dry run
Validates tool availability and prints all tasks without executing any command:
Normal execution
Changes
SubprocessRunner: extended withworkingDirectoryandinheritStdinparameters (standardInputstaysnullDevicefor all existing install paths)Pipeline,PipelineTask(Codablevalue types)PipelineLoader,PipelineValidator,PipelineRunner— each with its own*ingprotocol and nested error typeRunCommandregistered under a new"Pipelines"groupDocumentation/specs/Type of Change
How Has This Been Tested?
PipelineLoaderTests,PipelineValidatorTests,PipelineRunnerTests)Checklist
swift build)swift test)Breaking Changes?