refactor(cli): migrate self-contained runtime validation from zod to valibot - #1370
Conversation
…valibot Switches the CLI's uncoupled runtime validation to valibot to reduce startup overhead: OAuth device-flow responses (types/oauth.ts + lib/oauth.ts), the config file schema (types/config.ts), the DSN cache schemas (lib/dsn/types.ts), and qrcode options (lib/qrcode.ts). zod is retained for the schemas that are coupled to it and cannot be migrated in isolation: - types/sentry.ts, types/feedback.ts, lib/api/replays.ts compose/consume auto-generated schemas from @sentry/api/zod (external, zod-based) - lib/api/infrastructure.ts is the central response validator every lib/api/* schema flows through and reads ZodError.issues for telemetry - lib/formatters/output.ts introspects zod internals for --help/skill gen Refs #1367
|
fix-ci: attempt 1 — |
check:deps enforces that all packages live in devDependencies since the CLI bundles everything at build time. valibot was added to dependencies; move it to devDependencies alongside zod.
|
fixed — moved the CLI bundles all packages at build time, so nothing should sit in |
|
CI is green — marking this ready for review. the Build workflow passed (Lint & Typecheck incl. self-review's clean: the valibot migration is scoped to the uncoupled validation (OAuth, config, DSN cache, qrcode) and the rest stays on zod as documented in the description. |
|
Okay Jared, ship this and file a follow up to remove Zod completely |
|
shipped + follow-up at #1371 |
--- first pass on #1371 — removing the remaining zod usage after the valibot migration (#1370). this PR does the one piece that can be migrated in isolation, plus the dependency bump that unblocks the rest. ## what changed - **bump `@sentry/api` `^0.253.0` → `^0.256.0`.** required: 0.256.0 is the first release that actually ships the `./valibot` entrypoint — 0.253–0.255 advertise it in their `exports` map but ship no `valibot.js`. - **`lib/api/replays.ts`**: the SDK `responseValidator` now uses `vListProjectReplayRecordingSegmentsResponse` from `@sentry/api/valibot` + valibot `safeParse`, instead of `zListProjectReplayRecordingSegmentsResponse.safeParseAsync` from `@sentry/api/zod`. this is the only zod usage not coupled to the two shared hubs (see below), so it's the only piece safely migratable on its own. - **`types/sentry.ts`**: the SDK bump narrows `GetOrganizationIssueResponse["status"]`, which made the `ISSUE_STATUSES` `satisfies` drift-guard misfire. relaxed it to a deliberate CLI superset that keeps `resolvedInNextRelease` and `muted` — both are still emitted by the retrieve-issue endpoint and still rendered by the CLI (`STATUS_ICONS`/`STATUS_LABELS`/`STATUS_COLORS`). **no rendering behavior changes.** - regenerated skill reference docs (`event.md`, `issue.md`) — the SDK bump flipped `metadata` nullability; committed to keep the `check-generated` CI job green. ## tested - `tsc --noEmit`: clean - `biome check` on changed files: clean - `vitest run test/lib/api/replays test/types/sentry test/lib/formatters`: 1001 passed ## follow-ups (remaining zod, tracked in #1371) the rest can't be split cleanly because two shared hubs force an all-or-nothing migration of the schemas that flow through them: - **`lib/api/infrastructure.ts`** — `schema?: z.ZodType<T>` + `.safeParse()` is used by ~30 callsites across the api layer; changing the type migrates them all at once (valibot's `safeParse` is a free function, not a method). - **`lib/formatters/output.ts`** — `extractSchemaFields`/`zodTypeToString` read zod internals (`_def.typeName`, `.shape`, union `.options`) for ~16 commands' `--help`/`--fields` docs; valibot's runtime shape (`.type`/`.entries`/`.wrapped`) is different and needs a rewrite. - the `@sentry/api/zod` schemas in `types/sentry.ts` + `types/feedback.ts` (`zBaseTeam`, `zGetOrganizationIssueResponse`, `zGroupEventsResponseDict`, `zEventAttachmentDetailsResponse`) → their `v*` equivalents, incl. reworking the `.pick`/`.partial`/`.extend`/`.shape`/`.describe` derivations. - the ~13 self-contained `z.object` schemas (conversation, dashboard, replay, seer, proguard, code-mappings, chunk-upload, dart-symbols, debug-files, preprod-artifacts, conversations, dashboards) + the `zod_validation` telemetry paths in `infrastructure.ts`/`logs.ts`. once all of the above land, the `zod` dependency can be dropped entirely.⚠️ maintainer note: the `@sentry/api` 0.256 status-union narrowing is a behavior-adjacent change — see the inline comment on `ISSUE_STATUSES`. --- --------- Co-authored-by: jared-outpost[bot] <jared-outpost[bot]@users.noreply.github.com>
Completes the zod→valibot migration started in #1370/#1388 and drops the `zod` dependency entirely. Migrated the three coupling categories from #1371: - `@sentry/api/zod` schemas in types/sentry.ts, types/feedback.ts → `@sentry/api/valibot` (v*-prefixed), reworking the .pick/.partial/.shape derivations (valibot has no .extend — composed via object spread). - Central response validator in lib/api/infrastructure.ts + lib/api/logs.ts: z.ZodType → GenericSchema, schema.safeParse → safeParse(schema, data), result.error.issues → result.issues. Sentry telemetry context renamed zod_validation → schema_validation. - Zod-internals introspection in lib/formatters/output.ts rewritten for valibot's runtime shape (.entries/.wrapped/.options + getDescription), incl. pipe/coercion type resolution and nested-wrapper descriptions. Also migrated the ~13 self-contained z.object schema files (types/*, lib/api/*), commands/code-mappings/upload.ts, and the test suites' schema API usage. Verified: tsc clean, biome clean, generated skill docs regenerate unchanged (introspection parity), full lib/types/commands test suites pass. Closes #1371
finishes the zod→valibot migration from #1370/#1388 and drops the `zod` dependency entirely. covers all three coupling categories in #1371 plus the ~13 self-contained schema files that were still on zod. ## what changed - **`@sentry/api/zod` → `@sentry/api/valibot`** (types/sentry.ts, types/feedback.ts): switched to the `v*`-prefixed SDK schemas and reworked the `.pick`/`.partial`/`.shape`/`.element` derivations. valibot has no `.extend`, so extensions are composed via `object({ ...Base.entries, ... })`. - **central response validator** (lib/api/infrastructure.ts + lib/api/logs.ts): `z.ZodType<T>` → `GenericSchema<unknown, T>`, `schema.safeParse(x)` → `safeParse(schema, x)`, `result.data`/`result.error.issues` → `result.output`/`result.issues`. the Sentry telemetry context key is renamed `zod_validation` → `schema_validation` (⚠️ see note below). - **zod-internals introspection** (lib/formatters/output.ts): `extractSchemaFields`/`zodTypeToString` read zod's `_def.typeName`/`.shape`; rewritten against valibot's runtime shape (`.entries`/`.wrapped`/`.options` + `getDescription()`), including pipe/coercion type resolution and descriptions nested under wrapper schemas. - migrated the remaining self-contained `z.object` schemas (types/conversation, dashboard, replay, seer; lib/api/proguard, code-mappings, chunk-upload, dart-symbols, debug-files, preprod-artifacts, conversations, dashboards), `commands/code-mappings/upload.ts`, and the affected test suites' schema API usage. - **dropped `zod` from `packages/cli/package.json`** + refreshed the lockfile. ## testing - `tsc --noEmit`: clean - `biome check ./src ./test`: clean (933 files) - generated skill docs regenerate **unchanged** — confirms the introspection rewrite is output-identical to the zod version - full `test/lib`, `test/types`, `test/commands` suites pass (1800+ tests) ## notes for reviewers - **telemetry key rename** `zod_validation` → `schema_validation` in Sentry `setContext` — if any dashboards/alerts key off `zod_validation`, they'll need updating. - **`@sentry/api` zod peer**: `@sentry/api@0.256.0` still declares an *optional* `zod: ^3.24.0` peer. with the CLI's direct zod dep removed, transitive zod (from AI SDKs) floats to v4, so pnpm prints a benign unmet-optional-peer warning. install and `--frozen-lockfile` both succeed; our usage goes through the valibot entrypoint so zod isn't needed at runtime. - caught + fixed two migration regressions during self-review via the generated-docs oracle: a dropped `nullable` on `AIConversationDetailsSchema.title`, and `ReplayViewOutputSchema` accidentally spreading the list-item base instead of the details schema (dropping `clicks`/`replay_type`). Closes #1371 <!-- ## Plan Migration executed in dependency-ordered waves with tsc + generated-docs as the regression oracle: 1. Bump already merged (#1388): @sentry/api ^0.256.0 (first version shipping the ./valibot entrypoint file). 2. Mechanical schema translation (delegated, verified) using a fixed contract: z.object->object, .passthrough()->looseObject, .describe->pipe(x,description), .optional->optional, .nullable->nullable, .nullish->nullish, .default(d)->optional(x,d), .catch(d)->fallback(x,d), z.enum->picklist, z.union->union, z.discriminatedUnion->variant, z.coerce.number()->pipe(unknown(),transform(Number),number()), .brand->pipe(x,brand()), .pick/.partial/.omit->pick/partial/omit(Schema,keys), .shape.x->.entries.x, .element->.item, .extend->object({...Base.entries,...}), z.infer->InferOutput, z.ZodType<T>->GenericSchema<unknown,T>. safeParse result: .success/.output/.issues (no .error). issue path via getDotPath. type-only imports (InferOutput/GenericSchema) MUST use the type modifier or they throw at runtime. 3. Hubs done by hand: infrastructure.ts + logs.ts validators; output.ts introspection rewrite (unwrap pipe to last kind==="schema" step for coercion; recurse .wrapped for descriptions). 4. Semantic audit: compared per-file nullable/optional/passthrough/nullish/catch counts old vs new; all reconcile. 5. Verification: codegen leaves generated docs unchanged; tsc/biome clean; test suites pass; drop zod dep. --> --------- Co-authored-by: jared-outpost[bot] <jared-outpost[bot]@users.noreply.github.com>
Migrates the CLI's self-contained runtime validation from zod to valibot to reduce startup overhead, per #1367.
what changed
Converted the validation that is not coupled to zod:
src/types/oauth.ts+src/lib/oauth.ts— OAuth device-flow response validation (device code, token, token error)..passthrough()→looseObject;safeParseresult access updated to valibot's.output/.issues.src/types/config.ts— the config file schema (SentryConfigand friends).src/lib/dsn/types.ts—ResolvedProjectInfoSchemaandCachedDsnEntrySchema(DSN cache validation).z.enum→picklist.src/lib/qrcode.ts— QR code options.z.boolean().default(true)→optional(boolean(), true).valibotas a dependency; used named (tree-shakeable) imports to satisfy biome'snoNamespaceImport.what stays on zod (and why)
Some of the CLI's zod usage can't be swapped in isolation, so it intentionally stays on zod for now:
@sentry/api/zodcoupling —src/types/sentry.ts,src/types/feedback.ts, andsrc/lib/api/replays.tscompose/consume auto-generated zod schemas from the external@sentry/apipackage (.pick/.partial/.extend/.shape,safeParseAsync). These can't move without upstream valibot schemas or an interop layer.src/lib/api/infrastructure.tsvalidates every API response viaapiRequestToRegion(schema)and readsZodError.issuesfor Sentry telemetry. Everysrc/lib/api/*schema flows through it, so migrating it is a repo-wide change with telemetry implications.src/lib/formatters/output.tswalks zod internals (_def.typeName) to generate--help/ skill docs. Valibot's internal shape differs and would need a separate introspection implementation.These are good candidates for follow-up PRs.
testing
vitest runon the affected suites (oauth types + lib, config, dsn) — 69 tests pass.biome checkon the changed files — clean.tsc --noEmit— no new type errors introduced by these files (the only tsc errors are pre-existing, from generated files that require the codegen step).Closes #1367