zenstack-kit 0.1.5 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -79,7 +79,7 @@ The `init` command offers two options:
79
79
  ### 4. Generate migrations
80
80
 
81
81
  ```bash
82
- zenstack-kit migrate:generate --name add_posts
82
+ zenstack-kit migrate create --name add_posts
83
83
  ```
84
84
 
85
85
  This creates a migration in Prisma format:
@@ -95,7 +95,7 @@ prisma/migrations/
95
95
  ### 5. Apply migrations
96
96
 
97
97
  ```bash
98
- zenstack-kit migrate:apply
98
+ zenstack-kit migrate apply
99
99
  ```
100
100
 
101
101
  Migrations are tracked in the `_prisma_migrations` table, making them compatible with `prisma migrate deploy`.
@@ -126,12 +126,12 @@ Options:
126
126
  - `--create-initial` - Create snapshot and initial migration
127
127
  - `-c, --config <path>` - Path to zenstack-kit config file
128
128
 
129
- ### `zenstack-kit migrate:generate`
129
+ ### `zenstack-kit migrate create`
130
130
 
131
131
  Generate a new SQL migration from schema changes.
132
132
 
133
133
  ```bash
134
- zenstack-kit migrate:generate --name add_users
134
+ zenstack-kit migrate create --name add_users
135
135
  ```
136
136
 
137
137
  Options:
@@ -141,12 +141,12 @@ Options:
141
141
  - `--dialect <dialect>` - Database dialect (`sqlite`, `postgres`, `mysql`)
142
142
  - `-c, --config <path>` - Path to zenstack-kit config file
143
143
 
144
- ### `zenstack-kit migrate:apply`
144
+ ### `zenstack-kit migrate apply`
145
145
 
146
146
  Apply pending migrations to the database.
147
147
 
148
148
  ```bash
149
- zenstack-kit migrate:apply
149
+ zenstack-kit migrate apply
150
150
  ```
151
151
 
152
152
  Options:
@@ -156,6 +156,7 @@ Options:
156
156
  - `--table <name>` - Migrations table name (default: `_prisma_migrations`)
157
157
  - `--db-schema <name>` - Database schema for migrations table (PostgreSQL only, default: `public`)
158
158
  - `--preview` - Preview pending migrations without applying
159
+ - `--mark-applied` - Mark pending migrations as applied without running SQL
159
160
  - `-c, --config <path>` - Path to zenstack-kit config file
160
161
 
161
162
  ### `zenstack-kit pull`
@@ -170,6 +171,7 @@ Options:
170
171
  - `-o, --output <path>` - Output path for schema (default: `./schema.zmodel`)
171
172
  - `--dialect <dialect>` - Database dialect
172
173
  - `--url <url>` - Database connection URL
174
+ - `--preview` - Preview generated schema and diff without writing files
173
175
  - `-c, --config <path>` - Path to zenstack-kit config file
174
176
 
175
177
  Features:
@@ -1 +1 @@
1
- {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../src/cli/app.tsx"],"names":[],"mappings":";AAEA;;;;;;;;GAQG;AA0SH,wBAAgB,MAAM,SAkBrB"}
1
+ {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../src/cli/app.tsx"],"names":[],"mappings":";AAEA;;;;;;;;GAQG;AA6SH,wBAAgB,MAAM,SAkBrB"}
package/dist/cli/app.js CHANGED
@@ -74,6 +74,9 @@ function parseArgs() {
74
74
  else if (arg === "--preview") {
75
75
  options.preview = true;
76
76
  }
77
+ else if (arg === "--mark-applied") {
78
+ options.markApplied = true;
79
+ }
77
80
  else if (arg === "--force" || arg === "-f") {
78
81
  options.force = true;
79
82
  }
@@ -101,7 +104,7 @@ function Status({ type, message }) {
101
104
  }
102
105
  // Help display component
103
106
  function HelpDisplay() {
104
- return (_jsxs(Box, { flexDirection: "column", paddingY: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: "zenstack-kit" }), _jsx(Text, { dimColor: true, children: "Database tooling for ZenStack schemas" }), _jsx(Text, { children: " " }), _jsx(Text, { bold: true, children: "Commands:" }), commands.filter(c => c.value !== "exit").map((cmd) => (_jsxs(Box, { marginLeft: 2, children: [_jsx(Box, { width: 20, children: _jsx(Text, { color: "yellow", children: cmd.label }) }), _jsx(Text, { dimColor: true, children: cmd.description })] }, cmd.value))), _jsx(Text, { children: " " }), _jsx(Text, { bold: true, children: "Options:" }), _jsxs(Box, { marginLeft: 2, flexDirection: "column", children: [_jsx(Text, { dimColor: true, children: "-s, --schema <path> Path to ZenStack schema" }), _jsx(Text, { dimColor: true, children: "-m, --migrations <path> Migrations directory" }), _jsx(Text, { dimColor: true, children: "-n, --name <name> Migration name" }), _jsx(Text, { dimColor: true, children: "--dialect <dialect> Database dialect (sqlite, postgres, mysql)" }), _jsx(Text, { dimColor: true, children: "--url <url> Database connection URL" }), _jsx(Text, { dimColor: true, children: "--create-initial Create initial migration (skip prompt)" }), _jsx(Text, { dimColor: true, children: "--baseline Create baseline only (skip prompt)" }), _jsx(Text, { dimColor: true, children: "--preview Preview pending migrations without applying" }), _jsx(Text, { dimColor: true, children: "-f, --force Force operation without confirmation" }), _jsx(Text, { dimColor: true, children: "-c, --config <path> Path to zenstack-kit config file" })] })] }));
107
+ return (_jsxs(Box, { flexDirection: "column", paddingY: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: "zenstack-kit" }), _jsx(Text, { dimColor: true, children: "Database tooling for ZenStack schemas" }), _jsx(Text, { children: " " }), _jsx(Text, { bold: true, children: "Commands:" }), commands.filter(c => c.value !== "exit").map((cmd) => (_jsxs(Box, { marginLeft: 2, children: [_jsx(Box, { width: 20, children: _jsx(Text, { color: "yellow", children: cmd.label }) }), _jsx(Text, { dimColor: true, children: cmd.description })] }, cmd.value))), _jsx(Text, { children: " " }), _jsx(Text, { bold: true, children: "Options:" }), _jsxs(Box, { marginLeft: 2, flexDirection: "column", children: [_jsx(Text, { dimColor: true, children: "-s, --schema <path> Path to ZenStack schema" }), _jsx(Text, { dimColor: true, children: "-m, --migrations <path> Migrations directory" }), _jsx(Text, { dimColor: true, children: "-n, --name <name> Migration name" }), _jsx(Text, { dimColor: true, children: "--dialect <dialect> Database dialect (sqlite, postgres, mysql)" }), _jsx(Text, { dimColor: true, children: "--url <url> Database connection URL" }), _jsx(Text, { dimColor: true, children: "--create-initial Create initial migration (skip prompt)" }), _jsx(Text, { dimColor: true, children: "--baseline Create baseline only (skip prompt)" }), _jsx(Text, { dimColor: true, children: "--preview Preview pending migrations without applying" }), _jsx(Text, { dimColor: true, children: "--mark-applied Mark pending migrations as applied without running SQL" }), _jsx(Text, { dimColor: true, children: "-f, --force Force operation without confirmation" }), _jsx(Text, { dimColor: true, children: "-c, --config <path> Path to zenstack-kit config file" })] })] }));
105
108
  }
106
109
  function CliApp({ initialCommand, options }) {
107
110
  const { exit } = useApp();
@@ -19,6 +19,7 @@ export interface CommandOptions {
19
19
  baseline?: boolean;
20
20
  createInitial?: boolean;
21
21
  preview?: boolean;
22
+ markApplied?: boolean;
22
23
  force?: boolean;
23
24
  config?: string;
24
25
  }
@@ -1 +1 @@
1
- {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../src/cli/commands.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../src/cli/commands.ts"],"names":[],"mappings":"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"}
@@ -6,6 +6,8 @@
6
6
  */
7
7
  import * as fs from "fs";
8
8
  import * as path from "path";
9
+ import * as os from "os";
10
+ import { execFileSync } from "child_process";
9
11
  import { loadConfig } from "../config/loader.js";
10
12
  import { pullSchema } from "../schema/pull.js";
11
13
  import { createPrismaMigration, applyPrismaMigrations, previewPrismaMigrations, hasPrismaSchemaChanges, hasSnapshot, scanMigrationFolders, writeMigrationLog, initializeSnapshot, createInitialMigration, detectPotentialRenames, } from "../migrations/prisma.js";
@@ -148,6 +150,9 @@ export async function runMigrateApply(ctx) {
148
150
  throw new CommandError("Database connection URL is required for non-sqlite dialects.");
149
151
  }
150
152
  const databasePath = dialect === "sqlite" ? connectionUrl : undefined;
153
+ if (ctx.options.preview && ctx.options.markApplied) {
154
+ throw new CommandError("Cannot use --preview and --mark-applied together.");
155
+ }
151
156
  // Preview mode - show pending migrations without applying
152
157
  if (ctx.options.preview) {
153
158
  ctx.log("info", "Preview mode - no changes will be applied.");
@@ -171,12 +176,21 @@ export async function runMigrateApply(ctx) {
171
176
  ctx.log("info", `${preview.alreadyApplied.length} migration(s) already applied`);
172
177
  }
173
178
  for (const migration of preview.pending) {
174
- ctx.log("info", `Migration: ${migration.name}`);
175
- ctx.log("info", `SQL:\n${migration.sql}`);
179
+ const statementCount = migration.sql
180
+ .split(/;(?:\s*\n|\s*$)/)
181
+ .map((s) => s.trim())
182
+ .filter((s) => s.length > 0 && !s.startsWith("--")).length;
183
+ ctx.log("info", `Migration: ${migration.name} (${statementCount} statement${statementCount === 1 ? "" : "s"})`);
176
184
  }
185
+ ctx.log("info", "Use the migration.sql files to review full SQL.");
177
186
  return;
178
187
  }
179
- ctx.log("info", "Applying migrations...");
188
+ if (ctx.options.markApplied) {
189
+ ctx.log("info", "Marking migrations as applied (no SQL will be executed)...");
190
+ }
191
+ else {
192
+ ctx.log("info", "Applying migrations...");
193
+ }
180
194
  const result = await applyPrismaMigrations({
181
195
  migrationsFolder: outputPath,
182
196
  dialect,
@@ -184,6 +198,7 @@ export async function runMigrateApply(ctx) {
184
198
  databasePath,
185
199
  migrationsTable,
186
200
  migrationsSchema,
201
+ markApplied: ctx.options.markApplied,
187
202
  });
188
203
  // Handle coherence errors
189
204
  if (result.coherenceErrors && result.coherenceErrors.length > 0) {
@@ -210,7 +225,8 @@ export async function runMigrateApply(ctx) {
210
225
  return;
211
226
  }
212
227
  for (const item of result.applied) {
213
- ctx.log("success", `Applied: ${item.migrationName} (${item.duration}ms)`);
228
+ const action = ctx.options.markApplied ? "Marked applied" : "Applied";
229
+ ctx.log("success", `${action}: ${item.migrationName} (${item.duration}ms)`);
214
230
  }
215
231
  if (result.failed) {
216
232
  throw new CommandError(`Migration failed: ${result.failed.migrationName} - ${result.failed.error}`);
@@ -334,6 +350,42 @@ export async function runPull(ctx) {
334
350
  return;
335
351
  }
336
352
  }
353
+ if (ctx.options.preview) {
354
+ ctx.log("info", "Preview mode - no files will be written.");
355
+ const result = await pullSchema({
356
+ dialect,
357
+ connectionUrl,
358
+ databasePath,
359
+ outputPath: schemaOutputPath,
360
+ writeFile: false,
361
+ });
362
+ if (fs.existsSync(schemaOutputPath)) {
363
+ const diffOutput = await buildSchemaDiff(schemaOutputPath, result.schema);
364
+ if (diffOutput) {
365
+ const { text, truncated } = truncateLines(diffOutput, 200);
366
+ ctx.log("info", `Diff (existing -> generated):\n${text}`);
367
+ if (truncated) {
368
+ ctx.log("info", "Diff truncated. Use --output to write and inspect the full schema.");
369
+ }
370
+ }
371
+ else {
372
+ ctx.log("info", "Diff unavailable; showing generated schema preview.");
373
+ const { text, truncated } = truncateLines(result.schema, 200);
374
+ ctx.log("info", `Generated schema:\n${text}`);
375
+ if (truncated) {
376
+ ctx.log("info", "Preview truncated. Use --output to write and inspect the full schema.");
377
+ }
378
+ }
379
+ }
380
+ else {
381
+ const { text, truncated } = truncateLines(result.schema, 200);
382
+ ctx.log("info", `Generated schema:\n${text}`);
383
+ if (truncated) {
384
+ ctx.log("info", "Preview truncated. Use --output to write and inspect the full schema.");
385
+ }
386
+ }
387
+ return;
388
+ }
337
389
  ctx.log("info", "Pulling schema from database...");
338
390
  const result = await pullSchema({
339
391
  dialect,
@@ -349,3 +401,33 @@ export async function runPull(ctx) {
349
401
  ctx.log("warning", "You should run 'zenstack-kit init' to reset the snapshot after reviewing the schema.");
350
402
  }
351
403
  }
404
+ async function buildSchemaDiff(existingPath, nextSchema) {
405
+ const tempDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), "zenstack-kit-pull-"));
406
+ const nextPath = path.join(tempDir, "schema.zmodel");
407
+ try {
408
+ await fs.promises.writeFile(nextPath, nextSchema, "utf-8");
409
+ try {
410
+ return execFileSync("git", ["diff", "--no-index", "--no-color", "--", existingPath, nextPath], { encoding: "utf-8" });
411
+ }
412
+ catch (error) {
413
+ const stdout = error.stdout;
414
+ if (stdout) {
415
+ return stdout.toString();
416
+ }
417
+ return null;
418
+ }
419
+ }
420
+ finally {
421
+ await fs.promises.rm(tempDir, { recursive: true, force: true });
422
+ }
423
+ }
424
+ function truncateLines(text, maxLines) {
425
+ const lines = text.split("\n");
426
+ if (lines.length <= maxLines) {
427
+ return { text, truncated: false };
428
+ }
429
+ return {
430
+ text: lines.slice(0, maxLines).join("\n"),
431
+ truncated: true,
432
+ };
433
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../../src/cli/prompts.tsx"],"names":[],"mappings":"AAAA;;GAEG;AAMH,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,cAAc,GAAG,UAAU,GAAG,gBAAgB,CAAC;AACjF,MAAM,MAAM,aAAa,GAAG,KAAK,GAAG,IAAI,CAAC;AAsCzC;;GAEG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,UAAU,CAAC,CAyBhE;AAED;;GAEG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,UAAU,CAAC,CAyB3D;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,aAAa,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CA+BjF;AAED,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,eAAe,CAAC;AA2CtD;;GAEG;AACH,wBAAsB,mBAAmB,CAAC,WAAW,GAAE,MAAoB,GAAG,OAAO,CAAC,MAAM,CAAC,CAc5F;AAED,MAAM,MAAM,sBAAsB,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAEzD;;GAEG;AACH,wBAAsB,sBAAsB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAkCnG;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAyBvF;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,GACT,OAAO,CAAC,YAAY,CAAC,CAyBvB"}
1
+ {"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../../src/cli/prompts.tsx"],"names":[],"mappings":"AAAA;;GAEG;AAMH,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,cAAc,GAAG,UAAU,GAAG,gBAAgB,CAAC;AACjF,MAAM,MAAM,aAAa,GAAG,KAAK,GAAG,IAAI,CAAC;AAsCzC;;GAEG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,UAAU,CAAC,CAyBhE;AAED;;GAEG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,UAAU,CAAC,CAyB3D;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,aAAa,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CA+BjF;AAED,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,eAAe,CAAC;AAyCtD;;GAEG;AACH,wBAAsB,mBAAmB,CAAC,WAAW,GAAE,MAAoB,GAAG,OAAO,CAAC,MAAM,CAAC,CAc5F;AAED,MAAM,MAAM,sBAAsB,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAEzD;;GAEG;AACH,wBAAsB,sBAAsB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAkCnG;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAyBvF;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,GACT,OAAO,CAAC,YAAY,CAAC,CAyBvB"}
@@ -3,7 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  * Interactive prompts for the init command using ink
4
4
  */
5
5
  import React, { useState } from "react";
6
- import { render, Box, Text } from "ink";
6
+ import { render, Box, Text, useInput } from "ink";
7
7
  import SelectInput from "ink-select-input";
8
8
  function SelectPrompt({ message, items, onSelect }) {
9
9
  const [selectedIndex, setSelectedIndex] = useState(0);
@@ -101,8 +101,6 @@ function TextInputPrompt({ message, placeholder, onSubmit, }) {
101
101
  setValue((prev) => prev + input);
102
102
  }
103
103
  };
104
- // Use ink's useInput hook
105
- const { useInput } = require("ink");
106
104
  useInput(handleInput);
107
105
  return (_jsx(Box, { flexDirection: "column", children: _jsxs(Box, { children: [_jsx(Text, { color: "cyan", children: "? " }), _jsxs(Text, { children: [message, " "] }), _jsxs(Text, { dimColor: true, children: ["(", placeholder, "): "] }), _jsx(Text, { children: value }), _jsx(Text, { color: "gray", children: "\u2588" })] }) }));
108
106
  }
@@ -12,6 +12,8 @@ export interface ApplyPrismaMigrationsOptions {
12
12
  migrationsTable?: string;
13
13
  /** Migrations schema (PostgreSQL only, default: public) */
14
14
  migrationsSchema?: string;
15
+ /** Mark migrations as applied without executing SQL */
16
+ markApplied?: boolean;
15
17
  }
16
18
  export interface ApplyPrismaMigrationsResult {
17
19
  applied: Array<{
@@ -1 +1 @@
1
- {"version":3,"file":"apply.d.ts","sourceRoot":"","sources":["../../../src/migrations/prisma/apply.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAIjE,MAAM,WAAW,4BAA4B;IAC3C,6BAA6B;IAC7B,gBAAgB,EAAE,MAAM,CAAC;IACzB,uBAAuB;IACvB,OAAO,EAAE,aAAa,CAAC;IACvB,8BAA8B;IAC9B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,2BAA2B;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,0DAA0D;IAC1D,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,2DAA2D;IAC3D,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,2BAA2B;IAC1C,OAAO,EAAE,KAAK,CAAC;QAAE,aAAa,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC5D,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,MAAM,CAAC,EAAE;QAAE,aAAa,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAClD,eAAe,CAAC,EAAE,uBAAuB,EAAE,CAAC;CAC7C;AAED,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,kBAAkB,GAAG,iBAAiB,GAAG,mBAAmB,GAAG,gBAAgB,GAAG,mBAAmB,CAAC;IAC5G,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,wBAAwB;IACvC,UAAU,EAAE,OAAO,CAAC;IACpB,MAAM,EAAE,uBAAuB,EAAE,CAAC;CACnC;AAED,MAAM,WAAW,6BAA6B;IAC5C,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC9C,cAAc,EAAE,MAAM,EAAE,CAAC;CAC1B;AA2QD;;GAEG;AACH,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,4BAA4B,GACpC,OAAO,CAAC,2BAA2B,CAAC,CAuHtC;AAED;;GAEG;AACH,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,4BAA4B,GACpC,OAAO,CAAC,6BAA6B,CAAC,CA0DxC"}
1
+ {"version":3,"file":"apply.d.ts","sourceRoot":"","sources":["../../../src/migrations/prisma/apply.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAIjE,MAAM,WAAW,4BAA4B;IAC3C,6BAA6B;IAC7B,gBAAgB,EAAE,MAAM,CAAC;IACzB,uBAAuB;IACvB,OAAO,EAAE,aAAa,CAAC;IACvB,8BAA8B;IAC9B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,2BAA2B;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,0DAA0D;IAC1D,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,2DAA2D;IAC3D,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,uDAAuD;IACvD,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,2BAA2B;IAC1C,OAAO,EAAE,KAAK,CAAC;QAAE,aAAa,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC5D,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,MAAM,CAAC,EAAE;QAAE,aAAa,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAClD,eAAe,CAAC,EAAE,uBAAuB,EAAE,CAAC;CAC7C;AAED,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,kBAAkB,GAAG,iBAAiB,GAAG,mBAAmB,GAAG,gBAAgB,GAAG,mBAAmB,CAAC;IAC5G,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,wBAAwB;IACvC,UAAU,EAAE,OAAO,CAAC;IACpB,MAAM,EAAE,uBAAuB,EAAE,CAAC;CACnC;AAED,MAAM,WAAW,6BAA6B;IAC5C,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC9C,cAAc,EAAE,MAAM,EAAE,CAAC;CAC1B;AA2QD;;GAEG;AACH,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,4BAA4B,GACpC,OAAO,CAAC,2BAA2B,CAAC,CAyHtC;AAED;;GAEG;AACH,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,4BAA4B,GACpC,OAAO,CAAC,6BAA6B,CAAC,CA0DxC"}
@@ -304,11 +304,13 @@ export async function applyPrismaMigrations(options) {
304
304
  }
305
305
  const startTime = Date.now();
306
306
  try {
307
- // Execute the migration SQL using direct driver access
308
- await executeRawSql(options.dialect, sqlContent, {
309
- connectionUrl: options.connectionUrl,
310
- databasePath: options.databasePath,
311
- });
307
+ if (!options.markApplied) {
308
+ // Execute the migration SQL using direct driver access
309
+ await executeRawSql(options.dialect, sqlContent, {
310
+ connectionUrl: options.connectionUrl,
311
+ databasePath: options.databasePath,
312
+ });
313
+ }
312
314
  // Record the migration (still use Kysely for this since it's simple INSERT)
313
315
  await recordMigration(db, migrationsTable, migrationsSchema, options.dialect, folderName, checksum);
314
316
  result.applied.push({
@@ -13,6 +13,8 @@ export interface PullOptions {
13
13
  databasePath?: string;
14
14
  /** Output path for schema */
15
15
  outputPath: string;
16
+ /** Write the schema to outputPath (default: true) */
17
+ writeFile?: boolean;
16
18
  }
17
19
  export interface PullResult {
18
20
  outputPath: string;
@@ -1 +1 @@
1
- {"version":3,"file":"pull.d.ts","sourceRoot":"","sources":["../../src/schema/pull.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,EAAuB,KAAK,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAEnF,MAAM,WAAW,WAAW;IAC1B,uBAAuB;IACvB,OAAO,EAAE,aAAa,CAAC;IACvB,8BAA8B;IAC9B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gDAAgD;IAChD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,6BAA6B;IAC7B,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;CACpB;AAofD,wBAAsB,UAAU,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAwC1E"}
1
+ {"version":3,"file":"pull.d.ts","sourceRoot":"","sources":["../../src/schema/pull.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,EAAuB,KAAK,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAEnF,MAAM,WAAW,WAAW;IAC1B,uBAAuB;IACvB,OAAO,EAAE,aAAa,CAAC;IACvB,8BAA8B;IAC9B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gDAAgD;IAChD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,6BAA6B;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,qDAAqD;IACrD,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;CACpB;AA8lBD,wBAAsB,UAAU,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CA4C1E"}
@@ -52,6 +52,10 @@ function normalizeType(dataType) {
52
52
  const isArray = lower.endsWith("[]");
53
53
  const base = isArray ? lower.slice(0, -2) : lower;
54
54
  const normalized = base.replace(/\(.+\)/, "").trim();
55
+ if (normalized.includes("uuid") || normalized.includes("citext"))
56
+ return { type: "String", isArray };
57
+ if (normalized.includes("jsonb"))
58
+ return { type: "Json", isArray };
55
59
  if (normalized.includes("bigint"))
56
60
  return { type: "BigInt", isArray };
57
61
  if (normalized.includes("int"))
@@ -87,7 +91,7 @@ function buildDatasourceBlock(dialect) {
87
91
  ].join("\n");
88
92
  }
89
93
  function buildModelBlock(options) {
90
- const { table, foreignKeys, indexes, primaryKeys, allTables } = options;
94
+ const { table, foreignKeys, indexes, primaryKeys, allTables, columnDefaults } = options;
91
95
  const modelName = toPascalCase(table.name) || "Model";
92
96
  const fieldLines = [];
93
97
  // Get primary key columns for this table
@@ -123,6 +127,45 @@ function buildModelBlock(options) {
123
127
  }
124
128
  }
125
129
  }
130
+ const getDefaultExpr = (columnName) => columnDefaults.get(columnName) ?? null;
131
+ const buildDefaultAttribute = (defaultExpr, dataType) => {
132
+ if (!defaultExpr)
133
+ return null;
134
+ const normalized = defaultExpr.trim();
135
+ if (!normalized || normalized.toLowerCase() === "null")
136
+ return null;
137
+ const lower = normalized.toLowerCase();
138
+ if (lower.includes("nextval("))
139
+ return null;
140
+ if (lower === "current_timestamp" ||
141
+ lower === "current_timestamp()" ||
142
+ lower === "now()" ||
143
+ lower.includes("datetime('now") ||
144
+ lower.includes("now()")) {
145
+ return "@default(now())";
146
+ }
147
+ if (lower.includes("uuid_generate_v4()") || lower.includes("gen_random_uuid()") || lower === "uuid()") {
148
+ return "@default(uuid())";
149
+ }
150
+ if (lower === "true" || lower === "false") {
151
+ return `@default(${lower})`;
152
+ }
153
+ if ((lower === "0" || lower === "1") && dataType.toLowerCase().includes("bool")) {
154
+ return `@default(${lower === "1" ? "true" : "false"})`;
155
+ }
156
+ if (/^-?\d+(\.\d+)?$/.test(normalized)) {
157
+ return `@default(${normalized})`;
158
+ }
159
+ if ((normalized.startsWith("'") && normalized.endsWith("'")) ||
160
+ (normalized.startsWith("\"") && normalized.endsWith("\""))) {
161
+ const unquoted = normalized
162
+ .slice(1, -1)
163
+ .replace(/''/g, "'")
164
+ .replace(/\\"/g, "\"");
165
+ return `@default(${JSON.stringify(unquoted)})`;
166
+ }
167
+ return "@default(dbgenerated())";
168
+ };
126
169
  const sortedColumns = [...table.columns].sort((a, b) => a.name.localeCompare(b.name));
127
170
  for (const column of sortedColumns) {
128
171
  const fieldName = toCamelCase(column.name) || column.name;
@@ -146,7 +189,13 @@ function buildModelBlock(options) {
146
189
  modifiers.push(`@map("${column.name}")`);
147
190
  }
148
191
  if (column.hasDefaultValue && !modifiers.some((m) => m.includes("@default"))) {
149
- modifiers.push("@default(dbgenerated())");
192
+ const attr = buildDefaultAttribute(getDefaultExpr(column.name), column.dataType);
193
+ if (attr) {
194
+ modifiers.push(attr);
195
+ }
196
+ else {
197
+ modifiers.push("@default(dbgenerated())");
198
+ }
150
199
  }
151
200
  const typeSuffix = isArray ? "[]" : "";
152
201
  const modifierText = modifiers.length > 0 ? ` ${modifiers.join(" ")}` : "";
@@ -389,6 +438,51 @@ async function extractPrimaryKeys(db, dialect, tableNames) {
389
438
  }
390
439
  return primaryKeys;
391
440
  }
441
+ async function extractColumnDefaults(db, dialect, tableNames) {
442
+ const defaultsByTable = new Map();
443
+ const tableSet = new Set(tableNames);
444
+ const setDefault = (table, column, value) => {
445
+ if (!defaultsByTable.has(table)) {
446
+ defaultsByTable.set(table, new Map());
447
+ }
448
+ defaultsByTable.get(table).set(column, value);
449
+ };
450
+ if (dialect === "sqlite") {
451
+ for (const tableName of tableNames) {
452
+ const result = await sql `
453
+ PRAGMA table_info(${sql.raw(`"${tableName}"`)})
454
+ `.execute(db);
455
+ for (const row of result.rows) {
456
+ setDefault(tableName, row.name, row.dflt_value);
457
+ }
458
+ }
459
+ }
460
+ else if (dialect === "postgres") {
461
+ const result = await sql `
462
+ SELECT table_name, column_name, column_default
463
+ FROM information_schema.columns
464
+ WHERE table_schema = 'public'
465
+ `.execute(db);
466
+ for (const row of result.rows) {
467
+ if (!tableSet.has(row.table_name))
468
+ continue;
469
+ setDefault(row.table_name, row.column_name, row.column_default);
470
+ }
471
+ }
472
+ else if (dialect === "mysql") {
473
+ const result = await sql `
474
+ SELECT TABLE_NAME, COLUMN_NAME, COLUMN_DEFAULT
475
+ FROM information_schema.COLUMNS
476
+ WHERE TABLE_SCHEMA = DATABASE()
477
+ `.execute(db);
478
+ for (const row of result.rows) {
479
+ if (!tableSet.has(row.TABLE_NAME))
480
+ continue;
481
+ setDefault(row.TABLE_NAME, row.COLUMN_NAME, row.COLUMN_DEFAULT);
482
+ }
483
+ }
484
+ return defaultsByTable;
485
+ }
392
486
  export async function pullSchema(options) {
393
487
  const { db, destroy } = await createKyselyAdapter({
394
488
  dialect: options.dialect,
@@ -403,16 +497,20 @@ export async function pullSchema(options) {
403
497
  const foreignKeys = await extractForeignKeys(db, options.dialect);
404
498
  const indexes = await extractIndexes(db, options.dialect, tableNames);
405
499
  const primaryKeys = await extractPrimaryKeys(db, options.dialect, tableNames);
500
+ const columnDefaultsByTable = await extractColumnDefaults(db, options.dialect, tableNames);
406
501
  const blocks = filtered.map((table) => buildModelBlock({
407
502
  table,
408
503
  foreignKeys,
409
504
  indexes,
410
505
  primaryKeys,
411
506
  allTables,
507
+ columnDefaults: columnDefaultsByTable.get(table.name) ?? new Map(),
412
508
  }));
413
509
  const schema = [buildDatasourceBlock(options.dialect), ...blocks].join("\n\n");
414
- await fs.mkdir(path.dirname(options.outputPath), { recursive: true });
415
- await fs.writeFile(options.outputPath, schema.trimEnd() + "\n", "utf-8");
510
+ if (options.writeFile !== false) {
511
+ await fs.mkdir(path.dirname(options.outputPath), { recursive: true });
512
+ await fs.writeFile(options.outputPath, schema.trimEnd() + "\n", "utf-8");
513
+ }
416
514
  return {
417
515
  outputPath: options.outputPath,
418
516
  schema,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zenstack-kit",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "Drizzle-kit like CLI tooling for ZenStack schemas with Kysely support",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",