Skip to content

Structured, coded errors and warnings in json-full output - #958

Open
theoephraim wants to merge 1 commit into
mainfrom
load-error-codes
Open

Structured, coded errors and warnings in json-full output#958
theoephraim wants to merge 1 commit into
mainfrom
load-error-codes

Conversation

@theoephraim

Copy link
Copy Markdown
Member

--format json-full reported errors as free-text strings, so anything consuming them programmatically had to regex English prose to work out what went wrong. They are now objects with a stable code:

"errors": {
  "root": [
    { "code": "no_env_files", "message": "No .env files found in /app", "severity": "error", "tip": "Run `varlock init` ..." }
  ],
  "configItems": {
    "API_KEY": [
      { "code": "empty_required_value", "message": "Value is required but is currently empty", "severity": "error" }
    ]
  }
},
"warnings": { /* same structure */ }

Codes are set explicitly per VarlockError subclass rather than derived from the class name, so renaming a class can't silently change a published contract. Each key maps to an array, since an item can hit several problems at once (config-item.ts composes schema, resolver, decorator, resolution, coercion and validation errors) and the old code joined them with '; '.

Warnings

Reported for the first time, under a separate warnings key. A warning-only item was previously invisible to any programmatic consumer even though pretty printed it.

They are deliberately not folded into errors with severity doing the work. The presence of errors has to keep meaning "the load failed" - runtime/env.ts:390 and cloudflare/src/index.ts:202 both test it for truthiness, and unifying would silently turn a warning-only load into a failure. There's a test pinning that.

Empty graph is now an error

A .env.schema is still optional; a project of plain .env files loads fine. What fails is finding no env files at all (no_env_files) or files that define no items (no_config_items).

json-full previously exited 0 for both, emitting config: {} and errors: null, so a tool pointed at a directory with no varlock project read it as success and silently contributed nothing. Plain --format json exited 1 for the same case, so the two formats disagreed. The predicate now lives on the graph (getEmptyGraphError) and is shared with the CLI's checkForNoEnvFiles, which had been about to drift: the CLI suppressed the "no items" message only for ParseError, while any root error should suppress it.

Decrypt failures

builtin-resolver.ts flattened every decrypt failure into a generic resolution_failed. It now passes the native encryption helper's own error code through, falling back to decrypt_failed. That also means future helper-side codes surface end to end without further plumbing.

Breaking

The errors shape changes from strings to objects. Marked minor: no in-repo consumer reads past truthiness and the shape was undocumented until this PR. Bump to major instead if published JSON output should be treated as a stability boundary. SerializedEnvGraphErrors is renamed to SerializedEnvGraphDiagnostics with the old name kept as a deprecated alias.

Anything that iterates the serialized graph looking for problems will now also see warnings it didn't before. Anything checking errors alone is unaffected.

Testing

11 new tests. Verified end to end against the built CLI:

Case json json-full code
valid 0 0 none
no env files 1 1 (was 0) no_env_files
files, no vars 1 1 no_config_items
.env only, has vars 0 0 none
parse error 1 1 parse_error
required empty 1 1 empty_required_value
bad type 1 1 coercion_failed
decrypt failed 1 1 decrypt_failed
unknown resolver / decorator 1 1 schema_error

Exit codes now agree between the two formats, and pretty output is unchanged.

Errors in the serialized graph were free-text strings, so anything
consuming them programmatically had to regex English prose to work out
what went wrong. They are now objects with a stable `code`, set
explicitly per VarlockError subclass so renaming a class cannot silently
change a published contract.

Warnings are reported for the first time, under a separate `warnings`
key with the same structure. Keeping them out of `errors` matters
because the presence of `errors` means the load failed, and both our
runtime and the cloudflare integration test it for truthiness.

An empty graph is now an error in the output rather than a silently
clean load. A .env.schema is still optional (plain .env files are a
valid project); what fails is finding no env files at all
(`no_env_files`) or files that define no items (`no_config_items`).
json-full previously exited 0 for both, so a consumer pointed at a
directory with no varlock project read it as success. The predicate
lives on the graph now, shared with the CLI check so the two cannot
drift on what counts as unusable.

Decrypt failures carry the native encryption helper's own error code
through instead of collapsing into a generic `resolution_failed`,
falling back to `decrypt_failed`.
@github-actions

Copy link
Copy Markdown
Contributor

bumpy-frog

The changes in this PR will be included in the next version bump.

minor Minor releases

  • varlock 1.14.1 → 1.15.0

Bump files in this PR

Click here if you want to add another bump file to this PR


This comment is maintained by bumpy.

@github-actions

Copy link
Copy Markdown
Contributor

📦 Bundle size

⚠️ grows the bundle by 15.0 KB (+0.3%)

Metric main This PR Δ
Total dist 4790.4 KB 4805.3 KB +15.0 KB (+0.3%)
JS 1656.2 KB 1659.6 KB +3.4 KB (+0.2%)
Sourcemaps 3058.2 KB 3067.1 KB +9.0 KB (+0.3%)
Type defs 76.0 KB 78.6 KB +2.6 KB (+3.4%)

dist/ only; native binaries are versioned separately and not counted here.

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
varlock-website 11b937d Commit Preview URL

Branch Preview URL
Jul 29 2026, 06:37 AM

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

Two graph states still produce incomplete or incorrect structured diagnostics and should be covered before merging.

Reviewed changes: This PR gives json-full diagnostics stable codes and separates warnings from load-blocking errors while aligning empty-graph exit behavior.

  • Structure serialized diagnostics: errors and warnings now contain coded objects grouped by root or config item.
  • Align empty-graph failures: shared graph logic emits no_env_files or no_config_items and makes json-full exit non-zero.
  • Preserve decrypt failure codes: local-encryption daemon codes flow through ResolutionError, with decrypt_failed as the fallback.
  • Document and test the contract: reference docs and focused tests cover the new shape, warning partitioning, and common failure codes.

Pullfrog  | Fix all ➔Fix 👍s ➔View workflow run | Using azure/gpt-5.6-sol𝕏

* two can't drift on what counts as an unusable graph.
*/
getEmptyGraphError(opts?: { hasOtherRootErrors?: boolean }): VarlockError | undefined {
if (Object.keys(this.configSchema).length > 0) return undefined;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ configSchema can be non-empty even when the files define no application items because a reference such as @currentEnv=$VARLOCK_ENV auto-registers the builtin VARLOCK_ENV item. I reproduced that builtin-only graph returning no errors, so json-full exits zero despite the documented no_config_items rule; base this decision on non-builtin or file-defined items and add this case to the empty-graph tests.

for (const source of this.sortedDataSources) {
for (const err of source.errors.filter((e) => !e.isWarning)) {
rootErrors.push(`${source.label}: ${err.message}`);
for (const err of source.errors) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ This only serializes source.errors, while plugins keep a separate plugin.warnings collection that pretty displays through showPluginWarnings. As a result, warnings such as the standard-variable wiring warning remain invisible in json-full; include plugin warnings in warnings.root with an appropriate source and cover that path.

@pkg-pr-new

pkg-pr-new Bot commented Jul 29, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/varlock@958

commit: 11b937d

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant