baasix 0.1.0
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 +355 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.mjs +2521 -0
- package/package.json +56 -0
- package/src/commands/extension.ts +447 -0
- package/src/commands/generate.ts +485 -0
- package/src/commands/init.ts +1409 -0
- package/src/commands/migrate.ts +573 -0
- package/src/index.ts +39 -0
- package/src/utils/api-client.ts +121 -0
- package/src/utils/get-config.ts +69 -0
- package/src/utils/get-package-info.ts +12 -0
- package/src/utils/package-manager.ts +62 -0
- package/tsconfig.json +19 -0
- package/tsup.config.ts +16 -0
|
@@ -0,0 +1,573 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import {
|
|
5
|
+
cancel,
|
|
6
|
+
confirm,
|
|
7
|
+
intro,
|
|
8
|
+
isCancel,
|
|
9
|
+
log,
|
|
10
|
+
outro,
|
|
11
|
+
select,
|
|
12
|
+
spinner,
|
|
13
|
+
text,
|
|
14
|
+
} from "@clack/prompts";
|
|
15
|
+
import chalk from "chalk";
|
|
16
|
+
import { Command } from "commander";
|
|
17
|
+
import { getConfig, type BaasixConfig } from "../utils/get-config.js";
|
|
18
|
+
import {
|
|
19
|
+
fetchMigrations,
|
|
20
|
+
runMigrations as apiRunMigrations,
|
|
21
|
+
rollbackMigrations as apiRollbackMigrations,
|
|
22
|
+
type MigrationInfo,
|
|
23
|
+
} from "../utils/api-client.js";
|
|
24
|
+
|
|
25
|
+
type MigrateAction = "status" | "run" | "create" | "rollback" | "reset" | "list";
|
|
26
|
+
|
|
27
|
+
interface MigrateOptions {
|
|
28
|
+
cwd: string;
|
|
29
|
+
url?: string;
|
|
30
|
+
action?: MigrateAction;
|
|
31
|
+
name?: string;
|
|
32
|
+
steps?: number;
|
|
33
|
+
yes?: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function migrateAction(action: MigrateAction | undefined, opts: MigrateOptions) {
|
|
37
|
+
const cwd = path.resolve(opts.cwd);
|
|
38
|
+
|
|
39
|
+
intro(chalk.bgMagenta.black(" Baasix Migrations "));
|
|
40
|
+
|
|
41
|
+
// Load config
|
|
42
|
+
const config = await getConfig(cwd);
|
|
43
|
+
if (!config && !opts.url) {
|
|
44
|
+
log.error(
|
|
45
|
+
"No Baasix configuration found. Create a .env file with BAASIX_URL or use --url flag."
|
|
46
|
+
);
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Override URL if provided
|
|
51
|
+
const effectiveConfig: BaasixConfig = config
|
|
52
|
+
? { ...config, url: opts.url || config.url }
|
|
53
|
+
: { url: opts.url || "http://localhost:8056" };
|
|
54
|
+
|
|
55
|
+
// Select action if not provided
|
|
56
|
+
let selectedAction = action || opts.action;
|
|
57
|
+
if (!selectedAction) {
|
|
58
|
+
const result = await select({
|
|
59
|
+
message: "What migration action do you want to perform?",
|
|
60
|
+
options: [
|
|
61
|
+
{
|
|
62
|
+
value: "status",
|
|
63
|
+
label: "Status",
|
|
64
|
+
hint: "Show current migration status",
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
value: "list",
|
|
68
|
+
label: "List",
|
|
69
|
+
hint: "List all available migrations",
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
value: "run",
|
|
73
|
+
label: "Run",
|
|
74
|
+
hint: "Run pending migrations",
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
value: "create",
|
|
78
|
+
label: "Create",
|
|
79
|
+
hint: "Create a new migration file",
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
value: "rollback",
|
|
83
|
+
label: "Rollback",
|
|
84
|
+
hint: "Rollback the last batch of migrations",
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
value: "reset",
|
|
88
|
+
label: "Reset",
|
|
89
|
+
hint: "Rollback all migrations (dangerous!)",
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
if (isCancel(result)) {
|
|
95
|
+
cancel("Operation cancelled");
|
|
96
|
+
process.exit(0);
|
|
97
|
+
}
|
|
98
|
+
selectedAction = result as MigrateAction;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const s = spinner();
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
switch (selectedAction) {
|
|
105
|
+
case "status":
|
|
106
|
+
await showStatus(s, effectiveConfig, cwd);
|
|
107
|
+
break;
|
|
108
|
+
|
|
109
|
+
case "list":
|
|
110
|
+
await listMigrations(s, effectiveConfig, cwd);
|
|
111
|
+
break;
|
|
112
|
+
|
|
113
|
+
case "run":
|
|
114
|
+
await runMigrations(s, effectiveConfig, cwd, opts.yes);
|
|
115
|
+
break;
|
|
116
|
+
|
|
117
|
+
case "create":
|
|
118
|
+
await createMigration(s, cwd, opts.name);
|
|
119
|
+
break;
|
|
120
|
+
|
|
121
|
+
case "rollback":
|
|
122
|
+
await rollbackMigrations(s, effectiveConfig, cwd, opts.steps || 1, opts.yes);
|
|
123
|
+
break;
|
|
124
|
+
|
|
125
|
+
case "reset":
|
|
126
|
+
await resetMigrations(s, effectiveConfig, cwd, opts.yes);
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
} catch (error) {
|
|
130
|
+
s.stop("Migration failed");
|
|
131
|
+
if (error instanceof Error) {
|
|
132
|
+
log.error(error.message);
|
|
133
|
+
} else {
|
|
134
|
+
log.error("Unknown error occurred");
|
|
135
|
+
}
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function showStatus(
|
|
141
|
+
s: ReturnType<typeof spinner>,
|
|
142
|
+
config: BaasixConfig,
|
|
143
|
+
cwd: string
|
|
144
|
+
) {
|
|
145
|
+
s.start("Checking migration status...");
|
|
146
|
+
|
|
147
|
+
// Get executed migrations from database
|
|
148
|
+
const executedMigrations = await getExecutedMigrations(config);
|
|
149
|
+
|
|
150
|
+
// Get local migration files
|
|
151
|
+
const localMigrations = await getLocalMigrations(cwd);
|
|
152
|
+
|
|
153
|
+
s.stop("Migration status retrieved");
|
|
154
|
+
|
|
155
|
+
// Calculate pending
|
|
156
|
+
const executedNames = new Set(executedMigrations.map((m) => m.name));
|
|
157
|
+
const pendingMigrations = localMigrations.filter((m) => !executedNames.has(m));
|
|
158
|
+
|
|
159
|
+
console.log();
|
|
160
|
+
console.log(chalk.bold("📊 Migration Status"));
|
|
161
|
+
console.log(chalk.dim("─".repeat(50)));
|
|
162
|
+
console.log(` Total migrations: ${chalk.cyan(localMigrations.length)}`);
|
|
163
|
+
console.log(` Executed: ${chalk.green(executedMigrations.length)}`);
|
|
164
|
+
console.log(
|
|
165
|
+
` Pending: ${pendingMigrations.length > 0 ? chalk.yellow(pendingMigrations.length) : chalk.gray("0")}`
|
|
166
|
+
);
|
|
167
|
+
console.log();
|
|
168
|
+
|
|
169
|
+
if (pendingMigrations.length > 0) {
|
|
170
|
+
console.log(chalk.bold("Pending migrations:"));
|
|
171
|
+
for (const migration of pendingMigrations) {
|
|
172
|
+
console.log(` ${chalk.yellow("○")} ${migration}`);
|
|
173
|
+
}
|
|
174
|
+
console.log();
|
|
175
|
+
console.log(
|
|
176
|
+
chalk.dim(`Run ${chalk.cyan("baasix migrate run")} to execute pending migrations.`)
|
|
177
|
+
);
|
|
178
|
+
} else {
|
|
179
|
+
console.log(chalk.green("✓ All migrations have been executed."));
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
outro("");
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async function listMigrations(
|
|
186
|
+
s: ReturnType<typeof spinner>,
|
|
187
|
+
config: BaasixConfig,
|
|
188
|
+
cwd: string
|
|
189
|
+
) {
|
|
190
|
+
s.start("Fetching migrations...");
|
|
191
|
+
|
|
192
|
+
const executedMigrations = await getExecutedMigrations(config);
|
|
193
|
+
const localMigrations = await getLocalMigrations(cwd);
|
|
194
|
+
|
|
195
|
+
s.stop("Migrations retrieved");
|
|
196
|
+
|
|
197
|
+
const executedMap = new Map(executedMigrations.map((m) => [m.name, m]));
|
|
198
|
+
|
|
199
|
+
console.log();
|
|
200
|
+
console.log(chalk.bold("📋 All Migrations"));
|
|
201
|
+
console.log(chalk.dim("─".repeat(70)));
|
|
202
|
+
|
|
203
|
+
if (localMigrations.length === 0) {
|
|
204
|
+
console.log(chalk.dim(" No migrations found."));
|
|
205
|
+
} else {
|
|
206
|
+
for (const name of localMigrations) {
|
|
207
|
+
const executed = executedMap.get(name);
|
|
208
|
+
if (executed) {
|
|
209
|
+
const executedDate = executed.executedAt
|
|
210
|
+
? new Date(executed.executedAt).toLocaleDateString()
|
|
211
|
+
: "unknown date";
|
|
212
|
+
console.log(
|
|
213
|
+
` ${chalk.green("✓")} ${name} ${chalk.dim(`(batch ${executed.batch || "?"}, ${executedDate})`)}`
|
|
214
|
+
);
|
|
215
|
+
} else {
|
|
216
|
+
console.log(` ${chalk.yellow("○")} ${name} ${chalk.dim("(pending)")}`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
console.log();
|
|
222
|
+
outro("");
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async function runMigrations(
|
|
226
|
+
s: ReturnType<typeof spinner>,
|
|
227
|
+
config: BaasixConfig,
|
|
228
|
+
cwd: string,
|
|
229
|
+
skipConfirm?: boolean
|
|
230
|
+
) {
|
|
231
|
+
s.start("Checking for pending migrations...");
|
|
232
|
+
|
|
233
|
+
const executedMigrations = await getExecutedMigrations(config);
|
|
234
|
+
const localMigrations = await getLocalMigrations(cwd);
|
|
235
|
+
|
|
236
|
+
const executedNames = new Set(executedMigrations.map((m) => m.name));
|
|
237
|
+
const pendingMigrations = localMigrations.filter((m) => !executedNames.has(m));
|
|
238
|
+
|
|
239
|
+
if (pendingMigrations.length === 0) {
|
|
240
|
+
s.stop("No pending migrations");
|
|
241
|
+
log.info("All migrations have already been executed.");
|
|
242
|
+
outro("");
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
s.stop(`Found ${pendingMigrations.length} pending migrations`);
|
|
247
|
+
|
|
248
|
+
console.log();
|
|
249
|
+
console.log(chalk.bold("Migrations to run:"));
|
|
250
|
+
for (const name of pendingMigrations) {
|
|
251
|
+
console.log(` ${chalk.cyan("→")} ${name}`);
|
|
252
|
+
}
|
|
253
|
+
console.log();
|
|
254
|
+
|
|
255
|
+
if (!skipConfirm) {
|
|
256
|
+
const confirmed = await confirm({
|
|
257
|
+
message: `Run ${pendingMigrations.length} migration(s)?`,
|
|
258
|
+
initialValue: true,
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
if (isCancel(confirmed) || !confirmed) {
|
|
262
|
+
cancel("Operation cancelled");
|
|
263
|
+
process.exit(0);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
s.start("Running migrations...");
|
|
268
|
+
|
|
269
|
+
try {
|
|
270
|
+
const result = await apiRunMigrations(config, {
|
|
271
|
+
step: pendingMigrations.length,
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
if (result.success) {
|
|
275
|
+
s.stop("Migrations executed");
|
|
276
|
+
outro(chalk.green(`✨ ${result.message}`));
|
|
277
|
+
} else {
|
|
278
|
+
s.stop("Migration failed");
|
|
279
|
+
log.error(result.message);
|
|
280
|
+
process.exit(1);
|
|
281
|
+
}
|
|
282
|
+
} catch (error) {
|
|
283
|
+
s.stop("Migration failed");
|
|
284
|
+
throw error;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
async function createMigration(
|
|
289
|
+
s: ReturnType<typeof spinner>,
|
|
290
|
+
cwd: string,
|
|
291
|
+
name?: string
|
|
292
|
+
) {
|
|
293
|
+
// Get migration name
|
|
294
|
+
let migrationName = name;
|
|
295
|
+
if (!migrationName) {
|
|
296
|
+
const result = await text({
|
|
297
|
+
message: "Migration name:",
|
|
298
|
+
placeholder: "create_users_table",
|
|
299
|
+
validate: (value) => {
|
|
300
|
+
if (!value) return "Migration name is required";
|
|
301
|
+
if (!/^[a-z0-9_]+$/i.test(value)) {
|
|
302
|
+
return "Migration name can only contain letters, numbers, and underscores";
|
|
303
|
+
}
|
|
304
|
+
return undefined;
|
|
305
|
+
},
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
if (isCancel(result)) {
|
|
309
|
+
cancel("Operation cancelled");
|
|
310
|
+
process.exit(0);
|
|
311
|
+
}
|
|
312
|
+
migrationName = result as string;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
s.start("Creating migration file...");
|
|
316
|
+
|
|
317
|
+
const migrationsDir = path.join(cwd, "migrations");
|
|
318
|
+
|
|
319
|
+
// Ensure migrations directory exists
|
|
320
|
+
if (!existsSync(migrationsDir)) {
|
|
321
|
+
await fs.mkdir(migrationsDir, { recursive: true });
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Generate timestamp prefix
|
|
325
|
+
const timestamp = new Date()
|
|
326
|
+
.toISOString()
|
|
327
|
+
.replace(/[-:T.Z]/g, "")
|
|
328
|
+
.slice(0, 14);
|
|
329
|
+
const filename = `${timestamp}_${migrationName}.js`;
|
|
330
|
+
const filepath = path.join(migrationsDir, filename);
|
|
331
|
+
|
|
332
|
+
// Check if file exists
|
|
333
|
+
if (existsSync(filepath)) {
|
|
334
|
+
s.stop("File already exists");
|
|
335
|
+
log.error(`Migration file ${filename} already exists.`);
|
|
336
|
+
process.exit(1);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Generate migration template
|
|
340
|
+
const template = `/**
|
|
341
|
+
* Migration: ${migrationName}
|
|
342
|
+
* Created: ${new Date().toISOString()}
|
|
343
|
+
*/
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Run the migration
|
|
347
|
+
* @param {import("@tspvivek/baasix-sdk").BaasixClient} baasix - Baasix client
|
|
348
|
+
*/
|
|
349
|
+
export async function up(baasix) {
|
|
350
|
+
// Example: Create a collection
|
|
351
|
+
// await baasix.schema.create("tableName", {
|
|
352
|
+
// name: "TableName",
|
|
353
|
+
// timestamps: true,
|
|
354
|
+
// fields: {
|
|
355
|
+
// id: { type: "UUID", primaryKey: true, defaultValue: { type: "UUIDV4" } },
|
|
356
|
+
// name: { type: "String", allowNull: false, values: { length: 255 } },
|
|
357
|
+
// },
|
|
358
|
+
// });
|
|
359
|
+
|
|
360
|
+
// Example: Add a field
|
|
361
|
+
// await baasix.schema.update("tableName", {
|
|
362
|
+
// fields: {
|
|
363
|
+
// newField: { type: "String", allowNull: true },
|
|
364
|
+
// },
|
|
365
|
+
// });
|
|
366
|
+
|
|
367
|
+
// Example: Insert data
|
|
368
|
+
// await baasix.items("tableName").create({ name: "Example" });
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Reverse the migration
|
|
373
|
+
* @param {import("@tspvivek/baasix-sdk").BaasixClient} baasix - Baasix client
|
|
374
|
+
*/
|
|
375
|
+
export async function down(baasix) {
|
|
376
|
+
// Reverse the changes made in up()
|
|
377
|
+
// Example: Drop a collection
|
|
378
|
+
// await baasix.schema.delete("tableName");
|
|
379
|
+
}
|
|
380
|
+
`;
|
|
381
|
+
|
|
382
|
+
await fs.writeFile(filepath, template);
|
|
383
|
+
|
|
384
|
+
s.stop("Migration created");
|
|
385
|
+
|
|
386
|
+
outro(chalk.green(`✨ Created migration: ${chalk.cyan(filename)}`));
|
|
387
|
+
console.log();
|
|
388
|
+
console.log(` Edit: ${chalk.dim(path.relative(cwd, filepath))}`);
|
|
389
|
+
console.log();
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
async function rollbackMigrations(
|
|
393
|
+
s: ReturnType<typeof spinner>,
|
|
394
|
+
config: BaasixConfig,
|
|
395
|
+
cwd: string,
|
|
396
|
+
steps: number,
|
|
397
|
+
skipConfirm?: boolean
|
|
398
|
+
) {
|
|
399
|
+
s.start("Fetching executed migrations...");
|
|
400
|
+
|
|
401
|
+
const executedMigrations = await getExecutedMigrations(config);
|
|
402
|
+
|
|
403
|
+
if (executedMigrations.length === 0) {
|
|
404
|
+
s.stop("No migrations to rollback");
|
|
405
|
+
log.info("No migrations have been executed.");
|
|
406
|
+
outro("");
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Get migrations to rollback (by batch, descending)
|
|
411
|
+
const sortedByBatch = [...executedMigrations].sort(
|
|
412
|
+
(a, b) => (b.batch || 0) - (a.batch || 0)
|
|
413
|
+
);
|
|
414
|
+
const batchesToRollback = new Set<number>();
|
|
415
|
+
const migrationsToRollback: MigrationInfo[] = [];
|
|
416
|
+
|
|
417
|
+
for (const migration of sortedByBatch) {
|
|
418
|
+
const batch = migration.batch || 0;
|
|
419
|
+
if (batchesToRollback.size < steps) {
|
|
420
|
+
batchesToRollback.add(batch);
|
|
421
|
+
}
|
|
422
|
+
if (batchesToRollback.has(batch)) {
|
|
423
|
+
migrationsToRollback.push(migration);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
s.stop(`Found ${migrationsToRollback.length} migration(s) to rollback`);
|
|
428
|
+
|
|
429
|
+
console.log();
|
|
430
|
+
console.log(chalk.bold("Migrations to rollback:"));
|
|
431
|
+
for (const migration of migrationsToRollback) {
|
|
432
|
+
console.log(
|
|
433
|
+
` ${chalk.red("←")} ${migration.name} ${chalk.dim(`(batch ${migration.batch || "?"})`)}`
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
console.log();
|
|
437
|
+
|
|
438
|
+
if (!skipConfirm) {
|
|
439
|
+
const confirmed = await confirm({
|
|
440
|
+
message: `Rollback ${migrationsToRollback.length} migration(s)?`,
|
|
441
|
+
initialValue: false,
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
if (isCancel(confirmed) || !confirmed) {
|
|
445
|
+
cancel("Operation cancelled");
|
|
446
|
+
process.exit(0);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
s.start("Rolling back migrations...");
|
|
451
|
+
|
|
452
|
+
try {
|
|
453
|
+
const result = await apiRollbackMigrations(config, {
|
|
454
|
+
step: steps,
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
if (result.success) {
|
|
458
|
+
s.stop("Rollback complete");
|
|
459
|
+
outro(chalk.green(`✨ ${result.message}`));
|
|
460
|
+
} else {
|
|
461
|
+
s.stop("Rollback failed");
|
|
462
|
+
log.error(result.message);
|
|
463
|
+
process.exit(1);
|
|
464
|
+
}
|
|
465
|
+
} catch (error) {
|
|
466
|
+
s.stop("Rollback failed");
|
|
467
|
+
throw error;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
async function resetMigrations(
|
|
472
|
+
s: ReturnType<typeof spinner>,
|
|
473
|
+
config: BaasixConfig,
|
|
474
|
+
cwd: string,
|
|
475
|
+
skipConfirm?: boolean
|
|
476
|
+
) {
|
|
477
|
+
s.start("Fetching all executed migrations...");
|
|
478
|
+
|
|
479
|
+
const executedMigrations = await getExecutedMigrations(config);
|
|
480
|
+
|
|
481
|
+
if (executedMigrations.length === 0) {
|
|
482
|
+
s.stop("No migrations to reset");
|
|
483
|
+
log.info("No migrations have been executed.");
|
|
484
|
+
outro("");
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
s.stop(`Found ${executedMigrations.length} executed migration(s)`);
|
|
489
|
+
|
|
490
|
+
console.log();
|
|
491
|
+
log.warn(chalk.red.bold("⚠️ This will rollback ALL migrations!"));
|
|
492
|
+
console.log();
|
|
493
|
+
|
|
494
|
+
if (!skipConfirm) {
|
|
495
|
+
const confirmed = await confirm({
|
|
496
|
+
message: `Reset all ${executedMigrations.length} migration(s)? This cannot be undone!`,
|
|
497
|
+
initialValue: false,
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
if (isCancel(confirmed) || !confirmed) {
|
|
501
|
+
cancel("Operation cancelled");
|
|
502
|
+
process.exit(0);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// Double confirmation for dangerous operation
|
|
506
|
+
const doubleConfirm = await text({
|
|
507
|
+
message: "Type 'reset' to confirm:",
|
|
508
|
+
placeholder: "reset",
|
|
509
|
+
validate: (value) =>
|
|
510
|
+
value !== "reset" ? "Please type 'reset' to confirm" : undefined,
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
if (isCancel(doubleConfirm)) {
|
|
514
|
+
cancel("Operation cancelled");
|
|
515
|
+
process.exit(0);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
s.start("Resetting all migrations...");
|
|
520
|
+
|
|
521
|
+
try {
|
|
522
|
+
// Rollback all batches
|
|
523
|
+
const maxBatch = Math.max(...executedMigrations.map((m) => m.batch || 0));
|
|
524
|
+
const result = await apiRollbackMigrations(config, {
|
|
525
|
+
step: maxBatch,
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
if (result.success) {
|
|
529
|
+
s.stop("Reset complete");
|
|
530
|
+
outro(chalk.green(`✨ ${result.message}`));
|
|
531
|
+
} else {
|
|
532
|
+
s.stop("Reset failed");
|
|
533
|
+
log.error(result.message);
|
|
534
|
+
process.exit(1);
|
|
535
|
+
}
|
|
536
|
+
} catch (error) {
|
|
537
|
+
s.stop("Reset failed");
|
|
538
|
+
throw error;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
async function getExecutedMigrations(config: BaasixConfig): Promise<MigrationInfo[]> {
|
|
543
|
+
try {
|
|
544
|
+
return await fetchMigrations(config);
|
|
545
|
+
} catch {
|
|
546
|
+
// If migrations endpoint doesn't exist, return empty
|
|
547
|
+
return [];
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
async function getLocalMigrations(cwd: string): Promise<string[]> {
|
|
552
|
+
const migrationsDir = path.join(cwd, "migrations");
|
|
553
|
+
|
|
554
|
+
if (!existsSync(migrationsDir)) {
|
|
555
|
+
return [];
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
const files = await fs.readdir(migrationsDir);
|
|
559
|
+
return files.filter((f) => f.endsWith(".js") || f.endsWith(".ts")).sort();
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
export const migrate = new Command("migrate")
|
|
563
|
+
.description("Run database migrations")
|
|
564
|
+
.argument(
|
|
565
|
+
"[action]",
|
|
566
|
+
"Migration action (status, list, run, create, rollback, reset)"
|
|
567
|
+
)
|
|
568
|
+
.option("-c, --cwd <path>", "Working directory", process.cwd())
|
|
569
|
+
.option("--url <url>", "Baasix server URL")
|
|
570
|
+
.option("-n, --name <name>", "Migration name (for create)")
|
|
571
|
+
.option("-s, --steps <number>", "Number of batches to rollback", parseInt)
|
|
572
|
+
.option("-y, --yes", "Skip confirmation prompts")
|
|
573
|
+
.action(migrateAction);
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { init } from "./commands/init.js";
|
|
3
|
+
import { generate } from "./commands/generate.js";
|
|
4
|
+
import { extension } from "./commands/extension.js";
|
|
5
|
+
import { migrate } from "./commands/migrate.js";
|
|
6
|
+
import { getPackageInfo } from "./utils/get-package-info.js";
|
|
7
|
+
|
|
8
|
+
import "dotenv/config";
|
|
9
|
+
|
|
10
|
+
// Handle exit signals
|
|
11
|
+
process.on("SIGINT", () => process.exit(0));
|
|
12
|
+
process.on("SIGTERM", () => process.exit(0));
|
|
13
|
+
|
|
14
|
+
async function main() {
|
|
15
|
+
const program = new Command("baasix");
|
|
16
|
+
|
|
17
|
+
let packageInfo: Record<string, unknown> = {};
|
|
18
|
+
try {
|
|
19
|
+
packageInfo = await getPackageInfo();
|
|
20
|
+
} catch {
|
|
21
|
+
// Ignore errors reading package.json
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
program
|
|
25
|
+
.addCommand(init)
|
|
26
|
+
.addCommand(generate)
|
|
27
|
+
.addCommand(extension)
|
|
28
|
+
.addCommand(migrate)
|
|
29
|
+
.version((packageInfo.version as string) || "0.1.0")
|
|
30
|
+
.description("Baasix CLI - Backend-as-a-Service toolkit")
|
|
31
|
+
.action(() => program.help());
|
|
32
|
+
|
|
33
|
+
program.parse();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
main().catch((error) => {
|
|
37
|
+
console.error("Error running Baasix CLI:", error);
|
|
38
|
+
process.exit(1);
|
|
39
|
+
});
|