Add machine-readable output modes (--output text|json), a typed SDK, and agent skills to drizzle-kit#5691
Open
dankochetov wants to merge 99 commits into
Open
Add machine-readable output modes (--output text|json), a typed SDK, and agent skills to drizzle-kit#5691dankochetov wants to merge 99 commits into
--output text|json), a typed SDK, and agent skills to drizzle-kit#5691dankochetov wants to merge 99 commits into
Conversation
…or better output formatting - Updated connection issue messages in mssql, mysql, and singlestore validations to use humanLog instead of console.log. - Enhanced error handling in Cockroach, MySQL, and PostgreSQL DDL processes to throw errors with detailed messages instead of logging invalid entities. - Replaced console.log with humanLog in various serializer files for consistent logging. - Introduced explainJsonOutput function to format hints for JSON output, ensuring ANSI codes are stripped and statements are excluded. - Added tests for JSON output in push and generate commands to validate structured responses. - Ensured that warnings do not leak to stdout in JSON mode, maintaining clean output.
…update related logic
- Introduced `runWithCliContext` and `getCliContext` functions to manage CLI context, allowing for better handling of JSON mode across tests and commands. - Updated various test files to utilize the new context management functions, ensuring consistent behavior in JSON mode. - Added a comprehensive JSON contract document detailing the expected behavior and structure for commands supporting `--json`. - Modified hints handling in tests to ensure proper context is passed, improving clarity and maintainability. - Enhanced error handling for missing hints in JSON mode, providing clearer feedback for users.
- Adjusted the date for skipping tests related to OR, AND, and NOT conditions in sql-builder.test.ts to June 26, 2026. - Modified the connection string retrieval in mysql.test.ts to prioritize the MYSQL_CONNECTION_STRING environment variable before falling back to createDockerDB().
1d51124 to
a32f7fa
Compare
Replace stale `docker run` notes with a real bash dispatcher that wraps
`docker compose -p drizzle-<dialect> -f compose/<dialect>.yml` for the
13 supported dialects. Provides up/down/logs/ps/wait/help subcommands;
defaults to `up` over every dialect when invoked with no args.
- `-p drizzle-<dialect>` is passed on every docker compose invocation so
projects don't collide and `down` is idempotent.
- `up` follows compose's `--wait --wait-timeout 120` with a host-side
TCP probe via `compose/wait.sh` to absorb Docker Desktop port-forward
races on macOS.
- `down -v` wipes volumes (test DBs are ephemeral).
- Unknown dialects fail fast with the full allowlist printed to stderr.
- Stays bash 3.2 compatible (macOS default): plain `case` for the
dialect map, and `${args[@]+"${args[@]}"}` to safely expand a possibly
empty positional array under `set -u`.
- Drops the macOS singlestore-on-3306 variant; compose/singlestore.yml
on 33307 is canonical for both Linux and macOS.
Wire connection-string env vars consumed by drizzle-kit's mariadb, mysql, postgres, cockroach, mssql, and singlestore test suites to the canonical compose/*.yml host ports driven by compose/dockers.sh. - Commit drizzle-kit/.env.example as the source of truth (ports, credentials, the cockroach `/defaultdb` correction, and a documented MYSQL_CONNECTION_STRING singlestore overload). - Ignore drizzle-kit/.env via the root .gitignore so each developer keeps a local copy without committing it. Did not introduce a global *.env rule; integration-tests/.env (separate package, separate conventions) remains unaffected. - Whitelist .env.example in drizzle-kit/.gitignore (its leading `/*` rule otherwise hides every new file in the package).
Without the postgres-vector branch, dockers.sh up postgres-vector would silently skip the host-side TCP gate (catch-all printed to stdout and returned 0). The catch-all now writes to stderr and exits 1 so a typo fails loudly instead of looking healthy.
Project-scoped skill at .claude/skills/run-tests/SKILL.md that walks through ensuring drizzle-kit/.env exists, picking the right pnpm test:* target, bringing up the matching dialect via compose/dockers.sh, and the singlestore MYSQL_CONNECTION_STRING overload. Update .claude gitignore to expose skills/ while keeping worktrees/ excluded.
…d document expired skipIf workflow The two cli test files relied on the env var being set by the top-level pnpm test script and failed under pnpm test:other, which doesn't set it. Match the existing pattern from cli-push.test.ts and cli-generate.test.ts so the files defend themselves regardless of which script invokes vitest. Also extend the run-tests skill with the expired skipIf-postpone workflow so future failures get checked for expired date gates before being treated as regressions.
Conflict resolution preserved v1.1 contract: no aborted reintroduction; hints.ts/errors.ts/context.ts/JSON_CONTRACT.md kept from ai (new on ai post merge-base, naturally preserved); consolidated unsupported_schema_change + meta.kind shape unchanged; FIX-01..03 push-handler json-mode gates preserved. Non-trivial conflicts (per 17-MERGE-SURVEY.md): - drizzle-kit/src/cli/commands/generate-sqlite.ts: kept ai's full json-mode + hint-aware flow; added beta's checkResult?: CheckHandlerResult parameter and threaded it through prepareSqliteSnapshot. Skipped beta's try/catch wrapper as it would swallow errors and conflict with the json contract. - drizzle-kit/src/dialects/sqlite/serializer.ts: kept humanLog import from ai (used in body), added CheckHandlerResult type and assertUnreachable imports from beta. CONTEXT.md anticipated heavy push-*.ts conflicts; post-unshallow merge-base (56b4b28) shows all v1.1 contract surface files (push-mysql/mssql/postgres/ cockroach/sqlite/singlestore, prompts.ts, generate-mysql/mssql/postgres/ cockroach/singlestore, cli/index.ts, cli/utils.ts, release-feature-branch.yaml, pnpm-lock.yaml) auto-merged unchanged because beta's eec7260 does not modify them relative to merge-base.
Introduce a programmatic SDK (drizzle-kit/src/sdk/) for generate/push, unify the CLI around an envelope contract (drizzle-kit/src/cli/contract.ts), and extract shared generate/push pipelines into schema.ts. Adds SDK conformance + regression tests and reworks affected CLI/dialect/api modules accordingly.
Non-trivial resolutions:
- Adopted beta's validations/cli.ts deletion + EntitiesFilterConfig move to validations/common.ts.
- Absorbed beta's per-command zod schemas (configPush, configPull, configCheck, configGenerate, configExport, configStudio, configMigrate) in validations/common.ts; preserved ai's typed-throw + HintsHandler stack across the boundary by relocating pullParams / pushParams / studioConfig / CliConfig into validations/common.ts and restoring AmbiguousParamsCliError / ConfigConnectionCliError throws in common.ts, libsql.ts, and mssql.ts (beta's console.log + process.exit fallbacks reverted to ai's typed-error pattern).
- Confirmed cli/schema.ts auto-merge preserves prepareGenerate / runGenerate / preparePush / runPush extraction + the statusToExitCode tail at the brocli handler exit; beta's CheckConfig typing + studio transform hook compose cleanly with ai's dispatch pipeline.
- Reconciled cli-{export,generate,migrate,push}.test.ts conflicts at the imports only; beta's rewritten test bodies retained as the new baseline.
- skipIf date conflicts in pg-*, sql-builder, and integration-tests/common-* tests resolved by keeping ai's later expiry dates (2026-06-01 / 2026-06-26).
- mysql integration test TestContext narrowed to env-var-driven connection (ai's fixture pattern) while adopting beta's MySql2Database single-generic signature from the MySQL refactor.
Accepted deletions:
- drizzle-kit/src/cli/validations/cli.ts, integration-tests/tests/mysql/mysql-v1.test.ts, integration-tests/tests/mysql/mysql.duplicates.test.ts (the three modify/delete conflicts).
- Additional clean upstream deletions: drizzle-kit/src/cli/validations/studio.ts (studioCliParams folded into common.ts), drizzle-orm/src/mysql-core/query-builders/_query.ts, drizzle-orm/type-tests/mysql/db-rel.ts, integration-tests/tests/mysql/mysql.duplicates.ts, integration-tests/tests/mysql/mysql.planetscale-v1.test.ts (per beta d8460d5).
Hint contract preserved:
- No aborted status reintroduced anywhere in drizzle-kit/src/cli.
- isJsonMode() caller-context gates on push throws intact: drop_pk_dependency in mysql, rename_blocked_by_check_constraint + rename_schema_unsupported in mssql.
- missing_hints exit-code 2 behavior unchanged; --hints / --hints-file parsing preserved through pushParams / pullParams retention.
Production changes - validations/common.ts: pre-merge import path lost its src/ alias; switch back to relative ../../utils/schemaValidator so jiti resolves at test time. - utils-node.ts loadModule: jiti.import branch was missing the mod.default ?? mod unwrap that the Bun/Deno and ESM branches both perform. Without it drizzleConfigFromFile receives a namespace object with default-forwarding proxy fields that zod's safeParse drops, so config.schema / config.sql / config.tablesFilter become undefined and the prepare* helpers throw RequiredParamsCliError on otherwise valid configs. - commands/utils.ts prepareExportConfig: take sql from the CLI options first, then from the config file, then default true; previously the CLI --sql=false flag was ignored when the rest of the command came from a config file. Also re-export the CheckConfig type the dispatch needs. Test adaptations - 8 cli-*.test.ts files: replaced vi.spyOn(console, 'log') + 'process.exit unexpectedly called with "1"' assertions with res.error.message checks built from the same error() / wrapParam() helpers the runtime uses, so the captured CliError message matches byte-for-byte (including ANSI). Three GenerateConfig / push expected fixtures gained the missing explain + hints fields. cli-check.test.ts, cli-pull.test.ts, cli-studio.test.ts now self-set TEST_CONFIG_PATH_PREFIX so they work under test:other (which doesn't set it). - conformance-live-db.test.ts push mysql describe gained a beforeAll that drops every table in the active mysql database. Without it, leftover tables from earlier mysql test files in the same run made the diff engine treat new tables as rename_or_create candidates and emit missing_hints instead of the expected ok / no_changes envelopes.
…-undefined
Today (2026-05-20) is the postpone date in five integration-test files for the
"Query error wrapping" / "Mappers: deep nullification" / "Same table name joined
between schemas" gates, so vitest started running them and they fail because sync
drivers (better-sqlite3, sqljs, postgres.js, mssql/tedious, cockroach pg) still
return raw driver errors instead of DrizzleQueryError. Per the repo's skipIf
convention (drizzle-orm/.claude/skills/tests), bump the dates to one month forward
rather than fix in-band: integration-tests/tests/{sqlite/sqlite-common,
pg/common-cache, pg/common-pt2, cockroach/common, mssql/mssql}.ts, plus the
@ts-ignore searchable-marker comment in pg/common-pt2.ts.
Also rewrite the no-useless-undefined violations in
drizzle-kit/tests/sdk/regressions/no-stdout.test.ts (process.exit mock
implementation): `() => undefined` to `() => {}` so the gate that ran lint on the
last green CI build (post-merge) passes. Behavior is identical; the spy still
swallows the exit call.
Tests
- cli-{check,export,generate,migrate,pull,push,studio}.test.ts: assertion text
for every `validate config #N` case now matches beta's exact wording
byte-for-byte, constructed via the same error() / wrapParam() helpers the
runtime uses. The mechanism is res.error.message (not console.log spy +
process.exit) because the cli path throws DrizzleCliError; the literal
text it asserts is identical to beta.
- cli-studio.test.ts #5/#6: prepareStudioConfig is still in the legacy
humanLog + process.exit path, so those tests keep beta's spy mechanism
unchanged; the only deviation is stripAnsi() on captured calls because
withStyle.error wraps the message with chalk codes.
Runtime
- validations/common.ts: configCommonSchema.dialect is now optional. The
early MissingConfigDialectCliError throw in drizzleConfigFromFile is gone
so each prepare* helper owns the dialect-missing wording (mirrors beta).
- commands/utils.ts:
- prepareCheckParams: wrapParam('dialect', config.dialect) was being
passed the imported zod schema; pass the actual config value.
- prepareGenerateConfig + prepareExportConfig: reorder RequiredParamsCliError
builders to dialect-first / schema-second; drop the wrapParam('out',...)
line from generate so the message matches beta exactly.
- drizzleConfigFromFile: removed the chalk.grey wrap around "Reading config
file" so spy assertions can compare against plain text (matches beta).
- validations/mssql.ts: rewrote printConfigConnectionIssues to match the
postgres.ts shape — url branch, server-form branch, generic fallthrough
with "Either connection \"url\" or \"server\", \"user\", \"password\" are
required for MsSQL database connection". The previous form duplicated the
same "Please provide required params for MsSQL driver:" header on both
branches and had no fallthrough.
Verified
- pnpm tsc --noEmit -p tsconfig.json + tsconfig.typetest.json both clean
- TEST_CONFIG_PATH_PREFIX=./tests/cli/ vitest run tests/other/ → 376/376 pass
loadModule used to apply `mod?.default ?? mod` in all three branches
(Bun/Deno, jiti, ESM Node), so every caller silently received the
default export when present. Only drizzleConfigFromFile actually needs
that — drizzle.config.ts uses `export default defineConfig(...)` —
while the 11 schema-loading call sites in cli/commands/studio.ts and
dialects/*/drizzle.ts iterate `Object.entries(mod)` to find PgTable /
MySqlTable / Relations values, where the unwrap masks bugs and silently
rescues non-canonical `export default { tables }` schemas.
Add an explicit `{ defaultExport }` options flag, default false. All
three branches now return `defaultExport ? (mod?.default ?? mod) : mod`.
drizzleConfigFromFile passes `{ defaultExport: true }`; the schema-
loading call sites are unchanged and now receive the raw namespace.
Behavior change: schema files using `export default { users, posts }`
stop working — must switch to named exports (`export const users =
pgTable(...)`), the documented drizzle pattern.
- pnpm exec tsc --noEmit -p tsconfig.json → 0
- vitest run tests/other/ → 376/376
…d and align `primary_key` hint kind on the wire Ship five SKILL.md files under `drizzle-kit/skills/` (drizzle-migrations, drizzle-generate, drizzle-push, drizzle-hints, drizzle-responses-and-errors) with deduped shared sections — response envelope owned by drizzle-responses-and-errors, reasons by drizzle-hints, per-dialect notes by drizzle-migrations. Descriptions trimmed to <=25 words; `metadata.version: "1.0.0"` on all five; worked examples added for the `privilege` 5-tuple and the `add_unique` constraint-name slot. Catalog ships via the new `drizzle-kit skills` install subcommand and a `tests/skills/catalog.test.ts` smoke gate; the build copies the catalog into `dist/skills/`. Rename the rename/create kind literal `primary key` (space) to `primary_key` (underscore) across the zod schema in `src/cli/hints.ts`, the runtime resolver call sites, and the hint test fixtures. Both rename-or-create and confirm-data-loss unions now carry a `primary_key` entry but stay discriminated by the outer `type` field. `foreign key` sites preserved. `JSON_CONTRACT.md` updated. Add a `humanizeKind` helper so TTY prompts render "primary key" rather than the wire literal.
…ocal-path `npx skills` invocation and package-manager detection
…adata.revision` bumps on `drizzle-kit/skills/**` Move skills revision into a dedicated `drizzle-kit skills version` subcommand; diff the whole feature branch against an auto-detected fork point so the gate covers multi-commit branches; and run the skills-revision gate only for pull requests, not raw pushes.
# Conflicts: # .github/workflows/release-feature-branch.yaml # drizzle-kit/tests/postgres/pg-columns.test.ts # drizzle-kit/tests/postgres/pg-enums.test.ts # drizzle-kit/tests/postgres/pg-tables.test.ts # drizzle-kit/tests/postgres/pull.test.ts # drizzle-orm/tests/sql-builder.test.ts # integration-tests/tests/mysql/mysql-common-8.ts # integration-tests/tests/pg/common-pt1.ts # integration-tests/tests/pg/common-pt2.ts # integration-tests/tests/pg/common-rqb.ts # integration-tests/tests/sqlite/sqlite-common.ts
…ractivity axes, and ship the public SDK and agent skills - Replace the `--json` boolean with `--output text|json` and decompose `isJsonMode()` into orthogonal `outputFormat()` and `isInteractive()` predicates (interactive = text output and a TTY) - Accept a raw `Hint[]` for the SDK `hints` option and root-export `Hint`/`MissingHint`; map a malformed `Hint[]` to `invalid_hints` rather than `internal_error` - Add a `drizzle-output-modes` skill; separate output format from interactivity in the generate/push skills; reference sibling skills by name instead of file links; correct the `confirm_data_loss` `view` availability to postgresql/cockroach materialized views only - Extract the output-agnostic hint vocabulary into `HINTS.md` and trim `JSON_CONTRACT.md`/`OUTPUT_MODES.md` - Drop the duplicated `--output` default; delete the redundant text-mode regression tests; convert the SDK `.test-d.ts` files to vitest type tests asserting flat push credentials and `hints: Hint[]`
--output text|json), a typed SDK, and agent skills to drizzle-kit
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Gives
drizzle-kit generate/pusha machine-readable, non-interactive contract so agents, build tools, and CI can drive them without scraping prompts or terminal output. The old single--jsonboolean is replaced by three orthogonal axes:--output text(human, default) vs--output json(one machine-readable envelope), controls how results render.interactive = output === 'text' && !!process.stdin.isTTY.--output jsonis therefore always non-interactive.--hints/--hints-filesupply rename/data-loss decisions up front so the CLI never has to ask, in either output mode.Three surfaces ship together: the
--outputCLI, a typed root-level SDK, and an installable Agent Skills catalog.What's included
1.
--output text | jsonforgenerateandpush--output jsonemits a single JSON object onstdout, a discriminated union keyed onstatus:ok— work succeeded (migration_pathforgenerate;statements/hintsin--explain)no_changes— schema in sync, nothing to domissing_hints— the diff has ambiguous or unsafe operations that need caller input; the response listsunresolveditems (exit code 2)error— structured failure with a stableerror.codeUnder the default
--output text, the same run is human-readable. When it is non-interactive (non-TTY) and a decision is still unresolved, it prints a missing-decisions report tostdoutand exits 2 instead of hanging on a prompt. Full envelope / status / error-code reference:drizzle-kit/JSON_CONTRACT.md; the text/interactivity model and the report shape:drizzle-kit/OUTPUT_MODES.md.2. Hints flow (output-agnostic)
Ambiguous diffs (rename vs. create+drop) and unsafe operations surface as typed
unresolveditems the caller resolves and resubmits — identically in text and JSON mode. Kinds:rename_or_createconfirm_data_loss—table,column,schema,view,primary_key,add_not_null,add_unique(each carries areason:non_empty,nulls_present,duplicates_present,type_change)unsupported_schema_change—drop_pk_dependency,fk_target_not_unique,rename_blocked_by_check_constraint,rename_schema_unsupportedImplemented for postgres, mysql, sqlite, mssql, cockroach, and singlestore. The full vocabulary (kinds, reasons, identifier-tuple shapes) lives in
drizzle-kit/HINTS.md.3. SDK — typed root-level exports
The same contract is callable as a TypeScript API:
The SDK always runs in JSON mode internally (no
--outputflag at the call site) and returns the same envelope the CLI prints.hintsis a rawHint[](the CLI--hintsflag is the JSON-string form); push credentials are passed flat (url/host/ …), with nodbCredentialswrapper. Response shapes are obtained viaAwaited<ReturnType<typeof generate | push>>and narrowed onstatus— they are not separate named exports. Full surface indrizzle-kit/SDK.md.4. Agent Skills catalog (
drizzle-kit skills)npx drizzle-kit skillsinstalls a bundled Agent Skills catalog into the host project —.claude/skills/<slug>/SKILL.mdfor Claude Code,.agents/skills/<slug>/SKILL.mdfor Codex / Cursor / Gemini CLI / Cline / Copilot and any tool following the openAGENTS.mdconvention. Skills shipped:drizzle— umbrella staleness check against the installeddrizzle-kitdrizzle-generate,drizzle-push,drizzle-migrations— the command workflowsdrizzle-hints— resolvingmissing_hintsdrizzle-responses-and-errors— decoding the envelope and error codesdrizzle-output-modes— choosing--output textvsjson, interactivity, and the text reportDocumentation
drizzle-kit/JSON_CONTRACT.md— the JSON-mode envelope (statuses, error codes) (new)drizzle-kit/OUTPUT_MODES.md— the--output text | jsonand interactivity model + missing-decisions text report (new)drizzle-kit/HINTS.md— the output-agnostic hint vocabulary (new)drizzle-kit/SDK.md— the programmatic SDK surface (new)Compatibility
--output textis the default and prompts on a TTY exactly as before.