@famgia/omnify-atlas 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1333 @@
1
+ // src/lock/lock-file.ts
2
+ import { createHash } from "crypto";
3
+ import { readFile, writeFile, stat } from "fs/promises";
4
+ var LOCK_FILE_NAME = ".omnify.lock";
5
+ var LOCK_FILE_VERSION = 1;
6
+ function computeHash(content) {
7
+ return createHash("sha256").update(content, "utf8").digest("hex");
8
+ }
9
+ function computeSchemaHash(schema) {
10
+ const content = JSON.stringify({
11
+ name: schema.name,
12
+ kind: schema.kind ?? "object",
13
+ properties: schema.properties ?? {},
14
+ options: schema.options ?? {},
15
+ values: schema.values ?? []
16
+ });
17
+ return computeHash(content);
18
+ }
19
+ function createEmptyLockFile(driver) {
20
+ return {
21
+ version: LOCK_FILE_VERSION,
22
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
23
+ driver,
24
+ schemas: {},
25
+ migrations: []
26
+ };
27
+ }
28
+ async function readLockFile(lockFilePath) {
29
+ try {
30
+ const content = await readFile(lockFilePath, "utf8");
31
+ const parsed = JSON.parse(content);
32
+ if (parsed.version !== LOCK_FILE_VERSION) {
33
+ throw new Error(
34
+ `Lock file version mismatch: expected ${LOCK_FILE_VERSION}, got ${parsed.version}`
35
+ );
36
+ }
37
+ return parsed;
38
+ } catch (error) {
39
+ if (error.code === "ENOENT") {
40
+ return null;
41
+ }
42
+ throw error;
43
+ }
44
+ }
45
+ async function writeLockFile(lockFilePath, lockFile) {
46
+ const content = JSON.stringify(lockFile, null, 2) + "\n";
47
+ await writeFile(lockFilePath, content, "utf8");
48
+ }
49
+ async function buildSchemaHashes(schemas) {
50
+ const hashes = {};
51
+ for (const [name, schema] of Object.entries(schemas)) {
52
+ const hash = computeSchemaHash(schema);
53
+ let modifiedAt;
54
+ try {
55
+ const stats = await stat(schema.filePath);
56
+ modifiedAt = stats.mtime.toISOString();
57
+ } catch {
58
+ modifiedAt = (/* @__PURE__ */ new Date()).toISOString();
59
+ }
60
+ hashes[name] = {
61
+ name,
62
+ hash,
63
+ relativePath: schema.relativePath,
64
+ modifiedAt
65
+ };
66
+ }
67
+ return hashes;
68
+ }
69
+ function compareSchemas(currentHashes, lockFile) {
70
+ const changes = [];
71
+ const unchanged = [];
72
+ const previousHashes = lockFile?.schemas ?? {};
73
+ const previousNames = new Set(Object.keys(previousHashes));
74
+ const currentNames = new Set(Object.keys(currentHashes));
75
+ for (const name of currentNames) {
76
+ if (!previousNames.has(name)) {
77
+ const current = currentHashes[name];
78
+ if (current) {
79
+ changes.push({
80
+ schemaName: name,
81
+ changeType: "added",
82
+ currentHash: current.hash
83
+ });
84
+ }
85
+ }
86
+ }
87
+ for (const name of previousNames) {
88
+ if (!currentNames.has(name)) {
89
+ const previous = previousHashes[name];
90
+ if (previous) {
91
+ changes.push({
92
+ schemaName: name,
93
+ changeType: "removed",
94
+ previousHash: previous.hash
95
+ });
96
+ }
97
+ }
98
+ }
99
+ for (const name of currentNames) {
100
+ if (previousNames.has(name)) {
101
+ const current = currentHashes[name];
102
+ const previous = previousHashes[name];
103
+ if (current && previous) {
104
+ if (current.hash !== previous.hash) {
105
+ changes.push({
106
+ schemaName: name,
107
+ changeType: "modified",
108
+ previousHash: previous.hash,
109
+ currentHash: current.hash
110
+ });
111
+ } else {
112
+ unchanged.push(name);
113
+ }
114
+ }
115
+ }
116
+ }
117
+ return {
118
+ hasChanges: changes.length > 0,
119
+ changes,
120
+ unchanged
121
+ };
122
+ }
123
+ function updateLockFile(existingLockFile, currentHashes, driver) {
124
+ return {
125
+ version: LOCK_FILE_VERSION,
126
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
127
+ driver,
128
+ schemas: currentHashes,
129
+ migrations: existingLockFile?.migrations ?? [],
130
+ hclChecksum: existingLockFile?.hclChecksum
131
+ };
132
+ }
133
+ function addMigrationRecord(lockFile, fileName, schemas, migrationContent) {
134
+ const record = {
135
+ fileName,
136
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
137
+ schemas,
138
+ checksum: computeHash(migrationContent)
139
+ };
140
+ return {
141
+ ...lockFile,
142
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
143
+ migrations: [...lockFile.migrations, record]
144
+ };
145
+ }
146
+
147
+ // src/hcl/type-mapper.ts
148
+ var MYSQL_TYPES = {
149
+ String: (prop) => ({
150
+ type: `varchar(${prop.length ?? 255})`,
151
+ nullable: prop.nullable ?? false,
152
+ default: prop.default !== void 0 ? `'${prop.default}'` : void 0
153
+ }),
154
+ Int: (prop) => ({
155
+ type: "int",
156
+ nullable: prop.nullable ?? false,
157
+ default: prop.default !== void 0 ? String(prop.default) : void 0,
158
+ unsigned: prop.unsigned ?? false
159
+ }),
160
+ BigInt: (prop) => ({
161
+ type: "bigint",
162
+ nullable: prop.nullable ?? false,
163
+ default: prop.default !== void 0 ? String(prop.default) : void 0,
164
+ unsigned: prop.unsigned ?? false
165
+ }),
166
+ Float: (prop) => ({
167
+ type: "double",
168
+ nullable: prop.nullable ?? false,
169
+ default: prop.default !== void 0 ? String(prop.default) : void 0
170
+ }),
171
+ Boolean: (prop) => ({
172
+ type: "tinyint(1)",
173
+ nullable: prop.nullable ?? false,
174
+ default: prop.default !== void 0 ? prop.default ? "1" : "0" : void 0
175
+ }),
176
+ Text: (prop) => ({
177
+ type: "text",
178
+ nullable: prop.nullable ?? false
179
+ }),
180
+ LongText: (prop) => ({
181
+ type: "longtext",
182
+ nullable: prop.nullable ?? false
183
+ }),
184
+ Date: (prop) => ({
185
+ type: "date",
186
+ nullable: prop.nullable ?? false
187
+ }),
188
+ Time: (prop) => ({
189
+ type: "time",
190
+ nullable: prop.nullable ?? false
191
+ }),
192
+ Timestamp: (prop) => ({
193
+ type: "timestamp",
194
+ nullable: prop.nullable ?? false
195
+ }),
196
+ Json: (prop) => ({
197
+ type: "json",
198
+ nullable: prop.nullable ?? false
199
+ }),
200
+ Email: (prop) => ({
201
+ type: "varchar(255)",
202
+ nullable: prop.nullable ?? false
203
+ }),
204
+ Password: (prop) => ({
205
+ type: "varchar(255)",
206
+ nullable: prop.nullable ?? false
207
+ }),
208
+ File: (prop) => ({
209
+ type: "varchar(500)",
210
+ nullable: prop.nullable ?? false
211
+ }),
212
+ MultiFile: (prop) => ({
213
+ type: "json",
214
+ nullable: prop.nullable ?? false
215
+ }),
216
+ Enum: (prop) => {
217
+ const enumProp = prop;
218
+ const values = enumProp.enum ?? [];
219
+ const enumDef = values.map((v) => `'${v}'`).join(", ");
220
+ return {
221
+ type: `enum(${enumDef})`,
222
+ nullable: prop.nullable ?? false,
223
+ default: prop.default !== void 0 ? `'${prop.default}'` : void 0
224
+ };
225
+ },
226
+ Select: (prop) => ({
227
+ type: "varchar(100)",
228
+ nullable: prop.nullable ?? false
229
+ }),
230
+ Lookup: (prop) => ({
231
+ type: "bigint",
232
+ nullable: prop.nullable ?? false,
233
+ unsigned: true
234
+ })
235
+ };
236
+ var POSTGRES_TYPES = {
237
+ String: (prop) => ({
238
+ type: `varchar(${prop.length ?? 255})`,
239
+ nullable: prop.nullable ?? false,
240
+ default: prop.default !== void 0 ? `'${prop.default}'` : void 0
241
+ }),
242
+ Int: (prop) => ({
243
+ type: "integer",
244
+ nullable: prop.nullable ?? false,
245
+ default: prop.default !== void 0 ? String(prop.default) : void 0
246
+ }),
247
+ BigInt: (prop) => ({
248
+ type: "bigint",
249
+ nullable: prop.nullable ?? false,
250
+ default: prop.default !== void 0 ? String(prop.default) : void 0
251
+ }),
252
+ Float: (prop) => ({
253
+ type: "double precision",
254
+ nullable: prop.nullable ?? false,
255
+ default: prop.default !== void 0 ? String(prop.default) : void 0
256
+ }),
257
+ Boolean: (prop) => ({
258
+ type: "boolean",
259
+ nullable: prop.nullable ?? false,
260
+ default: prop.default !== void 0 ? String(prop.default) : void 0
261
+ }),
262
+ Text: (prop) => ({
263
+ type: "text",
264
+ nullable: prop.nullable ?? false
265
+ }),
266
+ LongText: (prop) => ({
267
+ type: "text",
268
+ nullable: prop.nullable ?? false
269
+ }),
270
+ Date: (prop) => ({
271
+ type: "date",
272
+ nullable: prop.nullable ?? false
273
+ }),
274
+ Time: (prop) => ({
275
+ type: "time",
276
+ nullable: prop.nullable ?? false
277
+ }),
278
+ Timestamp: (prop) => ({
279
+ type: "timestamp",
280
+ nullable: prop.nullable ?? false
281
+ }),
282
+ Json: (prop) => ({
283
+ type: "jsonb",
284
+ nullable: prop.nullable ?? false
285
+ }),
286
+ Email: (prop) => ({
287
+ type: "varchar(255)",
288
+ nullable: prop.nullable ?? false
289
+ }),
290
+ Password: (prop) => ({
291
+ type: "varchar(255)",
292
+ nullable: prop.nullable ?? false
293
+ }),
294
+ File: (prop) => ({
295
+ type: "varchar(500)",
296
+ nullable: prop.nullable ?? false
297
+ }),
298
+ MultiFile: (prop) => ({
299
+ type: "jsonb",
300
+ nullable: prop.nullable ?? false
301
+ }),
302
+ // For PostgreSQL, enums are separate types
303
+ Enum: (prop) => ({
304
+ type: "varchar(100)",
305
+ nullable: prop.nullable ?? false,
306
+ default: prop.default !== void 0 ? `'${prop.default}'` : void 0
307
+ }),
308
+ Select: (prop) => ({
309
+ type: "varchar(100)",
310
+ nullable: prop.nullable ?? false
311
+ }),
312
+ Lookup: (prop) => ({
313
+ type: "bigint",
314
+ nullable: prop.nullable ?? false
315
+ })
316
+ };
317
+ var SQLITE_TYPES = {
318
+ String: (prop) => ({
319
+ type: "text",
320
+ nullable: prop.nullable ?? false,
321
+ default: prop.default !== void 0 ? `'${prop.default}'` : void 0
322
+ }),
323
+ Int: (prop) => ({
324
+ type: "integer",
325
+ nullable: prop.nullable ?? false,
326
+ default: prop.default !== void 0 ? String(prop.default) : void 0
327
+ }),
328
+ BigInt: (prop) => ({
329
+ type: "integer",
330
+ nullable: prop.nullable ?? false,
331
+ default: prop.default !== void 0 ? String(prop.default) : void 0
332
+ }),
333
+ Float: (prop) => ({
334
+ type: "real",
335
+ nullable: prop.nullable ?? false,
336
+ default: prop.default !== void 0 ? String(prop.default) : void 0
337
+ }),
338
+ Boolean: (prop) => ({
339
+ type: "integer",
340
+ nullable: prop.nullable ?? false,
341
+ default: prop.default !== void 0 ? prop.default ? "1" : "0" : void 0
342
+ }),
343
+ Text: (prop) => ({
344
+ type: "text",
345
+ nullable: prop.nullable ?? false
346
+ }),
347
+ LongText: (prop) => ({
348
+ type: "text",
349
+ nullable: prop.nullable ?? false
350
+ }),
351
+ Date: (prop) => ({
352
+ type: "text",
353
+ nullable: prop.nullable ?? false
354
+ }),
355
+ Time: (prop) => ({
356
+ type: "text",
357
+ nullable: prop.nullable ?? false
358
+ }),
359
+ Timestamp: (prop) => ({
360
+ type: "text",
361
+ nullable: prop.nullable ?? false
362
+ }),
363
+ Json: (prop) => ({
364
+ type: "text",
365
+ nullable: prop.nullable ?? false
366
+ }),
367
+ Email: (prop) => ({
368
+ type: "text",
369
+ nullable: prop.nullable ?? false
370
+ }),
371
+ Password: (prop) => ({
372
+ type: "text",
373
+ nullable: prop.nullable ?? false
374
+ }),
375
+ File: (prop) => ({
376
+ type: "text",
377
+ nullable: prop.nullable ?? false
378
+ }),
379
+ MultiFile: (prop) => ({
380
+ type: "text",
381
+ nullable: prop.nullable ?? false
382
+ }),
383
+ Enum: (prop) => ({
384
+ type: "text",
385
+ nullable: prop.nullable ?? false,
386
+ default: prop.default !== void 0 ? `'${prop.default}'` : void 0
387
+ }),
388
+ Select: (prop) => ({
389
+ type: "text",
390
+ nullable: prop.nullable ?? false
391
+ }),
392
+ Lookup: (prop) => ({
393
+ type: "integer",
394
+ nullable: prop.nullable ?? false
395
+ })
396
+ };
397
+ var DRIVER_TYPE_MAPS = {
398
+ mysql: MYSQL_TYPES,
399
+ postgres: POSTGRES_TYPES,
400
+ pgsql: POSTGRES_TYPES,
401
+ // Alias for postgres
402
+ sqlite: SQLITE_TYPES,
403
+ mariadb: MYSQL_TYPES,
404
+ // MariaDB uses same types as MySQL
405
+ sqlsrv: MYSQL_TYPES
406
+ // SQL Server uses similar types to MySQL for now
407
+ };
408
+ function mapPropertyToSql(property, driver) {
409
+ const typeMap = DRIVER_TYPE_MAPS[driver];
410
+ const mapper = typeMap[property.type];
411
+ const baseProp = property;
412
+ if (mapper) {
413
+ return mapper(baseProp);
414
+ }
415
+ return {
416
+ type: "varchar(255)",
417
+ nullable: baseProp.nullable ?? false
418
+ };
419
+ }
420
+ function getPrimaryKeyType(pkType, driver) {
421
+ switch (pkType) {
422
+ case "Int":
423
+ return {
424
+ type: driver === "postgres" ? "serial" : "int",
425
+ nullable: false,
426
+ autoIncrement: driver !== "postgres",
427
+ unsigned: driver === "mysql" || driver === "mariadb"
428
+ };
429
+ case "BigInt":
430
+ return {
431
+ type: driver === "postgres" ? "bigserial" : "bigint",
432
+ nullable: false,
433
+ autoIncrement: driver !== "postgres",
434
+ unsigned: driver === "mysql" || driver === "mariadb"
435
+ };
436
+ case "Uuid":
437
+ return {
438
+ type: driver === "postgres" ? "uuid" : "char(36)",
439
+ nullable: false
440
+ };
441
+ case "String":
442
+ return {
443
+ type: "varchar(255)",
444
+ nullable: false
445
+ };
446
+ default:
447
+ return {
448
+ type: driver === "postgres" ? "bigserial" : "bigint",
449
+ nullable: false,
450
+ autoIncrement: driver !== "postgres",
451
+ unsigned: driver === "mysql" || driver === "mariadb"
452
+ };
453
+ }
454
+ }
455
+ function getTimestampType(driver) {
456
+ return {
457
+ type: driver === "postgres" ? "timestamp" : "timestamp",
458
+ nullable: true
459
+ };
460
+ }
461
+ function schemaNameToTableName(schemaName) {
462
+ const snakeCase = schemaName.replace(/([A-Z])/g, "_$1").toLowerCase().replace(/^_/, "");
463
+ if (snakeCase.endsWith("y")) {
464
+ return snakeCase.slice(0, -1) + "ies";
465
+ } else if (snakeCase.endsWith("s") || snakeCase.endsWith("x") || snakeCase.endsWith("ch") || snakeCase.endsWith("sh")) {
466
+ return snakeCase + "es";
467
+ } else {
468
+ return snakeCase + "s";
469
+ }
470
+ }
471
+ function propertyNameToColumnName(propertyName) {
472
+ return propertyName.replace(/([A-Z])/g, "_$1").toLowerCase();
473
+ }
474
+
475
+ // src/hcl/generator.ts
476
+ function generateHclTable(schema, allSchemas, driver) {
477
+ const tableName = schemaNameToTableName(schema.name);
478
+ const columns = [];
479
+ const indexes = [];
480
+ const foreignKeys = [];
481
+ const pkType = schema.options?.primaryKeyType ?? "BigInt";
482
+ columns.push({
483
+ name: "id",
484
+ type: getPrimaryKeyType(pkType, driver),
485
+ primaryKey: true
486
+ });
487
+ if (schema.properties) {
488
+ for (const [propName, property] of Object.entries(schema.properties)) {
489
+ if (property.type === "Association") {
490
+ const assocProp = property;
491
+ if (assocProp.relation === "ManyToOne" || assocProp.relation === "OneToOne") {
492
+ const columnName2 = propertyNameToColumnName(propName) + "_id";
493
+ const targetSchema = assocProp.target ? allSchemas[assocProp.target] : void 0;
494
+ const targetTable = assocProp.target ? schemaNameToTableName(assocProp.target) : "unknown";
495
+ const targetPkType = targetSchema?.options?.primaryKeyType ?? "BigInt";
496
+ const fkType = getPrimaryKeyType(
497
+ targetPkType,
498
+ driver
499
+ );
500
+ const isNullable = assocProp.relation === "ManyToOne";
501
+ columns.push({
502
+ name: columnName2,
503
+ type: {
504
+ ...fkType,
505
+ nullable: isNullable,
506
+ autoIncrement: false
507
+ }
508
+ });
509
+ foreignKeys.push({
510
+ name: `fk_${tableName}_${columnName2}`,
511
+ columns: [columnName2],
512
+ refTable: targetTable,
513
+ refColumns: ["id"],
514
+ onDelete: assocProp.onDelete ?? "RESTRICT",
515
+ onUpdate: assocProp.onUpdate ?? "CASCADE"
516
+ });
517
+ indexes.push({
518
+ name: `idx_${tableName}_${columnName2}`,
519
+ columns: [columnName2]
520
+ });
521
+ }
522
+ continue;
523
+ }
524
+ const baseProp = property;
525
+ const columnName = propertyNameToColumnName(propName);
526
+ const sqlType = mapPropertyToSql(property, driver);
527
+ columns.push({
528
+ name: columnName,
529
+ type: sqlType,
530
+ unique: baseProp.unique ?? false
531
+ });
532
+ if (baseProp.unique) {
533
+ indexes.push({
534
+ name: `idx_${tableName}_${columnName}_unique`,
535
+ columns: [columnName],
536
+ unique: true
537
+ });
538
+ }
539
+ }
540
+ }
541
+ if (schema.options?.timestamps !== false) {
542
+ const timestampType = getTimestampType(driver);
543
+ columns.push(
544
+ { name: "created_at", type: timestampType },
545
+ { name: "updated_at", type: timestampType }
546
+ );
547
+ }
548
+ if (schema.options?.softDelete) {
549
+ columns.push({
550
+ name: "deleted_at",
551
+ type: getTimestampType(driver)
552
+ });
553
+ }
554
+ if (schema.options?.indexes) {
555
+ for (const index of schema.options.indexes) {
556
+ const indexColumns = index.columns.map(propertyNameToColumnName);
557
+ indexes.push({
558
+ name: index.name ?? `idx_${tableName}_${indexColumns.join("_")}`,
559
+ columns: indexColumns,
560
+ unique: index.unique ?? false
561
+ });
562
+ }
563
+ }
564
+ if (schema.options?.unique) {
565
+ const uniqueConstraints = Array.isArray(schema.options.unique[0]) ? schema.options.unique : [schema.options.unique];
566
+ for (const constraint of uniqueConstraints) {
567
+ const constraintColumns = constraint.map(propertyNameToColumnName);
568
+ indexes.push({
569
+ name: `idx_${tableName}_${constraintColumns.join("_")}_unique`,
570
+ columns: constraintColumns,
571
+ unique: true
572
+ });
573
+ }
574
+ }
575
+ return {
576
+ name: tableName,
577
+ columns,
578
+ indexes,
579
+ foreignKeys
580
+ };
581
+ }
582
+ function generateHclSchema(schemas, options) {
583
+ const tables = [];
584
+ for (const schema of Object.values(schemas)) {
585
+ if (schema.kind === "enum") {
586
+ continue;
587
+ }
588
+ const table = generateHclTable(schema, schemas, options.driver);
589
+ tables.push(table);
590
+ }
591
+ const enums = Object.values(schemas).filter((s) => s.kind === "enum").map((s) => ({
592
+ name: s.name.toLowerCase(),
593
+ values: s.values ?? []
594
+ }));
595
+ return {
596
+ driver: options.driver,
597
+ schemaName: options.schemaName,
598
+ tables,
599
+ enums
600
+ };
601
+ }
602
+ function formatHclColumn(column, driver) {
603
+ const parts = [` column "${column.name}" {`];
604
+ parts.push(` type = ${formatSqlType(column.type.type, driver)}`);
605
+ if (column.type.nullable) {
606
+ parts.push(" null = true");
607
+ }
608
+ if (column.type.default !== void 0) {
609
+ parts.push(` default = ${column.type.default}`);
610
+ }
611
+ if (column.type.autoIncrement) {
612
+ parts.push(" auto_increment = true");
613
+ }
614
+ if (column.type.unsigned && (driver === "mysql" || driver === "mariadb")) {
615
+ parts.push(" unsigned = true");
616
+ }
617
+ parts.push(" }");
618
+ return parts.join("\n");
619
+ }
620
+ function formatSqlType(type, driver) {
621
+ if (type.startsWith("enum(")) {
622
+ if (driver === "mysql" || driver === "mariadb") {
623
+ return type;
624
+ }
625
+ return "varchar(100)";
626
+ }
627
+ return type;
628
+ }
629
+ function formatHclIndex(index) {
630
+ const columns = index.columns.map((c) => `"${c}"`).join(", ");
631
+ const unique = index.unique ? "unique = true\n " : "";
632
+ return ` index "${index.name}" {
633
+ columns = [${columns}]
634
+ ${unique}}`;
635
+ }
636
+ function formatHclForeignKey(fk) {
637
+ const columns = fk.columns.map((c) => `"${c}"`).join(", ");
638
+ const refColumns = fk.refColumns.map((c) => `"${c}"`).join(", ");
639
+ return ` foreign_key "${fk.name}" {
640
+ columns = [${columns}]
641
+ ref_columns = [${refColumns}]
642
+ on_update = ${fk.onUpdate ?? "CASCADE"}
643
+ on_delete = ${fk.onDelete ?? "RESTRICT"}
644
+ }`;
645
+ }
646
+ function renderHcl(schema) {
647
+ const lines = [];
648
+ const schemaPrefix = schema.schemaName ? `schema "${schema.schemaName}" {
649
+ }
650
+
651
+ ` : "";
652
+ lines.push(schemaPrefix);
653
+ for (const table of schema.tables) {
654
+ lines.push(`table "${table.name}" {`);
655
+ if (schema.schemaName) {
656
+ lines.push(` schema = schema.${schema.schemaName}`);
657
+ }
658
+ for (const column of table.columns) {
659
+ lines.push(formatHclColumn(column, schema.driver));
660
+ }
661
+ const pkColumn = table.columns.find((c) => c.primaryKey);
662
+ if (pkColumn) {
663
+ lines.push(` primary_key {
664
+ columns = ["${pkColumn.name}"]
665
+ }`);
666
+ }
667
+ for (const index of table.indexes) {
668
+ lines.push(formatHclIndex(index));
669
+ }
670
+ for (const fk of table.foreignKeys) {
671
+ lines.push(formatHclForeignKey(fk));
672
+ }
673
+ lines.push("}\n");
674
+ }
675
+ return lines.join("\n");
676
+ }
677
+
678
+ // src/atlas/runner.ts
679
+ import { execa } from "execa";
680
+ import { mkdir, writeFile as writeFile2, rm } from "fs/promises";
681
+ import { join } from "path";
682
+ import { tmpdir } from "os";
683
+ import { randomUUID } from "crypto";
684
+ import { atlasError, atlasNotFoundError } from "@famgia/omnify-core";
685
+ var DEFAULT_CONFIG = {
686
+ binaryPath: "atlas",
687
+ timeout: 6e4
688
+ // 60 seconds
689
+ };
690
+ function getSchemaUrl(_driver, path) {
691
+ return `file://${path}`;
692
+ }
693
+ function normalizeDevUrl(devUrl, driver) {
694
+ if (devUrl === "docker") {
695
+ switch (driver) {
696
+ case "mysql":
697
+ return "docker://mysql/8/dev";
698
+ case "mariadb":
699
+ return "docker://mariadb/latest/dev";
700
+ case "postgres":
701
+ return "docker://postgres/15/dev";
702
+ default:
703
+ return devUrl;
704
+ }
705
+ }
706
+ return devUrl;
707
+ }
708
+ async function createTempDir() {
709
+ const tempPath = join(tmpdir(), `omnify-atlas-${randomUUID()}`);
710
+ await mkdir(tempPath, { recursive: true });
711
+ return tempPath;
712
+ }
713
+ async function executeAtlas(config, args) {
714
+ const binaryPath = config.binaryPath ?? DEFAULT_CONFIG.binaryPath;
715
+ const timeout = config.timeout ?? DEFAULT_CONFIG.timeout;
716
+ const startTime = Date.now();
717
+ try {
718
+ const result = config.workDir ? await execa(binaryPath, args, {
719
+ timeout,
720
+ reject: false,
721
+ cwd: config.workDir
722
+ }) : await execa(binaryPath, args, {
723
+ timeout,
724
+ reject: false
725
+ });
726
+ const stdout = typeof result.stdout === "string" ? result.stdout : "";
727
+ const stderr = typeof result.stderr === "string" ? result.stderr : "";
728
+ return {
729
+ success: result.exitCode === 0,
730
+ stdout,
731
+ stderr,
732
+ exitCode: result.exitCode ?? 0,
733
+ duration: Date.now() - startTime
734
+ };
735
+ } catch (error) {
736
+ const err = error;
737
+ if (err.code === "ENOENT") {
738
+ throw atlasNotFoundError();
739
+ }
740
+ throw atlasError(`Failed to execute Atlas: ${err.message}`, err);
741
+ }
742
+ }
743
+ async function checkAtlasVersion(config = {}) {
744
+ const fullConfig = {
745
+ ...DEFAULT_CONFIG,
746
+ ...config,
747
+ driver: config.driver ?? "mysql",
748
+ devUrl: config.devUrl ?? ""
749
+ };
750
+ try {
751
+ const result = await executeAtlas(fullConfig, ["version"]);
752
+ if (result.success) {
753
+ const match = result.stdout.match(/v?(\d+\.\d+\.\d+)/);
754
+ return {
755
+ version: match?.[1] ?? result.stdout.trim(),
756
+ available: true
757
+ };
758
+ }
759
+ return {
760
+ version: "",
761
+ available: false
762
+ };
763
+ } catch {
764
+ return {
765
+ version: "",
766
+ available: false
767
+ };
768
+ }
769
+ }
770
+ async function runAtlasDiff(config, options) {
771
+ const devUrl = normalizeDevUrl(config.devUrl, config.driver);
772
+ const toUrl = getSchemaUrl(config.driver, options.toPath);
773
+ const args = [
774
+ "schema",
775
+ "diff",
776
+ "--dev-url",
777
+ devUrl,
778
+ "--to",
779
+ toUrl,
780
+ "--format",
781
+ '{{ sql . " " }}'
782
+ ];
783
+ if (options.fromPath) {
784
+ const fromUrl = getSchemaUrl(config.driver, options.fromPath);
785
+ args.push("--from", fromUrl);
786
+ }
787
+ const result = await executeAtlas(config, args);
788
+ const sql = result.stdout.trim();
789
+ const hasChanges = sql.length > 0 && !sql.includes("-- No changes");
790
+ return {
791
+ ...result,
792
+ hasChanges,
793
+ sql: hasChanges ? sql : ""
794
+ };
795
+ }
796
+ async function diffHclSchemas(config, fromHcl, toHcl) {
797
+ const tempDir = await createTempDir();
798
+ try {
799
+ const toPath = join(tempDir, "to.hcl");
800
+ await writeFile2(toPath, toHcl, "utf8");
801
+ let fromPath;
802
+ if (fromHcl) {
803
+ fromPath = join(tempDir, "from.hcl");
804
+ await writeFile2(fromPath, fromHcl, "utf8");
805
+ }
806
+ return await runAtlasDiff(config, {
807
+ fromPath,
808
+ toPath
809
+ });
810
+ } finally {
811
+ await rm(tempDir, { recursive: true, force: true });
812
+ }
813
+ }
814
+ async function validateHcl(config, hclPath) {
815
+ const devUrl = normalizeDevUrl(config.devUrl, config.driver);
816
+ return executeAtlas(config, [
817
+ "schema",
818
+ "inspect",
819
+ "--dev-url",
820
+ devUrl,
821
+ "--url",
822
+ getSchemaUrl(config.driver, hclPath),
823
+ "--format",
824
+ "{{ sql . }}"
825
+ ]);
826
+ }
827
+ async function applySchema(config, hclPath) {
828
+ const devUrl = normalizeDevUrl(config.devUrl, config.driver);
829
+ return executeAtlas(config, [
830
+ "schema",
831
+ "apply",
832
+ "--dev-url",
833
+ devUrl,
834
+ "--to",
835
+ getSchemaUrl(config.driver, hclPath),
836
+ "--auto-approve"
837
+ ]);
838
+ }
839
+
840
+ // src/diff/parser.ts
841
+ var PATTERNS = {
842
+ createTable: /^CREATE TABLE\s+[`"]?(\w+)[`"]?/i,
843
+ dropTable: /^DROP TABLE\s+(?:IF EXISTS\s+)?[`"]?(\w+)[`"]?/i,
844
+ alterTable: /^ALTER TABLE\s+[`"]?(\w+)[`"]?/i,
845
+ addColumn: /ADD\s+(?:COLUMN\s+)?[`"]?(\w+)[`"]?/i,
846
+ dropColumn: /DROP\s+(?:COLUMN\s+)?[`"]?(\w+)[`"]?/i,
847
+ modifyColumn: /MODIFY\s+(?:COLUMN\s+)?[`"]?(\w+)[`"]?/i,
848
+ changeColumn: /CHANGE\s+(?:COLUMN\s+)?[`"]?(\w+)[`"]?/i,
849
+ alterColumn: /ALTER\s+(?:COLUMN\s+)?[`"]?(\w+)[`"]?/i,
850
+ createIndex: /^CREATE\s+(?:UNIQUE\s+)?INDEX\s+[`"]?(\w+)[`"]?\s+ON\s+[`"]?(\w+)[`"]?/i,
851
+ dropIndex: /^DROP\s+INDEX\s+[`"]?(\w+)[`"]?(?:\s+ON\s+[`"]?(\w+)[`"]?)?/i,
852
+ addConstraint: /ADD\s+CONSTRAINT\s+[`"]?(\w+)[`"]?/i,
853
+ dropConstraint: /DROP\s+CONSTRAINT\s+[`"]?(\w+)[`"]?/i,
854
+ addForeignKey: /ADD\s+(?:CONSTRAINT\s+[`"]?\w+[`"]?\s+)?FOREIGN KEY/i,
855
+ dropForeignKey: /DROP\s+FOREIGN KEY\s+[`"]?(\w+)[`"]?/i
856
+ };
857
+ function splitStatements(sql) {
858
+ const statements = [];
859
+ let current = "";
860
+ let inString = false;
861
+ let stringChar = "";
862
+ for (let i = 0; i < sql.length; i++) {
863
+ const char = sql[i];
864
+ if ((char === "'" || char === '"') && sql[i - 1] !== "\\") {
865
+ if (!inString) {
866
+ inString = true;
867
+ stringChar = char;
868
+ } else if (char === stringChar) {
869
+ inString = false;
870
+ }
871
+ }
872
+ if (char === ";" && !inString) {
873
+ const stmt = current.trim();
874
+ if (stmt) {
875
+ statements.push(stmt);
876
+ }
877
+ current = "";
878
+ } else {
879
+ current += char;
880
+ }
881
+ }
882
+ const final = current.trim();
883
+ if (final) {
884
+ statements.push(final);
885
+ }
886
+ return statements;
887
+ }
888
+ function parseStatement(sql) {
889
+ const trimmedSql = sql.trim();
890
+ let match = trimmedSql.match(PATTERNS.createTable);
891
+ if (match && match[1]) {
892
+ return {
893
+ sql: trimmedSql,
894
+ type: "CREATE_TABLE",
895
+ tableName: match[1],
896
+ severity: "safe"
897
+ };
898
+ }
899
+ match = trimmedSql.match(PATTERNS.dropTable);
900
+ if (match && match[1]) {
901
+ return {
902
+ sql: trimmedSql,
903
+ type: "DROP_TABLE",
904
+ tableName: match[1],
905
+ severity: "destructive"
906
+ };
907
+ }
908
+ match = trimmedSql.match(PATTERNS.createIndex);
909
+ if (match && match[1] && match[2]) {
910
+ return {
911
+ sql: trimmedSql,
912
+ type: "CREATE_INDEX",
913
+ tableName: match[2],
914
+ indexName: match[1],
915
+ severity: "safe"
916
+ };
917
+ }
918
+ match = trimmedSql.match(PATTERNS.dropIndex);
919
+ if (match && match[1]) {
920
+ return {
921
+ sql: trimmedSql,
922
+ type: "DROP_INDEX",
923
+ tableName: match[2] ?? "",
924
+ indexName: match[1],
925
+ severity: "warning"
926
+ };
927
+ }
928
+ match = trimmedSql.match(PATTERNS.alterTable);
929
+ if (match && match[1]) {
930
+ const tableName = match[1];
931
+ const alterPart = trimmedSql.slice(match[0].length);
932
+ const addColMatch = alterPart.match(PATTERNS.addColumn);
933
+ if (addColMatch && addColMatch[1]) {
934
+ return {
935
+ sql: trimmedSql,
936
+ type: "ADD_COLUMN",
937
+ tableName,
938
+ columnName: addColMatch[1],
939
+ severity: "safe"
940
+ };
941
+ }
942
+ const dropColMatch = alterPart.match(PATTERNS.dropColumn);
943
+ if (dropColMatch && dropColMatch[1]) {
944
+ return {
945
+ sql: trimmedSql,
946
+ type: "DROP_COLUMN",
947
+ tableName,
948
+ columnName: dropColMatch[1],
949
+ severity: "destructive"
950
+ };
951
+ }
952
+ const modifyColMatch = alterPart.match(PATTERNS.modifyColumn) || alterPart.match(PATTERNS.changeColumn) || alterPart.match(PATTERNS.alterColumn);
953
+ if (modifyColMatch && modifyColMatch[1]) {
954
+ return {
955
+ sql: trimmedSql,
956
+ type: "MODIFY_COLUMN",
957
+ tableName,
958
+ columnName: modifyColMatch[1],
959
+ severity: "warning"
960
+ };
961
+ }
962
+ if (PATTERNS.addForeignKey.test(alterPart)) {
963
+ const constraintMatch = alterPart.match(PATTERNS.addConstraint);
964
+ const fkConstraintName = constraintMatch?.[1];
965
+ if (fkConstraintName) {
966
+ return {
967
+ sql: trimmedSql,
968
+ type: "ADD_FOREIGN_KEY",
969
+ tableName,
970
+ constraintName: fkConstraintName,
971
+ severity: "safe"
972
+ };
973
+ }
974
+ return {
975
+ sql: trimmedSql,
976
+ type: "ADD_FOREIGN_KEY",
977
+ tableName,
978
+ severity: "safe"
979
+ };
980
+ }
981
+ const dropFkMatch = alterPart.match(PATTERNS.dropForeignKey);
982
+ if (dropFkMatch && dropFkMatch[1]) {
983
+ return {
984
+ sql: trimmedSql,
985
+ type: "DROP_FOREIGN_KEY",
986
+ tableName,
987
+ constraintName: dropFkMatch[1],
988
+ severity: "warning"
989
+ };
990
+ }
991
+ const addConstraintMatch = alterPart.match(PATTERNS.addConstraint);
992
+ if (addConstraintMatch && addConstraintMatch[1]) {
993
+ return {
994
+ sql: trimmedSql,
995
+ type: "ADD_CONSTRAINT",
996
+ tableName,
997
+ constraintName: addConstraintMatch[1],
998
+ severity: "safe"
999
+ };
1000
+ }
1001
+ const dropConstraintMatch = alterPart.match(PATTERNS.dropConstraint);
1002
+ if (dropConstraintMatch && dropConstraintMatch[1]) {
1003
+ return {
1004
+ sql: trimmedSql,
1005
+ type: "DROP_CONSTRAINT",
1006
+ tableName,
1007
+ constraintName: dropConstraintMatch[1],
1008
+ severity: "warning"
1009
+ };
1010
+ }
1011
+ return {
1012
+ sql: trimmedSql,
1013
+ type: "ALTER_TABLE",
1014
+ tableName,
1015
+ severity: "warning"
1016
+ };
1017
+ }
1018
+ return {
1019
+ sql: trimmedSql,
1020
+ type: "UNKNOWN",
1021
+ tableName: "",
1022
+ severity: "warning"
1023
+ };
1024
+ }
1025
+ function createEmptyTableChange(tableName) {
1026
+ return {
1027
+ tableName,
1028
+ isNew: false,
1029
+ isDropped: false,
1030
+ addedColumns: [],
1031
+ droppedColumns: [],
1032
+ modifiedColumns: [],
1033
+ addedIndexes: [],
1034
+ droppedIndexes: [],
1035
+ addedForeignKeys: [],
1036
+ droppedForeignKeys: []
1037
+ };
1038
+ }
1039
+ function getOrCreateTable(tables, tableName) {
1040
+ const existing = tables[tableName];
1041
+ if (existing) {
1042
+ return existing;
1043
+ }
1044
+ const newTable = createEmptyTableChange(tableName);
1045
+ tables[tableName] = newTable;
1046
+ return newTable;
1047
+ }
1048
+ function groupByTable(statements) {
1049
+ const tables = {};
1050
+ for (const stmt of statements) {
1051
+ if (!stmt.tableName) continue;
1052
+ const table = getOrCreateTable(tables, stmt.tableName);
1053
+ switch (stmt.type) {
1054
+ case "CREATE_TABLE":
1055
+ tables[stmt.tableName] = { ...table, isNew: true };
1056
+ break;
1057
+ case "DROP_TABLE":
1058
+ tables[stmt.tableName] = { ...table, isDropped: true };
1059
+ break;
1060
+ case "ADD_COLUMN":
1061
+ if (stmt.columnName) {
1062
+ tables[stmt.tableName] = {
1063
+ ...table,
1064
+ addedColumns: [...table.addedColumns, stmt.columnName]
1065
+ };
1066
+ }
1067
+ break;
1068
+ case "DROP_COLUMN":
1069
+ if (stmt.columnName) {
1070
+ tables[stmt.tableName] = {
1071
+ ...table,
1072
+ droppedColumns: [...table.droppedColumns, stmt.columnName]
1073
+ };
1074
+ }
1075
+ break;
1076
+ case "MODIFY_COLUMN":
1077
+ if (stmt.columnName) {
1078
+ tables[stmt.tableName] = {
1079
+ ...table,
1080
+ modifiedColumns: [...table.modifiedColumns, stmt.columnName]
1081
+ };
1082
+ }
1083
+ break;
1084
+ case "CREATE_INDEX":
1085
+ if (stmt.indexName) {
1086
+ tables[stmt.tableName] = {
1087
+ ...table,
1088
+ addedIndexes: [...table.addedIndexes, stmt.indexName]
1089
+ };
1090
+ }
1091
+ break;
1092
+ case "DROP_INDEX":
1093
+ if (stmt.indexName) {
1094
+ tables[stmt.tableName] = {
1095
+ ...table,
1096
+ droppedIndexes: [...table.droppedIndexes, stmt.indexName]
1097
+ };
1098
+ }
1099
+ break;
1100
+ case "ADD_FOREIGN_KEY":
1101
+ tables[stmt.tableName] = {
1102
+ ...table,
1103
+ addedForeignKeys: [
1104
+ ...table.addedForeignKeys,
1105
+ stmt.constraintName ?? "unnamed"
1106
+ ]
1107
+ };
1108
+ break;
1109
+ case "DROP_FOREIGN_KEY":
1110
+ if (stmt.constraintName) {
1111
+ tables[stmt.tableName] = {
1112
+ ...table,
1113
+ droppedForeignKeys: [...table.droppedForeignKeys, stmt.constraintName]
1114
+ };
1115
+ }
1116
+ break;
1117
+ }
1118
+ }
1119
+ return tables;
1120
+ }
1121
+ function calculateSummary(statements) {
1122
+ return {
1123
+ totalStatements: statements.length,
1124
+ tablesCreated: statements.filter((s) => s.type === "CREATE_TABLE").length,
1125
+ tablesDropped: statements.filter((s) => s.type === "DROP_TABLE").length,
1126
+ tablesAltered: statements.filter((s) => s.type === "ALTER_TABLE").length,
1127
+ columnsAdded: statements.filter((s) => s.type === "ADD_COLUMN").length,
1128
+ columnsDropped: statements.filter((s) => s.type === "DROP_COLUMN").length,
1129
+ columnsModified: statements.filter((s) => s.type === "MODIFY_COLUMN").length,
1130
+ indexesAdded: statements.filter((s) => s.type === "CREATE_INDEX").length,
1131
+ indexesDropped: statements.filter((s) => s.type === "DROP_INDEX").length,
1132
+ foreignKeysAdded: statements.filter((s) => s.type === "ADD_FOREIGN_KEY").length,
1133
+ foreignKeysDropped: statements.filter((s) => s.type === "DROP_FOREIGN_KEY").length
1134
+ };
1135
+ }
1136
+ function parseDiffOutput(sql) {
1137
+ const trimmedSql = sql.trim();
1138
+ if (!trimmedSql || trimmedSql === "-- No changes") {
1139
+ return {
1140
+ hasChanges: false,
1141
+ hasDestructiveChanges: false,
1142
+ statements: [],
1143
+ tableChanges: {},
1144
+ summary: {
1145
+ totalStatements: 0,
1146
+ tablesCreated: 0,
1147
+ tablesDropped: 0,
1148
+ tablesAltered: 0,
1149
+ columnsAdded: 0,
1150
+ columnsDropped: 0,
1151
+ columnsModified: 0,
1152
+ indexesAdded: 0,
1153
+ indexesDropped: 0,
1154
+ foreignKeysAdded: 0,
1155
+ foreignKeysDropped: 0
1156
+ },
1157
+ rawSql: trimmedSql
1158
+ };
1159
+ }
1160
+ const sqlWithoutComments = trimmedSql.split("\n").filter((line) => !line.trim().startsWith("--")).join("\n");
1161
+ const rawStatements = splitStatements(sqlWithoutComments);
1162
+ const statements = rawStatements.map(parseStatement);
1163
+ const hasDestructiveChanges = statements.some(
1164
+ (s) => s.severity === "destructive"
1165
+ );
1166
+ return {
1167
+ hasChanges: statements.length > 0,
1168
+ hasDestructiveChanges,
1169
+ statements,
1170
+ tableChanges: groupByTable(statements),
1171
+ summary: calculateSummary(statements),
1172
+ rawSql: trimmedSql
1173
+ };
1174
+ }
1175
+ function formatDiffSummary(result) {
1176
+ if (!result.hasChanges) {
1177
+ return "No schema changes detected.";
1178
+ }
1179
+ const lines = ["Schema changes detected:"];
1180
+ const { summary } = result;
1181
+ if (summary.tablesCreated > 0) {
1182
+ lines.push(` + ${summary.tablesCreated} table(s) created`);
1183
+ }
1184
+ if (summary.tablesDropped > 0) {
1185
+ lines.push(` - ${summary.tablesDropped} table(s) dropped [DESTRUCTIVE]`);
1186
+ }
1187
+ if (summary.columnsAdded > 0) {
1188
+ lines.push(` + ${summary.columnsAdded} column(s) added`);
1189
+ }
1190
+ if (summary.columnsDropped > 0) {
1191
+ lines.push(` - ${summary.columnsDropped} column(s) dropped [DESTRUCTIVE]`);
1192
+ }
1193
+ if (summary.columnsModified > 0) {
1194
+ lines.push(` ~ ${summary.columnsModified} column(s) modified`);
1195
+ }
1196
+ if (summary.indexesAdded > 0) {
1197
+ lines.push(` + ${summary.indexesAdded} index(es) added`);
1198
+ }
1199
+ if (summary.indexesDropped > 0) {
1200
+ lines.push(` - ${summary.indexesDropped} index(es) dropped`);
1201
+ }
1202
+ if (summary.foreignKeysAdded > 0) {
1203
+ lines.push(` + ${summary.foreignKeysAdded} foreign key(s) added`);
1204
+ }
1205
+ if (summary.foreignKeysDropped > 0) {
1206
+ lines.push(` - ${summary.foreignKeysDropped} foreign key(s) dropped`);
1207
+ }
1208
+ lines.push("");
1209
+ lines.push(`Total: ${summary.totalStatements} statement(s)`);
1210
+ if (result.hasDestructiveChanges) {
1211
+ lines.push("");
1212
+ lines.push("WARNING: This diff contains destructive changes!");
1213
+ }
1214
+ return lines.join("\n");
1215
+ }
1216
+
1217
+ // src/preview/preview.ts
1218
+ import { join as join2 } from "path";
1219
+ import { atlasNotFoundError as atlasNotFoundError2 } from "@famgia/omnify-core";
1220
+ async function generatePreview(schemas, atlasConfig, options = {}) {
1221
+ const atlasVersion = await checkAtlasVersion(atlasConfig);
1222
+ if (!atlasVersion.available) {
1223
+ throw atlasNotFoundError2();
1224
+ }
1225
+ const currentHashes = await buildSchemaHashes(schemas);
1226
+ const lockFilePath = join2(atlasConfig.workDir ?? process.cwd(), LOCK_FILE_NAME);
1227
+ const existingLockFile = await readLockFile(lockFilePath);
1228
+ const schemaChanges = compareSchemas(currentHashes, existingLockFile);
1229
+ const currentHcl = renderHcl(
1230
+ generateHclSchema(schemas, {
1231
+ driver: atlasConfig.driver
1232
+ })
1233
+ );
1234
+ let previousHcl = null;
1235
+ if (existingLockFile?.hclChecksum) {
1236
+ previousHcl = null;
1237
+ }
1238
+ const atlasDiff = await diffHclSchemas(atlasConfig, previousHcl, currentHcl);
1239
+ const databaseChanges = parseDiffOutput(atlasDiff.sql);
1240
+ const summary = buildSummary(schemaChanges, databaseChanges, options);
1241
+ return {
1242
+ hasChanges: schemaChanges.hasChanges || databaseChanges.hasChanges,
1243
+ hasDestructiveChanges: databaseChanges.hasDestructiveChanges,
1244
+ schemaChanges,
1245
+ databaseChanges,
1246
+ summary,
1247
+ sql: atlasDiff.sql
1248
+ };
1249
+ }
1250
+ function buildSummary(schemaChanges, databaseChanges, options) {
1251
+ const lines = [];
1252
+ if (!schemaChanges.hasChanges && !databaseChanges.hasChanges) {
1253
+ return "No changes detected. Schema is up to date.";
1254
+ }
1255
+ if (schemaChanges.hasChanges) {
1256
+ lines.push("Schema file changes:");
1257
+ for (const change of schemaChanges.changes) {
1258
+ const icon = change.changeType === "added" ? "+" : change.changeType === "removed" ? "-" : "~";
1259
+ lines.push(` ${icon} ${change.schemaName} (${change.changeType})`);
1260
+ }
1261
+ lines.push("");
1262
+ }
1263
+ if (databaseChanges.hasChanges) {
1264
+ lines.push(formatDiffSummary(databaseChanges));
1265
+ }
1266
+ if (options.warnDestructive && databaseChanges.hasDestructiveChanges) {
1267
+ lines.push("");
1268
+ lines.push("\u26A0\uFE0F WARNING: This preview contains destructive changes!");
1269
+ lines.push(" Review carefully before generating migrations.");
1270
+ }
1271
+ return lines.join("\n");
1272
+ }
1273
+ async function previewSchemaChanges(schemas, lockFilePath) {
1274
+ const currentHashes = await buildSchemaHashes(schemas);
1275
+ const existingLockFile = await readLockFile(lockFilePath);
1276
+ return compareSchemas(currentHashes, existingLockFile);
1277
+ }
1278
+ function formatPreview(preview, format = "text") {
1279
+ switch (format) {
1280
+ case "json":
1281
+ return JSON.stringify(preview, null, 2);
1282
+ case "minimal":
1283
+ if (!preview.hasChanges) {
1284
+ return "No changes";
1285
+ }
1286
+ const parts = [];
1287
+ const { summary } = preview.databaseChanges;
1288
+ if (summary.tablesCreated > 0) parts.push(`+${summary.tablesCreated} tables`);
1289
+ if (summary.tablesDropped > 0) parts.push(`-${summary.tablesDropped} tables`);
1290
+ if (summary.columnsAdded > 0) parts.push(`+${summary.columnsAdded} columns`);
1291
+ if (summary.columnsDropped > 0) parts.push(`-${summary.columnsDropped} columns`);
1292
+ return parts.join(", ") || "Changes detected";
1293
+ case "text":
1294
+ default:
1295
+ return preview.summary;
1296
+ }
1297
+ }
1298
+ function hasBlockingIssues(_preview) {
1299
+ return false;
1300
+ }
1301
+ export {
1302
+ LOCK_FILE_NAME,
1303
+ LOCK_FILE_VERSION,
1304
+ addMigrationRecord,
1305
+ applySchema,
1306
+ buildSchemaHashes,
1307
+ checkAtlasVersion,
1308
+ compareSchemas,
1309
+ computeHash,
1310
+ computeSchemaHash,
1311
+ createEmptyLockFile,
1312
+ diffHclSchemas,
1313
+ formatDiffSummary,
1314
+ formatPreview,
1315
+ generateHclSchema,
1316
+ generateHclTable,
1317
+ generatePreview,
1318
+ getPrimaryKeyType,
1319
+ getTimestampType,
1320
+ hasBlockingIssues,
1321
+ mapPropertyToSql,
1322
+ parseDiffOutput,
1323
+ previewSchemaChanges,
1324
+ propertyNameToColumnName,
1325
+ readLockFile,
1326
+ renderHcl,
1327
+ runAtlasDiff,
1328
+ schemaNameToTableName,
1329
+ updateLockFile,
1330
+ validateHcl,
1331
+ writeLockFile
1332
+ };
1333
+ //# sourceMappingURL=index.js.map