@xubylele/schema-forge 1.4.0 → 1.5.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.
Files changed (2) hide show
  1. package/dist/cli.js +138 -2254
  2. package/package.json +2 -1
package/dist/cli.js CHANGED
@@ -29,7 +29,7 @@ var import_commander6 = require("commander");
29
29
  // package.json
30
30
  var package_default = {
31
31
  name: "@xubylele/schema-forge",
32
- version: "1.4.0",
32
+ version: "1.5.0",
33
33
  description: "Universal migration generator from schema DSL",
34
34
  main: "dist/cli.js",
35
35
  type: "commonjs",
@@ -69,6 +69,7 @@ var package_default = {
69
69
  node: ">=18.0.0"
70
70
  },
71
71
  dependencies: {
72
+ "@xubylele/schema-forge-core": "^1.0.4",
72
73
  boxen: "^8.0.1",
73
74
  chalk: "^5.6.2",
74
75
  commander: "^14.0.3"
@@ -85,375 +86,7 @@ var package_default = {
85
86
 
86
87
  // src/commands/diff.ts
87
88
  var import_commander = require("commander");
88
- var import_path4 = __toESM(require("path"));
89
-
90
- // src/core/normalize.ts
91
- function normalizeIdent(input) {
92
- return input.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "");
93
- }
94
- function pkName(table) {
95
- return `pk_${normalizeIdent(table)}`;
96
- }
97
- function uqName(table, column) {
98
- return `uq_${normalizeIdent(table)}_${normalizeIdent(column)}`;
99
- }
100
- function legacyPkName(table) {
101
- return `${normalizeIdent(table)}_pkey`;
102
- }
103
- function legacyUqName(table, column) {
104
- return `${normalizeIdent(table)}_${normalizeIdent(column)}_key`;
105
- }
106
- function normalizeSpacesOutsideQuotes(value) {
107
- let result = "";
108
- let inSingleQuote = false;
109
- let inDoubleQuote = false;
110
- let pendingSpace = false;
111
- for (const char of value) {
112
- if (char === "'" && !inDoubleQuote) {
113
- if (pendingSpace && result.length > 0 && result[result.length - 1] !== " ") {
114
- result += " ";
115
- }
116
- pendingSpace = false;
117
- inSingleQuote = !inSingleQuote;
118
- result += char;
119
- continue;
120
- }
121
- if (char === '"' && !inSingleQuote) {
122
- if (pendingSpace && result.length > 0 && result[result.length - 1] !== " ") {
123
- result += " ";
124
- }
125
- pendingSpace = false;
126
- inDoubleQuote = !inDoubleQuote;
127
- result += char;
128
- continue;
129
- }
130
- if (!inSingleQuote && !inDoubleQuote && /\s/.test(char)) {
131
- pendingSpace = true;
132
- continue;
133
- }
134
- if (pendingSpace && result.length > 0 && result[result.length - 1] !== " ") {
135
- result += " ";
136
- }
137
- pendingSpace = false;
138
- result += char;
139
- }
140
- return result.trim();
141
- }
142
- function normalizeKnownFunctionsOutsideQuotes(value) {
143
- let result = "";
144
- let inSingleQuote = false;
145
- let inDoubleQuote = false;
146
- let buffer = "";
147
- function flushBuffer() {
148
- if (!buffer) {
149
- return;
150
- }
151
- result += buffer.replace(/\bnow\s*\(\s*\)/gi, "now()").replace(/\bgen_random_uuid\s*\(\s*\)/gi, "gen_random_uuid()");
152
- buffer = "";
153
- }
154
- for (const char of value) {
155
- if (char === "'" && !inDoubleQuote) {
156
- flushBuffer();
157
- inSingleQuote = !inSingleQuote;
158
- result += char;
159
- continue;
160
- }
161
- if (char === '"' && !inSingleQuote) {
162
- flushBuffer();
163
- inDoubleQuote = !inDoubleQuote;
164
- result += char;
165
- continue;
166
- }
167
- if (inSingleQuote || inDoubleQuote) {
168
- result += char;
169
- continue;
170
- }
171
- buffer += char;
172
- }
173
- flushBuffer();
174
- return result;
175
- }
176
- function normalizePunctuationOutsideQuotes(value) {
177
- let result = "";
178
- let inSingleQuote = false;
179
- let inDoubleQuote = false;
180
- for (let index = 0; index < value.length; index++) {
181
- const char = value[index];
182
- if (char === "'" && !inDoubleQuote) {
183
- inSingleQuote = !inSingleQuote;
184
- result += char;
185
- continue;
186
- }
187
- if (char === '"' && !inSingleQuote) {
188
- inDoubleQuote = !inDoubleQuote;
189
- result += char;
190
- continue;
191
- }
192
- if (!inSingleQuote && !inDoubleQuote && (char === "(" || char === ")")) {
193
- while (result.endsWith(" ")) {
194
- result = result.slice(0, -1);
195
- }
196
- result += char;
197
- let lookahead = index + 1;
198
- while (lookahead < value.length && value[lookahead] === " ") {
199
- lookahead++;
200
- }
201
- index = lookahead - 1;
202
- continue;
203
- }
204
- if (!inSingleQuote && !inDoubleQuote && char === ",") {
205
- while (result.endsWith(" ")) {
206
- result = result.slice(0, -1);
207
- }
208
- result += ", ";
209
- let lookahead = index + 1;
210
- while (lookahead < value.length && value[lookahead] === " ") {
211
- lookahead++;
212
- }
213
- index = lookahead - 1;
214
- continue;
215
- }
216
- result += char;
217
- }
218
- return result;
219
- }
220
- function normalizeDefault(expr) {
221
- if (expr === void 0 || expr === null) {
222
- return null;
223
- }
224
- const trimmed = expr.trim();
225
- if (trimmed.length === 0) {
226
- return null;
227
- }
228
- const normalizedSpacing = normalizeSpacesOutsideQuotes(trimmed);
229
- const normalizedPunctuation = normalizePunctuationOutsideQuotes(normalizedSpacing);
230
- return normalizeKnownFunctionsOutsideQuotes(normalizedPunctuation);
231
- }
232
-
233
- // src/core/diff.ts
234
- function getTableNamesFromState(state) {
235
- return new Set(Object.keys(state.tables));
236
- }
237
- function getTableNamesFromSchema(schema) {
238
- return new Set(Object.keys(schema.tables));
239
- }
240
- function getColumnNamesFromState(stateColumns) {
241
- return new Set(Object.keys(stateColumns));
242
- }
243
- function getColumnNamesFromSchema(dbColumns) {
244
- return new Set(dbColumns.map((column) => column.name));
245
- }
246
- function getSortedNames(names) {
247
- return Array.from(names).sort((a, b) => a.localeCompare(b));
248
- }
249
- function normalizeColumnType(type) {
250
- return type.toLowerCase().trim().replace(/\s+/g, " ").replace(/\s*\(\s*/g, "(").replace(/\s*,\s*/g, ",").replace(/\s*\)\s*/g, ")");
251
- }
252
- function resolveStatePrimaryKey(table) {
253
- return table.primaryKey ?? Object.entries(table.columns).find(([, column]) => column.primaryKey)?.[0] ?? null;
254
- }
255
- function resolveSchemaPrimaryKey(table) {
256
- return table.primaryKey ?? table.columns.find((column) => column.primaryKey)?.name ?? null;
257
- }
258
- function normalizeNullable(nullable) {
259
- return nullable ?? true;
260
- }
261
- function diffSchemas(oldState, newSchema) {
262
- const operations = [];
263
- const oldTableNames = getTableNamesFromState(oldState);
264
- const newTableNames = getTableNamesFromSchema(newSchema);
265
- const sortedNewTableNames = getSortedNames(newTableNames);
266
- const sortedOldTableNames = getSortedNames(oldTableNames);
267
- for (const tableName of sortedNewTableNames) {
268
- if (!oldTableNames.has(tableName)) {
269
- operations.push({
270
- kind: "create_table",
271
- table: newSchema.tables[tableName]
272
- });
273
- }
274
- }
275
- const commonTableNames = sortedNewTableNames.filter(
276
- (tableName) => oldTableNames.has(tableName)
277
- );
278
- for (const tableName of commonTableNames) {
279
- const newTable = newSchema.tables[tableName];
280
- const oldTable = oldState.tables[tableName];
281
- if (!newTable || !oldTable) {
282
- continue;
283
- }
284
- for (const column of newTable.columns) {
285
- const previousColumn = oldTable.columns[column.name];
286
- if (!previousColumn) {
287
- continue;
288
- }
289
- const previousType = normalizeColumnType(previousColumn.type);
290
- const currentType = normalizeColumnType(column.type);
291
- if (previousType !== currentType) {
292
- operations.push({
293
- kind: "column_type_changed",
294
- tableName,
295
- columnName: column.name,
296
- fromType: previousColumn.type,
297
- toType: column.type
298
- });
299
- }
300
- }
301
- }
302
- for (const tableName of commonTableNames) {
303
- const newTable = newSchema.tables[tableName];
304
- const oldTable = oldState.tables[tableName];
305
- if (!newTable || !oldTable) {
306
- continue;
307
- }
308
- const previousPrimaryKey = resolveStatePrimaryKey(oldTable);
309
- const currentPrimaryKey = resolveSchemaPrimaryKey(newTable);
310
- if (previousPrimaryKey !== null && previousPrimaryKey !== currentPrimaryKey) {
311
- operations.push({
312
- kind: "drop_primary_key_constraint",
313
- tableName
314
- });
315
- }
316
- }
317
- for (const tableName of commonTableNames) {
318
- const newTable = newSchema.tables[tableName];
319
- const oldTable = oldState.tables[tableName];
320
- if (!newTable || !oldTable) {
321
- continue;
322
- }
323
- for (const column of newTable.columns) {
324
- const previousColumn = oldTable.columns[column.name];
325
- if (!previousColumn) {
326
- continue;
327
- }
328
- const previousUnique = previousColumn.unique ?? false;
329
- const currentUnique = column.unique ?? false;
330
- if (previousUnique !== currentUnique) {
331
- operations.push({
332
- kind: "column_unique_changed",
333
- tableName,
334
- columnName: column.name,
335
- from: previousUnique,
336
- to: currentUnique
337
- });
338
- }
339
- }
340
- }
341
- for (const tableName of commonTableNames) {
342
- const newTable = newSchema.tables[tableName];
343
- const oldTable = oldState.tables[tableName];
344
- if (!newTable || !oldTable) {
345
- continue;
346
- }
347
- for (const column of newTable.columns) {
348
- const previousColumn = oldTable.columns[column.name];
349
- if (!previousColumn) {
350
- continue;
351
- }
352
- const previousNullable = normalizeNullable(previousColumn.nullable);
353
- const currentNullable = normalizeNullable(column.nullable);
354
- if (previousNullable !== currentNullable) {
355
- operations.push({
356
- kind: "column_nullability_changed",
357
- tableName,
358
- columnName: column.name,
359
- from: previousNullable,
360
- to: currentNullable
361
- });
362
- }
363
- }
364
- }
365
- for (const tableName of commonTableNames) {
366
- const newTable = newSchema.tables[tableName];
367
- const oldTable = oldState.tables[tableName];
368
- if (!newTable || !oldTable) {
369
- continue;
370
- }
371
- for (const column of newTable.columns) {
372
- const previousColumn = oldTable.columns[column.name];
373
- if (!previousColumn) {
374
- continue;
375
- }
376
- const previousDefault = normalizeDefault(previousColumn.default);
377
- const currentDefault = normalizeDefault(column.default);
378
- if (previousDefault !== currentDefault) {
379
- operations.push({
380
- kind: "column_default_changed",
381
- tableName,
382
- columnName: column.name,
383
- fromDefault: previousDefault,
384
- toDefault: currentDefault
385
- });
386
- }
387
- }
388
- }
389
- for (const tableName of commonTableNames) {
390
- const newTable = newSchema.tables[tableName];
391
- const oldTable = oldState.tables[tableName];
392
- if (!newTable || !oldTable) {
393
- continue;
394
- }
395
- const oldColumnNames = getColumnNamesFromState(oldTable.columns);
396
- for (const column of newTable.columns) {
397
- if (!oldColumnNames.has(column.name)) {
398
- operations.push({
399
- kind: "add_column",
400
- tableName,
401
- column
402
- });
403
- }
404
- }
405
- }
406
- for (const tableName of commonTableNames) {
407
- const newTable = newSchema.tables[tableName];
408
- const oldTable = oldState.tables[tableName];
409
- if (!newTable || !oldTable) {
410
- continue;
411
- }
412
- const previousPrimaryKey = resolveStatePrimaryKey(oldTable);
413
- const currentPrimaryKey = resolveSchemaPrimaryKey(newTable);
414
- if (currentPrimaryKey !== null && previousPrimaryKey !== currentPrimaryKey) {
415
- operations.push({
416
- kind: "add_primary_key_constraint",
417
- tableName,
418
- columnName: currentPrimaryKey
419
- });
420
- }
421
- }
422
- for (const tableName of commonTableNames) {
423
- const newTable = newSchema.tables[tableName];
424
- const oldTable = oldState.tables[tableName];
425
- if (!newTable || !oldTable) {
426
- continue;
427
- }
428
- const newColumnNames = getColumnNamesFromSchema(newTable.columns);
429
- for (const columnName of Object.keys(oldTable.columns)) {
430
- if (!newColumnNames.has(columnName)) {
431
- operations.push({
432
- kind: "drop_column",
433
- tableName,
434
- columnName
435
- });
436
- }
437
- }
438
- }
439
- for (const tableName of sortedOldTableNames) {
440
- if (!newTableNames.has(tableName)) {
441
- operations.push({
442
- kind: "drop_table",
443
- tableName
444
- });
445
- }
446
- }
447
- return { operations };
448
- }
449
-
450
- // src/core/errors.ts
451
- var SchemaValidationError = class extends Error {
452
- constructor(message) {
453
- super(message);
454
- this.name = "SchemaValidationError";
455
- }
456
- };
89
+ var import_path3 = __toESM(require("path"));
457
90
 
458
91
  // src/core/fs.ts
459
92
  var import_fs = require("fs");
@@ -509,329 +142,6 @@ async function writeJsonFile(filePath, data) {
509
142
  throw new Error(`Failed to write JSON file ${filePath}: ${error2}`);
510
143
  }
511
144
  }
512
- async function findFiles(dirPath, pattern) {
513
- const results = [];
514
- try {
515
- const items = await import_fs.promises.readdir(dirPath, { withFileTypes: true });
516
- for (const item of items) {
517
- const fullPath = import_path.default.join(dirPath, item.name);
518
- if (item.isDirectory()) {
519
- const subResults = await findFiles(fullPath, pattern);
520
- results.push(...subResults);
521
- } else if (item.isFile() && pattern.test(item.name)) {
522
- results.push(fullPath);
523
- }
524
- }
525
- } catch (error2) {
526
- throw new Error(`Failed to find files in ${dirPath}: ${error2}`);
527
- }
528
- return results;
529
- }
530
-
531
- // src/utils/output.ts
532
- var import_boxen = __toESM(require("boxen"));
533
- var import_chalk = require("chalk");
534
- var isInteractive = Boolean(process.stdout?.isTTY);
535
- var colorsEnabled = isInteractive && process.env.FORCE_COLOR !== "0" && !("NO_COLOR" in process.env);
536
- var color = new import_chalk.Chalk({ level: colorsEnabled ? 3 : 0 });
537
- var theme = {
538
- primary: color.cyanBright,
539
- success: color.hex("#00FF88"),
540
- warning: color.hex("#FFD166"),
541
- error: color.hex("#EF476F"),
542
- accent: color.magentaBright
543
- };
544
- function success(message) {
545
- const text = theme.success(`[OK] ${message}`);
546
- if (!isInteractive) {
547
- console.log(text);
548
- return;
549
- }
550
- try {
551
- console.log(
552
- (0, import_boxen.default)(text, {
553
- padding: 1,
554
- borderColor: "cyan",
555
- borderStyle: "round"
556
- })
557
- );
558
- } catch {
559
- console.log(text);
560
- }
561
- }
562
- function info(message) {
563
- console.log(theme.primary(message));
564
- }
565
- function warning(message) {
566
- console.warn(theme.warning(`[WARN] ${message}`));
567
- }
568
- function error(message) {
569
- console.error(theme.error(`[ERROR] ${message}`));
570
- }
571
-
572
- // src/core/parser.ts
573
- var SchemaParser = class {
574
- /**
575
- * Parse a schema from a JSON file
576
- */
577
- async parseSchemaFile(filePath) {
578
- try {
579
- const schema = await readJsonFile(filePath, {});
580
- return this.normalizeSchema(schema);
581
- } catch (error2) {
582
- throw new Error(`Failed to parse schema file ${filePath}: ${error2}`);
583
- }
584
- }
585
- /**
586
- * Parse multiple schema files from a directory
587
- */
588
- async parseSchemaDirectory(dirPath) {
589
- const schemaFiles = await findFiles(dirPath, /\.schema\.json$/);
590
- const schemas = [];
591
- for (const file of schemaFiles) {
592
- try {
593
- const schema = await this.parseSchemaFile(file);
594
- schemas.push(schema);
595
- } catch (error2) {
596
- const reason = error2 instanceof Error ? error2.message : String(error2);
597
- warning(`Could not parse ${file}: ${reason}`);
598
- }
599
- }
600
- return schemas;
601
- }
602
- /**
603
- * Merge multiple schemas into one
604
- */
605
- mergeSchemas(schemas) {
606
- if (schemas.length === 0) {
607
- throw new Error("Cannot merge empty schema array");
608
- }
609
- const baseSchema = schemas[0];
610
- const mergedTables = [];
611
- for (const schema of schemas) {
612
- for (const table of schema.tables) {
613
- const existingIndex = mergedTables.findIndex((t) => t.name === table.name);
614
- if (existingIndex >= 0) {
615
- warning(`Duplicate table '${table.name}' found, using first occurrence`);
616
- } else {
617
- mergedTables.push(table);
618
- }
619
- }
620
- }
621
- return {
622
- version: baseSchema.version,
623
- database: baseSchema.database,
624
- tables: mergedTables
625
- };
626
- }
627
- /**
628
- * Normalize schema to ensure consistent structure
629
- */
630
- normalizeSchema(schema) {
631
- return {
632
- version: schema.version || "1.0.0",
633
- database: schema.database || "postgres",
634
- tables: schema.tables.map((table) => ({
635
- ...table,
636
- fields: table.fields.map((field) => ({
637
- ...field,
638
- required: field.required ?? false,
639
- unique: field.unique ?? false
640
- })),
641
- indexes: table.indexes || [],
642
- constraints: table.constraints || []
643
- }))
644
- };
645
- }
646
- /**
647
- * Convert schema to JSON string
648
- */
649
- schemaToJson(schema, pretty = true) {
650
- return pretty ? JSON.stringify(schema, null, 2) : JSON.stringify(schema);
651
- }
652
- /**
653
- * Parse schema from JSON string
654
- */
655
- parseSchemaString(jsonString) {
656
- try {
657
- const schema = JSON.parse(jsonString);
658
- return this.normalizeSchema(schema);
659
- } catch (error2) {
660
- throw new Error(`Failed to parse schema JSON: ${error2}`);
661
- }
662
- }
663
- };
664
- var defaultParser = new SchemaParser();
665
- function parseSchema(source) {
666
- const lines = source.split("\n");
667
- const tables = {};
668
- let currentLine = 0;
669
- const validBaseColumnTypes = /* @__PURE__ */ new Set([
670
- "uuid",
671
- "varchar",
672
- "text",
673
- "int",
674
- "bigint",
675
- "boolean",
676
- "timestamptz",
677
- "date"
678
- ]);
679
- function normalizeColumnType3(type) {
680
- return type.toLowerCase().trim().replace(/\s+/g, " ").replace(/\s*\(\s*/g, "(").replace(/\s*,\s*/g, ",").replace(/\s*\)\s*/g, ")");
681
- }
682
- function isValidColumnType2(type) {
683
- const normalizedType = normalizeColumnType3(type);
684
- if (validBaseColumnTypes.has(normalizedType)) {
685
- return true;
686
- }
687
- return /^varchar\(\d+\)$/.test(normalizedType) || /^numeric\(\d+,\d+\)$/.test(normalizedType);
688
- }
689
- function cleanLine(line) {
690
- const commentIndex = line.search(/(?:\/\/|#)/);
691
- if (commentIndex !== -1) {
692
- line = line.substring(0, commentIndex);
693
- }
694
- return line.trim();
695
- }
696
- function parseForeignKey(fkRef, lineNum) {
697
- const parts = fkRef.split(".");
698
- if (parts.length !== 2 || !parts[0] || !parts[1]) {
699
- throw new Error(`Line ${lineNum}: Invalid foreign key format '${fkRef}'. Expected format: table.column`);
700
- }
701
- return {
702
- table: parts[0],
703
- column: parts[1]
704
- };
705
- }
706
- function parseColumn(line, lineNum) {
707
- const tokens = line.split(/\s+/).filter((t) => t.length > 0);
708
- const modifiers = /* @__PURE__ */ new Set(["pk", "unique", "nullable", "default", "fk"]);
709
- if (tokens.length < 2) {
710
- throw new Error(`Line ${lineNum}: Invalid column definition. Expected: <name> <type> [modifiers...]`);
711
- }
712
- const colName = tokens[0];
713
- const colType = normalizeColumnType3(tokens[1]);
714
- if (!isValidColumnType2(colType)) {
715
- throw new Error(
716
- `Line ${lineNum}: Invalid column type '${tokens[1]}'. Valid types: ${Array.from(validBaseColumnTypes).join(", ")}, varchar(n), numeric(p,s)`
717
- );
718
- }
719
- const column = {
720
- name: colName,
721
- type: colType,
722
- nullable: true
723
- };
724
- let i = 2;
725
- while (i < tokens.length) {
726
- const modifier = tokens[i];
727
- switch (modifier) {
728
- case "pk":
729
- column.primaryKey = true;
730
- i++;
731
- break;
732
- case "unique":
733
- column.unique = true;
734
- i++;
735
- break;
736
- case "nullable":
737
- column.nullable = true;
738
- i++;
739
- break;
740
- case "not":
741
- if (tokens[i + 1] !== "null") {
742
- throw new Error(`Line ${lineNum}: Unknown modifier 'not'`);
743
- }
744
- column.nullable = false;
745
- i += 2;
746
- break;
747
- case "default":
748
- i++;
749
- if (i >= tokens.length) {
750
- throw new Error(`Line ${lineNum}: 'default' modifier requires a value`);
751
- }
752
- {
753
- const defaultTokens = [];
754
- while (i < tokens.length && !modifiers.has(tokens[i])) {
755
- defaultTokens.push(tokens[i]);
756
- i++;
757
- }
758
- if (defaultTokens.length === 0) {
759
- throw new Error(`Line ${lineNum}: 'default' modifier requires a value`);
760
- }
761
- column.default = defaultTokens.join(" ");
762
- }
763
- break;
764
- case "fk":
765
- i++;
766
- if (i >= tokens.length) {
767
- throw new Error(`Line ${lineNum}: 'fk' modifier requires a table.column reference`);
768
- }
769
- column.foreignKey = parseForeignKey(tokens[i], lineNum);
770
- i++;
771
- break;
772
- default:
773
- throw new Error(`Line ${lineNum}: Unknown modifier '${modifier}'`);
774
- }
775
- }
776
- return column;
777
- }
778
- function parseTableBlock(startLine) {
779
- const firstLine = cleanLine(lines[startLine]);
780
- const match = firstLine.match(/^table\s+(\w+)\s*\{?\s*$/);
781
- if (!match) {
782
- throw new Error(`Line ${startLine + 1}: Invalid table definition. Expected: table <name> {`);
783
- }
784
- const tableName = match[1];
785
- if (tables[tableName]) {
786
- throw new Error(`Line ${startLine + 1}: Duplicate table definition '${tableName}'`);
787
- }
788
- const columns = [];
789
- let lineIdx = startLine + 1;
790
- let foundClosingBrace = false;
791
- while (lineIdx < lines.length) {
792
- const cleaned = cleanLine(lines[lineIdx]);
793
- if (!cleaned) {
794
- lineIdx++;
795
- continue;
796
- }
797
- if (cleaned === "}") {
798
- foundClosingBrace = true;
799
- break;
800
- }
801
- try {
802
- const column = parseColumn(cleaned, lineIdx + 1);
803
- columns.push(column);
804
- } catch (error2) {
805
- throw error2;
806
- }
807
- lineIdx++;
808
- }
809
- if (!foundClosingBrace) {
810
- throw new Error(`Line ${startLine + 1}: Table '${tableName}' block not closed (missing '}')`);
811
- }
812
- const primaryKeyColumn = columns.find((column) => column.primaryKey)?.name ?? null;
813
- tables[tableName] = {
814
- name: tableName,
815
- columns,
816
- ...primaryKeyColumn !== null && { primaryKey: primaryKeyColumn }
817
- };
818
- return lineIdx;
819
- }
820
- while (currentLine < lines.length) {
821
- const cleaned = cleanLine(lines[currentLine]);
822
- if (!cleaned) {
823
- currentLine++;
824
- continue;
825
- }
826
- if (cleaned.startsWith("table ")) {
827
- currentLine = parseTableBlock(currentLine);
828
- } else {
829
- throw new Error(`Line ${currentLine + 1}: Unexpected content '${cleaned}'. Expected table definition.`);
830
- }
831
- currentLine++;
832
- }
833
- return { tables };
834
- }
835
145
 
836
146
  // src/core/paths.ts
837
147
  var import_path2 = __toESM(require("path"));
@@ -856,568 +166,129 @@ function getStatePath(root, config) {
856
166
  return import_path2.default.join(schemaForgeDir, fileName);
857
167
  }
858
168
 
859
- // src/core/state-manager.ts
860
- var import_path3 = __toESM(require("path"));
861
- var StateManager = class {
862
- constructor(root = process.cwd()) {
863
- this.config = null;
864
- this.root = root;
865
- }
866
- /**
867
- * Initialize a new SchemaForge project
868
- */
869
- async initializeProject(directory = ".", force = false) {
870
- const configPath = import_path3.default.join(directory, "schemaforge.config.json");
871
- if (await fileExists(configPath) && !force) {
872
- throw new Error("SchemaForge project already initialized. Use --force to overwrite.");
873
- }
874
- const defaultConfig = {
875
- version: "1.0.0",
876
- database: "postgres",
877
- schemaDir: "schemas",
878
- outputDir: "output",
879
- migrationDir: "migrations"
880
- };
881
- await writeJsonFile(configPath, defaultConfig);
882
- await ensureDir(import_path3.default.join(directory, defaultConfig.schemaDir));
883
- await ensureDir(import_path3.default.join(directory, defaultConfig.outputDir));
884
- await ensureDir(import_path3.default.join(directory, defaultConfig.migrationDir));
885
- const exampleSchema = {
886
- version: "1.0.0",
887
- database: "postgres",
888
- tables: [
889
- {
890
- name: "users",
891
- fields: [
892
- { name: "id", type: "uuid", required: true, unique: true },
893
- { name: "email", type: "string", required: true, unique: true, length: 255 },
894
- { name: "name", type: "string", required: true, length: 255 },
895
- { name: "created_at", type: "datetime", required: true }
896
- ],
897
- indexes: [
898
- { name: "idx_users_email", fields: ["email"], unique: true }
899
- ]
900
- }
901
- ]
902
- };
903
- const exampleSchemaPath = import_path3.default.join(
904
- directory,
905
- defaultConfig.schemaDir,
906
- "example.schema.json"
907
- );
908
- await writeJsonFile(exampleSchemaPath, exampleSchema);
909
- this.config = defaultConfig;
910
- }
911
- /**
912
- * Load configuration from file
913
- */
914
- async loadConfig(directory = ".") {
915
- const configPath = import_path3.default.join(directory, "schemaforge.config.json");
916
- if (!await fileExists(configPath)) {
917
- throw new Error('SchemaForge project not initialized. Run "schemaforge init" first.');
918
- }
919
- this.config = await readJsonFile(configPath, {});
920
- return this.config;
921
- }
922
- /**
923
- * Save configuration to file
924
- */
925
- async saveConfig(config, directory = ".") {
926
- const configPath = import_path3.default.join(directory, "schemaforge.config.json");
927
- await writeJsonFile(configPath, config);
928
- this.config = config;
929
- }
930
- /**
931
- * Get current configuration
932
- */
933
- getConfig() {
934
- return this.config;
935
- }
936
- /**
937
- * Update configuration
938
- */
939
- updateConfig(updates) {
940
- if (!this.config) {
941
- throw new Error("No configuration loaded");
942
- }
943
- this.config = { ...this.config, ...updates };
169
+ // src/core/provider.ts
170
+ var DEFAULT_PROVIDER = "postgres";
171
+ function resolveProvider(provider) {
172
+ if (!provider) {
173
+ return { provider: DEFAULT_PROVIDER, usedDefault: true };
944
174
  }
945
- /**
946
- * Check if project is initialized
947
- */
948
- async isInitialized(directory = ".") {
949
- const configPath = import_path3.default.join(directory, "schemaforge.config.json");
950
- return await fileExists(configPath);
951
- }
952
- /**
953
- * Get schema directory path
954
- */
955
- getSchemaDir() {
956
- if (!this.config) {
957
- throw new Error("No configuration loaded");
958
- }
959
- return import_path3.default.join(this.root, this.config.schemaDir);
960
- }
961
- /**
962
- * Get output directory path
963
- */
964
- getOutputDir() {
965
- if (!this.config) {
966
- throw new Error("No configuration loaded");
967
- }
968
- return import_path3.default.join(this.root, this.config.outputDir);
969
- }
970
- /**
971
- * Get migration directory path
972
- */
973
- getMigrationDir() {
974
- if (!this.config) {
975
- throw new Error("No configuration loaded");
976
- }
977
- return import_path3.default.join(this.root, this.config.migrationDir);
978
- }
979
- };
980
- async function schemaToState(schema) {
981
- const tables = {};
982
- for (const [tableName, table] of Object.entries(schema.tables)) {
983
- const columns = {};
984
- const primaryKeyColumn = table.primaryKey ?? table.columns.find((column) => column.primaryKey)?.name ?? null;
985
- for (const column of table.columns) {
986
- columns[column.name] = {
987
- type: column.type,
988
- ...column.primaryKey !== void 0 && { primaryKey: column.primaryKey },
989
- ...column.unique !== void 0 && { unique: column.unique },
990
- nullable: column.nullable ?? true,
991
- ...column.default !== void 0 && { default: column.default },
992
- ...column.foreignKey !== void 0 && { foreignKey: column.foreignKey }
993
- };
994
- }
995
- tables[tableName] = {
996
- columns,
997
- ...primaryKeyColumn !== null && { primaryKey: primaryKeyColumn }
998
- };
999
- }
1000
- return {
1001
- version: 1,
1002
- tables
1003
- };
1004
- }
1005
- async function loadState(statePath) {
1006
- return await readJsonFile(statePath, { version: 1, tables: {} });
1007
- }
1008
- async function saveState(statePath, state) {
1009
- const dirPath = import_path3.default.dirname(statePath);
1010
- await ensureDir(dirPath);
1011
- await writeJsonFile(statePath, state);
175
+ return { provider, usedDefault: false };
1012
176
  }
1013
- var defaultStateManager = new StateManager();
1014
177
 
1015
- // src/core/validator.ts
1016
- var SchemaValidator = class {
1017
- /**
1018
- * Validate a complete schema
1019
- */
1020
- validateSchema(schema) {
1021
- const errors = [];
1022
- if (!schema.version) {
1023
- errors.push({
1024
- path: "schema.version",
1025
- message: "Schema version is required",
1026
- severity: "error"
1027
- });
1028
- }
1029
- if (!schema.database) {
1030
- errors.push({
1031
- path: "schema.database",
1032
- message: "Database type is required",
1033
- severity: "error"
1034
- });
1035
- }
1036
- if (!schema.tables || schema.tables.length === 0) {
1037
- errors.push({
1038
- path: "schema.tables",
1039
- message: "Schema must contain at least one table",
1040
- severity: "error"
1041
- });
1042
- }
1043
- if (schema.tables) {
1044
- const tableNames = /* @__PURE__ */ new Set();
1045
- for (let i = 0; i < schema.tables.length; i++) {
1046
- const table = schema.tables[i];
1047
- const tableErrors = this.validateTable(table, i);
1048
- errors.push(...tableErrors);
1049
- if (tableNames.has(table.name)) {
1050
- errors.push({
1051
- path: `schema.tables[${i}].name`,
1052
- message: `Duplicate table name: ${table.name}`,
1053
- severity: "error"
1054
- });
1055
- }
1056
- tableNames.add(table.name);
1057
- }
1058
- errors.push(...this.validateReferences(schema));
1059
- }
1060
- return {
1061
- valid: errors.filter((e) => e.severity === "error").length === 0,
1062
- errors
1063
- };
1064
- }
1065
- /**
1066
- * Validate a table
1067
- */
1068
- validateTable(table, tableIndex) {
1069
- const errors = [];
1070
- const basePath = `schema.tables[${tableIndex}]`;
1071
- if (!table.name || table.name.trim() === "") {
1072
- errors.push({
1073
- path: `${basePath}.name`,
1074
- message: "Table name is required",
1075
- severity: "error"
1076
- });
1077
- }
1078
- if (table.name && !/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(table.name)) {
1079
- errors.push({
1080
- path: `${basePath}.name`,
1081
- message: `Invalid table name '${table.name}': must start with letter or underscore and contain only alphanumeric characters and underscores`,
1082
- severity: "error"
1083
- });
1084
- }
1085
- if (!table.fields || table.fields.length === 0) {
1086
- errors.push({
1087
- path: `${basePath}.fields`,
1088
- message: `Table '${table.name}' must have at least one field`,
1089
- severity: "error"
1090
- });
1091
- }
1092
- if (table.fields) {
1093
- const fieldNames = /* @__PURE__ */ new Set();
1094
- for (let i = 0; i < table.fields.length; i++) {
1095
- const field = table.fields[i];
1096
- const fieldErrors = this.validateField(field, basePath, i);
1097
- errors.push(...fieldErrors);
1098
- if (fieldNames.has(field.name)) {
1099
- errors.push({
1100
- path: `${basePath}.fields[${i}].name`,
1101
- message: `Duplicate field name: ${field.name}`,
1102
- severity: "error"
1103
- });
1104
- }
1105
- fieldNames.add(field.name);
1106
- }
1107
- }
1108
- return errors;
1109
- }
1110
- /**
1111
- * Validate a field
1112
- */
1113
- validateField(field, tablePath, fieldIndex) {
1114
- const errors = [];
1115
- const basePath = `${tablePath}.fields[${fieldIndex}]`;
1116
- if (!field.name || field.name.trim() === "") {
1117
- errors.push({
1118
- path: `${basePath}.name`,
1119
- message: "Field name is required",
1120
- severity: "error"
1121
- });
1122
- }
1123
- if (field.name && !/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(field.name)) {
1124
- errors.push({
1125
- path: `${basePath}.name`,
1126
- message: `Invalid field name '${field.name}': must start with letter or underscore and contain only alphanumeric characters and underscores`,
1127
- severity: "error"
1128
- });
1129
- }
1130
- if (!field.type) {
1131
- errors.push({
1132
- path: `${basePath}.type`,
1133
- message: "Field type is required",
1134
- severity: "error"
1135
- });
1136
- }
1137
- if (field.type === "enum") {
1138
- if (!field.enumValues || field.enumValues.length === 0) {
1139
- errors.push({
1140
- path: `${basePath}.enumValues`,
1141
- message: "Enum type requires enumValues array",
1142
- severity: "error"
1143
- });
1144
- }
1145
- }
1146
- if (field.type === "string" && field.length && field.length <= 0) {
1147
- errors.push({
1148
- path: `${basePath}.length`,
1149
- message: "String length must be greater than 0",
1150
- severity: "error"
1151
- });
1152
- }
1153
- return errors;
1154
- }
1155
- /**
1156
- * Validate foreign key references
1157
- */
1158
- validateReferences(schema) {
1159
- const errors = [];
1160
- const tableNames = new Set(schema.tables.map((t) => t.name));
1161
- for (let i = 0; i < schema.tables.length; i++) {
1162
- const table = schema.tables[i];
1163
- for (let j = 0; j < table.fields.length; j++) {
1164
- const field = table.fields[j];
1165
- if (field.references) {
1166
- const refTable = field.references.table;
1167
- const refField = field.references.field;
1168
- if (!tableNames.has(refTable)) {
1169
- errors.push({
1170
- path: `schema.tables[${i}].fields[${j}].references.table`,
1171
- message: `Referenced table '${refTable}' does not exist`,
1172
- severity: "error"
1173
- });
1174
- } else {
1175
- const referencedTable = schema.tables.find((t) => t.name === refTable);
1176
- if (referencedTable) {
1177
- const referencedField = referencedTable.fields.find((f) => f.name === refField);
1178
- if (!referencedField) {
1179
- errors.push({
1180
- path: `schema.tables[${i}].fields[${j}].references.field`,
1181
- message: `Referenced field '${refField}' does not exist in table '${refTable}'`,
1182
- severity: "error"
1183
- });
1184
- }
1185
- }
1186
- }
1187
- }
1188
- }
1189
- }
1190
- return errors;
1191
- }
1192
- };
1193
- var defaultValidator = new SchemaValidator();
1194
- var VALID_BASE_COLUMN_TYPES = [
1195
- "uuid",
1196
- "varchar",
1197
- "text",
1198
- "int",
1199
- "bigint",
1200
- "boolean",
1201
- "timestamptz",
1202
- "date"
1203
- ];
1204
- function isValidColumnType(type) {
1205
- const normalizedType = type.toLowerCase().trim().replace(/\s+/g, " ").replace(/\s*\(\s*/g, "(").replace(/\s*,\s*/g, ",").replace(/\s*\)\s*/g, ")");
1206
- if (VALID_BASE_COLUMN_TYPES.includes(normalizedType)) {
1207
- return true;
1208
- }
1209
- return /^varchar\(\d+\)$/.test(normalizedType) || /^numeric\(\d+,\d+\)$/.test(normalizedType);
1210
- }
1211
- function validateSchema(schema) {
1212
- validateDuplicateTables(schema);
1213
- for (const tableName in schema.tables) {
1214
- const table = schema.tables[tableName];
1215
- validateTableColumns(tableName, table, schema.tables);
178
+ // src/domain.ts
179
+ var corePromise;
180
+ async function loadCore() {
181
+ if (!corePromise) {
182
+ corePromise = import("@xubylele/schema-forge-core");
1216
183
  }
184
+ return corePromise;
1217
185
  }
1218
- function validateDuplicateTables(schema) {
1219
- const tableNames = Object.keys(schema.tables);
1220
- const seen = /* @__PURE__ */ new Set();
1221
- for (const tableName of tableNames) {
1222
- if (seen.has(tableName)) {
1223
- throw new Error(`Duplicate table: '${tableName}'`);
1224
- }
1225
- seen.add(tableName);
1226
- }
186
+ async function parseSchema(source) {
187
+ const core = await loadCore();
188
+ return core.parseSchema(source);
1227
189
  }
1228
- function validateTableColumns(tableName, table, allTables) {
1229
- const columnNames = /* @__PURE__ */ new Set();
1230
- const primaryKeyColumns = [];
1231
- for (const column of table.columns) {
1232
- if (columnNames.has(column.name)) {
1233
- throw new Error(`Table '${tableName}': duplicate column '${column.name}'`);
1234
- }
1235
- columnNames.add(column.name);
1236
- if (column.primaryKey) {
1237
- primaryKeyColumns.push(column.name);
1238
- }
1239
- if (!isValidColumnType(column.type)) {
1240
- throw new Error(
1241
- `Table '${tableName}', column '${column.name}': type '${column.type}' is not valid. Supported types: ${VALID_BASE_COLUMN_TYPES.join(", ")}, varchar(n), numeric(p,s)`
1242
- );
1243
- }
1244
- if (column.foreignKey) {
1245
- const fkTable = column.foreignKey.table;
1246
- const fkColumn = column.foreignKey.column;
1247
- if (!allTables[fkTable]) {
1248
- throw new Error(
1249
- `Table '${tableName}', column '${column.name}': referenced table '${fkTable}' does not exist`
1250
- );
1251
- }
1252
- const referencedTable = allTables[fkTable];
1253
- const columnExists = referencedTable.columns.some((col) => col.name === fkColumn);
1254
- if (!columnExists) {
1255
- throw new Error(
1256
- `Table '${tableName}', column '${column.name}': table '${fkTable}' does not have column '${fkColumn}'`
1257
- );
1258
- }
1259
- }
1260
- }
1261
- if (primaryKeyColumns.length > 1) {
1262
- throw new Error(`Table '${tableName}': can only have one primary key (found ${primaryKeyColumns.length})`);
1263
- }
1264
- const normalizedPrimaryKey = table.primaryKey ?? primaryKeyColumns[0] ?? null;
1265
- if (table.primaryKey && !columnNames.has(table.primaryKey)) {
1266
- throw new Error(
1267
- `Table '${tableName}': primary key column '${table.primaryKey}' does not exist`
1268
- );
1269
- }
1270
- if (table.primaryKey && primaryKeyColumns.length === 1 && primaryKeyColumns[0] !== table.primaryKey) {
1271
- throw new Error(
1272
- `Table '${tableName}': column-level primary key '${primaryKeyColumns[0]}' does not match table primary key '${table.primaryKey}'`
1273
- );
1274
- }
1275
- if (normalizedPrimaryKey) {
1276
- const pkMatches = table.columns.filter((column) => column.name === normalizedPrimaryKey);
1277
- if (pkMatches.length !== 1) {
1278
- throw new Error(
1279
- `Table '${tableName}': primary key column '${normalizedPrimaryKey}' is invalid`
1280
- );
1281
- }
1282
- }
190
+ async function validateSchema(schema) {
191
+ const core = await loadCore();
192
+ core.validateSchema(schema);
1283
193
  }
1284
-
1285
- // src/generator/sql-generator.ts
1286
- function generateSql(diff, provider, sqlConfig) {
1287
- const statements = [];
1288
- for (const operation of diff.operations) {
1289
- const sql = generateOperation(operation, provider, sqlConfig);
1290
- if (sql) {
1291
- statements.push(sql);
1292
- }
1293
- }
1294
- return statements.join("\n\n");
194
+ async function diffSchemas(previousState, currentSchema) {
195
+ const core = await loadCore();
196
+ return core.diffSchemas(previousState, currentSchema);
1295
197
  }
1296
- function generateOperation(operation, provider, sqlConfig) {
1297
- switch (operation.kind) {
1298
- case "create_table":
1299
- return generateCreateTable(operation.table, provider, sqlConfig);
1300
- case "drop_table":
1301
- return generateDropTable(operation.tableName);
1302
- case "column_type_changed":
1303
- return generateAlterColumnType(
1304
- operation.tableName,
1305
- operation.columnName,
1306
- operation.toType
1307
- );
1308
- case "column_nullability_changed":
1309
- return generateAlterColumnNullability(
1310
- operation.tableName,
1311
- operation.columnName,
1312
- operation.to
1313
- );
1314
- case "add_column":
1315
- return generateAddColumn(operation.tableName, operation.column, provider, sqlConfig);
1316
- case "column_default_changed":
1317
- return generateAlterColumnDefault(
1318
- operation.tableName,
1319
- operation.columnName,
1320
- operation.toDefault
1321
- );
1322
- case "drop_column":
1323
- return generateDropColumn(operation.tableName, operation.columnName);
1324
- case "column_unique_changed":
1325
- return operation.to ? generateAddUniqueConstraint(operation.tableName, operation.columnName) : generateDropUniqueConstraint(operation.tableName, operation.columnName);
1326
- case "drop_primary_key_constraint":
1327
- return generateDropPrimaryKeyConstraint(operation.tableName);
1328
- case "add_primary_key_constraint":
1329
- return generateAddPrimaryKeyConstraint(operation.tableName, operation.columnName);
1330
- }
198
+ async function generateSql(diff, provider, config) {
199
+ const core = await loadCore();
200
+ return core.generateSql(diff, provider, config);
1331
201
  }
1332
- function generateCreateTable(table, provider, sqlConfig) {
1333
- const columnDefs = table.columns.map(
1334
- (col) => generateColumnDefinition(col, provider, sqlConfig)
1335
- );
1336
- const lines = ["CREATE TABLE " + table.name + " ("];
1337
- columnDefs.forEach((colDef, index) => {
1338
- const isLast = index === columnDefs.length - 1;
1339
- lines.push(" " + colDef + (isLast ? "" : ","));
1340
- });
1341
- lines.push(");");
1342
- return lines.join("\n");
202
+ async function schemaToState(schema) {
203
+ const core = await loadCore();
204
+ return core.schemaToState(schema);
1343
205
  }
1344
- function generateColumnDefinition(column, provider, sqlConfig) {
1345
- const parts = [column.name, column.type];
1346
- if (column.foreignKey) {
1347
- parts.push(
1348
- `references ${column.foreignKey.table}(${column.foreignKey.column})`
1349
- );
1350
- }
1351
- if (column.primaryKey) {
1352
- parts.push("primary key");
1353
- }
1354
- if (column.unique) {
1355
- parts.push("unique");
1356
- }
1357
- if (column.nullable === false) {
1358
- parts.push("not null");
1359
- }
1360
- if (column.default !== void 0) {
1361
- parts.push("default " + column.default);
1362
- } else if (column.type === "uuid" && column.primaryKey && provider === "supabase") {
1363
- parts.push("default gen_random_uuid()");
1364
- }
1365
- return parts.join(" ");
206
+ async function loadState(statePath) {
207
+ const core = await loadCore();
208
+ return core.loadState(statePath);
1366
209
  }
1367
- function generateDropTable(tableName) {
1368
- return `DROP TABLE ${tableName};`;
210
+ async function saveState(statePath, state) {
211
+ const core = await loadCore();
212
+ return core.saveState(statePath, state);
1369
213
  }
1370
- function generateAddColumn(tableName, column, provider, sqlConfig) {
1371
- const colDef = generateColumnDefinition(column, provider, sqlConfig);
1372
- return `ALTER TABLE ${tableName} ADD COLUMN ${colDef};`;
214
+ async function validateSchemaChanges(previousState, currentSchema) {
215
+ const core = await loadCore();
216
+ return core.validateSchemaChanges(previousState, currentSchema);
1373
217
  }
1374
- function generateDropColumn(tableName, columnName) {
1375
- return `ALTER TABLE ${tableName} DROP COLUMN ${columnName};`;
218
+ async function toValidationReport(findings) {
219
+ const core = await loadCore();
220
+ return core.toValidationReport(findings);
1376
221
  }
1377
- function generateAlterColumnType(tableName, columnName, newType) {
1378
- return `ALTER TABLE ${tableName} ALTER COLUMN ${columnName} TYPE ${newType} USING ${columnName}::${newType};`;
222
+ async function parseMigrationSql(sql) {
223
+ const core = await loadCore();
224
+ return core.parseMigrationSql(sql);
1379
225
  }
1380
- function generateAddUniqueConstraint(tableName, columnName) {
1381
- const deterministicConstraintName = uqName(tableName, columnName);
1382
- return `ALTER TABLE ${tableName} ADD CONSTRAINT ${deterministicConstraintName} UNIQUE (${columnName});`;
226
+ async function applySqlOps(ops) {
227
+ const core = await loadCore();
228
+ return core.applySqlOps(ops);
1383
229
  }
1384
- function generateDropUniqueConstraint(tableName, columnName) {
1385
- const deterministicConstraintName = uqName(tableName, columnName);
1386
- const fallbackConstraintName = legacyUqName(tableName, columnName);
1387
- return generateDropConstraintStatements(tableName, [deterministicConstraintName, fallbackConstraintName]);
230
+ async function schemaToDsl(schema) {
231
+ const core = await loadCore();
232
+ return core.schemaToDsl(schema);
1388
233
  }
1389
- function generateDropPrimaryKeyConstraint(tableName) {
1390
- const deterministicConstraintName = pkName(tableName);
1391
- const fallbackConstraintName = legacyPkName(tableName);
1392
- return generateDropConstraintStatements(tableName, [deterministicConstraintName, fallbackConstraintName]);
234
+ async function loadMigrationSqlInput(inputPath) {
235
+ const core = await loadCore();
236
+ return core.loadMigrationSqlInput(inputPath);
1393
237
  }
1394
- function generateAddPrimaryKeyConstraint(tableName, columnName) {
1395
- const deterministicConstraintName = pkName(tableName);
1396
- return `ALTER TABLE ${tableName} ADD CONSTRAINT ${deterministicConstraintName} PRIMARY KEY (${columnName});`;
238
+ async function createSchemaValidationError(message) {
239
+ const core = await loadCore();
240
+ return new core.SchemaValidationError(message);
1397
241
  }
1398
- function generateDropConstraintStatements(tableName, constraintNames) {
1399
- const uniqueConstraintNames = Array.from(new Set(constraintNames));
1400
- return uniqueConstraintNames.map(
1401
- (constraintName) => `ALTER TABLE ${tableName} DROP CONSTRAINT IF EXISTS ${constraintName};`
1402
- ).join("\n");
242
+ async function isSchemaValidationError(error2) {
243
+ const core = await loadCore();
244
+ return error2 instanceof core.SchemaValidationError;
1403
245
  }
1404
- function generateAlterColumnDefault(tableName, columnName, newDefault) {
1405
- if (newDefault === null) {
1406
- return `ALTER TABLE ${tableName} ALTER COLUMN ${columnName} DROP DEFAULT;`;
246
+
247
+ // src/utils/output.ts
248
+ var import_boxen = __toESM(require("boxen"));
249
+ var import_chalk = require("chalk");
250
+ var isInteractive = Boolean(process.stdout?.isTTY);
251
+ var colorsEnabled = isInteractive && process.env.FORCE_COLOR !== "0" && !("NO_COLOR" in process.env);
252
+ var color = new import_chalk.Chalk({ level: colorsEnabled ? 3 : 0 });
253
+ var theme = {
254
+ primary: color.cyanBright,
255
+ success: color.hex("#00FF88"),
256
+ warning: color.hex("#FFD166"),
257
+ error: color.hex("#EF476F"),
258
+ accent: color.magentaBright
259
+ };
260
+ function success(message) {
261
+ const text = theme.success(`[OK] ${message}`);
262
+ if (!isInteractive) {
263
+ console.log(text);
264
+ return;
1407
265
  }
1408
- return `ALTER TABLE ${tableName} ALTER COLUMN ${columnName} SET DEFAULT ${newDefault};`;
1409
- }
1410
- function generateAlterColumnNullability(tableName, columnName, toNullable) {
1411
- if (toNullable) {
1412
- return `ALTER TABLE ${tableName} ALTER COLUMN ${columnName} DROP NOT NULL;`;
266
+ try {
267
+ console.log(
268
+ (0, import_boxen.default)(text, {
269
+ padding: 1,
270
+ borderColor: "cyan",
271
+ borderStyle: "round"
272
+ })
273
+ );
274
+ } catch {
275
+ console.log(text);
1413
276
  }
1414
- return `ALTER TABLE ${tableName} ALTER COLUMN ${columnName} SET NOT NULL;`;
277
+ }
278
+ function info(message) {
279
+ console.log(theme.primary(message));
280
+ }
281
+ function warning(message) {
282
+ console.warn(theme.warning(`[WARN] ${message}`));
283
+ }
284
+ function error(message) {
285
+ console.error(theme.error(`[ERROR] ${message}`));
1415
286
  }
1416
287
 
1417
288
  // src/commands/diff.ts
1418
289
  var REQUIRED_CONFIG_FIELDS = ["schemaFile", "stateFile"];
1419
290
  function resolveConfigPath(root, targetPath) {
1420
- return import_path4.default.isAbsolute(targetPath) ? targetPath : import_path4.default.join(root, targetPath);
291
+ return import_path3.default.isAbsolute(targetPath) ? targetPath : import_path3.default.join(root, targetPath);
1421
292
  }
1422
293
  async function runDiff() {
1423
294
  const root = getProjectRoot();
@@ -1434,33 +305,30 @@ async function runDiff() {
1434
305
  }
1435
306
  const schemaPath = resolveConfigPath(root, config.schemaFile);
1436
307
  const statePath = resolveConfigPath(root, config.stateFile);
1437
- if (config.provider && config.provider !== "postgres" && config.provider !== "supabase") {
1438
- throw new Error(`Unsupported provider '${config.provider}'.`);
1439
- }
1440
- const provider = config.provider ?? "postgres";
308
+ const { provider } = resolveProvider(config.provider);
1441
309
  const schemaSource = await readTextFile(schemaPath);
1442
- const schema = parseSchema(schemaSource);
310
+ const schema = await parseSchema(schemaSource);
1443
311
  try {
1444
- validateSchema(schema);
312
+ await validateSchema(schema);
1445
313
  } catch (error2) {
1446
314
  if (error2 instanceof Error) {
1447
- throw new SchemaValidationError(error2.message);
315
+ throw await createSchemaValidationError(error2.message);
1448
316
  }
1449
317
  throw error2;
1450
318
  }
1451
319
  const previousState = await loadState(statePath);
1452
- const diff = diffSchemas(previousState, schema);
320
+ const diff = await diffSchemas(previousState, schema);
1453
321
  if (diff.operations.length === 0) {
1454
322
  success("No changes detected");
1455
323
  return;
1456
324
  }
1457
- const sql = generateSql(diff, provider, config.sql);
325
+ const sql = await generateSql(diff, provider, config.sql);
1458
326
  console.log(sql);
1459
327
  }
1460
328
 
1461
329
  // src/commands/generate.ts
1462
330
  var import_commander2 = require("commander");
1463
- var import_path5 = __toESM(require("path"));
331
+ var import_path4 = __toESM(require("path"));
1464
332
 
1465
333
  // src/core/utils.ts
1466
334
  function nowTimestamp() {
@@ -1479,7 +347,7 @@ var REQUIRED_CONFIG_FIELDS2 = [
1479
347
  "outputDir"
1480
348
  ];
1481
349
  function resolveConfigPath2(root, targetPath) {
1482
- return import_path5.default.isAbsolute(targetPath) ? targetPath : import_path5.default.join(root, targetPath);
350
+ return import_path4.default.isAbsolute(targetPath) ? targetPath : import_path4.default.join(root, targetPath);
1483
351
  }
1484
352
  async function runGenerate(options) {
1485
353
  const root = getProjectRoot();
@@ -1497,36 +365,33 @@ async function runGenerate(options) {
1497
365
  const schemaPath = resolveConfigPath2(root, config.schemaFile);
1498
366
  const statePath = resolveConfigPath2(root, config.stateFile);
1499
367
  const outputDir = resolveConfigPath2(root, config.outputDir);
1500
- if (config.provider && config.provider !== "postgres" && config.provider !== "supabase") {
1501
- throw new Error(`Unsupported provider '${config.provider}'.`);
1502
- }
1503
- const provider = config.provider ?? "postgres";
1504
- if (!config.provider) {
368
+ const { provider, usedDefault } = resolveProvider(config.provider);
369
+ if (usedDefault) {
1505
370
  info("Provider not set; defaulting to postgres.");
1506
371
  }
1507
372
  info("Generating SQL...");
1508
373
  const schemaSource = await readTextFile(schemaPath);
1509
- const schema = parseSchema(schemaSource);
374
+ const schema = await parseSchema(schemaSource);
1510
375
  try {
1511
- validateSchema(schema);
376
+ await validateSchema(schema);
1512
377
  } catch (error2) {
1513
378
  if (error2 instanceof Error) {
1514
- throw new SchemaValidationError(error2.message);
379
+ throw await createSchemaValidationError(error2.message);
1515
380
  }
1516
381
  throw error2;
1517
382
  }
1518
383
  const previousState = await loadState(statePath);
1519
- const diff = diffSchemas(previousState, schema);
384
+ const diff = await diffSchemas(previousState, schema);
1520
385
  if (diff.operations.length === 0) {
1521
386
  info("No changes detected");
1522
387
  return;
1523
388
  }
1524
- const sql = generateSql(diff, provider, config.sql);
389
+ const sql = await generateSql(diff, provider, config.sql);
1525
390
  const timestamp = nowTimestamp();
1526
391
  const slug = slugifyName(options.name ?? "migration");
1527
392
  const fileName = `${timestamp}-${slug}.sql`;
1528
393
  await ensureDir(outputDir);
1529
- const migrationPath = import_path5.default.join(outputDir, fileName);
394
+ const migrationPath = import_path4.default.join(outputDir, fileName);
1530
395
  await writeTextFile(migrationPath, sql + "\n");
1531
396
  const nextState = await schemaToState(schema);
1532
397
  await saveState(statePath, nextState);
@@ -1535,840 +400,9 @@ async function runGenerate(options) {
1535
400
 
1536
401
  // src/commands/import.ts
1537
402
  var import_commander3 = require("commander");
1538
- var import_path7 = __toESM(require("path"));
1539
-
1540
- // src/core/sql/apply-ops.ts
1541
- function toSchemaColumn(column) {
1542
- return {
1543
- name: column.name,
1544
- type: column.type,
1545
- nullable: column.nullable,
1546
- ...column.default !== void 0 ? { default: column.default } : {},
1547
- ...column.unique !== void 0 ? { unique: column.unique } : {},
1548
- ...column.primaryKey !== void 0 ? { primaryKey: column.primaryKey } : {}
1549
- };
1550
- }
1551
- function applySingleColumnConstraint(table, constraint) {
1552
- if (constraint.columns.length !== 1) {
1553
- return false;
1554
- }
1555
- const targetColumn = table.columns.find((column) => column.name === constraint.columns[0]);
1556
- if (!targetColumn) {
1557
- return false;
1558
- }
1559
- if (constraint.type === "PRIMARY_KEY") {
1560
- table.primaryKey = targetColumn.name;
1561
- targetColumn.primaryKey = true;
1562
- targetColumn.nullable = false;
1563
- return true;
1564
- }
1565
- targetColumn.unique = true;
1566
- return true;
1567
- }
1568
- function clearConstraintByName(table, name) {
1569
- if (name.endsWith("_pkey") || name.startsWith("pk_")) {
1570
- if (table.primaryKey) {
1571
- const pkColumn = table.columns.find((column) => column.name === table.primaryKey);
1572
- if (pkColumn) {
1573
- pkColumn.primaryKey = false;
1574
- }
1575
- table.primaryKey = null;
1576
- }
1577
- return;
1578
- }
1579
- if (name.endsWith("_key") || name.startsWith("uq_")) {
1580
- for (const column of table.columns) {
1581
- if (column.unique) {
1582
- column.unique = false;
1583
- }
1584
- }
1585
- }
1586
- }
1587
- function getOrCreateTable(tables, name) {
1588
- if (!tables[name]) {
1589
- tables[name] = { name, columns: [] };
1590
- }
1591
- return tables[name];
1592
- }
1593
- function applySqlOps(ops) {
1594
- const tables = {};
1595
- const warnings = [];
1596
- for (const op of ops) {
1597
- switch (op.kind) {
1598
- case "CREATE_TABLE": {
1599
- const table = {
1600
- name: op.table,
1601
- columns: op.columns.map(toSchemaColumn)
1602
- };
1603
- for (const column of table.columns) {
1604
- if (column.primaryKey) {
1605
- table.primaryKey = column.name;
1606
- }
1607
- }
1608
- for (const constraint of op.constraints) {
1609
- const applied = applySingleColumnConstraint(table, constraint);
1610
- if (!applied) {
1611
- warnings.push({
1612
- statement: `CREATE TABLE ${op.table}`,
1613
- reason: `Constraint ${constraint.type}${constraint.name ? ` (${constraint.name})` : ""} is unsupported for schema reconstruction`
1614
- });
1615
- }
1616
- }
1617
- tables[op.table] = table;
1618
- break;
1619
- }
1620
- case "ADD_COLUMN": {
1621
- const table = getOrCreateTable(tables, op.table);
1622
- table.columns = table.columns.filter((column) => column.name !== op.column.name);
1623
- table.columns.push(toSchemaColumn(op.column));
1624
- if (op.column.primaryKey) {
1625
- table.primaryKey = op.column.name;
1626
- }
1627
- break;
1628
- }
1629
- case "ALTER_COLUMN_TYPE": {
1630
- const table = tables[op.table];
1631
- if (!table) {
1632
- break;
1633
- }
1634
- const column = table.columns.find((item) => item.name === op.column);
1635
- if (column) {
1636
- column.type = op.toType;
1637
- }
1638
- break;
1639
- }
1640
- case "SET_NOT_NULL": {
1641
- const table = tables[op.table];
1642
- const column = table?.columns.find((item) => item.name === op.column);
1643
- if (column) {
1644
- column.nullable = false;
1645
- }
1646
- break;
1647
- }
1648
- case "DROP_NOT_NULL": {
1649
- const table = tables[op.table];
1650
- const column = table?.columns.find((item) => item.name === op.column);
1651
- if (column) {
1652
- column.nullable = true;
1653
- }
1654
- break;
1655
- }
1656
- case "SET_DEFAULT": {
1657
- const table = tables[op.table];
1658
- const column = table?.columns.find((item) => item.name === op.column);
1659
- if (column) {
1660
- column.default = op.expr;
1661
- }
1662
- break;
1663
- }
1664
- case "DROP_DEFAULT": {
1665
- const table = tables[op.table];
1666
- const column = table?.columns.find((item) => item.name === op.column);
1667
- if (column) {
1668
- column.default = null;
1669
- }
1670
- break;
1671
- }
1672
- case "ADD_CONSTRAINT": {
1673
- const table = tables[op.table];
1674
- if (!table) {
1675
- break;
1676
- }
1677
- const applied = applySingleColumnConstraint(table, op.constraint);
1678
- if (!applied) {
1679
- warnings.push({
1680
- statement: `ALTER TABLE ${op.table} ADD CONSTRAINT ${op.constraint.name ?? "<unnamed>"}`,
1681
- reason: `Constraint ${op.constraint.type} is unsupported for schema reconstruction`
1682
- });
1683
- }
1684
- break;
1685
- }
1686
- case "DROP_CONSTRAINT": {
1687
- const table = tables[op.table];
1688
- if (!table) {
1689
- break;
1690
- }
1691
- clearConstraintByName(table, op.name);
1692
- break;
1693
- }
1694
- case "DROP_COLUMN": {
1695
- const table = tables[op.table];
1696
- if (!table) {
1697
- break;
1698
- }
1699
- table.columns = table.columns.filter((column) => column.name !== op.column);
1700
- if (table.primaryKey === op.column) {
1701
- table.primaryKey = null;
1702
- }
1703
- break;
1704
- }
1705
- case "DROP_TABLE": {
1706
- delete tables[op.table];
1707
- break;
1708
- }
1709
- }
1710
- }
1711
- const schema = { tables };
1712
- return { schema, warnings };
1713
- }
1714
-
1715
- // src/core/sql/load-migrations.ts
1716
- var import_fs6 = require("fs");
1717
- var import_path6 = __toESM(require("path"));
1718
- async function loadMigrationSqlInput(inputPath) {
1719
- const stats = await import_fs6.promises.stat(inputPath);
1720
- if (stats.isFile()) {
1721
- if (!inputPath.toLowerCase().endsWith(".sql")) {
1722
- throw new Error(`Input file must be a .sql file: ${inputPath}`);
1723
- }
1724
- return [{ filePath: inputPath, sql: await readTextFile(inputPath) }];
1725
- }
1726
- if (!stats.isDirectory()) {
1727
- throw new Error(`Input path must be a .sql file or directory: ${inputPath}`);
1728
- }
1729
- const sqlFiles = await findFiles(inputPath, /\.sql$/i);
1730
- sqlFiles.sort((left, right) => import_path6.default.basename(left).localeCompare(import_path6.default.basename(right)));
1731
- const result = [];
1732
- for (const filePath of sqlFiles) {
1733
- result.push({
1734
- filePath,
1735
- sql: await readTextFile(filePath)
1736
- });
1737
- }
1738
- return result;
1739
- }
1740
-
1741
- // src/core/sql/split-statements.ts
1742
- function splitSqlStatements(sql) {
1743
- const statements = [];
1744
- let current = "";
1745
- let inSingleQuote = false;
1746
- let inDoubleQuote = false;
1747
- let inLineComment = false;
1748
- let inBlockComment = false;
1749
- let dollarTag = null;
1750
- let index = 0;
1751
- while (index < sql.length) {
1752
- const char = sql[index];
1753
- const next = index + 1 < sql.length ? sql[index + 1] : "";
1754
- if (inLineComment) {
1755
- current += char;
1756
- if (char === "\n") {
1757
- inLineComment = false;
1758
- }
1759
- index++;
1760
- continue;
1761
- }
1762
- if (inBlockComment) {
1763
- current += char;
1764
- if (char === "*" && next === "/") {
1765
- current += next;
1766
- inBlockComment = false;
1767
- index += 2;
1768
- continue;
1769
- }
1770
- index++;
1771
- continue;
1772
- }
1773
- if (!inSingleQuote && !inDoubleQuote && dollarTag === null) {
1774
- if (char === "-" && next === "-") {
1775
- current += char + next;
1776
- inLineComment = true;
1777
- index += 2;
1778
- continue;
1779
- }
1780
- if (char === "/" && next === "*") {
1781
- current += char + next;
1782
- inBlockComment = true;
1783
- index += 2;
1784
- continue;
1785
- }
1786
- }
1787
- if (!inDoubleQuote && dollarTag === null && char === "'") {
1788
- current += char;
1789
- if (inSingleQuote && next === "'") {
1790
- current += next;
1791
- index += 2;
1792
- continue;
1793
- }
1794
- inSingleQuote = !inSingleQuote;
1795
- index++;
1796
- continue;
1797
- }
1798
- if (!inSingleQuote && dollarTag === null && char === '"') {
1799
- current += char;
1800
- if (inDoubleQuote && next === '"') {
1801
- current += next;
1802
- index += 2;
1803
- continue;
1804
- }
1805
- inDoubleQuote = !inDoubleQuote;
1806
- index++;
1807
- continue;
1808
- }
1809
- if (!inSingleQuote && !inDoubleQuote) {
1810
- if (dollarTag === null && char === "$") {
1811
- const remainder = sql.slice(index);
1812
- const match = remainder.match(/^\$[a-zA-Z_][a-zA-Z0-9_]*\$|^\$\$/);
1813
- if (match) {
1814
- dollarTag = match[0];
1815
- current += match[0];
1816
- index += match[0].length;
1817
- continue;
1818
- }
1819
- }
1820
- if (dollarTag !== null && sql.startsWith(dollarTag, index)) {
1821
- current += dollarTag;
1822
- index += dollarTag.length;
1823
- dollarTag = null;
1824
- continue;
1825
- }
1826
- }
1827
- if (!inSingleQuote && !inDoubleQuote && dollarTag === null && char === ";") {
1828
- const statement = current.trim();
1829
- if (statement.length > 0) {
1830
- statements.push(statement);
1831
- }
1832
- current = "";
1833
- index++;
1834
- continue;
1835
- }
1836
- current += char;
1837
- index++;
1838
- }
1839
- const tail = current.trim();
1840
- if (tail.length > 0) {
1841
- statements.push(tail);
1842
- }
1843
- return statements;
1844
- }
1845
-
1846
- // src/core/sql/parse-migration.ts
1847
- var COLUMN_CONSTRAINT_KEYWORDS = /* @__PURE__ */ new Set([
1848
- "primary",
1849
- "unique",
1850
- "not",
1851
- "null",
1852
- "default",
1853
- "constraint",
1854
- "references",
1855
- "check"
1856
- ]);
1857
- function normalizeSqlType(type) {
1858
- return type.trim().toLowerCase().replace(/\s+/g, " ").replace(/\s*\(\s*/g, "(").replace(/\s*,\s*/g, ",").replace(/\s*\)\s*/g, ")");
1859
- }
1860
- function unquoteIdentifier(value) {
1861
- const trimmed = value.trim();
1862
- if (trimmed.startsWith('"') && trimmed.endsWith('"') && trimmed.length >= 2) {
1863
- return trimmed.slice(1, -1).replace(/""/g, '"');
1864
- }
1865
- return trimmed;
1866
- }
1867
- function normalizeIdentifier(identifier) {
1868
- const parts = identifier.trim().split(".").map((part) => unquoteIdentifier(part)).filter((part) => part.length > 0);
1869
- const leaf = parts.length > 0 ? parts[parts.length - 1] : identifier.trim();
1870
- return leaf.toLowerCase();
1871
- }
1872
- function removeSqlComments(statement) {
1873
- let result = "";
1874
- let inSingleQuote = false;
1875
- let inDoubleQuote = false;
1876
- let inLineComment = false;
1877
- let inBlockComment = false;
1878
- for (let index = 0; index < statement.length; index++) {
1879
- const char = statement[index];
1880
- const next = index + 1 < statement.length ? statement[index + 1] : "";
1881
- if (inLineComment) {
1882
- if (char === "\n") {
1883
- inLineComment = false;
1884
- result += char;
1885
- }
1886
- continue;
1887
- }
1888
- if (inBlockComment) {
1889
- if (char === "*" && next === "/") {
1890
- inBlockComment = false;
1891
- index++;
1892
- }
1893
- continue;
1894
- }
1895
- if (!inSingleQuote && !inDoubleQuote) {
1896
- if (char === "-" && next === "-") {
1897
- inLineComment = true;
1898
- index++;
1899
- continue;
1900
- }
1901
- if (char === "/" && next === "*") {
1902
- inBlockComment = true;
1903
- index++;
1904
- continue;
1905
- }
1906
- }
1907
- if (char === "'" && !inDoubleQuote) {
1908
- if (inSingleQuote && next === "'") {
1909
- result += "''";
1910
- index++;
1911
- continue;
1912
- }
1913
- inSingleQuote = !inSingleQuote;
1914
- result += char;
1915
- continue;
1916
- }
1917
- if (char === '"' && !inSingleQuote) {
1918
- if (inDoubleQuote && next === '"') {
1919
- result += '""';
1920
- index++;
1921
- continue;
1922
- }
1923
- inDoubleQuote = !inDoubleQuote;
1924
- result += char;
1925
- continue;
1926
- }
1927
- result += char;
1928
- }
1929
- return result.trim();
1930
- }
1931
- function splitTopLevelComma(input) {
1932
- const parts = [];
1933
- let current = "";
1934
- let depth = 0;
1935
- let inSingleQuote = false;
1936
- let inDoubleQuote = false;
1937
- for (let index = 0; index < input.length; index++) {
1938
- const char = input[index];
1939
- const next = index + 1 < input.length ? input[index + 1] : "";
1940
- if (char === "'" && !inDoubleQuote) {
1941
- current += char;
1942
- if (inSingleQuote && next === "'") {
1943
- current += next;
1944
- index++;
1945
- continue;
1946
- }
1947
- inSingleQuote = !inSingleQuote;
1948
- continue;
1949
- }
1950
- if (char === '"' && !inSingleQuote) {
1951
- current += char;
1952
- if (inDoubleQuote && next === '"') {
1953
- current += next;
1954
- index++;
1955
- continue;
1956
- }
1957
- inDoubleQuote = !inDoubleQuote;
1958
- continue;
1959
- }
1960
- if (!inSingleQuote && !inDoubleQuote) {
1961
- if (char === "(") {
1962
- depth++;
1963
- } else if (char === ")") {
1964
- depth = Math.max(0, depth - 1);
1965
- } else if (char === "," && depth === 0) {
1966
- const segment = current.trim();
1967
- if (segment.length > 0) {
1968
- parts.push(segment);
1969
- }
1970
- current = "";
1971
- continue;
1972
- }
1973
- }
1974
- current += char;
1975
- }
1976
- const tail = current.trim();
1977
- if (tail.length > 0) {
1978
- parts.push(tail);
1979
- }
1980
- return parts;
1981
- }
1982
- function tokenize(segment) {
1983
- const tokens = [];
1984
- let current = "";
1985
- let depth = 0;
1986
- let inSingleQuote = false;
1987
- let inDoubleQuote = false;
1988
- for (let index = 0; index < segment.length; index++) {
1989
- const char = segment[index];
1990
- const next = index + 1 < segment.length ? segment[index + 1] : "";
1991
- if (char === "'" && !inDoubleQuote) {
1992
- current += char;
1993
- if (inSingleQuote && next === "'") {
1994
- current += next;
1995
- index++;
1996
- continue;
1997
- }
1998
- inSingleQuote = !inSingleQuote;
1999
- continue;
2000
- }
2001
- if (char === '"' && !inSingleQuote) {
2002
- current += char;
2003
- if (inDoubleQuote && next === '"') {
2004
- current += next;
2005
- index++;
2006
- continue;
2007
- }
2008
- inDoubleQuote = !inDoubleQuote;
2009
- continue;
2010
- }
2011
- if (!inSingleQuote && !inDoubleQuote) {
2012
- if (char === "(") {
2013
- depth++;
2014
- } else if (char === ")") {
2015
- depth = Math.max(0, depth - 1);
2016
- }
2017
- if (/\s/.test(char) && depth === 0) {
2018
- if (current.length > 0) {
2019
- tokens.push(current);
2020
- current = "";
2021
- }
2022
- continue;
2023
- }
2024
- }
2025
- current += char;
2026
- }
2027
- if (current.length > 0) {
2028
- tokens.push(current);
2029
- }
2030
- return tokens;
2031
- }
2032
- function parseColumnDefinition(segment) {
2033
- const tokens = tokenize(segment);
2034
- if (tokens.length < 2) {
2035
- return null;
2036
- }
2037
- const name = normalizeIdentifier(tokens[0]);
2038
- let cursor = 1;
2039
- const typeTokens = [];
2040
- while (cursor < tokens.length) {
2041
- const lower = tokens[cursor].toLowerCase();
2042
- if (COLUMN_CONSTRAINT_KEYWORDS.has(lower)) {
2043
- break;
2044
- }
2045
- typeTokens.push(tokens[cursor]);
2046
- cursor++;
2047
- }
2048
- if (typeTokens.length === 0) {
2049
- return null;
2050
- }
2051
- const parsed = {
2052
- name,
2053
- type: normalizeSqlType(typeTokens.join(" ")),
2054
- nullable: true
2055
- };
2056
- while (cursor < tokens.length) {
2057
- const lower = tokens[cursor].toLowerCase();
2058
- if (lower === "primary" && tokens[cursor + 1]?.toLowerCase() === "key") {
2059
- parsed.primaryKey = true;
2060
- parsed.nullable = false;
2061
- cursor += 2;
2062
- continue;
2063
- }
2064
- if (lower === "unique") {
2065
- parsed.unique = true;
2066
- cursor++;
2067
- continue;
2068
- }
2069
- if (lower === "not" && tokens[cursor + 1]?.toLowerCase() === "null") {
2070
- parsed.nullable = false;
2071
- cursor += 2;
2072
- continue;
2073
- }
2074
- if (lower === "null") {
2075
- parsed.nullable = true;
2076
- cursor++;
2077
- continue;
2078
- }
2079
- if (lower === "default") {
2080
- cursor++;
2081
- const defaultTokens = [];
2082
- while (cursor < tokens.length) {
2083
- const probe = tokens[cursor].toLowerCase();
2084
- if (probe === "constraint" || probe === "references" || probe === "check" || probe === "not" && tokens[cursor + 1]?.toLowerCase() === "null" || probe === "null" || probe === "unique" || probe === "primary" && tokens[cursor + 1]?.toLowerCase() === "key") {
2085
- break;
2086
- }
2087
- defaultTokens.push(tokens[cursor]);
2088
- cursor++;
2089
- }
2090
- parsed.default = normalizeDefault(defaultTokens.join(" "));
2091
- continue;
2092
- }
2093
- cursor++;
2094
- }
2095
- return parsed;
2096
- }
2097
- function parseCreateTableConstraint(segment) {
2098
- const normalized = segment.trim().replace(/\s+/g, " ");
2099
- const constraintMatch = normalized.match(/^constraint\s+([^\s]+)\s+(primary\s+key|unique)\s*\((.+)\)$/i);
2100
- if (constraintMatch) {
2101
- const [, rawName, kind, rawColumns] = constraintMatch;
2102
- const columns = splitTopLevelComma(rawColumns).map((item) => normalizeIdentifier(item));
2103
- if (kind.toLowerCase().includes("primary")) {
2104
- return { type: "PRIMARY_KEY", name: normalizeIdentifier(rawName), columns };
2105
- }
2106
- return { type: "UNIQUE", name: normalizeIdentifier(rawName), columns };
2107
- }
2108
- const barePk = normalized.match(/^primary\s+key\s*\((.+)\)$/i);
2109
- if (barePk) {
2110
- const columns = splitTopLevelComma(barePk[1]).map((item) => normalizeIdentifier(item));
2111
- return { type: "PRIMARY_KEY", columns };
2112
- }
2113
- const bareUnique = normalized.match(/^unique\s*\((.+)\)$/i);
2114
- if (bareUnique) {
2115
- const columns = splitTopLevelComma(bareUnique[1]).map((item) => normalizeIdentifier(item));
2116
- return { type: "UNIQUE", columns };
2117
- }
2118
- return null;
2119
- }
2120
- function parseAlterTablePrefix(stmt) {
2121
- const match = stmt.match(/^alter\s+table\s+(?:if\s+exists\s+)?(?:only\s+)?(.+)$/i);
2122
- if (!match) {
2123
- return null;
2124
- }
2125
- const remainder = match[1].trim();
2126
- const tokens = tokenize(remainder);
2127
- if (tokens.length < 2) {
2128
- return null;
2129
- }
2130
- const tableToken = tokens[0];
2131
- const table = normalizeIdentifier(tableToken);
2132
- const rest = remainder.slice(tableToken.length).trim();
2133
- return { table, rest };
2134
- }
2135
- function parseCreateTable(stmt) {
2136
- const match = stmt.match(/^create\s+table\s+(?:if\s+not\s+exists\s+)?(.+?)\s*\((.*)\)$/is);
2137
- if (!match) {
2138
- return null;
2139
- }
2140
- const table = normalizeIdentifier(match[1]);
2141
- const body = match[2];
2142
- const segments = splitTopLevelComma(body);
2143
- const columns = [];
2144
- const constraints = [];
2145
- for (const segment of segments) {
2146
- const constraint = parseCreateTableConstraint(segment);
2147
- if (constraint) {
2148
- constraints.push(constraint);
2149
- continue;
2150
- }
2151
- const column = parseColumnDefinition(segment);
2152
- if (column) {
2153
- columns.push(column);
2154
- }
2155
- }
2156
- return {
2157
- kind: "CREATE_TABLE",
2158
- table,
2159
- columns,
2160
- constraints
2161
- };
2162
- }
2163
- function parseAlterTableAddColumn(stmt) {
2164
- const prefix = parseAlterTablePrefix(stmt);
2165
- if (!prefix) {
2166
- return null;
2167
- }
2168
- const match = prefix.rest.match(/^add\s+column\s+(?:if\s+not\s+exists\s+)?(.+)$/i);
2169
- if (!match) {
2170
- return null;
2171
- }
2172
- const column = parseColumnDefinition(match[1]);
2173
- if (!column) {
2174
- return null;
2175
- }
2176
- return { kind: "ADD_COLUMN", table: prefix.table, column };
2177
- }
2178
- function parseAlterColumnType(stmt) {
2179
- const prefix = parseAlterTablePrefix(stmt);
2180
- if (!prefix) {
2181
- return null;
2182
- }
2183
- const match = prefix.rest.match(/^alter\s+column\s+([^\s]+)\s+type\s+(.+)$/i);
2184
- if (!match) {
2185
- return null;
2186
- }
2187
- const column = normalizeIdentifier(match[1]);
2188
- const toType = normalizeSqlType(match[2].replace(/\s+using\s+[\s\S]*$/i, "").trim());
2189
- return {
2190
- kind: "ALTER_COLUMN_TYPE",
2191
- table: prefix.table,
2192
- column,
2193
- toType
2194
- };
2195
- }
2196
- function parseSetDropNotNull(stmt) {
2197
- const prefix = parseAlterTablePrefix(stmt);
2198
- if (!prefix) {
2199
- return null;
2200
- }
2201
- const setMatch = prefix.rest.match(/^alter\s+column\s+([^\s]+)\s+set\s+not\s+null$/i);
2202
- if (setMatch) {
2203
- return {
2204
- kind: "SET_NOT_NULL",
2205
- table: prefix.table,
2206
- column: normalizeIdentifier(setMatch[1])
2207
- };
2208
- }
2209
- const dropMatch = prefix.rest.match(/^alter\s+column\s+([^\s]+)\s+drop\s+not\s+null$/i);
2210
- if (dropMatch) {
2211
- return {
2212
- kind: "DROP_NOT_NULL",
2213
- table: prefix.table,
2214
- column: normalizeIdentifier(dropMatch[1])
2215
- };
2216
- }
2217
- return null;
2218
- }
2219
- function parseSetDropDefault(stmt) {
2220
- const prefix = parseAlterTablePrefix(stmt);
2221
- if (!prefix) {
2222
- return null;
2223
- }
2224
- const setMatch = prefix.rest.match(/^alter\s+column\s+([^\s]+)\s+set\s+default\s+(.+)$/i);
2225
- if (setMatch) {
2226
- return {
2227
- kind: "SET_DEFAULT",
2228
- table: prefix.table,
2229
- column: normalizeIdentifier(setMatch[1]),
2230
- expr: normalizeDefault(setMatch[2].trim()) ?? setMatch[2].trim()
2231
- };
2232
- }
2233
- const dropMatch = prefix.rest.match(/^alter\s+column\s+([^\s]+)\s+drop\s+default$/i);
2234
- if (dropMatch) {
2235
- return {
2236
- kind: "DROP_DEFAULT",
2237
- table: prefix.table,
2238
- column: normalizeIdentifier(dropMatch[1])
2239
- };
2240
- }
2241
- return null;
2242
- }
2243
- function parseAddDropConstraint(stmt) {
2244
- const prefix = parseAlterTablePrefix(stmt);
2245
- if (!prefix) {
2246
- return null;
2247
- }
2248
- const addMatch = prefix.rest.match(/^add\s+constraint\s+([^\s]+)\s+(primary\s+key|unique)\s*\((.+)\)$/i);
2249
- if (addMatch) {
2250
- const [, rawName, kind, rawColumns] = addMatch;
2251
- const columns = splitTopLevelComma(rawColumns).map((item) => normalizeIdentifier(item));
2252
- const constraint = kind.toLowerCase().includes("primary") ? { type: "PRIMARY_KEY", name: normalizeIdentifier(rawName), columns } : { type: "UNIQUE", name: normalizeIdentifier(rawName), columns };
2253
- return {
2254
- kind: "ADD_CONSTRAINT",
2255
- table: prefix.table,
2256
- constraint
2257
- };
2258
- }
2259
- const dropMatch = prefix.rest.match(/^drop\s+constraint\s+(?:if\s+exists\s+)?([^\s]+)(?:\s+cascade)?$/i);
2260
- if (dropMatch) {
2261
- return {
2262
- kind: "DROP_CONSTRAINT",
2263
- table: prefix.table,
2264
- name: normalizeIdentifier(dropMatch[1])
2265
- };
2266
- }
2267
- return null;
2268
- }
2269
- function parseDropColumn(stmt) {
2270
- const prefix = parseAlterTablePrefix(stmt);
2271
- if (!prefix) {
2272
- return null;
2273
- }
2274
- const match = prefix.rest.match(/^drop\s+column\s+(?:if\s+exists\s+)?([^\s]+)(?:\s+cascade)?$/i);
2275
- if (!match) {
2276
- return null;
2277
- }
2278
- return {
2279
- kind: "DROP_COLUMN",
2280
- table: prefix.table,
2281
- column: normalizeIdentifier(match[1])
2282
- };
2283
- }
2284
- function parseDropTable(stmt) {
2285
- const match = stmt.match(/^drop\s+table\s+(?:if\s+exists\s+)?([^\s]+)(?:\s+cascade)?$/i);
2286
- if (!match) {
2287
- return null;
2288
- }
2289
- return {
2290
- kind: "DROP_TABLE",
2291
- table: normalizeIdentifier(match[1])
2292
- };
2293
- }
2294
- var PARSERS = [
2295
- parseCreateTable,
2296
- parseAlterTableAddColumn,
2297
- parseAlterColumnType,
2298
- parseSetDropNotNull,
2299
- parseSetDropDefault,
2300
- parseAddDropConstraint,
2301
- parseDropColumn,
2302
- parseDropTable
2303
- ];
2304
- function parseMigrationSql(sql) {
2305
- const statements = splitSqlStatements(sql);
2306
- const ops = [];
2307
- const warnings = [];
2308
- for (const raw of statements) {
2309
- const stmt = removeSqlComments(raw).trim();
2310
- if (!stmt) {
2311
- continue;
2312
- }
2313
- let parsed = null;
2314
- for (const parseFn of PARSERS) {
2315
- parsed = parseFn(stmt);
2316
- if (parsed) {
2317
- break;
2318
- }
2319
- }
2320
- if (parsed) {
2321
- ops.push(parsed);
2322
- } else {
2323
- warnings.push({
2324
- statement: stmt,
2325
- reason: "Unsupported or unrecognized statement"
2326
- });
2327
- }
2328
- }
2329
- return { ops, warnings };
2330
- }
2331
-
2332
- // src/core/sql/schema-to-dsl.ts
2333
- function renderColumn(column) {
2334
- const parts = [column.name, column.type];
2335
- if (column.primaryKey) {
2336
- parts.push("pk");
2337
- }
2338
- if (column.unique) {
2339
- parts.push("unique");
2340
- }
2341
- if (column.nullable === false && !column.primaryKey) {
2342
- parts.push("not null");
2343
- }
2344
- if (column.default !== void 0 && column.default !== null) {
2345
- parts.push(`default ${column.default}`);
2346
- }
2347
- return ` ${parts.join(" ")}`;
2348
- }
2349
- function schemaToDsl(schema) {
2350
- const tableNames = Object.keys(schema.tables).sort((left, right) => left.localeCompare(right));
2351
- const blocks = tableNames.map((tableName) => {
2352
- const table = schema.tables[tableName];
2353
- const lines = [`table ${table.name} {`];
2354
- for (const column of table.columns) {
2355
- lines.push(renderColumn(column));
2356
- }
2357
- lines.push("}");
2358
- return lines.join("\n");
2359
- });
2360
- if (blocks.length === 0) {
2361
- return "# SchemaForge schema definition\n";
2362
- }
2363
- return `# SchemaForge schema definition
2364
-
2365
- ${blocks.join("\n\n")}
2366
- `;
2367
- }
2368
-
2369
- // src/commands/import.ts
403
+ var import_path5 = __toESM(require("path"));
2370
404
  function resolveConfigPath3(root, targetPath) {
2371
- return import_path7.default.isAbsolute(targetPath) ? targetPath : import_path7.default.join(root, targetPath);
405
+ return import_path5.default.isAbsolute(targetPath) ? targetPath : import_path5.default.join(root, targetPath);
2372
406
  }
2373
407
  async function runImport(inputPath, options = {}) {
2374
408
  const root = getProjectRoot();
@@ -2380,15 +414,15 @@ async function runImport(inputPath, options = {}) {
2380
414
  const allOps = [];
2381
415
  const parseWarnings = [];
2382
416
  for (const input of inputs) {
2383
- const result = parseMigrationSql(input.sql);
417
+ const result = await parseMigrationSql(input.sql);
2384
418
  allOps.push(...result.ops);
2385
419
  parseWarnings.push(...result.warnings.map((item) => ({
2386
- statement: `[${import_path7.default.basename(input.filePath)}] ${item.statement}`,
420
+ statement: `[${import_path5.default.basename(input.filePath)}] ${item.statement}`,
2387
421
  reason: item.reason
2388
422
  })));
2389
423
  }
2390
- const applied = applySqlOps(allOps);
2391
- const dsl = schemaToDsl(applied.schema);
424
+ const applied = await applySqlOps(allOps);
425
+ const dsl = await schemaToDsl(applied.schema);
2392
426
  let targetPath = options.out;
2393
427
  if (!targetPath) {
2394
428
  const configPath = getConfigPath(root);
@@ -2453,8 +487,8 @@ table users {
2453
487
  await writeTextFile(schemaFilePath, schemaContent);
2454
488
  success(`Created ${schemaFilePath}`);
2455
489
  const config = {
2456
- provider: "supabase",
2457
- outputDir: "supabase/migrations",
490
+ provider: "postgres",
491
+ outputDir: "migrations",
2458
492
  schemaFile: "schemaforge/schema.sf",
2459
493
  stateFile: "schemaforge/state.json",
2460
494
  sql: {
@@ -2470,7 +504,7 @@ table users {
2470
504
  };
2471
505
  await writeJsonFile(statePath, state);
2472
506
  success(`Created ${statePath}`);
2473
- const outputDir = "supabase/migrations";
507
+ const outputDir = "migrations";
2474
508
  await ensureDir(outputDir);
2475
509
  success(`Created ${outputDir}`);
2476
510
  success("Project initialized successfully");
@@ -2481,160 +515,10 @@ table users {
2481
515
 
2482
516
  // src/commands/validate.ts
2483
517
  var import_commander5 = require("commander");
2484
- var import_path8 = __toESM(require("path"));
2485
-
2486
- // src/core/validate.ts
2487
- function normalizeColumnType2(type) {
2488
- return type.toLowerCase().trim().replace(/\s+/g, " ").replace(/\s*\(\s*/g, "(").replace(/\s*,\s*/g, ",").replace(/\s*\)\s*/g, ")");
2489
- }
2490
- function parseVarcharLength(type) {
2491
- const match = normalizeColumnType2(type).match(/^varchar\((\d+)\)$/);
2492
- return match ? Number(match[1]) : null;
2493
- }
2494
- function parseNumericType(type) {
2495
- const match = normalizeColumnType2(type).match(/^numeric\((\d+),(\d+)\)$/);
2496
- if (!match) {
2497
- return null;
2498
- }
2499
- return {
2500
- precision: Number(match[1]),
2501
- scale: Number(match[2])
2502
- };
2503
- }
2504
- function classifyTypeChange(from, to) {
2505
- const fromType = normalizeColumnType2(from);
2506
- const toType = normalizeColumnType2(to);
2507
- const uuidInvolved = fromType === "uuid" || toType === "uuid";
2508
- if (uuidInvolved && fromType !== toType) {
2509
- return {
2510
- severity: "error",
2511
- message: `Type changed from ${fromType} to ${toType} (likely incompatible cast)`
2512
- };
2513
- }
2514
- if (fromType === "int" && toType === "bigint") {
2515
- return {
2516
- severity: "warning",
2517
- message: "Type widened from int to bigint"
2518
- };
2519
- }
2520
- if (fromType === "bigint" && toType === "int") {
2521
- return {
2522
- severity: "error",
2523
- message: "Type narrowed from bigint to int (likely incompatible cast)"
2524
- };
2525
- }
2526
- if (fromType === "text" && parseVarcharLength(toType) !== null) {
2527
- return {
2528
- severity: "error",
2529
- message: `Type changed from text to ${toType} (may truncate existing values)`
2530
- };
2531
- }
2532
- if (parseVarcharLength(fromType) !== null && toType === "text") {
2533
- return {
2534
- severity: "warning",
2535
- message: "Type widened from varchar(n) to text"
2536
- };
2537
- }
2538
- const fromVarcharLength = parseVarcharLength(fromType);
2539
- const toVarcharLength = parseVarcharLength(toType);
2540
- if (fromVarcharLength !== null && toVarcharLength !== null) {
2541
- if (toVarcharLength >= fromVarcharLength) {
2542
- return {
2543
- severity: "warning",
2544
- message: `Type widened from varchar(${fromVarcharLength}) to varchar(${toVarcharLength})`
2545
- };
2546
- }
2547
- return {
2548
- severity: "error",
2549
- message: `Type narrowed from varchar(${fromVarcharLength}) to varchar(${toVarcharLength})`
2550
- };
2551
- }
2552
- const fromNumeric = parseNumericType(fromType);
2553
- const toNumeric = parseNumericType(toType);
2554
- if (fromNumeric && toNumeric && fromNumeric.scale === toNumeric.scale) {
2555
- if (toNumeric.precision >= fromNumeric.precision) {
2556
- return {
2557
- severity: "warning",
2558
- message: `Type widened from numeric(${fromNumeric.precision},${fromNumeric.scale}) to numeric(${toNumeric.precision},${toNumeric.scale})`
2559
- };
2560
- }
2561
- return {
2562
- severity: "error",
2563
- message: `Type narrowed from numeric(${fromNumeric.precision},${fromNumeric.scale}) to numeric(${toNumeric.precision},${toNumeric.scale})`
2564
- };
2565
- }
2566
- return {
2567
- severity: "warning",
2568
- message: `Type changed from ${fromType} to ${toType} (compatibility unknown)`
2569
- };
2570
- }
2571
- function validateSchemaChanges(previousState, currentSchema) {
2572
- const findings = [];
2573
- const diff = diffSchemas(previousState, currentSchema);
2574
- for (const operation of diff.operations) {
2575
- switch (operation.kind) {
2576
- case "drop_table":
2577
- findings.push({
2578
- severity: "error",
2579
- code: "DROP_TABLE",
2580
- table: operation.tableName,
2581
- message: "Table removed"
2582
- });
2583
- break;
2584
- case "drop_column":
2585
- findings.push({
2586
- severity: "error",
2587
- code: "DROP_COLUMN",
2588
- table: operation.tableName,
2589
- column: operation.columnName,
2590
- message: "Column removed"
2591
- });
2592
- break;
2593
- case "column_type_changed": {
2594
- const classification = classifyTypeChange(operation.fromType, operation.toType);
2595
- findings.push({
2596
- severity: classification.severity,
2597
- code: "ALTER_COLUMN_TYPE",
2598
- table: operation.tableName,
2599
- column: operation.columnName,
2600
- from: normalizeColumnType2(operation.fromType),
2601
- to: normalizeColumnType2(operation.toType),
2602
- message: classification.message
2603
- });
2604
- break;
2605
- }
2606
- case "column_nullability_changed":
2607
- if (operation.from && !operation.to) {
2608
- findings.push({
2609
- severity: "warning",
2610
- code: "SET_NOT_NULL",
2611
- table: operation.tableName,
2612
- column: operation.columnName,
2613
- message: "Column changed to NOT NULL (may fail if data contains NULLs)"
2614
- });
2615
- }
2616
- break;
2617
- default:
2618
- break;
2619
- }
2620
- }
2621
- return findings;
2622
- }
2623
- function toValidationReport(findings) {
2624
- const errors = findings.filter((finding) => finding.severity === "error");
2625
- const warnings = findings.filter((finding) => finding.severity === "warning");
2626
- return {
2627
- hasErrors: errors.length > 0,
2628
- hasWarnings: warnings.length > 0,
2629
- errors: errors.map(({ severity, ...finding }) => finding),
2630
- warnings: warnings.map(({ severity, ...finding }) => finding)
2631
- };
2632
- }
2633
-
2634
- // src/commands/validate.ts
518
+ var import_path6 = __toESM(require("path"));
2635
519
  var REQUIRED_CONFIG_FIELDS3 = ["schemaFile", "stateFile"];
2636
520
  function resolveConfigPath4(root, targetPath) {
2637
- return import_path8.default.isAbsolute(targetPath) ? targetPath : import_path8.default.join(root, targetPath);
521
+ return import_path6.default.isAbsolute(targetPath) ? targetPath : import_path6.default.join(root, targetPath);
2638
522
  }
2639
523
  async function runValidate(options = {}) {
2640
524
  const root = getProjectRoot();
@@ -2652,18 +536,18 @@ async function runValidate(options = {}) {
2652
536
  const schemaPath = resolveConfigPath4(root, config.schemaFile);
2653
537
  const statePath = resolveConfigPath4(root, config.stateFile);
2654
538
  const schemaSource = await readTextFile(schemaPath);
2655
- const schema = parseSchema(schemaSource);
539
+ const schema = await parseSchema(schemaSource);
2656
540
  try {
2657
- validateSchema(schema);
541
+ await validateSchema(schema);
2658
542
  } catch (error2) {
2659
543
  if (error2 instanceof Error) {
2660
- throw new SchemaValidationError(error2.message);
544
+ throw await createSchemaValidationError(error2.message);
2661
545
  }
2662
546
  throw error2;
2663
547
  }
2664
548
  const previousState = await loadState(statePath);
2665
- const findings = validateSchemaChanges(previousState, schema);
2666
- const report = toValidationReport(findings);
549
+ const findings = await validateSchemaChanges(previousState, schema);
550
+ const report = await toValidationReport(findings);
2667
551
  if (options.json) {
2668
552
  console.log(JSON.stringify(report, null, 2));
2669
553
  process.exitCode = report.hasErrors ? 1 : 0;
@@ -2694,8 +578,8 @@ async function runValidate(options = {}) {
2694
578
  // src/cli.ts
2695
579
  var program = new import_commander6.Command();
2696
580
  program.name("schema-forge").description("CLI tool for schema management and SQL generation").version(package_default.version);
2697
- function handleError(error2) {
2698
- if (error2 instanceof SchemaValidationError) {
581
+ async function handleError(error2) {
582
+ if (await isSchemaValidationError(error2) && error2 instanceof Error) {
2699
583
  error(error2.message);
2700
584
  process.exitCode = 2;
2701
585
  return;
@@ -2711,35 +595,35 @@ program.command("init").description("Initialize a new schema project").action(as
2711
595
  try {
2712
596
  await runInit();
2713
597
  } catch (error2) {
2714
- handleError(error2);
598
+ await handleError(error2);
2715
599
  }
2716
600
  });
2717
601
  program.command("generate").description("Generate SQL from schema files").option("--name <string>", "Schema name to generate").action(async (options) => {
2718
602
  try {
2719
603
  await runGenerate(options);
2720
604
  } catch (error2) {
2721
- handleError(error2);
605
+ await handleError(error2);
2722
606
  }
2723
607
  });
2724
608
  program.command("diff").description("Compare two schema versions and generate migration SQL").action(async () => {
2725
609
  try {
2726
610
  await runDiff();
2727
611
  } catch (error2) {
2728
- handleError(error2);
612
+ await handleError(error2);
2729
613
  }
2730
614
  });
2731
615
  program.command("import").description("Import schema from SQL migrations").argument("<path>", "Path to .sql file or migrations directory").option("--out <path>", "Output schema file path").action(async (targetPath, options) => {
2732
616
  try {
2733
617
  await runImport(targetPath, options);
2734
618
  } catch (error2) {
2735
- handleError(error2);
619
+ await handleError(error2);
2736
620
  }
2737
621
  });
2738
622
  program.command("validate").description("Detect destructive or risky schema changes").option("--json", "Output structured JSON").action(async (options) => {
2739
623
  try {
2740
624
  await runValidate(options);
2741
625
  } catch (error2) {
2742
- handleError(error2);
626
+ await handleError(error2);
2743
627
  }
2744
628
  });
2745
629
  program.parse(process.argv);