fix(database-integrations): throw typed error for malformed integrations YAML - #425
fix(database-integrations): throw typed error for malformed integrations YAML#425tkislan wants to merge 7 commits into
Conversation
…ons YAML `mergeApiIntegrationsIntoYaml` failed with the cryptic `Error: Document with errors cannot be stringified` when given readable-but-malformed YAML, such as a file left with unresolved git merge conflict markers. `yaml.parseDocument()` never throws — it returns a Document with `.errors` populated — so the `?? createNewDocument()` fallback never fired and the failure surfaced much later, at serialize time, with no indication of which file or line was broken. Move the failure to parse time behind a typed, exported error: - `parseIntegrationsDocument` now throws `IntegrationsYamlParseError` when `doc.errors` is non-empty. This is the same predicate `Document.toString()` uses to refuse, so the set of inputs that now fail early is identical by construction to the set that already failed late — nothing that works today starts failing. - The CLI wraps it as `MalformedIntegrationsFileError`, which names the file path and carries the parse detail (line, column and a code frame), and exits with code 2 (invalid usage) across `pull`, `add` and `edit`. - `pull` now reads the local file before the empty-response early return, so a malformed file is reported on every run rather than only when the workspace has integrations. This also removes a partial-write window: previously a malformed file could leave a half-written `.env` behind, since `updateDotEnv` ran before the failing serialize. Both errors are exported so downstream consumers (webapp git-sync, the VS Code extension) can catch them and rebuild from scratch via `mergeApiIntegrationsIntoYaml(null, apiIntegrations)` if they prefer that to surfacing the error. Fixes #424 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PyawjygDS8h5qdX1ZWJoRH
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #425 +/- ##
==========================================
+ Coverage 87.36% 87.59% +0.23%
==========================================
Files 181 182 +1
Lines 9494 9515 +21
Branches 2699 2634 -65
==========================================
+ Hits 8294 8335 +41
+ Misses 1199 1179 -20
Partials 1 1 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
…ons README `MalformedIntegrationsFileError` was exported from its own module but not re-exported from the package entry point, so it was unreachable as `@deepnote/cli` — leaving downstream consumers unable to catch it by type, which is the whole point of throwing a typed error. Verified against the built artifacts: the class is now present in `dist/index.d.ts`, `dist/index.d.cts` and the runtime bundles, and `instanceof` holds across the package boundary. `IntegrationsYamlParseError` needs no change — `export * from './loading'` already surfaces it at the `@deepnote/database-integrations` root, and every other custom error in that package is reachable the same way. README corrections for `deepnote integrations`, checked line by line against the command definitions in `cli.ts:845-867`: - `--file` default was documented as `integrations.yaml`; the real default is `.deepnote.env.yaml` (`DEFAULT_INTEGRATIONS_FILE`). - `--url` default was the vague "Deepnote API"; it is `https://api.deepnote.com`. - `integrations add` and `integrations edit` were missing from the README entirely despite both shipping. Documented both, including the optional `[id]` argument to `edit` and the shared invalid-YAML failure behaviour. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PyawjygDS8h5qdX1ZWJoRH
…ons tests Two review points, applied across every file this branch touches rather than only the two lines they were raised on. Removed GitHub issue references from code comments. The fixtures now describe the scenario they reproduce — an integrations file left with unresolved git merge conflict markers, and a hand-edit typo — which is what a reader needs; the issue number belongs in the commit history, not the source. Replaced force casts in tests with `assert` type guards. Each site previously did `expect(error).toBeInstanceOf(X)` followed by `const e = error as X`, because the matcher does not narrow. `assert(error instanceof X)` from vitest both asserts and narrows, so the cast and the now-redundant matcher call are gone and the assertions read against `error` directly. This matches the existing convention in `fetch-integrations.test.ts`. Verified the assertions were not weakened by the swap: a probe asserting a deliberately wrong error type fails with an AssertionError, confirming `assert` is a real runtime check and not just a compile-time narrowing hint. 10 casts removed across 5 files; no `as` outside `as const` remains on this branch. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PyawjygDS8h5qdX1ZWJoRH
|
@coderabbitai full review |
✅ Action performedFull review finished. |
📝 WalkthroughWalkthroughThe integrations YAML parser now reports structured parse errors with YAML diagnostics. CLI commands wrap these errors with file paths, return exit code Estimated code review effort: 3 (Moderate) | ~25 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 6✅ Passed checks (6 passed)
Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (2)
packages/database-integrations/src/loading/integrations-document.test.ts (1)
25-32: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueUse
ts-dedentfor multiline YAML fixtures. Preserve each fixture’s trailing newline when converting.
packages/database-integrations/src/loading/integrations-document.test.ts#L25-L32: wrapVALID_YAMLwithts-dedent.packages/database-integrations/src/loading/merge-into-yaml.test.ts#L43-L58: wrapEXISTING_WITH_COMMENTSwithts-dedent.As per coding guidelines, use
ts-dedentfor clean multiline template strings.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/database-integrations/src/loading/integrations-document.test.ts` around lines 25 - 32, Wrap the VALID_YAML fixture in integrations-document.test.ts with ts-dedent while preserving its trailing newline; likewise wrap EXISTING_WITH_COMMENTS in merge-into-yaml.test.ts with ts-dedent and preserve its trailing newline. Ensure both test files use the existing ts-dedent import pattern.Source: Coding guidelines
packages/cli/src/commands/integrations.test.ts (1)
31-51: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
CONFLICT_MARKERS_YAMLfixture duplicated verbatim across three test files. Same root cause: no shared fixture module for malformed-integrations-YAML test data.
packages/cli/src/commands/integrations.test.ts#L31-L51: extract this fixture into a shared test helper (e.g.packages/cli/src/commands/integrations/tests/fixtures.ts) and import it here.packages/cli/src/commands/integrations/edit-integration.test.ts#L16-L28: import the shared fixture instead of redefining it.packages/cli/src/commands/integrations/tests/add-integration.pgsql.test.ts#L18-L30: import the shared fixture instead of redefining it.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/cli/src/commands/integrations.test.ts` around lines 31 - 51, The CONFLICT_MARKERS_YAML fixture is duplicated across three integration test files; extract one shared fixture and reuse it everywhere. In packages/cli/src/commands/integrations.test.ts#L31-L51, create the shared fixture module and import it; in packages/cli/src/commands/integrations/edit-integration.test.ts#L16-L28 and packages/cli/src/commands/integrations/tests/add-integration.pgsql.test.ts#L18-L30, remove the local definitions and import the shared CONFLICT_MARKERS_YAML fixture.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@packages/cli/src/commands/integrations.test.ts`:
- Around line 31-51: The CONFLICT_MARKERS_YAML fixture is duplicated across
three integration test files; extract one shared fixture and reuse it
everywhere. In packages/cli/src/commands/integrations.test.ts#L31-L51, create
the shared fixture module and import it; in
packages/cli/src/commands/integrations/edit-integration.test.ts#L16-L28 and
packages/cli/src/commands/integrations/tests/add-integration.pgsql.test.ts#L18-L30,
remove the local definitions and import the shared CONFLICT_MARKERS_YAML
fixture.
In `@packages/database-integrations/src/loading/integrations-document.test.ts`:
- Around line 25-32: Wrap the VALID_YAML fixture in
integrations-document.test.ts with ts-dedent while preserving its trailing
newline; likewise wrap EXISTING_WITH_COMMENTS in merge-into-yaml.test.ts with
ts-dedent and preserve its trailing newline. Ensure both test files use the
existing ts-dedent import pattern.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 17dce952-4392-443a-81d7-a30547ab992f
📒 Files selected for processing (14)
packages/cli/README.mdpackages/cli/src/commands/integrations.test.tspackages/cli/src/commands/integrations.tspackages/cli/src/commands/integrations/add-integration.tspackages/cli/src/commands/integrations/edit-integration.test.tspackages/cli/src/commands/integrations/edit-integration.tspackages/cli/src/commands/integrations/tests/add-integration.pgsql.test.tspackages/cli/src/index.tspackages/database-integrations/src/loading/integrations-document.test.tspackages/database-integrations/src/loading/integrations-document.tspackages/database-integrations/src/loading/merge-into-yaml.test.tspackages/database-integrations/src/loading/merge-into-yaml.tsskills/deepnote/SKILL.mdskills/deepnote/references/cli-integrations.md
…rations tests The malformed-YAML fixture was defined identically in three CLI test files. Extract it into `commands/integrations/test-helpers.ts`, matching the existing `commands/test-helpers.ts` convention, and import it in all three. The `database-integrations` copies are left in place: sharing across the package boundary would mean exporting a test fixture through the package's public API. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A7XdccD84jRqXcKctzJfd3
…tions-yaml-typed-error
Fixes #424
Problem
mergeApiIntegrationsIntoYaml(existingContent, apiIntegrations)failed with the crypticError: Document with errors cannot be stringifiedwhenexistingContentwas readable-but-malformed YAML — most commonly a file left with unresolved git merge conflict markers.yaml.parseDocument()never throws; it returns aDocumentwith.errorspopulated. So the?? createNewDocument()fallback never fired, the errored document flowed through the whole merge, and the failure only surfaced at serialize time — with no indication of which file or which line was broken.The same defect reached the CLI through
readIntegrationsDocument, affectingintegrations pull,addandedit.pullandaddhit the serialize-time crash;editfailed earlier with a misleading semantic error (Integration with ID "…" not found), because itsisSeq-guarded helpers silently returned empty on the errored document.Approach
Fail fast at parse time behind a typed, exported error. No silent self-healing — the user is shown the path and the parse detail, and recovery is manual.
parseIntegrationsDocumentthrowsIntegrationsYamlParseErrorwhendoc.errorsis non-empty. Contract is now:nullfor blank,Documentfor valid, throws for malformed.MalformedIntegrationsFileError, carrying the file path plus the parse detail (line, column, code frame), and maps it to exit code 2 (invalid usage) in all three commands.pullnow reads the local file before the empty-response early return, so a malformed file is caught on every run — not just when the workspace has integrations to pull.Both error classes are exported so downstream consumers (webapp git-sync, the VS Code extension) can catch them and rebuild via
mergeApiIntegrationsIntoYaml(null, apiIntegrations)if they'd rather discard a corrupt file than surface the error.Before / after
Why
doc.errorsis the right gateThis moves a failure earlier on a shared code path, so it's worth being precise about the blast radius.
Document.toString()refuses onif (this.errors.length > 0)(yaml/dist/doc/Document.js:322) — the same predicate as the new gate. The set of documents that now throw at parse time is therefore identical by construction to the set that already threw at serialize time. Nothing that works today starts failing; a cryptic late failure becomes an actionable early one.Verified empirically across the boundary cases: duplicate keys, tab-as-indent and bad scalar starts all already threw at serialize time (so they improve), while CRLF, trailing tabs and
postgres://h:5432/dbproduce zero errors and are untouched. Warnings are deliberately not gated on —TAG_RESOLVE_FAILED(e.g.a: !custom v) lands indoc.warningsand stringifies fine; gating on those would have been a real regression.One intentional behavior change:
pullagainst a zero-integration workspace with a malformed local file previously exited 0 without ever reading the file. It now exits 2. This is deliberate, tested, and documented.Bonus fix:
updateDotEnvpreviously ran before the failing serialize, so a malformed file could leave a half-written.envbehind. The relocated read closes that window — the new tests assert.envis never created.Tests
26 new tests.
mergeApiIntegrationsIntoYamlhad zero coverage before this, so it gets baseline coverage alongside the regression cases.integrations-document.test.ts(new) — blank →null, valid → round-trips, conflict markers anda: b: c→ typed error exposing.errorsand line info.merge-into-yaml.test.ts(new) — fresh-document and comment-preserving merge snapshots, both malformed fixtures,InvalidIntegrationsTypeError, and a test that executes the documented catch-and-rebuild recovery recipe. One test asserts the message does not containDocument with errors cannot be stringified, pinning the actual regression.readIntegrationsDocumentunit coverage, plus end-to-end cases for all three commands asserting exit code 2, the YAML file byte-identical afterwards, and.envnever created. Theeditaction test pins both branches of the new ternary (malformed → 2, missing file → 1).Known gap (pre-existing, not addressed here)
An unresolved YAML alias (
password: *missing) produceserrors: 0, warnings: 0yet still throws attoString(). Same shape as this issue — late crash, cryptic message, partial.envwrite — but a different trigger, and behavior is identical before and after this change. Worth a follow-up issue; out of scope here.Verification
pnpm testpnpm run typecheckpnpm lintAndFormatpnpm spell-checkpnpm buildDocs updated per
AGENTS.md: newskills/deepnote/references/cli-integrations.md(registered inSKILL.md) and a note underintegrations pullin the CLI README.🤖 Generated with Claude Code
https://claude.ai/code/session_01PyawjygDS8h5qdX1ZWJoRH
Summary by CodeRabbit
Bug Fixes
integrations pull|add|editnow consistently exit with code 2 and preserve the integrations YAML and any secret.envfile (no partial writes).Documentation
integrations pull/add/editdocs with new defaults, shared options, interactive behavior notes, and explicit invalid-YAML guarantees.Tests