feat: shared options block on every task type (closes #54)#82
Conversation
Introduces a composable `options:` block on the base task definition
(via `$defs/taskOptions` in the schema) so cross-task fields stay in
one place. Every task type inherits the block — no per-type
duplication in YAML. User-defined commands accept the same shape so
options compose at both layers.
Initial field: `showExeTime` (bool, default false). When true, raid
prints a dim line to stderr after the task or command completes:
→ task-name (1.2s)
It fires for both success and failure so the user always sees how long
the task ran. Task-level and command-level flags are independent — set
both to time the command end-to-end and see per-task breakdowns:
commands:
- name: build
options: {showExeTime: true}
tasks:
- type: Shell
name: server-build
cmd: swift build
options: {showExeTime: true}
→ server-build (164.1s)
→ build (164.1s)
Notes on the implementation:
- TaskProps gains an Options *TaskOptions pointer; Command gains its
own. ExecuteTask wraps the dispatch with timing; runCommand wraps
the inner runCommandTasks similarly. timeNowFn is the seam for
deterministic test output.
- Task.Label() returns Name when set, falling back to the raw task
type so unnamed tasks still produce a meaningful line.
- formatExeDuration mirrors the recent-runs formatter in cmd/context
(250ms / 1.5s / 1m15s / 2h00m) so durations read consistently
across raid's surfaces.
- Schema rejects unknown option fields (additionalProperties: false on
the taskOptions $def) so the future `quiet`/`timeout` work doesn't
silently accept typos.
- The static schema copy under site/static/schema/v1/ is re-synced —
the embedded vs published cross-check test guards this.
Tests:
- TestExecuteTask_showExeTime_* cover the emit line, the type-fallback
label, the failure-path emission, and the no-options default.
- TestRunCommand_showExeTime_* cover command-level emission and the
default-off behavior.
- TestValidateProfile_optionsAccepted_onEveryTaskType locks the
taskCommon inheritance — every task variant accepts `options:` plus
the command-level block.
- TestValidateProfile_optionsRejectsUnknownField guards against the
future `quiet` field landing silently.
- TestFormatExeDuration locks the bucket boundaries.
Version 0.13.0-beta → 0.14.0-beta. Docs updated: schema reference adds
an Options section + command-level row, whats-new entry for 0.14.0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #82 +/- ##
==========================================
+ Coverage 92.00% 92.24% +0.24%
==========================================
Files 36 36
Lines 3303 3341 +38
==========================================
+ Hits 3039 3082 +43
+ Misses 172 167 -5
Partials 92 92 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
This PR implements issue #54 by introducing a shared options: block that applies uniformly to every task type and to user-defined commands, with an initial showExeTime: bool option that prints an elapsed-time line after completion. It updates the JSON Schema, Go runtime/types, documentation, and tests to support and validate the new block.
Changes:
- Add
TaskProps.Options/Command.Options(*TaskOptions) and implementshowExeTimetiming output for both tasks and commands. - Refactor task dispatch to enable timing wrappers (
ExecuteTask→dispatchTask) and add duration formatting helpers. - Extend the JSON Schema + docs to accept
optionseverywhere and reject unknown option fields; add targeted tests.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| src/resources/app.properties | Bumps app version to 0.14.0-beta. |
| src/internal/lib/task.go | Adds TaskOptions, TaskProps.Options, and Task.Label() fallback behavior. |
| src/internal/lib/task_runner.go | Implements per-task showExeTime timing wrapper and duration formatting helpers. |
| src/internal/lib/task_runner_test.go | Adds deterministic timing + stderr-capture tests for task-level showExeTime and duration formatting. |
| src/internal/lib/profile_test.go | Adds schema validation tests ensuring options is accepted on all task types and rejects unknown fields. |
| src/internal/lib/command.go | Adds command-level options and wraps command execution with showExeTime timing. |
| src/internal/lib/command_test.go | Adds tests for command-level showExeTime default/behavior. |
| site/docs/whats-new.mdx | Documents the upcoming 0.14.0 feature and closes #54. |
| site/docs/references/schema.mdx | Documents the new options block and showExeTime semantics + example output. |
| schemas/raid-defs.schema.json | Adds $defs/taskOptions and references it from task common fields and command entries; disallows unknown option fields. |
| func runCommand(cmd Command) error { | ||
| // Command-level showExeTime is independent of per-task timing — both | ||
| // flags can be set together. The command line is emitted after the | ||
| // final task (and after any per-task lines) so the timeline reads | ||
| // top-down. Like the task variant, it fires for both success and | ||
| // failure so the elapsed time is always visible. | ||
| if cmd.Options != nil && cmd.Options.ShowExeTime { | ||
| start := timeNowFn() | ||
| err := runCommandTasks(cmd) | ||
| emitExeTime(cmd.Name, timeNowFn().Sub(start)) | ||
| return err |
… line Move the command-level showExeTime emission inside runCommand's Out wrapping. Previously the elapsed-time line was printed by runCommand *after* runCommandTasks returned, but the deferred restore of commandStdout/commandStderr had already run — so the line ignored `out.stderr: false` and was not captured by `out.file`. Inline the wrapping into runCommand (drops runCommandTasks) and emit the line in the same defer, before the file is closed and the writers are restored. Adds a test that exercises stderr suppression + file capture together. Co-Authored-By: Copilot <copilot@github.com>
|
Auto-review by meeseeks Updates pushed: 1 commit
Copilot comments addressed: 1 of 1
Skipped: 0 Codecov patch: pass (project: pass) |
Drops the '→ <name> (<time>)' prefix in favour of a more readable sentence form. Same ANSI dimming and stderr emission; only the string changes. Updates the runtime emitter, schema description, schema reference doc, whats-new entry, and all tests that asserted the old format. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The linter-added TestRunCommand_showExeTime_respectsOutSuppressionAndFile still asserted the old '→ build (750ms)' string; bring it in line with the new '<name> complete in <time>' format. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a second field to the shared task `options:` block (introduced in #54): `continueOnFailure: bool` (default false). When true, a task's non-zero exit no longer aborts the parent command — subsequent tasks still run and the ignored failure is surfaced as a dim warning on stderr (`warning: <label> failed (continueOnFailure): <err>`). Useful for cleanup teardown, optional lint/format probes, and best-effort smoke checks that shouldn't block the rest of the command. The command's overall exit code is only affected by *non-ignored* failures, so strict failures elsewhere in the sequence still propagate through cleanly (verified end-to-end: an ignored exit 1 followed by a real exit 7 still surfaces exit 7). Implementation notes: - TaskOptions.ContinueOnFailure (bool, omitempty). Schema entry on taskOptions $def with description + additionalProperties:false inherited from #54. - Sequential branch in ExecuteTasks: on error, check isContinueOnFailure(task) — if true, emit warning and `continue` the loop instead of unwinding. Otherwise fail-fast as before. - Concurrent branch in the goroutine: on error, same check — emit-and-return vs push-to-errorChan as before. - emitContinueOnFailureWarning writes the dim warning to commandStderr matching the styling used by showExeTime. - isContinueOnFailure is a nil-safe helper so call sites don't re-derive the pointer guard. Tests: - Sequential ignored failure: follow-up task runs, command returns nil, stderr carries the dim warning. - Sequential ignored + sequential non-ignored: non-ignored failure still surfaces; aborts subsequent tasks. - Concurrent ignored failure with a sequential follow-up: follow-up runs, warning emitted, no error returned. Uses a mutex-wrapped syncStderr in tests because bytes.Buffer races between the warning goroutine and concurrent subprocess stderr piping. - Concurrent non-ignored failure still aborts. - Default behavior (no options) unchanged — regression guard. - isContinueOnFailure helper covers nil-Options, default-false, and true cases. - Schema validation accepts options.continueOnFailure on every task type plus combined with showExeTime. Docs: schema reference Options table gets the new row plus an example showing best-effort lint + cleanup. whats-new entry under 0.14.0 (same release as #54 since they're the same options block landing together). Stacked on top of #82 (issue #54) — the `options:` block lives there. Rebases cleanly onto main once #82 merges. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: options.continueOnFailure for best-effort tasks (closes #76) Adds a second field to the shared task `options:` block (introduced in #54): `continueOnFailure: bool` (default false). When true, a task's non-zero exit no longer aborts the parent command — subsequent tasks still run and the ignored failure is surfaced as a dim warning on stderr (`warning: <label> failed (continueOnFailure): <err>`). Useful for cleanup teardown, optional lint/format probes, and best-effort smoke checks that shouldn't block the rest of the command. The command's overall exit code is only affected by *non-ignored* failures, so strict failures elsewhere in the sequence still propagate through cleanly (verified end-to-end: an ignored exit 1 followed by a real exit 7 still surfaces exit 7). Implementation notes: - TaskOptions.ContinueOnFailure (bool, omitempty). Schema entry on taskOptions $def with description + additionalProperties:false inherited from #54. - Sequential branch in ExecuteTasks: on error, check isContinueOnFailure(task) — if true, emit warning and `continue` the loop instead of unwinding. Otherwise fail-fast as before. - Concurrent branch in the goroutine: on error, same check — emit-and-return vs push-to-errorChan as before. - emitContinueOnFailureWarning writes the dim warning to commandStderr matching the styling used by showExeTime. - isContinueOnFailure is a nil-safe helper so call sites don't re-derive the pointer guard. Tests: - Sequential ignored failure: follow-up task runs, command returns nil, stderr carries the dim warning. - Sequential ignored + sequential non-ignored: non-ignored failure still surfaces; aborts subsequent tasks. - Concurrent ignored failure with a sequential follow-up: follow-up runs, warning emitted, no error returned. Uses a mutex-wrapped syncStderr in tests because bytes.Buffer races between the warning goroutine and concurrent subprocess stderr piping. - Concurrent non-ignored failure still aborts. - Default behavior (no options) unchanged — regression guard. - isContinueOnFailure helper covers nil-Options, default-false, and true cases. - Schema validation accepts options.continueOnFailure on every task type plus combined with showExeTime. Docs: schema reference Options table gets the new row plus an example showing best-effort lint + cleanup. whats-new entry under 0.14.0 (same release as #54 since they're the same options block landing together). Stacked on top of #82 (issue #54) — the `options:` block lives there. Rebases cleanly onto main once #82 merges. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test: cover Group task type in continueOnFailure schema test Address Copilot review: the test name claims coverage of every task variant but omitted Group. Add a Group task referencing a minimal task_groups entry so a regression in Group accepting options would be caught. Co-Authored-By: Copilot <copilot@github.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: Mr. Meeseeks <meeseeks@alexsalerno.dev> Co-authored-by: Copilot <copilot@github.com>
Summary
Resolves #54 — Shared options: block on every task type.
Adds a composable
options:block on the base task definition (via\$defs/taskOptionsin the schema) so cross-task fields don't have to be re-declared per task type. Every task type inherits the block; user-defined commands accept the same shape.Initial field:
showExeTime(bool). When true, raid prints a dim line to stderr after the task or command completes:```
→ task-name (1.2s)
```
Fires on both success and failure. Task-level and command-level flags are independent — set both to time the command end-to-end and see per-task breakdowns.
```yaml
commands:
options:
showExeTime: true # one final line for the whole command
tasks:
name: server-build
cmd: swift build
options:
showExeTime: true # per-task line
```
Smoke-tested locally:
```
$ raid hi
hello from hi
→ Print (0ms)
→ sleep-task (116ms)
→ hi (116ms)
```
Implementation
Test plan
Out of scope (per the issue)
🤖 Generated with Claude Code