Skip to content

feat(cli): convert checkly config validation to diagnostics [RED-914] - #1455

Open
sorccu wants to merge 5 commits into
mainfrom
simo/red-914-config-validation-diagnostics
Open

feat(cli): convert checkly config validation to diagnostics [RED-914]#1455
sorccu wants to merge 5 commits into
mainfrom
simo/red-914-config-validation-diagnostics

Conversation

@sorccu

@sorccu sorccu commented Aug 27, 2026

Copy link
Copy Markdown
Member

Linear: RED-914

Affected Components

  • CLI

loadChecklyConfig() used to validate the config with four throw-based, fail-fast validators: a broken config reported only the first problem, as a raw Error rendered by oclif's default handler — unlike construct validation, which collects everything into the Diagnostics system and renders it nicely.

This converts config validation to diagnostics, in three commits:

  1. Loader: the validators now report into a ConfigFileDiagnostics collector, so all config problems are reported in one run (both required fields, every bad bundle.packages.embed spec, every unknown runner key). Each diagnostic is attributed to the config file (e.g. [checkly.config.ts] Invalid property value; <generated config> when the config was synthesized in memory). When any diagnostic is fatal, loading fails with a new InvalidConfigError carrying the diagnostics; otherwise the collector is returned alongside the config.
  2. Command layer: CommandStyle.diagnostics() is now the single severity-to-renderer dispatch, AuthCommand.validateProject() absorbs the validate/render/exit block that was duplicated across five commands, and BaseCommand.catch() renders InvalidConfigError diagnostics and exits 1 — every command gets styled output for a fatally invalid config without per-command error handling.
  3. Call sites: deploy, test, pw-test, validate and import plan seed the returned config diagnostics into project validation ahead of project.validate(), so config diagnostics always render before project/construct diagnostics; destroy, trigger and the other import commands render them right after loading. Today every config diagnostic is fatal (so the returned collector is always empty), making this the plumbing for future warning-level config diagnostics.

Nested validators, too: the runner.registries and bundle.packages.prune validators used to stop at their first problem, so several mistakes inside one block still surfaced one edit-run cycle at a time. They now walk the whole value and report each issue through a callback the loader turns into its own diagnostic (the throwing entry points the bundler uses rethrow the first issue, keeping their contract). Cascading noise is gated — an invalid/empty upstreams block skips the per-rule existence checks, an invalid final pattern skips the match-all requirement, and a misplaced match-all isn't additionally reported as missing. Per-class prune pattern issues carry the dependency class so identical patterns in two classes stay distinguishable.

Notes for the Reviewer

Intentional behavior changes:

  • Config error texts change from Config field 'x' ... sentences to the diagnostics phrasing (Property "x" is required and must be set. / The value provided for property "x" is not valid. + Reason: ...). The underlying cause strings are preserved verbatim in the Reason: line.
  • Config errors now render on stdout via CommandStyle (matching how project diagnostics already render), instead of oclif's default stderr dump. Exit code stays 1.
  • checkly trigger now fails on an invalid config it previously ignored silently. Deliberately narrow: only InvalidConfigError is fatal — a missing config, an unloadable config (e.g. dependencies not installed in a trigger-only CI image), or any other load failure is still tolerated as before.
  • checkly debug parse-project keeps its machine-readable contract: an invalid config emits the standard ParseProjectOutput JSON shape with the diagnostics and payload: null at exit 0, while any other load failure (e.g. missing config) still exits non-zero. ParseProjectOutput.payload is now typed | null to match the actual contract.

Regression coverage added: loader specs with multi-error fixtures, CommandStyle.diagnostics + BaseCommand.catch unit specs, a deploy-level spec asserting config diagnostics render before project diagnostics, a trigger spec pinning which load failures are tolerated, and two sandbox tests running the real packed CLI through debug parse-project for the invalid-config and missing-config exit-code contracts.

  • Diagnostic messages now end with terminal punctuation. This also adds periods to the embedded-package spec errors and dependency-cache version errors, which are shared with the bundler/materializer paths — their message text changes there too, though only cosmetically.

Other changes

  • The trigger e2e tests now pass EXECUTION_ID to every checkly trigger invocation (previously only the deploy/destroy setup steps did). The fixture config derives its logicalId from that variable, so the trigger runs used to load an invalid config that the old behavior silently ignored — with this PR the invalid config aborts the command, which is what failed CI.

🤖 Generated with Claude Code

sorccu and others added 5 commits August 28, 2026 01:08
…cklyConfig [RED-914]

Convert the four throw-based, fail-fast config validators to report through
the Diagnostics system instead. All config problems are now collected in a
single run into a ConfigFileDiagnostics collector that attributes each
diagnostic to the config file. When any fatal diagnostic is present, loading
fails with a new InvalidConfigError carrying the diagnostics; otherwise the
collector is returned alongside the config for later rendering.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…yer [RED-914]

Add CommandStyle.diagnostics() as the single severity-to-renderer dispatch,
AuthCommand.validateProject() to absorb the project validation block that
was duplicated across five commands, and an InvalidConfigError branch in
BaseCommand.catch() that renders the carried config diagnostics and exits 1,
giving every command styled output for a fatally invalid config without
per-command error handling.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ics first [RED-914]

Wire the diagnostics returned by loadChecklyConfig into every command that
loads a config: deploy, test, pw-test, validate and import plan seed them
into project validation so config diagnostics render ahead of project
diagnostics, while destroy, trigger and the other import commands render
them right after loading. Fatal config diagnostics abort every command via
the shared InvalidConfigError handling.

trigger no longer silently ignores an invalid config (other load failures,
including a missing config, are still tolerated). debug parse-project keeps
its machine-readable JSON contract for an invalid config, emitting the
diagnostics with a null payload, and preserves its non-zero exit code for
any other load failure.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The trigger fixture config derives its logicalId from EXECUTION_ID, and
runCheckly runs with extendEnv: false, so invocations that omit the
variable load a config with no logicalId. Trigger aborts on an invalid
config, so every invocation must pass the variable, not just the
deploy/destroy setup steps.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…gnostic [RED-914]

The runner.registries and bundle.packages.prune validators used to stop at
their first problem, so a config with several mistakes inside one of those
blocks surfaced them one edit-run cycle at a time. Both validators now walk
the whole value, reporting each issue through a callback that the config
loader turns into an individual diagnostic; the throwing entry points the
bundler uses keep their contract by rethrowing the first issue.

Cascading noise is gated rather than reported: an invalid or empty
upstreams block skips the per-rule upstream-existence checks, an invalid
final pattern skips the match-all requirement, and a match-all rule that
merely sits in the wrong place is not additionally reported as missing.
Pattern issues from the per-class prune map are prefixed with the
dependency class so the same bad pattern in two classes stays
distinguishable.

Validation messages that feed diagnostics Reason: lines now end with
terminal punctuation; this also touches the embedded-package spec errors
and dependency cache version errors shared with other code paths.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant