Pipex is a TypeScript CLI tool for parsing, validating, and dry-running CI workflows locally. It targets GitHub Actions-style workflows with a modular parser and execution engine designed to support multiple CI platforms.
- Tolerant Parser: Parses GitHub Actions workflows with warnings for missing fields and supports reusable workflows (
uses:jobs don't requireruns-on). - Dry-run Executor: Simulates job/step execution respecting dependencies (
needs:), evaluates conditional expressions (if:), and records mock results without running actual commands. - Flexible Evaluator: Supports nested and flattened dot-path context resolution (e.g.,
steps.cache.outputs.hitor"steps.cache.outputs.hit": true). - Config-driven Testing: Load context variables from JSON config files to control condition evaluation.
- Reporter: Output execution results in CLI (colored, human-readable) or JSON formats.
- Structured Warnings: Parser warnings available programmatically via
parsePipelineWithWarnings.
Clone the repository and install dependencies:
npm installBuild the TypeScript source:
npm run buildParse and display workflow structure with warnings:
node dist/cli/index.js parse [workflow-file]Example:
node dist/cli/index.js parse .github/workflows/ci.ymlValidate workflow syntax, dependencies, and best practices:
node dist/cli/index.js validate [workflow-file]Execute workflow in dry-run mode (simulates execution without running commands):
node dist/cli/index.js run [options]Options:
-f, --file <path>- Path to workflow YAML file (default:.github/workflows/ci.yml)-c, --config <path>- Path to JSON config file for context variables-r, --report <mode>- Report mode:cli(default) orjson--no-dry-run- Placeholder for actual execution (not implemented)
Default discovery:
- When
--fileis omitted, the CLI searches.github/workflows/**recursively and uses the first.yml/.yamlfile found. - If none are found, the command exits with an error.
Example with config:
node dist/cli/index.js run --file test.yaml --config testConfig.json --report cliCreate a JSON config file to provide context variables for condition evaluation:
{
"env": {
"NODE_ENV": "production",
"TEST": "yes"
},
"secrets": {
"API_KEY": "test-key"
},
"steps.cacheNodeModules.outputs.cache-hit": "true"
}Supported Config Properties:
env: Environment variables (accessible asenv.VAR_NAME)secrets: Secret values (accessible assecrets.SECRET_NAME)mockOutputs: Mock output values for expressions- Flattened dot keys: Any top-level key with dots (e.g.,
"steps.job.outputs.value": "data") is expanded into nested context
Dot-path Resolution:
The evaluator supports flexible resolution:
- Nested:
{ steps: { cache: { outputs: { hit: true } } } } - Flattened at root:
{ "steps.cache.outputs.hit": true } - Mixed:
{ steps: { "cache.outputs.hit": true } }
All forms resolve correctly in expressions like ${{ steps.cache.outputs.hit == true }}.
The parser handles workflows tolerantly:
- Reusable workflow jobs (containing
uses:) can omitruns-on- a warning is logged. - Normal jobs (containing
steps:) must haveruns-onor an error is thrown. - Missing or malformed optional fields produce warnings rather than errors.
- Step
ifconditions are parsed and passed to the executor as strings.
jobs:
deploy:
uses: org/repo/.github/workflows/deploy.yml@main
with:
env: productionThis is valid and will not require runs-on.
The executor simulates workflow execution:
- Resolves job execution order using topological sort (respects
needs:dependencies). - Evaluates step
if:conditions and marks steps assuccess,skipped, orunknown. - Records mock outputs without executing actual commands.
- Detects circular dependencies and reports errors.
Step Condition Evaluation:
if: true→ step executes (mocked success)if: false→ step skippedif: ${{ env.MISSING }}→ unknown (variable not found in context)
Two output modes:
- CLI: Human-friendly colored output with job/step hierarchy and status symbols (✔ success, ↷ skipped, ? unknown, ✖ error).
- JSON: Machine-readable structured output with all job and step states.
Example CLI output:
Job: main [mocked]
✔ Cache node modules - success
↷ Get npm cache directory path - skipped
[Dry-run] Step skipped due to condition
✔ Run tests - success
Run the test suite:
npm testRun specific test file:
npx jest src/evaluator/__tests__/index.test.tsTests cover:
- Parser (workflow parsing,
ifconditions, reusable workflows) - Executor (dependency resolution, condition evaluation, dry-run simulation)
- Evaluator (expression parsing, dot-path resolution, context lookups)
- Reporter (CLI and JSON formatting)
- Config loader (flattened key expansion, env overrides)
import { parsePipelineWithWarnings } from './src/parser';
import { PipelineExecutor } from './src/executor';
import { Reporter } from './src/reporter';
(async () => {
const { model, warnings } = await parsePipelineWithWarnings('.github/workflows/ci.yml');
if (warnings.length > 0) {
console.warn('Parser warnings:', warnings);
}
const executor = new PipelineExecutor({
context: {
env: { NODE_ENV: 'test' },
'steps.cache.outputs.hit': 'true'
}
});
const state = await executor.execute(model);
const reporter = new Reporter('cli');
console.log(reporter.report(state));
})();The parser is modular:
- To support other CI platforms (GitLab, CircleCI, Bitbucket), create an adapter that converts the platform's workflow format into the
PipelineModelinterface. - Reuse the
Evaluator,Executor, andReportermodules without modification.
Contributions welcome! Please open an issue or submit a pull request.
MIT