zenstack-kit 0.1.1

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.
Files changed (93) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +313 -0
  3. package/dist/cli/app.d.ts +12 -0
  4. package/dist/cli/app.d.ts.map +1 -0
  5. package/dist/cli/app.js +253 -0
  6. package/dist/cli/commands.d.ts +70 -0
  7. package/dist/cli/commands.d.ts.map +1 -0
  8. package/dist/cli/commands.js +308 -0
  9. package/dist/cli/index.d.ts +12 -0
  10. package/dist/cli/index.d.ts.map +1 -0
  11. package/dist/cli/index.js +12 -0
  12. package/dist/cli/prompt-provider.d.ts +10 -0
  13. package/dist/cli/prompt-provider.d.ts.map +1 -0
  14. package/dist/cli/prompt-provider.js +41 -0
  15. package/dist/cli/prompts.d.ts +27 -0
  16. package/dist/cli/prompts.d.ts.map +1 -0
  17. package/dist/cli/prompts.js +133 -0
  18. package/dist/cli.d.ts +12 -0
  19. package/dist/cli.d.ts.map +1 -0
  20. package/dist/cli.js +240 -0
  21. package/dist/config/index.d.ts +96 -0
  22. package/dist/config/index.d.ts.map +1 -0
  23. package/dist/config/index.js +48 -0
  24. package/dist/config/loader.d.ts +11 -0
  25. package/dist/config/loader.d.ts.map +1 -0
  26. package/dist/config/loader.js +44 -0
  27. package/dist/config-loader.d.ts +6 -0
  28. package/dist/config-loader.d.ts.map +1 -0
  29. package/dist/config-loader.js +36 -0
  30. package/dist/config.d.ts +62 -0
  31. package/dist/config.d.ts.map +1 -0
  32. package/dist/config.js +44 -0
  33. package/dist/index.d.ts +19 -0
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/index.js +23 -0
  36. package/dist/init-prompts.d.ts +13 -0
  37. package/dist/init-prompts.d.ts.map +1 -0
  38. package/dist/init-prompts.js +64 -0
  39. package/dist/introspect.d.ts +54 -0
  40. package/dist/introspect.d.ts.map +1 -0
  41. package/dist/introspect.js +75 -0
  42. package/dist/kysely-adapter.d.ts +49 -0
  43. package/dist/kysely-adapter.d.ts.map +1 -0
  44. package/dist/kysely-adapter.js +74 -0
  45. package/dist/migrate-apply.d.ts +18 -0
  46. package/dist/migrate-apply.d.ts.map +1 -0
  47. package/dist/migrate-apply.js +61 -0
  48. package/dist/migrate.d.ts +108 -0
  49. package/dist/migrate.d.ts.map +1 -0
  50. package/dist/migrate.js +127 -0
  51. package/dist/migrations/apply.d.ts +18 -0
  52. package/dist/migrations/apply.d.ts.map +1 -0
  53. package/dist/migrations/apply.js +61 -0
  54. package/dist/migrations/diff.d.ts +161 -0
  55. package/dist/migrations/diff.d.ts.map +1 -0
  56. package/dist/migrations/diff.js +620 -0
  57. package/dist/migrations/prisma.d.ts +193 -0
  58. package/dist/migrations/prisma.d.ts.map +1 -0
  59. package/dist/migrations/prisma.js +929 -0
  60. package/dist/migrations.d.ts +161 -0
  61. package/dist/migrations.d.ts.map +1 -0
  62. package/dist/migrations.js +620 -0
  63. package/dist/prisma-migrations.d.ts +160 -0
  64. package/dist/prisma-migrations.d.ts.map +1 -0
  65. package/dist/prisma-migrations.js +789 -0
  66. package/dist/prompts.d.ts +10 -0
  67. package/dist/prompts.d.ts.map +1 -0
  68. package/dist/prompts.js +41 -0
  69. package/dist/pull.d.ts +23 -0
  70. package/dist/pull.d.ts.map +1 -0
  71. package/dist/pull.js +424 -0
  72. package/dist/schema/introspect.d.ts +54 -0
  73. package/dist/schema/introspect.d.ts.map +1 -0
  74. package/dist/schema/introspect.js +75 -0
  75. package/dist/schema/pull.d.ts +23 -0
  76. package/dist/schema/pull.d.ts.map +1 -0
  77. package/dist/schema/pull.js +424 -0
  78. package/dist/schema/snapshot.d.ts +46 -0
  79. package/dist/schema/snapshot.d.ts.map +1 -0
  80. package/dist/schema/snapshot.js +278 -0
  81. package/dist/schema-snapshot.d.ts +45 -0
  82. package/dist/schema-snapshot.d.ts.map +1 -0
  83. package/dist/schema-snapshot.js +265 -0
  84. package/dist/sql/compiler.d.ts +74 -0
  85. package/dist/sql/compiler.d.ts.map +1 -0
  86. package/dist/sql/compiler.js +270 -0
  87. package/dist/sql/kysely-adapter.d.ts +49 -0
  88. package/dist/sql/kysely-adapter.d.ts.map +1 -0
  89. package/dist/sql/kysely-adapter.js +74 -0
  90. package/dist/sql-compiler.d.ts +74 -0
  91. package/dist/sql-compiler.d.ts.map +1 -0
  92. package/dist/sql-compiler.js +243 -0
  93. package/package.json +81 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,313 @@
1
+ # zenstack-kit
2
+
3
+ Drizzle-kit like CLI tooling for ZenStack schemas with Prisma-compatible migrations.
4
+
5
+ ## Features
6
+
7
+ - **Prisma-Compatible Migrations** - Generates SQL migrations in Prisma folder format (`migrations/<timestamp>_<name>/migration.sql`)
8
+ - **Migration Tracking** - Uses `_prisma_migrations` table, compatible with `prisma migrate deploy`
9
+ - **Database Introspection** - Generate ZenStack schemas from existing databases
10
+ - **Interactive CLI** - Beautiful terminal UI powered by Ink with command selection and prompts
11
+ - **No Prisma Dependency** - Uses ZenStack AST directly for diffing and Kysely for SQL compilation
12
+ - **Multi-Dialect Support** - SQLite, PostgreSQL, and MySQL
13
+ - **Type-Safe Configuration** - Configuration via `defineConfig()` with full TypeScript support
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ pnpm add zenstack-kit kysely
19
+ ```
20
+
21
+ ## Quick Start
22
+
23
+ ### 1. Create a configuration file
24
+
25
+ ```ts
26
+ // zenstack-kit.config.ts
27
+ import { defineConfig } from "zenstack-kit";
28
+
29
+ export default defineConfig({
30
+ schema: "./schema.zmodel",
31
+ dialect: "postgres",
32
+ dbCredentials: {
33
+ url: process.env.DATABASE_URL,
34
+ },
35
+ });
36
+ ```
37
+
38
+ For SQLite, use `file` instead of `url`:
39
+
40
+ ```ts
41
+ export default defineConfig({
42
+ schema: "./schema.zmodel",
43
+ dialect: "sqlite",
44
+ dbCredentials: {
45
+ file: "./dev.db",
46
+ },
47
+ });
48
+ ```
49
+
50
+ ### 2. Create a ZenStack schema
51
+
52
+ ```zmodel
53
+ datasource db {
54
+ provider = "postgres"
55
+ url = env("DATABASE_URL")
56
+ }
57
+
58
+ model User {
59
+ id Int @id @default(autoincrement())
60
+ name String
61
+ email String @unique
62
+ }
63
+ ```
64
+
65
+ ### 3. Initialize the migration system
66
+
67
+ ```bash
68
+ # Interactive mode - launches the CLI menu
69
+ zenstack-kit
70
+
71
+ # Or run init directly
72
+ zenstack-kit init
73
+ ```
74
+
75
+ The `init` command offers two options:
76
+ - **Baseline only** - Create a snapshot without generating a migration (use when your database already matches the schema)
77
+ - **Create initial migration** - Create a snapshot and generate an initial migration (use when starting fresh)
78
+
79
+ ### 4. Generate migrations
80
+
81
+ ```bash
82
+ zenstack-kit migrate:generate --name add_posts
83
+ ```
84
+
85
+ This creates a migration in Prisma format:
86
+ ```
87
+ prisma/migrations/
88
+ 20240115120000_add_posts/
89
+ migration.sql
90
+ meta/
91
+ _snapshot.json
92
+ _migration_log
93
+ ```
94
+
95
+ ### 5. Apply migrations
96
+
97
+ ```bash
98
+ zenstack-kit migrate:apply
99
+ ```
100
+
101
+ Migrations are tracked in the `_prisma_migrations` table, making them compatible with `prisma migrate deploy`.
102
+
103
+ ## CLI Commands
104
+
105
+ Run `zenstack-kit` without arguments to launch the interactive menu, or run commands directly.
106
+
107
+ ### `zenstack-kit init`
108
+
109
+ Initialize the migration system for your project.
110
+
111
+ ```bash
112
+ # Interactive mode
113
+ zenstack-kit init
114
+
115
+ # Non-interactive: baseline only (database already matches schema)
116
+ zenstack-kit init --baseline
117
+
118
+ # Non-interactive: create initial migration (fresh database)
119
+ zenstack-kit init --create-initial
120
+ ```
121
+
122
+ Options:
123
+ - `-s, --schema <path>` - Path to ZenStack schema
124
+ - `-m, --migrations <path>` - Migrations directory
125
+ - `--baseline` - Create snapshot only, no migration
126
+ - `--create-initial` - Create snapshot and initial migration
127
+
128
+ ### `zenstack-kit migrate:generate`
129
+
130
+ Generate a new SQL migration from schema changes.
131
+
132
+ ```bash
133
+ zenstack-kit migrate:generate --name add_users
134
+ ```
135
+
136
+ Options:
137
+ - `-n, --name <name>` - Migration name (prompted if omitted in interactive mode)
138
+ - `-s, --schema <path>` - Path to ZenStack schema
139
+ - `-m, --migrations <path>` - Migrations directory
140
+ - `--dialect <dialect>` - Database dialect (`sqlite`, `postgres`, `mysql`)
141
+
142
+ ### `zenstack-kit migrate:apply`
143
+
144
+ Apply pending migrations to the database.
145
+
146
+ ```bash
147
+ zenstack-kit migrate:apply
148
+ ```
149
+
150
+ Options:
151
+ - `-m, --migrations <path>` - Migrations directory
152
+ - `--dialect <dialect>` - Database dialect
153
+ - `--url <url>` - Database connection URL (overrides config)
154
+ - `--table <name>` - Migrations table name (default: `_prisma_migrations`)
155
+ - `--db-schema <name>` - Database schema for migrations table (PostgreSQL only, default: `public`)
156
+
157
+ ### `zenstack-kit pull`
158
+
159
+ Introspect an existing database and generate a ZenStack schema.
160
+
161
+ ```bash
162
+ zenstack-kit pull --output schema.zmodel
163
+ ```
164
+
165
+ Options:
166
+ - `-o, --output <path>` - Output path for schema (default: `./schema.zmodel`)
167
+ - `--dialect <dialect>` - Database dialect
168
+ - `--url <url>` - Database connection URL
169
+
170
+ Features:
171
+ - Detects tables, columns, and types
172
+ - Extracts primary keys (single and composite)
173
+ - Extracts unique constraints and indexes
174
+ - Extracts foreign key relationships
175
+ - Converts snake_case to camelCase with `@map` attributes
176
+
177
+ ## Configuration
178
+
179
+ Create a `zenstack-kit.config.ts` file in your project root:
180
+
181
+ ```ts
182
+ import { defineConfig } from "zenstack-kit";
183
+
184
+ export default defineConfig({
185
+ // Path to your ZenStack schema
186
+ schema: "./schema.zmodel",
187
+
188
+ // Database dialect: "sqlite" | "postgres" | "mysql"
189
+ dialect: "postgres",
190
+
191
+ // Database credentials (dialect-specific)
192
+ dbCredentials: {
193
+ url: process.env.DATABASE_URL, // For postgres/mysql
194
+ // file: "./dev.db", // For sqlite
195
+ },
196
+
197
+ // Migration settings
198
+ migrations: {
199
+ migrationsFolder: "./prisma/migrations", // Default
200
+ migrationsTable: "_prisma_migrations", // Default
201
+ migrationsSchema: "public", // PostgreSQL only
202
+ },
203
+ });
204
+ ```
205
+
206
+ CLI flags override config file values.
207
+
208
+ ## Programmatic API
209
+
210
+ ### `createPrismaMigration(options)`
211
+
212
+ Create a Prisma-compatible SQL migration.
213
+
214
+ ```ts
215
+ import { createPrismaMigration } from "zenstack-kit";
216
+
217
+ const migration = await createPrismaMigration({
218
+ name: "add_users",
219
+ schemaPath: "./schema.zmodel",
220
+ outputPath: "./prisma/migrations",
221
+ dialect: "postgres",
222
+ });
223
+
224
+ if (migration) {
225
+ console.log(migration.folderName); // "20240115120000_add_users"
226
+ console.log(migration.sql);
227
+ }
228
+ ```
229
+
230
+ ### `applyPrismaMigrations(options)`
231
+
232
+ Apply pending migrations to the database.
233
+
234
+ ```ts
235
+ import { applyPrismaMigrations } from "zenstack-kit";
236
+
237
+ const result = await applyPrismaMigrations({
238
+ migrationsFolder: "./prisma/migrations",
239
+ dialect: "postgres",
240
+ connectionUrl: process.env.DATABASE_URL,
241
+ });
242
+
243
+ console.log(result.applied); // [{ migrationName: "...", duration: 42 }]
244
+ console.log(result.alreadyApplied); // ["20240114000000_init"]
245
+ ```
246
+
247
+ ### `hasPrismaSchemaChanges(options)`
248
+
249
+ Check if there are pending schema changes.
250
+
251
+ ```ts
252
+ import { hasPrismaSchemaChanges } from "zenstack-kit";
253
+
254
+ const hasChanges = await hasPrismaSchemaChanges({
255
+ schemaPath: "./schema.zmodel",
256
+ outputPath: "./prisma/migrations",
257
+ });
258
+ ```
259
+
260
+ ### `pullSchema(options)`
261
+
262
+ Introspect a database and generate a ZenStack schema.
263
+
264
+ ```ts
265
+ import { pullSchema } from "zenstack-kit";
266
+
267
+ const result = await pullSchema({
268
+ dialect: "postgres",
269
+ connectionUrl: process.env.DATABASE_URL,
270
+ outputPath: "./schema.zmodel",
271
+ });
272
+
273
+ console.log(result.tableCount); // 5
274
+ ```
275
+
276
+ ### `createKyselyAdapter(options)`
277
+
278
+ Create a Kysely instance for your database.
279
+
280
+ ```ts
281
+ import { createKyselyAdapter } from "zenstack-kit";
282
+
283
+ const { db, destroy } = await createKyselyAdapter({
284
+ dialect: "postgres",
285
+ connectionUrl: process.env.DATABASE_URL,
286
+ });
287
+
288
+ // Use db for queries...
289
+ await destroy();
290
+ ```
291
+
292
+ ## Prisma Compatibility
293
+
294
+ zenstack-kit is designed to be compatible with Prisma's migration system:
295
+
296
+ - **Same folder structure** - `migrations/<timestamp>_<name>/migration.sql`
297
+ - **Same tracking table** - `_prisma_migrations` with identical schema
298
+ - **Interoperable** - Teams can use `prisma migrate deploy` to apply zenstack-kit migrations
299
+
300
+ Constraint naming follows Prisma conventions:
301
+ - Primary keys: `<table>_pkey`
302
+ - Unique constraints: `<table>_<columns>_key`
303
+ - Indexes: `<table>_<columns>_idx`
304
+ - Foreign keys: `<table>_<columns>_fkey`
305
+
306
+ ## Requirements
307
+
308
+ - Node.js 18+
309
+ - `kysely` >= 0.27.0
310
+
311
+ ## License
312
+
313
+ MIT
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * zenstack-kit CLI - Database tooling for ZenStack schemas
4
+ *
5
+ * Commands:
6
+ * migrate create Generate a new SQL migration
7
+ * migrate apply Apply pending migrations
8
+ * init Initialize snapshot from existing schema
9
+ * pull Introspect database and generate schema
10
+ */
11
+ export declare function runCli(): void;
12
+ //# sourceMappingURL=app.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../src/cli/app.tsx"],"names":[],"mappings":";AAEA;;;;;;;;GAQG;AAiVH,wBAAgB,MAAM,SAkBrB"}
@@ -0,0 +1,253 @@
1
+ #!/usr/bin/env node
2
+ import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
3
+ /**
4
+ * zenstack-kit CLI - Database tooling for ZenStack schemas
5
+ *
6
+ * Commands:
7
+ * migrate create Generate a new SQL migration
8
+ * migrate apply Apply pending migrations
9
+ * init Initialize snapshot from existing schema
10
+ * pull Introspect database and generate schema
11
+ */
12
+ import { useState, useEffect } from "react";
13
+ import { render, Box, Text, useApp, useInput } from "ink";
14
+ import SelectInput from "ink-select-input";
15
+ import { runMigrateGenerate, runMigrateApply, runInit, runPull, CommandError, } from "./commands.js";
16
+ import { promptSnapshotExists, promptFreshInit, promptPullConfirm, promptTableRename, promptColumnRename, } from "./prompts.js";
17
+ const commands = [
18
+ { label: "migrate create", value: "migrate create", description: "Generate a new SQL migration file" },
19
+ { label: "migrate apply", value: "migrate apply", description: "Apply pending SQL migrations" },
20
+ { label: "init", value: "init", description: "Initialize snapshot from existing schema" },
21
+ { label: "pull", value: "pull", description: "Introspect database and generate schema" },
22
+ { label: "help", value: "help", description: "Show help information" },
23
+ { label: "exit", value: "exit", description: "Exit the CLI" },
24
+ ];
25
+ // Parse command line arguments
26
+ function parseArgs() {
27
+ const args = process.argv.slice(2);
28
+ const options = {};
29
+ let command;
30
+ for (let i = 0; i < args.length; i++) {
31
+ const arg = args[i];
32
+ // Handle "migrate create" and "migrate apply" subcommands
33
+ if (arg === "migrate" && args[i + 1] === "create") {
34
+ command = "migrate create";
35
+ i++; // Skip the next argument
36
+ }
37
+ else if (arg === "migrate" && args[i + 1] === "apply") {
38
+ command = "migrate apply";
39
+ i++; // Skip the next argument
40
+ }
41
+ else if (arg === "init" || arg === "pull" || arg === "help") {
42
+ command = arg;
43
+ }
44
+ else if (arg === "--name" || arg === "-n") {
45
+ options.name = args[++i];
46
+ }
47
+ else if (arg === "--schema" || arg === "-s") {
48
+ options.schema = args[++i];
49
+ }
50
+ else if (arg === "--migrations" || arg === "-m") {
51
+ options.migrations = args[++i];
52
+ }
53
+ else if (arg === "--dialect") {
54
+ options.dialect = args[++i];
55
+ }
56
+ else if (arg === "--url") {
57
+ options.url = args[++i];
58
+ }
59
+ else if (arg === "--output" || arg === "-o") {
60
+ options.output = args[++i];
61
+ }
62
+ else if (arg === "--table") {
63
+ options.table = args[++i];
64
+ }
65
+ else if (arg === "--db-schema") {
66
+ options.dbSchema = args[++i];
67
+ }
68
+ else if (arg === "--baseline") {
69
+ options.baseline = true;
70
+ }
71
+ else if (arg === "--create-initial") {
72
+ options.createInitial = true;
73
+ }
74
+ else if (arg === "--preview") {
75
+ options.preview = true;
76
+ }
77
+ else if (arg === "--force" || arg === "-f") {
78
+ options.force = true;
79
+ }
80
+ }
81
+ return { command, options };
82
+ }
83
+ // Status component for showing messages
84
+ function Status({ type, message }) {
85
+ const colors = {
86
+ info: "blue",
87
+ success: "green",
88
+ error: "red",
89
+ warning: "yellow",
90
+ };
91
+ const symbols = {
92
+ info: "ℹ",
93
+ success: "✓",
94
+ error: "✗",
95
+ warning: "⚠",
96
+ };
97
+ return (_jsxs(Text, { color: colors[type], children: [symbols[type], " ", message] }));
98
+ }
99
+ // Help display component
100
+ function HelpDisplay() {
101
+ 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" })] })] }));
102
+ }
103
+ // Text input component for migration name
104
+ function TextInput({ prompt, defaultValue, onSubmit }) {
105
+ const [value, setValue] = useState("");
106
+ useInput((input, key) => {
107
+ if (key.return) {
108
+ onSubmit(value.trim() || defaultValue);
109
+ }
110
+ else if (key.backspace || key.delete) {
111
+ setValue((prev) => prev.slice(0, -1));
112
+ }
113
+ else if (!key.ctrl && !key.meta && input) {
114
+ setValue((prev) => prev + input);
115
+ }
116
+ });
117
+ return (_jsxs(Box, { children: [_jsx(Text, { color: "cyan", children: "? " }), _jsxs(Text, { children: [prompt, " "] }), _jsxs(Text, { dimColor: true, children: ["(", defaultValue, "): "] }), _jsx(Text, { children: value }), _jsx(Text, { color: "gray", children: "\u2588" })] }));
118
+ }
119
+ function CliApp({ initialCommand, options }) {
120
+ const { exit } = useApp();
121
+ const [command, setCommand] = useState(initialCommand || null);
122
+ const [phase, setPhase] = useState(initialCommand ? "running" : "select");
123
+ const [migrationName, setMigrationName] = useState(options.name || null);
124
+ const [logs, setLogs] = useState([]);
125
+ const log = (type, message) => {
126
+ setLogs((prev) => [...prev, { type, message }]);
127
+ };
128
+ // Handle command selection
129
+ const handleSelect = (item) => {
130
+ if (item.value === "exit") {
131
+ exit();
132
+ return;
133
+ }
134
+ if (item.value === "help") {
135
+ setCommand("help");
136
+ setPhase("done");
137
+ return;
138
+ }
139
+ setCommand(item.value);
140
+ if (item.value === "migrate create" && !migrationName) {
141
+ setPhase("input");
142
+ }
143
+ else {
144
+ setPhase("running");
145
+ }
146
+ };
147
+ // Handle migration name input
148
+ const handleNameSubmit = (name) => {
149
+ setMigrationName(name);
150
+ setPhase("running");
151
+ };
152
+ // Execute commands
153
+ useEffect(() => {
154
+ if (phase !== "running" || !command)
155
+ return;
156
+ const run = async () => {
157
+ const ctx = {
158
+ cwd: process.cwd(),
159
+ options: { ...options, name: migrationName || options.name },
160
+ log,
161
+ promptSnapshotExists: async () => {
162
+ const choice = await promptSnapshotExists();
163
+ return choice;
164
+ },
165
+ promptFreshInit: async () => {
166
+ const choice = await promptFreshInit();
167
+ return choice;
168
+ },
169
+ promptPullConfirm: async (existingFiles) => {
170
+ return await promptPullConfirm(existingFiles);
171
+ },
172
+ promptTableRename: async (from, to) => {
173
+ return await promptTableRename(from, to);
174
+ },
175
+ promptColumnRename: async (table, from, to) => {
176
+ return await promptColumnRename(table, from, to);
177
+ },
178
+ };
179
+ try {
180
+ if (command === "migrate create") {
181
+ await runMigrateGenerate(ctx);
182
+ }
183
+ else if (command === "migrate apply") {
184
+ await runMigrateApply(ctx);
185
+ }
186
+ else if (command === "init") {
187
+ await runInit(ctx);
188
+ }
189
+ else if (command === "pull") {
190
+ await runPull(ctx);
191
+ }
192
+ }
193
+ catch (err) {
194
+ if (err instanceof CommandError) {
195
+ log("error", err.message);
196
+ }
197
+ else {
198
+ log("error", `Error: ${err instanceof Error ? err.message : String(err)}`);
199
+ }
200
+ }
201
+ setPhase("done");
202
+ };
203
+ run();
204
+ }, [phase, command]);
205
+ // Exit after command completes (for non-interactive mode, or on error)
206
+ useEffect(() => {
207
+ if (phase === "done" && command !== "help") {
208
+ const hasError = logs.some((l) => l.type === "error");
209
+ // Always exit on error, or exit in non-interactive mode
210
+ if (hasError || initialCommand) {
211
+ setTimeout(() => {
212
+ if (hasError) {
213
+ process.exitCode = 1;
214
+ }
215
+ exit();
216
+ }, 100);
217
+ }
218
+ }
219
+ }, [phase, initialCommand, logs, command, exit]);
220
+ // Handle exit on 'q' or Escape in interactive mode
221
+ useInput((input, key) => {
222
+ if (phase === "done" && !initialCommand) {
223
+ if (input === "q" || key.escape) {
224
+ exit();
225
+ }
226
+ else if (key.return) {
227
+ // Reset to command selection
228
+ setCommand(null);
229
+ setPhase("select");
230
+ setLogs([]);
231
+ setMigrationName(null);
232
+ }
233
+ }
234
+ });
235
+ return (_jsxs(Box, { flexDirection: "column", paddingY: 1, children: [phase === "select" && (_jsxs(_Fragment, { children: [_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: "zenstack-kit" }), _jsx(Text, { dimColor: true, children: " - Select a command" })] }), _jsx(SelectInput, { items: commands, onSelect: handleSelect })] })), phase === "input" && command === "migrate create" && (_jsx(TextInput, { prompt: "Migration name", defaultValue: "migration", onSubmit: handleNameSubmit })), command === "help" && _jsx(HelpDisplay, {}), logs.map((l, i) => (_jsx(Status, { type: l.type, message: l.message }, i))), phase === "done" && command !== "help" && !initialCommand && !logs.some((l) => l.type === "error") && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Press Enter to continue, 'q' or Escape to exit" }) }))] }));
236
+ }
237
+ // Entry point
238
+ export function runCli() {
239
+ const { command, options } = parseArgs();
240
+ // Show version
241
+ if (process.argv.includes("--version") || process.argv.includes("-v")) {
242
+ console.log("0.1.0");
243
+ process.exit(0);
244
+ }
245
+ // Show help if no command or --help/-h flag
246
+ if (!command || process.argv.includes("--help") || process.argv.includes("-h")) {
247
+ const { waitUntilExit } = render(_jsx(HelpDisplay, {}));
248
+ waitUntilExit().then(() => process.exit(0));
249
+ return;
250
+ }
251
+ const { waitUntilExit } = render(_jsx(CliApp, { initialCommand: command, options: options }));
252
+ waitUntilExit();
253
+ }
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Command implementations for zenstack-kit CLI
3
+ *
4
+ * These functions contain the core logic and can be tested independently
5
+ * from the CLI/UI layer.
6
+ */
7
+ import type { RenameChoice } from "./prompts.js";
8
+ import type { ZenStackKitConfig } from "../config/index.js";
9
+ export type LogFn = (type: "info" | "success" | "error" | "warning", message: string) => void;
10
+ export interface CommandOptions {
11
+ schema?: string;
12
+ migrations?: string;
13
+ name?: string;
14
+ dialect?: string;
15
+ url?: string;
16
+ output?: string;
17
+ table?: string;
18
+ dbSchema?: string;
19
+ baseline?: boolean;
20
+ createInitial?: boolean;
21
+ preview?: boolean;
22
+ force?: boolean;
23
+ }
24
+ export interface CommandContext {
25
+ cwd: string;
26
+ options: CommandOptions;
27
+ log: LogFn;
28
+ promptSnapshotExists?: () => Promise<"skip" | "reinitialize">;
29
+ promptFreshInit?: () => Promise<"baseline" | "create_initial">;
30
+ promptPullConfirm?: (existingFiles: string[]) => Promise<boolean>;
31
+ promptTableRename?: (from: string, to: string) => Promise<RenameChoice>;
32
+ promptColumnRename?: (table: string, from: string, to: string) => Promise<RenameChoice>;
33
+ }
34
+ export declare class CommandError extends Error {
35
+ constructor(message: string);
36
+ }
37
+ /**
38
+ * Load and validate config, returning resolved paths
39
+ */
40
+ export declare function resolveConfig(ctx: CommandContext): Promise<{
41
+ config: ZenStackKitConfig;
42
+ schemaPath: string;
43
+ outputPath: string;
44
+ dialect: "sqlite" | "postgres" | "mysql";
45
+ }>;
46
+ /**
47
+ * Validate that the schema file exists (schemaPath should be absolute)
48
+ */
49
+ export declare function validateSchemaExists(schemaPath: string): void;
50
+ /**
51
+ * Get connection URL from config based on dialect
52
+ */
53
+ export declare function getConnectionUrl(config: ZenStackKitConfig, dialect: "sqlite" | "postgres" | "mysql"): string | undefined;
54
+ /**
55
+ * migrate:generate command
56
+ */
57
+ export declare function runMigrateGenerate(ctx: CommandContext): Promise<void>;
58
+ /**
59
+ * migrate:apply command
60
+ */
61
+ export declare function runMigrateApply(ctx: CommandContext): Promise<void>;
62
+ /**
63
+ * init command
64
+ */
65
+ export declare function runInit(ctx: CommandContext): Promise<void>;
66
+ /**
67
+ * pull command
68
+ */
69
+ export declare function runPull(ctx: CommandContext): Promise<void>;
70
+ //# sourceMappingURL=commands.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../src/cli/commands.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAkBH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAE5D,MAAM,MAAM,KAAK,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;AAE9F,MAAM,WAAW,cAAc;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,cAAc,CAAC;IACxB,GAAG,EAAE,KAAK,CAAC;IACX,oBAAoB,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,cAAc,CAAC,CAAC;IAC9D,eAAe,CAAC,EAAE,MAAM,OAAO,CAAC,UAAU,GAAG,gBAAgB,CAAC,CAAC;IAC/D,iBAAiB,CAAC,EAAE,CAAC,aAAa,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAClE,iBAAiB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;IACxE,kBAAkB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;CACzF;AAED,qBAAa,YAAa,SAAQ,KAAK;gBACzB,OAAO,EAAE,MAAM;CAI5B;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC;IAChE,MAAM,EAAE,iBAAiB,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,QAAQ,GAAG,UAAU,GAAG,OAAO,CAAC;CAC1C,CAAC,CAkBD;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAI7D;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,iBAAiB,EACzB,OAAO,EAAE,QAAQ,GAAG,UAAU,GAAG,OAAO,GACvC,MAAM,GAAG,SAAS,CAKpB;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAkE3E;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAiFxE;AAED;;GAEG;AACH,wBAAsB,OAAO,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CA0FhE;AAED;;GAEG;AACH,wBAAsB,OAAO,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAiEhE"}