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