Skip to content

feat(config): add per-command env, dir, and silent overrides - #52

Merged
quike merged 1 commit into
mainfrom
feat/per-command-controls
Sep 16, 2026
Merged

quike merged 1 commit into
mainfrom
feat/per-command-controls

Conversation

@quike

@quike quike commented Sep 16, 2026

Copy link
Copy Markdown
Owner

Implements item 1 of #37. Items 2 (continue-on-error/always) and 3 (the composition decision) are untouched.

Map-form commands: entries accept three optional keys:

groups:
  - name: release
    env:
      BUILD_MODE: release
    commands:
      - { command: go, params: [build, "./..."], env: { CGO_ENABLED: "0" } }
      - { command: ./package.sh, dir: dist }
      - { command: ./notify.sh, silent: true }

String-form entries stay knob-free by construction — they have no keys — so the shape keeps selecting the capabilities.

Why dir matters most

Running one command elsewhere previously meant cd dist && ./package.sh, which is a string-form entry and therefore forces shell: on the whole group, giving up safe argv exec for every command in it. dir keeps the group in argv mode.

Decisions

  • env and dir are templated like command/params, so dir: 'dist/{{ env "TARGET" }}' and env: { SHA: '{{ output "build" }}' } work. Env keys stay literal.
  • silent is excluded from the cache fingerprint. It changes only what is streamed to the terminal — capture into RunResult is untouched, so {{ output }}, the event stream, and cache replay are unaffected. Toggling it keeps a valid cached result. env and dir do fold in.
  • No fingerprint version bump. The new fields are emitted only when set, so a config using none of them hashes exactly as before and keeps its cache. A test reproduces the old v3 byte stream independently to guard this.
  • Empty-value checks live in UnmarshalYAML, not validateGroupCommands: only the YAML node distinguishes dir: "" from an absent dir. validateGroupCommands needed no change — dir on a string-form entry is impossible by construction.

Runner interface

Run now takes the CommandSpec in place of params []string:

Run(ctx, g *config.Group, spec config.CommandSpec, globalEnv map[string]string) (result.RunResult, error)

runSequence already set sg.Params = s.Params and passed s.Params separately, which is why the interface doc had to warn implementations not to read g.Params. The spec carries the new fields with no extra plumbing. Seven test doubles get a mechanical signature change.

Two touch points the issue did not list

Both silently drop data rather than failing to compile, so both are worth a reviewer's attention:

  1. expandCommands rebuilt CommandSpec{Command, Params, IsShell} field-by-field, discarding anything else before it reached the runner or the cache. It now copies the spec and overrides only what it renders, so fields added later carry through by default. The first test run caught Silent being dropped exactly this way.
  2. ExtractRefs scanned only Command and Params. It drives the dag scheduler's edges, load-time reference validation, and keepup graph — so a templated env/dir referencing another group produced no dependency edge, and the value silently rendered empty because the producing group was not scheduled first. Found by running a real config, not by a unit test. Env keys are sorted during extraction since map order is random and these refs become graph edges.

Verification

gofmt, go vet, golangci-lint, and go test -race ./... all clean.

Beyond the unit tests, a real config exercising all three knobs was run through the built binary:

a31550e                              # producer group
group                                # group env over global env
command                              # command env over group env
/private/tmp/.../knobs/sub           # dir honoured
$(whoami) not-expanded               # argv exec preserved alongside dir
sha-a31550e                          # templated env resolving a dag dependency
                                     # (silent entry printed nothing)

keepup graph confirms the env-derived reference produces a real edge (build --> checks).

Note for the reviewer

settings.working-dir is parsed and migrated but never read anywhere in the codebase — dir is resolved relative to the process working directory. Left alone here as unrelated, but it is dead config and probably wants its own issue.

@codecov

codecov Bot commented Sep 16, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.61017% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 92.84%. Comparing base (afbf847) to head (de6abd9).

Files with missing lines Patch % Lines
internal/config/config.go 95.23% 1 Missing ⚠️
internal/config/refs.go 80.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #52      +/-   ##
==========================================
+ Coverage   92.71%   92.84%   +0.13%     
==========================================
  Files          27       27              
  Lines        1482     1524      +42     
==========================================
+ Hits         1374     1415      +41     
- Misses        105      106       +1     
  Partials        3        3              
Files with missing lines Coverage Δ
internal/cache/cache.go 92.45% <100.00%> (+0.61%) ⬆️
internal/engine/engine.go 96.22% <100.00%> (+0.35%) ⬆️
internal/engine/runner.go 94.36% <100.00%> (+0.24%) ⬆️
internal/config/config.go 95.62% <95.23%> (+0.30%) ⬆️
internal/config/refs.go 91.03% <80.00%> (-0.40%) ⬇️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@quike
quike force-pushed the feat/per-command-controls branch from 22463a7 to 8b9fde7 Compare September 16, 2026 19:26
@quike
quike force-pushed the feat/per-command-controls branch from 8b9fde7 to de6abd9 Compare September 16, 2026 19:32
@quike
quike merged commit 0e2c6aa into main Sep 16, 2026
4 checks passed
@quike
quike deleted the feat/per-command-controls branch September 16, 2026 19:36
@quike

quike commented Sep 16, 2026

Copy link
Copy Markdown
Owner Author

🎉 This PR is included in version 1.30.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant