mirror of
https://github.com/redwoodjs/local-ci.git
synced 2026-09-19 07:54:29 +08:00
417bd260b6
* Support private workflow container images * Resolve private image expression contexts * feat: rename Agent CI to Local CI
625 lines
31 KiB
JSON
625 lines
31 KiB
JSON
{
|
|
"$schema": "./compatibility.schema.json",
|
|
"intro": "Local CI aims to run real GitHub Actions workflows locally. The table below shows current support against the [official workflow syntax](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions).",
|
|
"legend": {
|
|
"supported": {
|
|
"icon": "✅",
|
|
"label": "Supported",
|
|
"description": "Fully implemented and behaves like GitHub Actions"
|
|
},
|
|
"partial": {
|
|
"icon": "⚠️",
|
|
"label": "Partial",
|
|
"description": "Works with limitations — see notes for details"
|
|
},
|
|
"unsupported": {
|
|
"icon": "❌",
|
|
"label": "Not supported",
|
|
"description": "Not implemented — the feature is skipped or errors"
|
|
},
|
|
"ignored": {
|
|
"icon": "🟡",
|
|
"label": "Ignored (no-op)",
|
|
"description": "Parsed without error but has no effect on the run"
|
|
},
|
|
"not-planned": {
|
|
"icon": "🚫",
|
|
"label": "Not planned",
|
|
"description": "Cannot be supported due to architectural constraints"
|
|
}
|
|
},
|
|
"proofNote": "`proof` lists workflow files that exercise the feature end-to-end under `pnpm local-ci-dev run --all`. Internal field — not rendered on the website or in the markdown table. An empty array on a `supported`/`partial` row is a coverage gap.",
|
|
"sections": [
|
|
{
|
|
"id": "workflow",
|
|
"title": "Workflow-Level Keys",
|
|
"description": "Top-level keys that control when and how a workflow runs — triggers, environment defaults, and concurrency.",
|
|
"rows": [
|
|
{
|
|
"key": "`name`",
|
|
"status": "supported",
|
|
"notes": "",
|
|
"proof": [".github/workflows/smoke-matrix.yml"]
|
|
},
|
|
{ "key": "`run-name`", "status": "ignored", "notes": "Parsed but not displayed anywhere" },
|
|
{
|
|
"key": "`on` (push, pull_request)",
|
|
"status": "supported",
|
|
"notes": "Branch and path filters are evaluated when using `--all`",
|
|
"proof": [".github/workflows/release.yml", ".github/workflows/tests.yml"]
|
|
},
|
|
{
|
|
"key": "`on.<event>.branches` / `branches-ignore`",
|
|
"status": "supported",
|
|
"notes": "Branch-name glob filters; `--all` only runs workflows whose filter matches the current branch (`main` for non-PR runs).",
|
|
"proof": [".github/workflows/cache.yml", ".github/workflows/tests.yml"]
|
|
},
|
|
{
|
|
"key": "`on.<event>.paths` / `paths-ignore`",
|
|
"status": "supported",
|
|
"notes": "Path-pattern filters. With `--all`, a workflow is skipped if none of the changed files match `paths:` or all of them match `paths-ignore:`.",
|
|
"proof": [".github/workflows/tests.yml"]
|
|
},
|
|
{
|
|
"key": "`on.<event>.tags` / `tags-ignore`",
|
|
"status": "unsupported",
|
|
"notes": "Tag-pattern filters for push events are not evaluated — `--all` treats a tagged run as an untagged push. Workflows gated only by tag patterns will run when you don't expect them to."
|
|
},
|
|
{
|
|
"key": "`on.<event>.types`",
|
|
"status": "ignored",
|
|
"notes": "Activity-type filters (e.g. `pull_request.types: [opened, synchronize]`) are parsed but not applied — `--all` runs the workflow regardless of event sub-type."
|
|
},
|
|
{
|
|
"key": "`on.workflow_dispatch.inputs`",
|
|
"status": "ignored",
|
|
"notes": "`workflow_dispatch` itself is not simulated, so its declared inputs are parsed but unreachable — the CLI cannot inject them."
|
|
},
|
|
{
|
|
"key": "`on.workflow_call.inputs.*` (`type` / `required` / `default`)",
|
|
"status": "supported",
|
|
"notes": "Reusable workflows resolve caller-side `with:` values against the callee's `inputs:` declarations, honoring `default:` when a caller omits an input.",
|
|
"proof": [".github/workflows/smoke-reusable-inputs-outputs.yml"]
|
|
},
|
|
{
|
|
"key": "`on.workflow_call.outputs.*.value`",
|
|
"status": "supported",
|
|
"notes": "Reusable-workflow callees can expose `outputs.<name>.value: ${{ jobs.<id>.outputs.<name> }}`, and callers read them the same way as a normal `needs.*.outputs.*`.",
|
|
"proof": [".github/workflows/smoke-reusable-inputs-outputs.yml"]
|
|
},
|
|
{
|
|
"key": "`on` (schedule, workflow_dispatch)",
|
|
"status": "ignored",
|
|
"notes": "Accepted without error, but Local CI does not simulate event triggers — workflows must be run manually"
|
|
},
|
|
{
|
|
"key": "`on` (workflow_call)",
|
|
"status": "supported",
|
|
"notes": "Local reusable workflows (`uses: ./.github/workflows/...`) are inlined into the caller's dependency graph. Remote refs are fetched from GitHub (requires `--github-token` or `LOCAL_CI_GITHUB_TOKEN`). `inputs:`/`outputs:` passing and nested reusable workflows are supported",
|
|
"proof": [
|
|
".github/workflows/smoke-reusable-workflows.yml",
|
|
".github/workflows/smoke-nested-level-1.yml",
|
|
".github/workflows/smoke-remote-private-workflow.yml"
|
|
]
|
|
},
|
|
{
|
|
"key": "`on` (other events)",
|
|
"status": "ignored",
|
|
"notes": "Covers every GitHub-Actions webhook event Local CI does not simulate (`issues`, `issue_comment`, `label`, `pull_request_review`, `release`, `registry_package`, `status`, `deployment`, `deployment_status`, `fork`, `watch`, `star`, `discussion`, `milestone`, `project`, `repository_dispatch`, `check_run`, `check_suite`, `workflow_run`, etc.). The parser accepts the event name but the run is not triggered — the workflow only ever runs when invoked manually via `pnpm local-ci run`."
|
|
},
|
|
{
|
|
"key": "`env`",
|
|
"status": "supported",
|
|
"notes": "Workflow-level env is propagated to all steps",
|
|
"proof": [".github/workflows/smoke-env-scoping.yml"]
|
|
},
|
|
{
|
|
"key": "`defaults.run.shell`",
|
|
"status": "supported",
|
|
"notes": "Non-bash shells (`sh`, `python`, `pwsh`) are invoked via a parse-time heredoc wrap because the runner ignores `inputs.shell` for script steps. `bash` is the default.",
|
|
"proof": [".github/workflows/smoke-shell-defaults.yml"]
|
|
},
|
|
{
|
|
"key": "`defaults.run.working-directory`",
|
|
"status": "supported",
|
|
"notes": "Passed through to the runner",
|
|
"proof": [".github/workflows/smoke-defaults-workdir.yml"]
|
|
},
|
|
{
|
|
"key": "`permissions`",
|
|
"status": "ignored",
|
|
"notes": "Accepted but not enforced — the mock GITHUB_TOKEN has full access"
|
|
},
|
|
{
|
|
"key": "`concurrency`",
|
|
"status": "not-planned",
|
|
"notes": "Concurrency groups are a GitHub-side queuing and cancellation mechanism. Local CI has no persistent server to track group state across runs, so this cannot be implemented locally"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": "jobs",
|
|
"title": "Job-Level Keys",
|
|
"description": "Per-job configuration — runner selection, dependencies, conditional execution, containers, and service sidecars.",
|
|
"rows": [
|
|
{
|
|
"key": "`jobs.<id>`",
|
|
"status": "supported",
|
|
"notes": "Multiple jobs in a single workflow",
|
|
"proof": [".github/workflows/smoke-outputs.yml", ".github/workflows/smoke-conditions.yml"]
|
|
},
|
|
{
|
|
"key": "`jobs.<id>.name`",
|
|
"status": "supported",
|
|
"notes": "",
|
|
"proof": [".github/workflows/smoke-matrix.yml"]
|
|
},
|
|
{
|
|
"key": "`jobs.<id>.needs`",
|
|
"status": "supported",
|
|
"notes": "Jobs are sorted topologically into dependency waves",
|
|
"proof": [".github/workflows/smoke-outputs.yml", ".github/workflows/smoke-conditions.yml"]
|
|
},
|
|
{
|
|
"key": "`jobs.<id>.if`",
|
|
"status": "supported",
|
|
"notes": "`success()`, `failure()`, `always()`, `cancelled()`, `==`/`!=`, `&&`/`||`, `!`, `needs.*.outputs.*`, `needs.*.result`",
|
|
"proof": [".github/workflows/smoke-conditions.yml"]
|
|
},
|
|
{
|
|
"key": "`jobs.<id>.runs-on`",
|
|
"status": "partial",
|
|
"notes": "`ubuntu-*` / `linux` (and unspecified `self-hosted` / custom labels) run in a Linux container — the default image is minimal, see [runner-image.md](./runner-image.md) to add system tools. `macos-*` runs in a real macOS VM on Apple Silicon hosts with [tart](https://github.com/cirruslabs/tart) + `sshpass` installed; other hosts skip the job with a reason. `windows-*` is not yet supported and skips on every host. When a label declares a larger runner spec (e.g. `ubuntu-latest-8-cores`) than the local machine provides, the job is tagged `degraded` and a warning is printed before it starts — execution is not blocked, only annotated.",
|
|
"proof": [
|
|
".github/workflows/smoke-matrix.yml",
|
|
".github/workflows/smoke-macos.yml",
|
|
".github/workflows/smoke-resource-mismatch.yml"
|
|
]
|
|
},
|
|
{
|
|
"key": "`jobs.<id>.environment`",
|
|
"status": "ignored",
|
|
"notes": "Accepted but not enforced — environment protection rules are GitHub-side only. Object form (`environment.name` / `environment.url`) is also accepted and ignored."
|
|
},
|
|
{
|
|
"key": "`jobs.<id>.permissions`",
|
|
"status": "ignored",
|
|
"notes": "Accepted but not enforced — job-level permission scopes have no effect because the mock `GITHUB_TOKEN` has full access to the local API emulation."
|
|
},
|
|
{
|
|
"key": "`jobs.<id>.env`",
|
|
"status": "supported",
|
|
"notes": "",
|
|
"proof": [".github/workflows/smoke-env-scoping.yml"]
|
|
},
|
|
{
|
|
"key": "`jobs.<id>.defaults.run`",
|
|
"status": "supported",
|
|
"notes": "`shell` and `working-directory`",
|
|
"proof": [".github/workflows/smoke-defaults-workdir.yml"]
|
|
},
|
|
{
|
|
"key": "`jobs.<id>.outputs`",
|
|
"status": "supported",
|
|
"notes": "Resolved after each job completes and accumulated across dependency waves",
|
|
"proof": [
|
|
".github/workflows/smoke-outputs.yml",
|
|
".github/workflows/smoke-reusable-inputs-outputs.yml"
|
|
]
|
|
},
|
|
{
|
|
"key": "`jobs.<id>.timeout-minutes`",
|
|
"status": "unsupported",
|
|
"notes": "Not implemented. Local CI's pause-on-failure model is the intended way to handle long-running steps — a hard timeout would destroy the container state that makes local debugging possible"
|
|
},
|
|
{
|
|
"key": "`jobs.<id>.continue-on-error`",
|
|
"status": "unsupported",
|
|
"notes": "Not implemented. Local CI pauses on failure so you can inspect and fix the container in place; `continue-on-error` would skip past failures and discard that debugging opportunity"
|
|
},
|
|
{
|
|
"key": "`jobs.<id>.concurrency`",
|
|
"status": "not-planned",
|
|
"notes": "See workflow-level `concurrency` above"
|
|
},
|
|
{
|
|
"key": "`jobs.<id>.container`",
|
|
"status": "partial",
|
|
"notes": "Short and long form; `image`, `env`, `ports`, `volumes` honored. Within `options:`, only `--env`/`-e` and `--label`/`-l` are forwarded to the runner container; other Docker flags (`--privileged`, `--user`, `--network`, `--cap-add`, `--workdir`, etc.) are silently ignored because they tend to clash with local-ci's own container orchestration.",
|
|
"proof": [
|
|
".github/workflows/smoke-artifacts.yml",
|
|
".github/workflows/smoke-container-options.yml"
|
|
]
|
|
},
|
|
{
|
|
"key": "`jobs.<id>.container.credentials`",
|
|
"status": "supported",
|
|
"notes": "Registry `username` / `password` credentials are expanded from workflow contexts and passed to Docker only when a missing private image must be pulled.",
|
|
"proof": [
|
|
"packages/cli/src/workflow/workflow-parser.test.ts",
|
|
"packages/cli/src/docker/image-pull.test.ts"
|
|
]
|
|
},
|
|
{
|
|
"key": "`jobs.<id>.services`",
|
|
"status": "supported",
|
|
"notes": "Sidecar containers with image, env, ports, and options",
|
|
"proof": [".github/workflows/smoke-artifacts.yml"]
|
|
},
|
|
{
|
|
"key": "`jobs.<id>.services.*.credentials`",
|
|
"status": "supported",
|
|
"notes": "Each service can provide registry `username` / `password` credentials. Expressions are expanded before Docker pulls a missing image.",
|
|
"proof": [
|
|
"packages/cli/src/workflow/workflow-parser.test.ts",
|
|
"packages/cli/src/docker/service-containers.test.ts"
|
|
]
|
|
},
|
|
{
|
|
"key": "`jobs.<id>.uses` (reusable workflows)",
|
|
"status": "supported",
|
|
"notes": "Local refs (`./`) are expanded inline. Remote refs are fetched from GitHub (requires `--github-token` or `LOCAL_CI_GITHUB_TOKEN`; public repos may work without auth). `with:` (inputs) and `secrets:` pass-through are supported",
|
|
"proof": [
|
|
".github/workflows/smoke-reusable-workflows.yml",
|
|
".github/workflows/smoke-reusable-inputs-outputs.yml",
|
|
".github/workflows/smoke-remote-private-workflow.yml"
|
|
]
|
|
},
|
|
{
|
|
"key": "`jobs.<id>.secrets`",
|
|
"status": "not-planned",
|
|
"notes": "Local CI cannot access GitHub's secret storage. Secrets are resolved from a `.env.local-ci` file at the project root or from shell environment variables as fallback. `--github-token` / `LOCAL_CI_GITHUB_TOKEN` auto-populates `secrets.GITHUB_TOKEN`. All are injected as `${{ secrets.* }}` expressions"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": "strategy",
|
|
"title": "Strategy / Matrix",
|
|
"description": "Matrix builds and failure behavior — how jobs are multiplied across configurations and when to stop on errors.",
|
|
"rows": [
|
|
{
|
|
"key": "`strategy.matrix`",
|
|
"status": "supported",
|
|
"notes": "Cartesian product of all array values is fully expanded",
|
|
"proof": [".github/workflows/smoke-matrix.yml"]
|
|
},
|
|
{
|
|
"key": "`strategy.matrix.include`",
|
|
"status": "unsupported",
|
|
"notes": "Not implemented. The matrix parser only processes array-valued keys; `include` entries (which are objects) are silently dropped. Adding support would require post-processing the Cartesian product"
|
|
},
|
|
{
|
|
"key": "`strategy.matrix.exclude`",
|
|
"status": "unsupported",
|
|
"notes": "Not implemented — same reason as `include`. `exclude` entries are objects and are dropped by the array-only parser"
|
|
},
|
|
{
|
|
"key": "`strategy.fail-fast`",
|
|
"status": "supported",
|
|
"notes": "Setting `fail-fast: false` allows remaining matrix jobs to continue after a failure",
|
|
"proof": [".github/workflows/smoke-matrix.yml"]
|
|
},
|
|
{
|
|
"key": "`strategy.max-parallel`",
|
|
"status": "unsupported",
|
|
"notes": "Not implemented. Parallelism is controlled by Local CI's host-level concurrency limiter (based on CPU count), not per-workflow job limits"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": "steps",
|
|
"title": "Step-Level Keys",
|
|
"description": "Individual step configuration — shell commands, action references, conditional execution, and environment variables.",
|
|
"rows": [
|
|
{
|
|
"key": "`steps[*].id`",
|
|
"status": "supported",
|
|
"notes": "",
|
|
"proof": [".github/workflows/smoke-artifacts.yml"]
|
|
},
|
|
{
|
|
"key": "`steps[*].name`",
|
|
"status": "supported",
|
|
"notes": "Expression expansion in names",
|
|
"proof": [".github/workflows/smoke-matrix.yml"]
|
|
},
|
|
{
|
|
"key": "`steps[*].if`",
|
|
"status": "partial",
|
|
"notes": "The condition is passed to the official runner binary, which evaluates it at runtime. Limitation: `steps.*.outputs.cache-hit` and similar outputs resolve to an empty string at parse time because prior steps have not yet run when the workflow is parsed",
|
|
"proof": [".github/workflows/smoke-artifacts.yml"]
|
|
},
|
|
{
|
|
"key": "`steps[*].run`",
|
|
"status": "supported",
|
|
"notes": "Multiline shell scripts with `${{ }}` expression expansion",
|
|
"proof": [".github/workflows/smoke-matrix.yml"]
|
|
},
|
|
{
|
|
"key": "`steps[*].uses`",
|
|
"status": "supported",
|
|
"notes": "Public actions are downloaded via the GitHub API",
|
|
"proof": [".github/workflows/smoke-binary.yml"]
|
|
},
|
|
{
|
|
"key": "`steps[*].uses` (local `./`)",
|
|
"status": "supported",
|
|
"notes": "Local actions (e.g. `uses: ./.github/actions/my-action`) are resolved from the workspace",
|
|
"proof": [".github/workflows/smoke-local-action.yml"]
|
|
},
|
|
{
|
|
"key": "`steps[*].uses` (`docker://…`)",
|
|
"status": "unsupported",
|
|
"notes": "Docker-image action references (`uses: docker://alpine:3.19`) are not resolved — local-ci treats every `uses:` as a repository action. The step fails with an action-not-found error rather than running the image."
|
|
},
|
|
{
|
|
"key": "`steps[*].with`",
|
|
"status": "supported",
|
|
"notes": "Expression expansion in values",
|
|
"proof": [".github/workflows/smoke-binary.yml"]
|
|
},
|
|
{
|
|
"key": "`steps[*].env`",
|
|
"status": "supported",
|
|
"notes": "Expression expansion in values",
|
|
"proof": [
|
|
".github/workflows/smoke-secrets-env-file.yml",
|
|
".github/workflows/smoke-artifacts.yml"
|
|
]
|
|
},
|
|
{
|
|
"key": "`steps[*].working-directory`",
|
|
"status": "supported",
|
|
"notes": "",
|
|
"proof": [
|
|
".github/workflows/smoke-vars-preflight.yml",
|
|
".github/workflows/smoke-defaults-workdir.yml"
|
|
]
|
|
},
|
|
{
|
|
"key": "`steps[*].shell`",
|
|
"status": "supported",
|
|
"notes": "See `defaults.run.shell` above — the same heredoc-wrap mechanism applies.",
|
|
"proof": [".github/workflows/smoke-shell-defaults.yml"]
|
|
},
|
|
{
|
|
"key": "`steps[*].continue-on-error`",
|
|
"status": "unsupported",
|
|
"notes": "Not implemented — see `jobs.<id>.continue-on-error` above for the reasoning"
|
|
},
|
|
{
|
|
"key": "`steps[*].timeout-minutes`",
|
|
"status": "unsupported",
|
|
"notes": "Not implemented — see `jobs.<id>.timeout-minutes` above for the reasoning"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": "expressions",
|
|
"title": "Expressions (`${{ }}`)",
|
|
"description": "The `${{ }}` expression language — built-in functions, context variables, and operators available in workflow YAML.",
|
|
"rows": [
|
|
{
|
|
"key": "`hashFiles(...)`",
|
|
"status": "supported",
|
|
"notes": "SHA-256 of matching files; supports multiple glob patterns. Descends into dotted directories (e.g. `.github/`) when the pattern asks for them.",
|
|
"proof": [".github/workflows/smoke-expressions.yml"]
|
|
},
|
|
{
|
|
"key": "`format(...)`",
|
|
"status": "supported",
|
|
"notes": "Template substitution with recursive expression expansion",
|
|
"proof": [".github/workflows/smoke-expressions.yml"]
|
|
},
|
|
{
|
|
"key": "`matrix.*`",
|
|
"status": "supported",
|
|
"notes": "",
|
|
"proof": [".github/workflows/smoke-matrix.yml"]
|
|
},
|
|
{
|
|
"key": "`secrets.*`",
|
|
"status": "supported",
|
|
"notes": "Resolved from `.env.local-ci` at the project root, with shell environment variables as fallback. `--github-token` auto-populates `secrets.GITHUB_TOKEN`",
|
|
"proof": [".github/workflows/smoke-secrets-env-file.yml"]
|
|
},
|
|
{
|
|
"key": "`vars.*`",
|
|
"status": "supported",
|
|
"notes": "Repository / organization / environment variables. Resolved from `--var NAME=VALUE` CLI flags or JSON supplied via `--var-file <path>` / `--var-file -`; unresolved `vars.*` references fail the run at pre-flight with a list of every missing name.",
|
|
"proof": [".github/workflows/smoke-vars-preflight.yml"]
|
|
},
|
|
{
|
|
"key": "`env.*`",
|
|
"status": "supported",
|
|
"notes": "The merged step environment (workflow-level → job-level → step-level, with step taking precedence) is available as `env.*` in `with:` inputs, `run:` scripts, `name:`, and `env:` value expressions. Not yet supported in `if:` conditions (the expression is evaluated by the runner, not the parser). Note: `env.*` references inside an `env:` block (one env var referencing another declared in the same block) are not evaluated — only the already-computed merged value is exposed.",
|
|
"proof": [".github/workflows/smoke-env-context.yml"]
|
|
},
|
|
{
|
|
"key": "`inputs.*`",
|
|
"status": "supported",
|
|
"notes": "Reusable-workflow and (nominally) `workflow_dispatch` input values. Inside a called workflow, `inputs.<name>` resolves to the caller's `with:` value or the declared `default:`.",
|
|
"proof": [".github/workflows/smoke-reusable-inputs-outputs.yml"]
|
|
},
|
|
{
|
|
"key": "`runner.os`",
|
|
"status": "supported",
|
|
"notes": "`Linux` for container-based jobs, `macOS` for jobs routed to the macOS VM",
|
|
"proof": [".github/workflows/smoke-artifacts.yml"]
|
|
},
|
|
{
|
|
"key": "`runner.arch`",
|
|
"status": "supported",
|
|
"notes": "`X64` for container-based jobs, `ARM64` for macOS VM jobs (Apple Silicon)",
|
|
"proof": [".github/workflows/smoke-expressions.yml"]
|
|
},
|
|
{
|
|
"key": "`runner.name`, `runner.temp`, `runner.tool_cache`, `runner.debug`, `runner.environment`",
|
|
"status": "unsupported",
|
|
"notes": "Only `runner.os` and `runner.arch` are populated; the rest of the `runner.*` context resolves to an empty string. Workflows that read `runner.tool_cache` (common in tool-setup actions) will see an empty value — the actions themselves typically fall back to the `RUNNER_TOOL_CACHE` env var which the runner does set."
|
|
},
|
|
{
|
|
"key": "`github.sha`",
|
|
"status": "supported",
|
|
"notes": "Real commit SHA from `HEAD`. When the working tree is dirty, a synthetic tree SHA is computed so that expressions that hash-seed from `github.sha` still differ between working-tree states.",
|
|
"proof": [".github/workflows/smoke-binary.yml"]
|
|
},
|
|
{
|
|
"key": "`github.repository`, `github.repository_owner`",
|
|
"status": "supported",
|
|
"notes": "Derived from the git remote origin URL (`owner/repo` and `owner`).",
|
|
"proof": [".github/workflows/smoke-binary.yml"]
|
|
},
|
|
{
|
|
"key": "`github.ref`, `github.ref_name`, `github.head_ref`, `github.base_ref`, `github.actor`, `github.run_id`, `github.run_number`, and other `github.*` fields",
|
|
"status": "partial",
|
|
"notes": "Returned as static defaults: `ref_name` / `head_ref` default to `main`, `base_ref` is empty, `actor` is the repo owner, `run_id` / `run_number` are `1`, `api_url` / `server_url` point at Local CI's local API emulation. Anything not in this row or one of the ones above resolves to an empty string — that covers `action_path`, `workflow_ref`, `workflow_sha`, `triggering_actor`, `retention_days`, `secret_source`, `token`, `graphql_url`, `job`, `run_attempt`, `ref_protected`, `ref_type`, `env`, `path`, and the rest of the context documented by GitHub.",
|
|
"proof": [".github/workflows/smoke-binary.yml"]
|
|
},
|
|
{
|
|
"key": "`github.event.*`",
|
|
"status": "partial",
|
|
"notes": "All event payload fields return empty strings — no real webhook event is triggered locally",
|
|
"proof": [".github/workflows/smoke-binary.yml"]
|
|
},
|
|
{
|
|
"key": "`strategy.job-total`, `strategy.job-index`",
|
|
"status": "supported",
|
|
"notes": "",
|
|
"proof": [".github/workflows/smoke-matrix.yml"]
|
|
},
|
|
{
|
|
"key": "`steps.*.outputs.*`",
|
|
"status": "partial",
|
|
"notes": "Always resolves to an empty string at parse time — the producing step has not run yet and the runner does not re-evaluate `${{ }}` inside `run:` script bodies. Use `needs.*.outputs.*` for cross-job values instead.",
|
|
"proof": [".github/workflows/smoke-artifacts.yml"]
|
|
},
|
|
{
|
|
"key": "`steps.*.conclusion` / `steps.*.outcome`",
|
|
"status": "unsupported",
|
|
"notes": "Step result states are not exposed — neither `conclusion` nor `outcome` resolves against a meaningful value. Workflows that branch on prior-step status via these context variables will always evaluate as if the step didn't produce a result."
|
|
},
|
|
{
|
|
"key": "`job.*` (`job.status`, `job.container.id`, `job.services.*`)",
|
|
"status": "unsupported",
|
|
"notes": "The per-job runtime context is not populated. Expressions that read `job.status` etc. resolve to empty strings."
|
|
},
|
|
{
|
|
"key": "`*` object-filter operator",
|
|
"status": "unsupported",
|
|
"notes": "Array-of-object filtering like `github.event.commits.*.author.name` is not implemented — the entire expression resolves to an empty string."
|
|
},
|
|
{
|
|
"key": "`needs.*.outputs.*`",
|
|
"status": "supported",
|
|
"notes": "Resolved after dependency jobs complete. The needs context is built from actual job outputs and passed into subsequent job evaluation",
|
|
"proof": [
|
|
".github/workflows/smoke-outputs.yml",
|
|
".github/workflows/smoke-reusable-inputs-outputs.yml"
|
|
]
|
|
},
|
|
{
|
|
"key": "Boolean/comparison operators",
|
|
"status": "supported",
|
|
"notes": "`==`, `!=`, `<`, `>`, `<=`, `>=`, `&&`, `||`, `!`, parentheses. Case-insensitive string comparison. Numeric coercion follows GitHub Actions semantics: empty string and `null` become `0`, numeric-looking strings are parsed, anything else becomes `NaN` and compares false.",
|
|
"proof": [
|
|
".github/workflows/smoke-conditions.yml",
|
|
".github/workflows/smoke-expressions.yml"
|
|
]
|
|
},
|
|
{
|
|
"key": "`toJSON`, `fromJSON`",
|
|
"status": "supported",
|
|
"notes": "`toJSON` emits 2-space-indented JSON matching GitHub Actions' pretty-printed output, so `hashFiles`/diff uses of `toJSON(x)` are reproducible.",
|
|
"proof": [".github/workflows/smoke-binary.yml", ".github/workflows/smoke-expressions.yml"]
|
|
},
|
|
{
|
|
"key": "`contains(search, item)`",
|
|
"status": "supported",
|
|
"notes": "Case-insensitive substring for strings; element membership for JSON arrays (e.g. `contains(fromJSON('[\"a\",\"b\"]'), 'b')`). The `*` object-filter form (`contains(labels.*.name, 'bug')`) is not supported — see the `*` object-filter row.",
|
|
"proof": [".github/workflows/smoke-binary.yml", ".github/workflows/smoke-expressions.yml"]
|
|
},
|
|
{
|
|
"key": "`startsWith(searchString, searchValue)`",
|
|
"status": "supported",
|
|
"notes": "Case-insensitive.",
|
|
"proof": [".github/workflows/smoke-expressions.yml"]
|
|
},
|
|
{
|
|
"key": "`endsWith(searchString, searchValue)`",
|
|
"status": "supported",
|
|
"notes": "Case-insensitive.",
|
|
"proof": [".github/workflows/smoke-expressions.yml"]
|
|
},
|
|
{
|
|
"key": "`join`",
|
|
"status": "supported",
|
|
"notes": "Joins JSON arrays with a separator (default `, `)",
|
|
"proof": [".github/workflows/smoke-expressions.yml"]
|
|
},
|
|
{
|
|
"key": "`success()`, `failure()`, `always()`, `cancelled()`",
|
|
"status": "partial",
|
|
"notes": "`success()` / `failure()` / `always()` are evaluated against the accumulated job status in dependency waves. `cancelled()` always returns `false` locally — there is no cancellation signal for Local CI to observe, so steps gated by `if: cancelled()` never run.",
|
|
"proof": [".github/workflows/smoke-conditions.yml"]
|
|
},
|
|
{
|
|
"key": "Type literals (`true`, `false`, `null`, numbers)",
|
|
"status": "supported",
|
|
"notes": "Boolean, null, and numeric literals supported in expressions",
|
|
"proof": [".github/workflows/smoke-expressions.yml"]
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": "github-api",
|
|
"title": "GitHub API Features (DTU Mock)",
|
|
"description": "Interactions with GitHub's API — action downloads, common actions (cache, checkout, setup-*), artifacts, and tokens.",
|
|
"rows": [
|
|
{
|
|
"key": "Action downloads",
|
|
"status": "supported",
|
|
"notes": "Action tarballs are resolved and downloaded from github.com",
|
|
"proof": [".github/workflows/smoke-binary.yml"]
|
|
},
|
|
{
|
|
"key": "`actions/cache`",
|
|
"status": "partial",
|
|
"notes": "Cache is stored on the local filesystem via bind-mount, giving ~0 ms round-trip on cache hits. Scoping is by cache key only — GitHub's ref-based isolation (a branch cannot read another branch's cache, default branch is a fallback) is not implemented. If two workflows on different refs share a key they share the cache.",
|
|
"proof": [".github/workflows/smoke-artifacts.yml"]
|
|
},
|
|
{
|
|
"key": "`actions/checkout`",
|
|
"status": "supported",
|
|
"notes": "The workspace is rsynced into the container with `clean: false` to preserve local changes",
|
|
"proof": [".github/workflows/smoke-binary.yml"]
|
|
},
|
|
{
|
|
"key": "`actions/setup-node`, `actions/setup-python`, etc.",
|
|
"status": "supported",
|
|
"notes": "Tool setup actions run natively inside the runner container",
|
|
"proof": [".github/workflows/smoke-binary.yml", ".github/workflows/smoke-node-setup.yml"]
|
|
},
|
|
{
|
|
"key": "`actions/upload-artifact` / `download-artifact`",
|
|
"status": "supported",
|
|
"notes": "Artifacts are stored on the local filesystem",
|
|
"proof": [".github/workflows/smoke-artifacts.yml"]
|
|
},
|
|
{
|
|
"key": "`GITHUB_TOKEN`",
|
|
"status": "partial",
|
|
"notes": "Not injected automatically. Pass `--github-token` or set `LOCAL_CI_GITHUB_TOKEN` to make it available as `secrets.GITHUB_TOKEN`. GitHub API calls from the runner are answered locally by Local CI's API emulation layer. OIDC ID-token issuance (`permissions.id-token: write` → federated cloud auth) is not implemented — actions that exchange the OIDC token with AWS/GCP/Vault will fail.",
|
|
"proof": [".github/workflows/smoke-remote-private-workflow.yml"]
|
|
},
|
|
{
|
|
"key": "Workflow commands (`::set-output::`, `::error::`, etc.)",
|
|
"status": "supported",
|
|
"notes": "Handled by the official runner binary",
|
|
"proof": [".github/workflows/smoke-outputs.yml", ".github/workflows/smoke-node-setup.yml"]
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|