zenstack-kit 0.1.11 → 0.1.12

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
@@ -100,6 +100,16 @@ zenstack-kit migrate apply
100
100
 
101
101
  Migrations are tracked in the `_prisma_migrations` table, making them compatible with `prisma migrate deploy`.
102
102
 
103
+ ### Migration log and checksums
104
+
105
+ zenstack-kit maintains `meta/_migration_log` alongside your migrations. It stores a SHA256 checksum for each `migration.sql` so integrity checks work even when you never apply migrations locally.
106
+
107
+ Flow:
108
+ - On `migrate create`, a checksum entry is appended to the log.
109
+ - On `migrate apply`, applied migrations are verified against the database and log.
110
+ - For pending migrations, the log is auto-updated from disk by default (so edits or empty migrations do not require manual rehash).
111
+ - Use `--strict` (or `ZENSTACK_MIGRATION_STRICT=1`) to disable auto-rehash and fail on any pending mismatch (recommended for CI).
112
+
103
113
  ## CLI Commands
104
114
 
105
115
  Run `zenstack-kit` without arguments to launch the interactive menu, or run commands directly. For CI or non-TTY environments, pass `--no-ui` to bypass Ink.
@@ -142,6 +152,8 @@ Options:
142
152
  - `-s, --schema <path>` - Path to ZenStack schema
143
153
  - `-m, --migrations <path>` - Migrations directory
144
154
  - `--dialect <dialect>` - Database dialect (`sqlite`, `postgres`, `mysql`)
155
+ - `--empty` - Create an empty migration (no schema diff)
156
+ - `--update-snapshot` - Update snapshot when used with `--empty`
145
157
  - `-c, --config <path>` - Path to zenstack-kit config file
146
158
 
147
159
  ### `zenstack-kit migrate apply`
@@ -158,13 +170,15 @@ Options:
158
170
  - `--url <url>` - Database connection URL (overrides config)
159
171
  - `--table <name>` - Migrations table name (default: `_prisma_migrations`)
160
172
  - `--db-schema <name>` - Database schema for migrations table (PostgreSQL only, default: `public`)
173
+ - `--migration <name>` - Apply a single migration (must be the next pending one)
161
174
  - `--preview` - Preview pending migrations without applying
162
175
  - `--mark-applied` - Mark pending migrations as applied without running SQL
176
+ - `--strict` - Enforce pending migration log checksums (no auto-rehash)
163
177
  - `-c, --config <path>` - Path to zenstack-kit config file
164
178
 
165
179
  ### `zenstack-kit migrate rehash`
166
180
 
167
- Rebuild the migration log checksums from the `migration.sql` files (useful after manual edits).
181
+ Rebuild the migration log checksums from the `migration.sql` files (useful after manual edits or when strict mode fails).
168
182
 
169
183
  ```bash
170
184
  zenstack-kit migrate rehash
@@ -1 +1 @@
1
- {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../src/cli/app.tsx"],"names":[],"mappings":";AAEA;;;;;;;;;GASG;AAgYH,wBAAgB,MAAM,SA4BrB"}
1
+ {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../src/cli/app.tsx"],"names":[],"mappings":";AAEA;;;;;;;;;GASG;AA4YH,wBAAgB,MAAM,SA4BrB"}
package/dist/cli/app.js CHANGED
@@ -89,6 +89,15 @@ function parseArgs() {
89
89
  else if (arg === "--mark-applied") {
90
90
  options.markApplied = true;
91
91
  }
92
+ else if (arg === "--strict") {
93
+ options.strict = true;
94
+ }
95
+ else if (arg === "--empty") {
96
+ options.empty = true;
97
+ }
98
+ else if (arg === "--update-snapshot") {
99
+ options.updateSnapshot = true;
100
+ }
92
101
  else if (arg === "--force" || arg === "-f") {
93
102
  options.force = true;
94
103
  }
@@ -116,7 +125,7 @@ function Status({ type, message }) {
116
125
  }
117
126
  // Help display component
118
127
  function HelpDisplay() {
119
- 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: "--migration <name> Target a single migration (rehash only)" }), _jsx(Text, { dimColor: true, children: "--no-ui Disable Ink UI (useful for CI/non-TTY)" }), _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" })] })] }));
128
+ 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: "--migration <name> Target a single migration (apply/rehash)" }), _jsx(Text, { dimColor: true, children: "--no-ui Disable Ink UI (useful for CI/non-TTY)" }), _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: "--empty Create an empty migration (no schema diff)" }), _jsx(Text, { dimColor: true, children: "--update-snapshot Update snapshot when used with --empty" }), _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: "--strict Enforce pending migration log checksums (no auto-rehash)" }), _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" })] })] }));
120
129
  }
121
130
  function CliApp({ initialCommand, options }) {
122
131
  const { exit } = useApp();
@@ -255,12 +264,15 @@ function printHelpText() {
255
264
  " -n, --name <name> Migration name",
256
265
  " --dialect <dialect> Database dialect (sqlite, postgres, mysql)",
257
266
  " --url <url> Database connection URL",
258
- " --migration <name> Target a single migration (rehash only)",
267
+ " --migration <name> Target a single migration (apply/rehash)",
259
268
  " --no-ui Disable Ink UI (useful for CI/non-TTY)",
260
269
  " --create-initial Create initial migration (skip prompt)",
261
270
  " --baseline Create baseline only (skip prompt)",
271
+ " --empty Create an empty migration (no schema diff)",
272
+ " --update-snapshot Update snapshot when used with --empty",
262
273
  " --preview Preview pending migrations without applying",
263
274
  " --mark-applied Mark pending migrations as applied without running SQL",
275
+ " --strict Enforce pending migration log checksums (no auto-rehash)",
264
276
  " -f, --force Force operation without confirmation",
265
277
  " -c, --config <path> Path to zenstack-kit config file",
266
278
  ];
@@ -24,6 +24,9 @@ export interface CommandOptions {
24
24
  markApplied?: boolean;
25
25
  force?: boolean;
26
26
  config?: string;
27
+ empty?: boolean;
28
+ updateSnapshot?: boolean;
29
+ strict?: boolean;
27
30
  }
28
31
  export interface CommandContext {
29
32
  cwd: string;
@@ -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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"}
@@ -10,7 +10,7 @@ import * as os from "os";
10
10
  import { execFileSync } from "child_process";
11
11
  import { loadConfig } from "../config/loader.js";
12
12
  import { pullSchema } from "../schema/pull.js";
13
- import { createPrismaMigration, applyPrismaMigrations, previewPrismaMigrations, hasPrismaSchemaChanges, hasSnapshot, scanMigrationFolders, readMigrationLog, writeMigrationLog, getMigrationLogPath, calculateChecksum, initializeSnapshot, createInitialMigration, detectPotentialRenames, } from "../migrations/prisma.js";
13
+ import { createPrismaMigration, createEmptyMigration, applyPrismaMigrations, previewPrismaMigrations, hasPrismaSchemaChanges, hasSnapshot, scanMigrationFolders, readMigrationLog, writeMigrationLog, getMigrationLogPath, calculateChecksum, initializeSnapshot, createInitialMigration, detectPotentialRenames, } from "../migrations/prisma.js";
14
14
  export class CommandError extends Error {
15
15
  constructor(message) {
16
16
  super(message);
@@ -64,17 +64,22 @@ export async function runMigrateGenerate(ctx) {
64
64
  if (!snapshotExists) {
65
65
  throw new CommandError("No snapshot found. Run 'zenstack-kit init' first.");
66
66
  }
67
+ const isEmpty = Boolean(ctx.options.empty);
67
68
  ctx.log("info", "Generating migration...");
68
- const hasChanges = await hasPrismaSchemaChanges({
69
- schemaPath,
70
- outputPath,
71
- });
72
- if (!hasChanges) {
73
- ctx.log("warning", "No schema changes detected");
74
- return;
69
+ if (!isEmpty) {
70
+ const hasChanges = await hasPrismaSchemaChanges({
71
+ schemaPath,
72
+ outputPath,
73
+ });
74
+ if (!hasChanges) {
75
+ ctx.log("warning", "No schema changes detected");
76
+ return;
77
+ }
75
78
  }
76
79
  // Detect potential renames and prompt user to disambiguate
77
- const potentialRenames = await detectPotentialRenames({ schemaPath, outputPath });
80
+ const potentialRenames = isEmpty
81
+ ? { tables: [], columns: [] }
82
+ : await detectPotentialRenames({ schemaPath, outputPath });
78
83
  const renameTables = [];
79
84
  const renameColumns = [];
80
85
  // Prompt for table renames
@@ -112,20 +117,30 @@ export async function runMigrateGenerate(ctx) {
112
117
  return;
113
118
  }
114
119
  }
115
- const migration = await createPrismaMigration({
116
- name,
117
- schemaPath,
118
- outputPath,
119
- dialect,
120
- renameTables: renameTables.length > 0 ? renameTables : undefined,
121
- renameColumns: renameColumns.length > 0 ? renameColumns : undefined,
122
- });
120
+ const migration = isEmpty
121
+ ? await createEmptyMigration({
122
+ name,
123
+ schemaPath,
124
+ outputPath,
125
+ updateSnapshot: ctx.options.updateSnapshot,
126
+ })
127
+ : await createPrismaMigration({
128
+ name,
129
+ schemaPath,
130
+ outputPath,
131
+ dialect,
132
+ renameTables: renameTables.length > 0 ? renameTables : undefined,
133
+ renameColumns: renameColumns.length > 0 ? renameColumns : undefined,
134
+ });
123
135
  if (!migration) {
124
136
  ctx.log("warning", "No schema changes detected");
125
137
  return;
126
138
  }
127
139
  ctx.log("success", `Migration created: ${migration.folderName}/migration.sql`);
128
140
  ctx.log("info", `Path: ${migration.folderPath}`);
141
+ if (isEmpty && ctx.options.updateSnapshot) {
142
+ ctx.log("info", "Snapshot updated to current schema");
143
+ }
129
144
  ctx.log("info", "Next: run 'zenstack-kit migrate apply' (or --preview to review SQL).");
130
145
  }
131
146
  /**
@@ -191,6 +206,10 @@ export async function runMigrateApply(ctx) {
191
206
  else {
192
207
  ctx.log("info", "Applying migrations...");
193
208
  }
209
+ const strictEnv = process.env.ZENSTACK_MIGRATION_STRICT;
210
+ const strict = ctx.options.strict === true ||
211
+ strictEnv === "1" ||
212
+ (strictEnv ? strictEnv.toLowerCase() === "true" : false);
194
213
  const result = await applyPrismaMigrations({
195
214
  migrationsFolder: outputPath,
196
215
  dialect,
@@ -199,6 +218,8 @@ export async function runMigrateApply(ctx) {
199
218
  migrationsTable,
200
219
  migrationsSchema,
201
220
  markApplied: ctx.options.markApplied,
221
+ strict,
222
+ targetMigration: ctx.options.migration,
202
223
  });
203
224
  // Handle coherence errors
204
225
  if (result.coherenceErrors && result.coherenceErrors.length > 0) {
package/dist/index.d.ts CHANGED
@@ -10,7 +10,7 @@ export { introspectSchema, type SchemaInfo, type ModelInfo, type FieldInfo } fro
10
10
  export { createMigration, getSchemaDiff, hasSchemaChanges, initSnapshot, type MigrationOptions, type Migration, type InitSnapshotOptions, type InitSnapshotResult, } from "./migrations/diff.js";
11
11
  export { applyMigrations, type ApplyMigrationsOptions } from "./migrations/apply.js";
12
12
  export { setPromptProvider, type PromptProvider } from "./cli/prompt-provider.js";
13
- export { createPrismaMigration, applyPrismaMigrations, previewPrismaMigrations, hasPrismaSchemaChanges, createInitialMigration, initializeSnapshot, hasSnapshot, scanMigrationFolders, readMigrationLog, writeMigrationLog, appendToMigrationLog, getMigrationLogPath, calculateChecksum, detectPotentialRenames, type PrismaMigrationOptions, type PrismaMigration, type ApplyPrismaMigrationsOptions, type ApplyPrismaMigrationsResult, type PreviewPrismaMigrationsResult, type CreateInitialMigrationOptions, type MigrationLogEntry, type PotentialTableRename, type PotentialColumnRename, type PotentialRenames, } from "./migrations/prisma.js";
13
+ export { createPrismaMigration, createEmptyMigration, applyPrismaMigrations, previewPrismaMigrations, hasPrismaSchemaChanges, createInitialMigration, initializeSnapshot, hasSnapshot, scanMigrationFolders, readMigrationLog, writeMigrationLog, appendToMigrationLog, getMigrationLogPath, calculateChecksum, detectPotentialRenames, type PrismaMigrationOptions, type PrismaMigration, type CreateEmptyMigrationOptions, type ApplyPrismaMigrationsOptions, type ApplyPrismaMigrationsResult, type PreviewPrismaMigrationsResult, type CreateInitialMigrationOptions, type MigrationLogEntry, type PotentialTableRename, type PotentialColumnRename, type PotentialRenames, } from "./migrations/prisma.js";
14
14
  export { migrate, type MigrateOptions, type MigrateResult } from "./migrate.js";
15
15
  export { defineConfig, type ZenStackKitConfig } from "./config/index.js";
16
16
  export { type RenameChoice } from "./cli/prompts.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,gBAAgB,EAAE,KAAK,UAAU,EAAE,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAC3G,OAAO,EACL,eAAe,EACf,aAAa,EACb,gBAAgB,EAChB,YAAY,EACZ,KAAK,gBAAgB,EACrB,KAAK,SAAS,EACd,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,GACxB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,eAAe,EAAE,KAAK,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AACrF,OAAO,EAAE,iBAAiB,EAAE,KAAK,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAGlF,OAAO,EACL,qBAAqB,EACrB,qBAAqB,EACrB,uBAAuB,EACvB,sBAAsB,EACtB,sBAAsB,EACtB,kBAAkB,EAClB,WAAW,EACX,oBAAoB,EACpB,gBAAgB,EAChB,iBAAiB,EACjB,oBAAoB,EACpB,mBAAmB,EACnB,iBAAiB,EACjB,sBAAsB,EACtB,KAAK,sBAAsB,EAC3B,KAAK,eAAe,EACpB,KAAK,4BAA4B,EACjC,KAAK,2BAA2B,EAChC,KAAK,6BAA6B,EAClC,KAAK,6BAA6B,EAClC,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,GACtB,MAAM,wBAAwB,CAAC;AAGhC,OAAO,EAAE,OAAO,EAAE,KAAK,cAAc,EAAE,KAAK,aAAa,EAAE,MAAM,cAAc,CAAC;AAGhF,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACzE,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAGrD,OAAO,EACL,mBAAmB,EACnB,KAAK,aAAa,EAClB,KAAK,aAAa,GACnB,MAAM,yBAAyB,CAAC;AAGjC,OAAO,EAAE,UAAU,EAAE,KAAK,WAAW,EAAE,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,gBAAgB,EAAE,KAAK,UAAU,EAAE,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAC3G,OAAO,EACL,eAAe,EACf,aAAa,EACb,gBAAgB,EAChB,YAAY,EACZ,KAAK,gBAAgB,EACrB,KAAK,SAAS,EACd,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,GACxB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,eAAe,EAAE,KAAK,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AACrF,OAAO,EAAE,iBAAiB,EAAE,KAAK,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAGlF,OAAO,EACL,qBAAqB,EACrB,oBAAoB,EACpB,qBAAqB,EACrB,uBAAuB,EACvB,sBAAsB,EACtB,sBAAsB,EACtB,kBAAkB,EAClB,WAAW,EACX,oBAAoB,EACpB,gBAAgB,EAChB,iBAAiB,EACjB,oBAAoB,EACpB,mBAAmB,EACnB,iBAAiB,EACjB,sBAAsB,EACtB,KAAK,sBAAsB,EAC3B,KAAK,eAAe,EACpB,KAAK,2BAA2B,EAChC,KAAK,4BAA4B,EACjC,KAAK,2BAA2B,EAChC,KAAK,6BAA6B,EAClC,KAAK,6BAA6B,EAClC,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,GACtB,MAAM,wBAAwB,CAAC;AAGhC,OAAO,EAAE,OAAO,EAAE,KAAK,cAAc,EAAE,KAAK,aAAa,EAAE,MAAM,cAAc,CAAC;AAGhF,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACzE,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAGrD,OAAO,EACL,mBAAmB,EACnB,KAAK,aAAa,EAClB,KAAK,aAAa,GACnB,MAAM,yBAAyB,CAAC;AAGjC,OAAO,EAAE,UAAU,EAAE,KAAK,WAAW,EAAE,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC"}
package/dist/index.js CHANGED
@@ -12,7 +12,7 @@ export { createMigration, getSchemaDiff, hasSchemaChanges, initSnapshot, } from
12
12
  export { applyMigrations } from "./migrations/apply.js";
13
13
  export { setPromptProvider } from "./cli/prompt-provider.js";
14
14
  // Prisma-compatible migrations (default)
15
- export { createPrismaMigration, applyPrismaMigrations, previewPrismaMigrations, hasPrismaSchemaChanges, createInitialMigration, initializeSnapshot, hasSnapshot, scanMigrationFolders, readMigrationLog, writeMigrationLog, appendToMigrationLog, getMigrationLogPath, calculateChecksum, detectPotentialRenames, } from "./migrations/prisma.js";
15
+ export { createPrismaMigration, createEmptyMigration, applyPrismaMigrations, previewPrismaMigrations, hasPrismaSchemaChanges, createInitialMigration, initializeSnapshot, hasSnapshot, scanMigrationFolders, readMigrationLog, writeMigrationLog, appendToMigrationLog, getMigrationLogPath, calculateChecksum, detectPotentialRenames, } from "./migrations/prisma.js";
16
16
  // High-level programmatic API
17
17
  export { migrate } from "./migrate.js";
18
18
  // CLI utilities
package/dist/migrate.d.ts CHANGED
@@ -61,6 +61,15 @@ export interface MigrateOptions {
61
61
  * @default false
62
62
  */
63
63
  preview?: boolean;
64
+ /**
65
+ * Apply a single migration by name (must be the next pending one).
66
+ */
67
+ migration?: string;
68
+ /**
69
+ * Enforce pending migration log checksums (no auto-rehash).
70
+ * @default false
71
+ */
72
+ strict?: boolean;
64
73
  /**
65
74
  * Current working directory for config resolution.
66
75
  * @default process.cwd()
@@ -1 +1 @@
1
- {"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../src/migrate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAGH,OAAO,EAGL,KAAK,2BAA2B,EAChC,KAAK,6BAA6B,EACnC,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAG7D,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B;;;OAGG;IACH,OAAO,CAAC,EAAE,aAAa,CAAC;IAExB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,MAAM,aAAa,GACrB,CAAC,2BAA2B,GAAG;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC,GACjD,CAAC,6BAA6B,GAAG;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC,CAAC;AAE1D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAsB,OAAO,CAAC,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,aAAa,CAAC,CAsFlF"}
1
+ {"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../src/migrate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAGH,OAAO,EAGL,KAAK,2BAA2B,EAChC,KAAK,6BAA6B,EACnC,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAG7D,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B;;;OAGG;IACH,OAAO,CAAC,EAAE,aAAa,CAAC;IAExB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IAEjB;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,MAAM,aAAa,GACrB,CAAC,2BAA2B,GAAG;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC,GACjD,CAAC,6BAA6B,GAAG;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC,CAAC;AAE1D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAsB,OAAO,CAAC,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,aAAa,CAAC,CAwFlF"}
package/dist/migrate.js CHANGED
@@ -119,6 +119,8 @@ export async function migrate(options = {}) {
119
119
  databasePath,
120
120
  migrationsTable,
121
121
  migrationsSchema,
122
+ strict: options.strict,
123
+ targetMigration: options.migration,
122
124
  });
123
125
  if (result.failed) {
124
126
  throw new Error(`Migration failed: ${result.failed.migrationName} - ${result.failed.error}`);
@@ -14,6 +14,10 @@ export interface ApplyPrismaMigrationsOptions {
14
14
  migrationsSchema?: string;
15
15
  /** Mark migrations as applied without executing SQL */
16
16
  markApplied?: boolean;
17
+ /** Enforce checksum/log consistency for pending migrations (no auto-rehash) */
18
+ strict?: boolean;
19
+ /** Apply a single migration by name */
20
+ targetMigration?: string;
17
21
  }
18
22
  export interface ApplyPrismaMigrationsResult {
19
23
  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;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,CA0HtC;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;IACtB,+EAA+E;IAC/E,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,uCAAuC;IACvC,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;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,CAoLtC;AAED;;GAEG;AACH,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,4BAA4B,GACpC,OAAO,CAAC,6BAA6B,CAAC,CA0DxC"}
@@ -3,7 +3,7 @@ import * as path from "path";
3
3
  import * as crypto from "crypto";
4
4
  import { sql } from "kysely";
5
5
  import { createKyselyAdapter } from "../../sql/kysely-adapter.js";
6
- import { calculateChecksum, readMigrationLog } from "./log.js";
6
+ import { calculateChecksum, readMigrationLog, writeMigrationLog } from "./log.js";
7
7
  /**
8
8
  * Ensure _prisma_migrations table exists
9
9
  */
@@ -276,7 +276,34 @@ export async function applyPrismaMigrations(options) {
276
276
  applied: [],
277
277
  alreadyApplied: [],
278
278
  };
279
- for (const folderName of migrationFoldersWithSql) {
279
+ const logIndex = new Map(migrationLog.map((entry, index) => [entry.name, index]));
280
+ let logUpdated = false;
281
+ let migrationFoldersToApply = migrationFoldersWithSql;
282
+ if (options.targetMigration) {
283
+ const target = options.targetMigration;
284
+ const pendingMigrations = migrationFoldersWithSql.filter((name) => !appliedMigrations.has(name));
285
+ if (appliedMigrations.has(target)) {
286
+ result.alreadyApplied.push(target);
287
+ return result;
288
+ }
289
+ if (!migrationFoldersWithSql.includes(target)) {
290
+ result.failed = {
291
+ migrationName: target,
292
+ error: `Migration ${target} not found in migrations folder.`,
293
+ };
294
+ return result;
295
+ }
296
+ if (pendingMigrations[0] !== target) {
297
+ result.failed = {
298
+ migrationName: target,
299
+ error: `Migration ${target} is not the next pending migration.\n` +
300
+ `Apply pending migrations in order or omit --migration.`,
301
+ };
302
+ return result;
303
+ }
304
+ migrationFoldersToApply = [target];
305
+ }
306
+ for (const folderName of migrationFoldersToApply) {
280
307
  if (appliedMigrations.has(folderName)) {
281
308
  result.alreadyApplied.push(folderName);
282
309
  continue;
@@ -290,18 +317,35 @@ export async function applyPrismaMigrations(options) {
290
317
  continue; // Skip if no migration.sql
291
318
  }
292
319
  const checksum = calculateChecksum(sqlContent);
293
- // Verify checksum against migration log (migrationLog already read above)
294
- const logEntry = migrationLog.find((m) => m.name === folderName);
295
- if (logEntry && logEntry.checksum !== checksum) {
296
- result.failed = {
297
- migrationName: folderName,
298
- error: `Checksum mismatch for migration ${folderName}.\n` +
299
- `Expected: ${logEntry.checksum}\n` +
300
- `Found: ${checksum}\n` +
301
- `The migration file may have been modified after generation.\n` +
302
- `If you intended this, run 'zenstack-kit migrate rehash' to rebuild log checksums.`,
303
- };
304
- break;
320
+ // Verify or update checksum against migration log (pending migrations only)
321
+ const logEntryIndex = logIndex.get(folderName);
322
+ if (logEntryIndex === undefined) {
323
+ if (options.strict) {
324
+ result.failed = {
325
+ migrationName: folderName,
326
+ error: `Migration ${folderName} is missing from the migration log.\n` +
327
+ `Run 'zenstack-kit migrate rehash' to rebuild log checksums or disable strict mode.`,
328
+ };
329
+ break;
330
+ }
331
+ migrationLog.push({ name: folderName, checksum });
332
+ logIndex.set(folderName, migrationLog.length - 1);
333
+ logUpdated = true;
334
+ }
335
+ else if (migrationLog[logEntryIndex].checksum !== checksum) {
336
+ if (options.strict) {
337
+ result.failed = {
338
+ migrationName: folderName,
339
+ error: `Checksum mismatch for migration ${folderName}.\n` +
340
+ `Expected: ${migrationLog[logEntryIndex].checksum}\n` +
341
+ `Found: ${checksum}\n` +
342
+ `The migration file may have been modified after generation.\n` +
343
+ `If you intended this, run 'zenstack-kit migrate rehash' to rebuild log checksums.`,
344
+ };
345
+ break;
346
+ }
347
+ migrationLog[logEntryIndex] = { name: folderName, checksum };
348
+ logUpdated = true;
305
349
  }
306
350
  const startTime = Date.now();
307
351
  try {
@@ -327,6 +371,11 @@ export async function applyPrismaMigrations(options) {
327
371
  break; // Stop on first failure
328
372
  }
329
373
  }
374
+ if (logUpdated) {
375
+ migrationLog.sort((a, b) => a.name.localeCompare(b.name));
376
+ await writeMigrationLog(options.migrationsFolder, migrationLog);
377
+ logUpdated = false;
378
+ }
330
379
  return result;
331
380
  }
332
381
  finally {
@@ -40,10 +40,24 @@ export interface CreateInitialMigrationOptions {
40
40
  /** Database dialect for SQL generation */
41
41
  dialect: KyselyDialect;
42
42
  }
43
+ export interface CreateEmptyMigrationOptions {
44
+ /** Migration name */
45
+ name: string;
46
+ /** Path to ZenStack schema file */
47
+ schemaPath: string;
48
+ /** Output directory for migration files */
49
+ outputPath: string;
50
+ /** Update snapshot to current schema */
51
+ updateSnapshot?: boolean;
52
+ }
43
53
  /**
44
54
  * Generate timestamp string for migration folder name
45
55
  */
46
56
  export declare function generateTimestamp(): string;
57
+ /**
58
+ * Create a Prisma-compatible empty migration
59
+ */
60
+ export declare function createEmptyMigration(options: CreateEmptyMigrationOptions): Promise<PrismaMigration>;
47
61
  /**
48
62
  * Create a Prisma-compatible migration
49
63
  */
@@ -1 +1 @@
1
- {"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../../src/migrations/prisma/create.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAMjE,MAAM,WAAW,sBAAsB;IACrC,qBAAqB;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,mCAAmC;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,2CAA2C;IAC3C,UAAU,EAAE,MAAM,CAAC;IACnB,0CAA0C;IAC1C,OAAO,EAAE,aAAa,CAAC;IACvB,4BAA4B;IAC5B,YAAY,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnD,6BAA6B;IAC7B,aAAa,CAAC,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACpE;AAED,MAAM,WAAW,eAAe;IAC9B,6CAA6C;IAC7C,UAAU,EAAE,MAAM,CAAC;IACnB,oCAAoC;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,kBAAkB;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,gBAAgB;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,6BAA6B;IAC5C,uCAAuC;IACvC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mCAAmC;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,2CAA2C;IAC3C,UAAU,EAAE,MAAM,CAAC;IACnB,0CAA0C;IAC1C,OAAO,EAAE,aAAa,CAAC;CACxB;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAU1C;AAED;;GAEG;AACH,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAiDjC;AAED;;;GAGG;AACH,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,eAAe,CAAC,CAwC1B;AAED;;GAEG;AACH,wBAAsB,sBAAsB,CAAC,OAAO,EAAE;IACpD,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB,GAAG,OAAO,CAAC,OAAO,CAAC,CAqBnB"}
1
+ {"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../../src/migrations/prisma/create.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAMjE,MAAM,WAAW,sBAAsB;IACrC,qBAAqB;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,mCAAmC;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,2CAA2C;IAC3C,UAAU,EAAE,MAAM,CAAC;IACnB,0CAA0C;IAC1C,OAAO,EAAE,aAAa,CAAC;IACvB,4BAA4B;IAC5B,YAAY,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnD,6BAA6B;IAC7B,aAAa,CAAC,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACpE;AAED,MAAM,WAAW,eAAe;IAC9B,6CAA6C;IAC7C,UAAU,EAAE,MAAM,CAAC;IACnB,oCAAoC;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,kBAAkB;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,gBAAgB;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,6BAA6B;IAC5C,uCAAuC;IACvC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mCAAmC;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,2CAA2C;IAC3C,UAAU,EAAE,MAAM,CAAC;IACnB,0CAA0C;IAC1C,OAAO,EAAE,aAAa,CAAC;CACxB;AAED,MAAM,WAAW,2BAA2B;IAC1C,qBAAqB;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,mCAAmC;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,2CAA2C;IAC3C,UAAU,EAAE,MAAM,CAAC;IACnB,wCAAwC;IACxC,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAU1C;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,2BAA2B,GACnC,OAAO,CAAC,eAAe,CAAC,CAgC1B;AAED;;GAEG;AACH,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAiDjC;AAED;;;GAGG;AACH,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,eAAe,CAAC,CAwC1B;AAED;;GAEG;AACH,wBAAsB,sBAAsB,CAAC,OAAO,EAAE;IACpD,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB,GAAG,OAAO,CAAC,OAAO,CAAC,CAqBnB"}
@@ -18,6 +18,37 @@ export function generateTimestamp() {
18
18
  String(now.getSeconds()).padStart(2, "0"),
19
19
  ].join("");
20
20
  }
21
+ /**
22
+ * Create a Prisma-compatible empty migration
23
+ */
24
+ export async function createEmptyMigration(options) {
25
+ const timestamp = Date.now();
26
+ const timestampStr = generateTimestamp();
27
+ const safeName = options.name.replace(/[^a-z0-9]/gi, "_").toLowerCase();
28
+ const folderName = `${timestampStr}_${safeName}`;
29
+ const folderPath = path.join(options.outputPath, folderName);
30
+ const sqlContent = [
31
+ `-- Migration: ${options.name}`,
32
+ `-- Generated at: ${new Date(timestamp).toISOString()}`,
33
+ "",
34
+ "",
35
+ ].join("\n");
36
+ await fs.mkdir(folderPath, { recursive: true });
37
+ await fs.writeFile(path.join(folderPath, "migration.sql"), sqlContent, "utf-8");
38
+ if (options.updateSnapshot) {
39
+ const currentSchema = await generateSchemaSnapshot(options.schemaPath);
40
+ const { snapshotPath } = getSnapshotPaths(options.outputPath);
41
+ await writeSnapshot(snapshotPath, currentSchema);
42
+ }
43
+ const checksum = calculateChecksum(sqlContent);
44
+ await appendToMigrationLog(options.outputPath, { name: folderName, checksum });
45
+ return {
46
+ folderName,
47
+ folderPath,
48
+ sql: sqlContent,
49
+ timestamp,
50
+ };
51
+ }
21
52
  /**
22
53
  * Create a Prisma-compatible migration
23
54
  */
@@ -1,4 +1,4 @@
1
- export { createPrismaMigration, createInitialMigration, hasPrismaSchemaChanges, type PrismaMigrationOptions, type PrismaMigration, type CreateInitialMigrationOptions, } from "./prisma/create.js";
1
+ export { createEmptyMigration, createPrismaMigration, createInitialMigration, hasPrismaSchemaChanges, type PrismaMigrationOptions, type PrismaMigration, type CreateInitialMigrationOptions, type CreateEmptyMigrationOptions, } from "./prisma/create.js";
2
2
  export { applyPrismaMigrations, previewPrismaMigrations, type ApplyPrismaMigrationsOptions, type ApplyPrismaMigrationsResult, type PreviewPrismaMigrationsResult, type MigrationCoherenceError, type MigrationCoherenceResult, } from "./prisma/apply.js";
3
3
  export { readMigrationLog, writeMigrationLog, appendToMigrationLog, scanMigrationFolders, getMigrationLogPath, calculateChecksum, type MigrationLogEntry, } from "./prisma/log.js";
4
4
  export { initializeSnapshot, hasSnapshot, getSnapshotPaths, writeSnapshot, } from "./prisma/snapshot.js";
@@ -1 +1 @@
1
- {"version":3,"file":"prisma.d.ts","sourceRoot":"","sources":["../../src/migrations/prisma.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qBAAqB,EACrB,sBAAsB,EACtB,sBAAsB,EACtB,KAAK,sBAAsB,EAC3B,KAAK,eAAe,EACpB,KAAK,6BAA6B,GACnC,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,qBAAqB,EACrB,uBAAuB,EACvB,KAAK,4BAA4B,EACjC,KAAK,2BAA2B,EAChC,KAAK,6BAA6B,EAClC,KAAK,uBAAuB,EAC5B,KAAK,wBAAwB,GAC9B,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,oBAAoB,EACpB,oBAAoB,EACpB,mBAAmB,EACnB,iBAAiB,EACjB,KAAK,iBAAiB,GACvB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACL,kBAAkB,EAClB,WAAW,EACX,gBAAgB,EAChB,aAAa,GACd,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EACL,sBAAsB,EACtB,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,GACtB,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"prisma.d.ts","sourceRoot":"","sources":["../../src/migrations/prisma.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,oBAAoB,EACpB,qBAAqB,EACrB,sBAAsB,EACtB,sBAAsB,EACtB,KAAK,sBAAsB,EAC3B,KAAK,eAAe,EACpB,KAAK,6BAA6B,EAClC,KAAK,2BAA2B,GACjC,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,qBAAqB,EACrB,uBAAuB,EACvB,KAAK,4BAA4B,EACjC,KAAK,2BAA2B,EAChC,KAAK,6BAA6B,EAClC,KAAK,uBAAuB,EAC5B,KAAK,wBAAwB,GAC9B,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,oBAAoB,EACpB,oBAAoB,EACpB,mBAAmB,EACnB,iBAAiB,EACjB,KAAK,iBAAiB,GACvB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACL,kBAAkB,EAClB,WAAW,EACX,gBAAgB,EAChB,aAAa,GACd,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EACL,sBAAsB,EACtB,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,GACtB,MAAM,oBAAoB,CAAC"}
@@ -1,4 +1,4 @@
1
- export { createPrismaMigration, createInitialMigration, hasPrismaSchemaChanges, } from "./prisma/create.js";
1
+ export { createEmptyMigration, createPrismaMigration, createInitialMigration, hasPrismaSchemaChanges, } from "./prisma/create.js";
2
2
  export { applyPrismaMigrations, previewPrismaMigrations, } from "./prisma/apply.js";
3
3
  export { readMigrationLog, writeMigrationLog, appendToMigrationLog, scanMigrationFolders, getMigrationLogPath, calculateChecksum, } from "./prisma/log.js";
4
4
  export { initializeSnapshot, hasSnapshot, getSnapshotPaths, writeSnapshot, } from "./prisma/snapshot.js";
@@ -46,7 +46,7 @@ async function parseZModelFile(schemaPath) {
46
46
  }
47
47
  models.push({
48
48
  name: modelName,
49
- tableName: modelName.toLowerCase(),
49
+ tableName: modelName,
50
50
  fields,
51
51
  });
52
52
  }
@@ -73,7 +73,7 @@ function getDefaultValue(field) {
73
73
  function getTableName(model) {
74
74
  const mapAttr = getAttribute(model, "@@map");
75
75
  const mapped = getAttributeStringArg(mapAttr, ["name", "map"]);
76
- return mapped ?? model.name.toLowerCase();
76
+ return mapped ?? model.name;
77
77
  }
78
78
  function getColumnName(field) {
79
79
  const mapAttr = getAttribute(field, "@map");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zenstack-kit",
3
- "version": "0.1.11",
3
+ "version": "0.1.12",
4
4
  "description": "Drizzle-kit like CLI tooling for ZenStack schemas with Kysely support",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -73,5 +73,10 @@
73
73
  "pg": "^8.13.1",
74
74
  "typescript": "^5.9.3",
75
75
  "vitest": "^4.0.16"
76
+ },
77
+ "pnpm": {
78
+ "onlyBuiltDependencies": [
79
+ "better-sqlite3"
80
+ ]
76
81
  }
77
82
  }