zenstack-kit 0.1.5 → 0.1.8
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 +21 -6
- package/dist/cli/app.d.ts +1 -0
- package/dist/cli/app.d.ts.map +1 -1
- package/dist/cli/app.js +18 -3
- package/dist/cli/commands.d.ts +6 -0
- package/dist/cli/commands.d.ts.map +1 -1
- package/dist/cli/commands.js +128 -5
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +1 -0
- package/dist/cli/prompts.d.ts.map +1 -1
- package/dist/cli/prompts.js +1 -3
- package/dist/migrations/prisma/apply.d.ts +2 -0
- package/dist/migrations/prisma/apply.d.ts.map +1 -1
- package/dist/migrations/prisma/apply.js +9 -6
- package/dist/schema/pull.d.ts +2 -0
- package/dist/schema/pull.d.ts.map +1 -1
- package/dist/schema/pull.js +102 -4
- package/package.json +1 -1
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
144
|
+
### `zenstack-kit migrate apply`
|
|
145
145
|
|
|
146
146
|
Apply pending migrations to the database.
|
|
147
147
|
|
|
148
148
|
```bash
|
|
149
|
-
zenstack-kit migrate
|
|
149
|
+
zenstack-kit migrate apply
|
|
150
150
|
```
|
|
151
151
|
|
|
152
152
|
Options:
|
|
@@ -156,6 +156,20 @@ 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
|
|
160
|
+
- `-c, --config <path>` - Path to zenstack-kit config file
|
|
161
|
+
|
|
162
|
+
### `zenstack-kit migrate rehash`
|
|
163
|
+
|
|
164
|
+
Rebuild the migration log checksums from the `migration.sql` files (useful after manual edits).
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
zenstack-kit migrate rehash
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Options:
|
|
171
|
+
- `-m, --migrations <path>` - Migrations directory
|
|
172
|
+
- `--migration <name>` - Rehash a single migration folder
|
|
159
173
|
- `-c, --config <path>` - Path to zenstack-kit config file
|
|
160
174
|
|
|
161
175
|
### `zenstack-kit pull`
|
|
@@ -170,6 +184,7 @@ Options:
|
|
|
170
184
|
- `-o, --output <path>` - Output path for schema (default: `./schema.zmodel`)
|
|
171
185
|
- `--dialect <dialect>` - Database dialect
|
|
172
186
|
- `--url <url>` - Database connection URL
|
|
187
|
+
- `--preview` - Preview generated schema and diff without writing files
|
|
173
188
|
- `-c, --config <path>` - Path to zenstack-kit config file
|
|
174
189
|
|
|
175
190
|
Features:
|
package/dist/cli/app.d.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* Commands:
|
|
6
6
|
* migrate create Generate a new SQL migration
|
|
7
7
|
* migrate apply Apply pending migrations
|
|
8
|
+
* migrate rehash Rebuild migration log checksums from migration.sql files
|
|
8
9
|
* init Initialize snapshot from existing schema
|
|
9
10
|
* pull Introspect database and generate schema
|
|
10
11
|
*/
|
package/dist/cli/app.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../src/cli/app.tsx"],"names":[],"mappings":";AAEA
|
|
1
|
+
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../src/cli/app.tsx"],"names":[],"mappings":";AAEA;;;;;;;;;GASG;AAuTH,wBAAgB,MAAM,SAkBrB"}
|
package/dist/cli/app.js
CHANGED
|
@@ -6,17 +6,19 @@ import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-run
|
|
|
6
6
|
* Commands:
|
|
7
7
|
* migrate create Generate a new SQL migration
|
|
8
8
|
* migrate apply Apply pending migrations
|
|
9
|
+
* migrate rehash Rebuild migration log checksums from migration.sql files
|
|
9
10
|
* init Initialize snapshot from existing schema
|
|
10
11
|
* pull Introspect database and generate schema
|
|
11
12
|
*/
|
|
12
13
|
import { useState, useEffect } from "react";
|
|
13
14
|
import { render, Box, Text, useApp, useInput } from "ink";
|
|
14
15
|
import SelectInput from "ink-select-input";
|
|
15
|
-
import { runMigrateGenerate, runMigrateApply, runInit, runPull, CommandError, } from "./commands.js";
|
|
16
|
+
import { runMigrateGenerate, runMigrateApply, runMigrateRehash, runInit, runPull, CommandError, } from "./commands.js";
|
|
16
17
|
import { promptSnapshotExists, promptFreshInit, promptPullConfirm, promptTableRename, promptColumnRename, promptMigrationName, promptMigrationConfirm, } from "./prompts.js";
|
|
17
18
|
const commands = [
|
|
18
19
|
{ label: "migrate create", value: "migrate create", description: "Generate a new SQL migration file" },
|
|
19
20
|
{ label: "migrate apply", value: "migrate apply", description: "Apply pending SQL migrations" },
|
|
21
|
+
{ label: "migrate rehash", value: "migrate rehash", description: "Rebuild migration log checksums" },
|
|
20
22
|
{ label: "init", value: "init", description: "Initialize snapshot from existing schema" },
|
|
21
23
|
{ label: "pull", value: "pull", description: "Introspect database and generate schema" },
|
|
22
24
|
{ label: "help", value: "help", description: "Show help information" },
|
|
@@ -29,7 +31,7 @@ function parseArgs() {
|
|
|
29
31
|
let command;
|
|
30
32
|
for (let i = 0; i < args.length; i++) {
|
|
31
33
|
const arg = args[i];
|
|
32
|
-
// Handle "migrate create" and "migrate
|
|
34
|
+
// Handle "migrate create", "migrate apply", and "migrate rehash" subcommands
|
|
33
35
|
if (arg === "migrate" && args[i + 1] === "create") {
|
|
34
36
|
command = "migrate create";
|
|
35
37
|
i++; // Skip the next argument
|
|
@@ -38,6 +40,10 @@ function parseArgs() {
|
|
|
38
40
|
command = "migrate apply";
|
|
39
41
|
i++; // Skip the next argument
|
|
40
42
|
}
|
|
43
|
+
else if (arg === "migrate" && args[i + 1] === "rehash") {
|
|
44
|
+
command = "migrate rehash";
|
|
45
|
+
i++; // Skip the next argument
|
|
46
|
+
}
|
|
41
47
|
else if (arg === "init" || arg === "pull" || arg === "help") {
|
|
42
48
|
command = arg;
|
|
43
49
|
}
|
|
@@ -50,6 +56,9 @@ function parseArgs() {
|
|
|
50
56
|
else if (arg === "--migrations" || arg === "-m") {
|
|
51
57
|
options.migrations = args[++i];
|
|
52
58
|
}
|
|
59
|
+
else if (arg === "--migration") {
|
|
60
|
+
options.migration = args[++i];
|
|
61
|
+
}
|
|
53
62
|
else if (arg === "--dialect") {
|
|
54
63
|
options.dialect = args[++i];
|
|
55
64
|
}
|
|
@@ -74,6 +83,9 @@ function parseArgs() {
|
|
|
74
83
|
else if (arg === "--preview") {
|
|
75
84
|
options.preview = true;
|
|
76
85
|
}
|
|
86
|
+
else if (arg === "--mark-applied") {
|
|
87
|
+
options.markApplied = true;
|
|
88
|
+
}
|
|
77
89
|
else if (arg === "--force" || arg === "-f") {
|
|
78
90
|
options.force = true;
|
|
79
91
|
}
|
|
@@ -101,7 +113,7 @@ function Status({ type, message }) {
|
|
|
101
113
|
}
|
|
102
114
|
// Help display component
|
|
103
115
|
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" })] })] }));
|
|
116
|
+
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: "--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
117
|
}
|
|
106
118
|
function CliApp({ initialCommand, options }) {
|
|
107
119
|
const { exit } = useApp();
|
|
@@ -167,6 +179,9 @@ function CliApp({ initialCommand, options }) {
|
|
|
167
179
|
else if (command === "migrate apply") {
|
|
168
180
|
await runMigrateApply(ctx);
|
|
169
181
|
}
|
|
182
|
+
else if (command === "migrate rehash") {
|
|
183
|
+
await runMigrateRehash(ctx);
|
|
184
|
+
}
|
|
170
185
|
else if (command === "init") {
|
|
171
186
|
await runInit(ctx);
|
|
172
187
|
}
|
package/dist/cli/commands.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ export interface CommandOptions {
|
|
|
11
11
|
schema?: string;
|
|
12
12
|
migrations?: string;
|
|
13
13
|
name?: string;
|
|
14
|
+
migration?: string;
|
|
14
15
|
dialect?: string;
|
|
15
16
|
url?: string;
|
|
16
17
|
output?: string;
|
|
@@ -19,6 +20,7 @@ export interface CommandOptions {
|
|
|
19
20
|
baseline?: boolean;
|
|
20
21
|
createInitial?: boolean;
|
|
21
22
|
preview?: boolean;
|
|
23
|
+
markApplied?: boolean;
|
|
22
24
|
force?: boolean;
|
|
23
25
|
config?: string;
|
|
24
26
|
}
|
|
@@ -63,6 +65,10 @@ export declare function runMigrateGenerate(ctx: CommandContext): Promise<void>;
|
|
|
63
65
|
* migrate:apply command
|
|
64
66
|
*/
|
|
65
67
|
export declare function runMigrateApply(ctx: CommandContext): Promise<void>;
|
|
68
|
+
/**
|
|
69
|
+
* migrate:rehash command
|
|
70
|
+
*/
|
|
71
|
+
export declare function runMigrateRehash(ctx: CommandContext): Promise<void>;
|
|
66
72
|
/**
|
|
67
73
|
* init command
|
|
68
74
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../src/cli/commands.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../src/cli/commands.ts"],"names":[],"mappings":"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"}
|
package/dist/cli/commands.js
CHANGED
|
@@ -6,9 +6,11 @@
|
|
|
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
|
-
import { createPrismaMigration, applyPrismaMigrations, previewPrismaMigrations, hasPrismaSchemaChanges, hasSnapshot, scanMigrationFolders, writeMigrationLog, initializeSnapshot, createInitialMigration, detectPotentialRenames, } from "../migrations/prisma.js";
|
|
13
|
+
import { createPrismaMigration, applyPrismaMigrations, previewPrismaMigrations, hasPrismaSchemaChanges, hasSnapshot, scanMigrationFolders, readMigrationLog, writeMigrationLog, getMigrationLogPath, calculateChecksum, initializeSnapshot, createInitialMigration, detectPotentialRenames, } from "../migrations/prisma.js";
|
|
12
14
|
export class CommandError extends Error {
|
|
13
15
|
constructor(message) {
|
|
14
16
|
super(message);
|
|
@@ -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
|
-
|
|
175
|
-
|
|
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.
|
|
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) {
|
|
@@ -198,8 +213,10 @@ export async function runMigrateApply(ctx) {
|
|
|
198
213
|
ctx.log("error", " - Migrations were applied manually without using zenstack-kit");
|
|
199
214
|
ctx.log("error", " - The migration log file was modified or deleted");
|
|
200
215
|
ctx.log("error", " - Different migration histories exist across environments");
|
|
216
|
+
ctx.log("error", " - Migration.sql files were edited after being logged");
|
|
201
217
|
ctx.log("error", "");
|
|
202
218
|
ctx.log("error", "To resolve, ensure your migration log matches your database state.");
|
|
219
|
+
ctx.log("error", "If you edited migration.sql files, run 'zenstack-kit migrate rehash' to rebuild log checksums.");
|
|
203
220
|
throw new CommandError("Migration history is inconsistent");
|
|
204
221
|
}
|
|
205
222
|
if (result.applied.length === 0 && !result.failed) {
|
|
@@ -210,12 +227,52 @@ export async function runMigrateApply(ctx) {
|
|
|
210
227
|
return;
|
|
211
228
|
}
|
|
212
229
|
for (const item of result.applied) {
|
|
213
|
-
ctx.
|
|
230
|
+
const action = ctx.options.markApplied ? "Marked applied" : "Applied";
|
|
231
|
+
ctx.log("success", `${action}: ${item.migrationName} (${item.duration}ms)`);
|
|
214
232
|
}
|
|
215
233
|
if (result.failed) {
|
|
216
234
|
throw new CommandError(`Migration failed: ${result.failed.migrationName} - ${result.failed.error}`);
|
|
217
235
|
}
|
|
218
236
|
}
|
|
237
|
+
/**
|
|
238
|
+
* migrate:rehash command
|
|
239
|
+
*/
|
|
240
|
+
export async function runMigrateRehash(ctx) {
|
|
241
|
+
const { outputPath } = await resolveConfig(ctx);
|
|
242
|
+
const targetMigration = ctx.options.migration;
|
|
243
|
+
if (targetMigration) {
|
|
244
|
+
const sqlPath = path.join(outputPath, targetMigration, "migration.sql");
|
|
245
|
+
if (!fs.existsSync(sqlPath)) {
|
|
246
|
+
throw new CommandError(`Migration not found: ${targetMigration}`);
|
|
247
|
+
}
|
|
248
|
+
const sqlContent = await fs.promises.readFile(sqlPath, "utf-8");
|
|
249
|
+
const checksum = calculateChecksum(sqlContent);
|
|
250
|
+
const entries = await readMigrationLog(outputPath);
|
|
251
|
+
const existingIndex = entries.findIndex((e) => e.name === targetMigration);
|
|
252
|
+
if (existingIndex === -1) {
|
|
253
|
+
entries.push({ name: targetMigration, checksum });
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
entries[existingIndex] = { name: targetMigration, checksum };
|
|
257
|
+
}
|
|
258
|
+
await writeMigrationLog(outputPath, entries);
|
|
259
|
+
const logPath = getMigrationLogPath(outputPath);
|
|
260
|
+
ctx.log("success", `Updated checksum for ${targetMigration}`);
|
|
261
|
+
ctx.log("info", `Log: ${logPath}`);
|
|
262
|
+
ctx.log("warning", "If this migration was already applied, make sure the database checksum matches the updated log.");
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
const migrations = await scanMigrationFolders(outputPath);
|
|
266
|
+
if (migrations.length === 0) {
|
|
267
|
+
ctx.log("warning", "No migrations found.");
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
await writeMigrationLog(outputPath, migrations);
|
|
271
|
+
const logPath = getMigrationLogPath(outputPath);
|
|
272
|
+
ctx.log("success", `Migration log rebuilt with ${migrations.length} migration(s)`);
|
|
273
|
+
ctx.log("info", `Log: ${logPath}`);
|
|
274
|
+
ctx.log("warning", "If these migrations were already applied, make sure the database checksums match the updated log.");
|
|
275
|
+
}
|
|
219
276
|
/**
|
|
220
277
|
* init command
|
|
221
278
|
*/
|
|
@@ -334,6 +391,42 @@ export async function runPull(ctx) {
|
|
|
334
391
|
return;
|
|
335
392
|
}
|
|
336
393
|
}
|
|
394
|
+
if (ctx.options.preview) {
|
|
395
|
+
ctx.log("info", "Preview mode - no files will be written.");
|
|
396
|
+
const result = await pullSchema({
|
|
397
|
+
dialect,
|
|
398
|
+
connectionUrl,
|
|
399
|
+
databasePath,
|
|
400
|
+
outputPath: schemaOutputPath,
|
|
401
|
+
writeFile: false,
|
|
402
|
+
});
|
|
403
|
+
if (fs.existsSync(schemaOutputPath)) {
|
|
404
|
+
const diffOutput = await buildSchemaDiff(schemaOutputPath, result.schema);
|
|
405
|
+
if (diffOutput) {
|
|
406
|
+
const { text, truncated } = truncateLines(diffOutput, 200);
|
|
407
|
+
ctx.log("info", `Diff (existing -> generated):\n${text}`);
|
|
408
|
+
if (truncated) {
|
|
409
|
+
ctx.log("info", "Diff truncated. Use --output to write and inspect the full schema.");
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
else {
|
|
413
|
+
ctx.log("info", "Diff unavailable; showing generated schema preview.");
|
|
414
|
+
const { text, truncated } = truncateLines(result.schema, 200);
|
|
415
|
+
ctx.log("info", `Generated schema:\n${text}`);
|
|
416
|
+
if (truncated) {
|
|
417
|
+
ctx.log("info", "Preview truncated. Use --output to write and inspect the full schema.");
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
else {
|
|
422
|
+
const { text, truncated } = truncateLines(result.schema, 200);
|
|
423
|
+
ctx.log("info", `Generated schema:\n${text}`);
|
|
424
|
+
if (truncated) {
|
|
425
|
+
ctx.log("info", "Preview truncated. Use --output to write and inspect the full schema.");
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
337
430
|
ctx.log("info", "Pulling schema from database...");
|
|
338
431
|
const result = await pullSchema({
|
|
339
432
|
dialect,
|
|
@@ -349,3 +442,33 @@ export async function runPull(ctx) {
|
|
|
349
442
|
ctx.log("warning", "You should run 'zenstack-kit init' to reset the snapshot after reviewing the schema.");
|
|
350
443
|
}
|
|
351
444
|
}
|
|
445
|
+
async function buildSchemaDiff(existingPath, nextSchema) {
|
|
446
|
+
const tempDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), "zenstack-kit-pull-"));
|
|
447
|
+
const nextPath = path.join(tempDir, "schema.zmodel");
|
|
448
|
+
try {
|
|
449
|
+
await fs.promises.writeFile(nextPath, nextSchema, "utf-8");
|
|
450
|
+
try {
|
|
451
|
+
return execFileSync("git", ["diff", "--no-index", "--no-color", "--", existingPath, nextPath], { encoding: "utf-8" });
|
|
452
|
+
}
|
|
453
|
+
catch (error) {
|
|
454
|
+
const stdout = error.stdout;
|
|
455
|
+
if (stdout) {
|
|
456
|
+
return stdout.toString();
|
|
457
|
+
}
|
|
458
|
+
return null;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
finally {
|
|
462
|
+
await fs.promises.rm(tempDir, { recursive: true, force: true });
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
function truncateLines(text, maxLines) {
|
|
466
|
+
const lines = text.split("\n");
|
|
467
|
+
if (lines.length <= maxLines) {
|
|
468
|
+
return { text, truncated: false };
|
|
469
|
+
}
|
|
470
|
+
return {
|
|
471
|
+
text: lines.slice(0, maxLines).join("\n"),
|
|
472
|
+
truncated: true,
|
|
473
|
+
};
|
|
474
|
+
}
|
package/dist/cli/index.d.ts
CHANGED
package/dist/cli/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";AAEA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";AAEA;;;;;;;;;GASG"}
|
package/dist/cli/index.js
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/cli/prompts.js
CHANGED
|
@@ -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;
|
|
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"}
|
|
@@ -298,17 +298,20 @@ export async function applyPrismaMigrations(options) {
|
|
|
298
298
|
error: `Checksum mismatch for migration ${folderName}.\n` +
|
|
299
299
|
`Expected: ${logEntry.checksum}\n` +
|
|
300
300
|
`Found: ${checksum}\n` +
|
|
301
|
-
`The migration file may have been modified after generation
|
|
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.`,
|
|
302
303
|
};
|
|
303
304
|
break;
|
|
304
305
|
}
|
|
305
306
|
const startTime = Date.now();
|
|
306
307
|
try {
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
308
|
+
if (!options.markApplied) {
|
|
309
|
+
// Execute the migration SQL using direct driver access
|
|
310
|
+
await executeRawSql(options.dialect, sqlContent, {
|
|
311
|
+
connectionUrl: options.connectionUrl,
|
|
312
|
+
databasePath: options.databasePath,
|
|
313
|
+
});
|
|
314
|
+
}
|
|
312
315
|
// Record the migration (still use Kysely for this since it's simple INSERT)
|
|
313
316
|
await recordMigration(db, migrationsTable, migrationsSchema, options.dialect, folderName, checksum);
|
|
314
317
|
result.applied.push({
|
package/dist/schema/pull.d.ts
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/schema/pull.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
415
|
-
|
|
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,
|