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.
- package/LICENSE +21 -0
- package/README.md +313 -0
- package/dist/cli/app.d.ts +12 -0
- package/dist/cli/app.d.ts.map +1 -0
- package/dist/cli/app.js +253 -0
- package/dist/cli/commands.d.ts +70 -0
- package/dist/cli/commands.d.ts.map +1 -0
- package/dist/cli/commands.js +308 -0
- package/dist/cli/index.d.ts +12 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +12 -0
- package/dist/cli/prompt-provider.d.ts +10 -0
- package/dist/cli/prompt-provider.d.ts.map +1 -0
- package/dist/cli/prompt-provider.js +41 -0
- package/dist/cli/prompts.d.ts +27 -0
- package/dist/cli/prompts.d.ts.map +1 -0
- package/dist/cli/prompts.js +133 -0
- package/dist/cli.d.ts +12 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +240 -0
- package/dist/config/index.d.ts +96 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +48 -0
- package/dist/config/loader.d.ts +11 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/loader.js +44 -0
- package/dist/config-loader.d.ts +6 -0
- package/dist/config-loader.d.ts.map +1 -0
- package/dist/config-loader.js +36 -0
- package/dist/config.d.ts +62 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +44 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/init-prompts.d.ts +13 -0
- package/dist/init-prompts.d.ts.map +1 -0
- package/dist/init-prompts.js +64 -0
- package/dist/introspect.d.ts +54 -0
- package/dist/introspect.d.ts.map +1 -0
- package/dist/introspect.js +75 -0
- package/dist/kysely-adapter.d.ts +49 -0
- package/dist/kysely-adapter.d.ts.map +1 -0
- package/dist/kysely-adapter.js +74 -0
- package/dist/migrate-apply.d.ts +18 -0
- package/dist/migrate-apply.d.ts.map +1 -0
- package/dist/migrate-apply.js +61 -0
- package/dist/migrate.d.ts +108 -0
- package/dist/migrate.d.ts.map +1 -0
- package/dist/migrate.js +127 -0
- package/dist/migrations/apply.d.ts +18 -0
- package/dist/migrations/apply.d.ts.map +1 -0
- package/dist/migrations/apply.js +61 -0
- package/dist/migrations/diff.d.ts +161 -0
- package/dist/migrations/diff.d.ts.map +1 -0
- package/dist/migrations/diff.js +620 -0
- package/dist/migrations/prisma.d.ts +193 -0
- package/dist/migrations/prisma.d.ts.map +1 -0
- package/dist/migrations/prisma.js +929 -0
- package/dist/migrations.d.ts +161 -0
- package/dist/migrations.d.ts.map +1 -0
- package/dist/migrations.js +620 -0
- package/dist/prisma-migrations.d.ts +160 -0
- package/dist/prisma-migrations.d.ts.map +1 -0
- package/dist/prisma-migrations.js +789 -0
- package/dist/prompts.d.ts +10 -0
- package/dist/prompts.d.ts.map +1 -0
- package/dist/prompts.js +41 -0
- package/dist/pull.d.ts +23 -0
- package/dist/pull.d.ts.map +1 -0
- package/dist/pull.js +424 -0
- package/dist/schema/introspect.d.ts +54 -0
- package/dist/schema/introspect.d.ts.map +1 -0
- package/dist/schema/introspect.js +75 -0
- package/dist/schema/pull.d.ts +23 -0
- package/dist/schema/pull.d.ts.map +1 -0
- package/dist/schema/pull.js +424 -0
- package/dist/schema/snapshot.d.ts +46 -0
- package/dist/schema/snapshot.d.ts.map +1 -0
- package/dist/schema/snapshot.js +278 -0
- package/dist/schema-snapshot.d.ts +45 -0
- package/dist/schema-snapshot.d.ts.map +1 -0
- package/dist/schema-snapshot.js +265 -0
- package/dist/sql/compiler.d.ts +74 -0
- package/dist/sql/compiler.d.ts.map +1 -0
- package/dist/sql/compiler.js +270 -0
- package/dist/sql/kysely-adapter.d.ts +49 -0
- package/dist/sql/kysely-adapter.d.ts.map +1 -0
- package/dist/sql/kysely-adapter.js +74 -0
- package/dist/sql-compiler.d.ts +74 -0
- package/dist/sql-compiler.d.ts.map +1 -0
- package/dist/sql-compiler.js +243 -0
- 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"}
|
package/dist/cli/app.js
ADDED
|
@@ -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"}
|