@famgia/omnify-laravel 0.0.16 → 0.0.18

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.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { PropertyDefinition, SchemaCollection, LoadedSchema } from '@famgia/omnify-types';
1
+ import { CustomTypeDefinition, PropertyDefinition, SchemaCollection, LoadedSchema } from '@famgia/omnify-types';
2
2
  import { SchemaChange } from '@famgia/omnify-atlas';
3
3
  export { LaravelPluginOptions, laravelPlugin } from './plugin.cjs';
4
4
 
@@ -7,6 +7,7 @@ export { LaravelPluginOptions, laravelPlugin } from './plugin.cjs';
7
7
  *
8
8
  * Types for Laravel migration generation.
9
9
  */
10
+
10
11
  /**
11
12
  * Laravel migration file structure.
12
13
  */
@@ -34,6 +35,8 @@ interface MigrationOptions {
34
35
  readonly generateDown?: boolean | undefined;
35
36
  /** Database connection name */
36
37
  readonly connection?: string | undefined;
38
+ /** Custom types from plugins (for compound type expansion) */
39
+ readonly customTypes?: ReadonlyMap<string, CustomTypeDefinition> | undefined;
37
40
  }
38
41
  /**
39
42
  * Schema Builder column method.
@@ -161,10 +164,17 @@ declare function generateForeignKey(propertyName: string, property: PropertyDefi
161
164
  foreignKey: ForeignKeyDefinition;
162
165
  index: IndexDefinition;
163
166
  } | null;
167
+ /**
168
+ * Options for schema to blueprint conversion.
169
+ */
170
+ interface SchemaToBlueprintOptions {
171
+ /** Custom types from plugins (for compound type expansion) */
172
+ customTypes?: ReadonlyMap<string, CustomTypeDefinition>;
173
+ }
164
174
  /**
165
175
  * Generates table blueprint from schema.
166
176
  */
167
- declare function schemaToBlueprint(schema: LoadedSchema, allSchemas: SchemaCollection): TableBlueprint;
177
+ declare function schemaToBlueprint(schema: LoadedSchema, allSchemas: SchemaCollection, options?: SchemaToBlueprintOptions): TableBlueprint;
168
178
  /**
169
179
  * Formats a column method to PHP code.
170
180
  */
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { PropertyDefinition, SchemaCollection, LoadedSchema } from '@famgia/omnify-types';
1
+ import { CustomTypeDefinition, PropertyDefinition, SchemaCollection, LoadedSchema } from '@famgia/omnify-types';
2
2
  import { SchemaChange } from '@famgia/omnify-atlas';
3
3
  export { LaravelPluginOptions, laravelPlugin } from './plugin.js';
4
4
 
@@ -7,6 +7,7 @@ export { LaravelPluginOptions, laravelPlugin } from './plugin.js';
7
7
  *
8
8
  * Types for Laravel migration generation.
9
9
  */
10
+
10
11
  /**
11
12
  * Laravel migration file structure.
12
13
  */
@@ -34,6 +35,8 @@ interface MigrationOptions {
34
35
  readonly generateDown?: boolean | undefined;
35
36
  /** Database connection name */
36
37
  readonly connection?: string | undefined;
38
+ /** Custom types from plugins (for compound type expansion) */
39
+ readonly customTypes?: ReadonlyMap<string, CustomTypeDefinition> | undefined;
37
40
  }
38
41
  /**
39
42
  * Schema Builder column method.
@@ -161,10 +164,17 @@ declare function generateForeignKey(propertyName: string, property: PropertyDefi
161
164
  foreignKey: ForeignKeyDefinition;
162
165
  index: IndexDefinition;
163
166
  } | null;
167
+ /**
168
+ * Options for schema to blueprint conversion.
169
+ */
170
+ interface SchemaToBlueprintOptions {
171
+ /** Custom types from plugins (for compound type expansion) */
172
+ customTypes?: ReadonlyMap<string, CustomTypeDefinition>;
173
+ }
164
174
  /**
165
175
  * Generates table blueprint from schema.
166
176
  */
167
- declare function schemaToBlueprint(schema: LoadedSchema, allSchemas: SchemaCollection): TableBlueprint;
177
+ declare function schemaToBlueprint(schema: LoadedSchema, allSchemas: SchemaCollection, options?: SchemaToBlueprintOptions): TableBlueprint;
168
178
  /**
169
179
  * Formats a column method to PHP code.
170
180
  */
package/dist/index.js CHANGED
@@ -19,7 +19,7 @@ import {
19
19
  schemaToBlueprint,
20
20
  toColumnName,
21
21
  toTableName
22
- } from "./chunk-CAWNYSF3.js";
22
+ } from "./chunk-H37M25AK.js";
23
23
  export {
24
24
  formatColumnMethod,
25
25
  formatForeignKey,
package/dist/plugin.cjs CHANGED
@@ -39,6 +39,7 @@ var TYPE_METHOD_MAP = {
39
39
  LongText: "longText",
40
40
  Date: "date",
41
41
  Time: "time",
42
+ DateTime: "dateTime",
42
43
  Timestamp: "timestamp",
43
44
  Json: "json",
44
45
  Email: "string",
@@ -111,6 +112,10 @@ function propertyToColumnMethod(propertyName, property) {
111
112
  if (baseProp.unsigned && (method === "integer" || method === "bigInteger")) {
112
113
  modifiers.push({ method: "unsigned" });
113
114
  }
115
+ const displayName = property.displayName;
116
+ if (displayName) {
117
+ modifiers.push({ method: "comment", args: [displayName] });
118
+ }
114
119
  return {
115
120
  name: columnName,
116
121
  method,
@@ -239,12 +244,15 @@ function generateForeignKey(propertyName, property, allSchemas) {
239
244
  method = "string";
240
245
  }
241
246
  const modifiers = [];
242
- if (assocProp.nullable || assocProp.relation === "ManyToOne") {
247
+ if (assocProp.nullable === true) {
243
248
  modifiers.push({ method: "nullable" });
244
249
  }
245
250
  if (assocProp.default !== void 0 && assocProp.default !== null) {
246
251
  modifiers.push({ method: "default", args: [assocProp.default] });
247
252
  }
253
+ if (assocProp.displayName) {
254
+ modifiers.push({ method: "comment", args: [assocProp.displayName] });
255
+ }
248
256
  const column = {
249
257
  name: columnName,
250
258
  method,
@@ -264,7 +272,65 @@ function generateForeignKey(propertyName, property, allSchemas) {
264
272
  };
265
273
  return { column, foreignKey, index };
266
274
  }
267
- function schemaToBlueprint(schema, allSchemas) {
275
+ function expandCompoundType(propName, property, customTypes) {
276
+ const typeDef = customTypes.get(property.type);
277
+ if (!typeDef || !typeDef.compound || !typeDef.expand) {
278
+ return null;
279
+ }
280
+ const expanded = [];
281
+ const baseProp = property;
282
+ for (const field of typeDef.expand) {
283
+ const suffixSnake = toColumnName(field.suffix);
284
+ const columnName = `${propName}_${suffixSnake}`;
285
+ const expandedProp = {
286
+ type: "String"
287
+ // Default type, will be overridden by sql definition
288
+ };
289
+ if (field.sql) {
290
+ const sqlType = field.sql.sqlType.toUpperCase();
291
+ if (sqlType === "VARCHAR" || sqlType === "CHAR" || sqlType === "STRING") {
292
+ expandedProp.type = "String";
293
+ if (field.sql.length) {
294
+ expandedProp.length = field.sql.length;
295
+ }
296
+ } else if (sqlType === "INT" || sqlType === "INTEGER") {
297
+ expandedProp.type = "Int";
298
+ } else if (sqlType === "BIGINT") {
299
+ expandedProp.type = "BigInt";
300
+ } else if (sqlType === "TEXT") {
301
+ expandedProp.type = "Text";
302
+ } else if (sqlType === "BOOLEAN" || sqlType === "BOOL") {
303
+ expandedProp.type = "Boolean";
304
+ } else if (sqlType === "DECIMAL") {
305
+ expandedProp.type = "Decimal";
306
+ if (field.sql.precision) expandedProp.precision = field.sql.precision;
307
+ if (field.sql.scale) expandedProp.scale = field.sql.scale;
308
+ } else if (sqlType === "DATE") {
309
+ expandedProp.type = "Date";
310
+ } else if (sqlType === "TIMESTAMP" || sqlType === "DATETIME") {
311
+ expandedProp.type = "Timestamp";
312
+ }
313
+ if (field.sql.nullable !== void 0) {
314
+ expandedProp.nullable = field.sql.nullable;
315
+ } else if (baseProp.nullable !== void 0) {
316
+ expandedProp.nullable = baseProp.nullable;
317
+ }
318
+ if (field.sql.default !== void 0) {
319
+ expandedProp.default = field.sql.default;
320
+ }
321
+ }
322
+ if (baseProp.displayName) {
323
+ expandedProp.displayName = `${baseProp.displayName} (${field.suffix})`;
324
+ }
325
+ expanded.push({
326
+ name: columnName,
327
+ property: expandedProp
328
+ });
329
+ }
330
+ return expanded;
331
+ }
332
+ function schemaToBlueprint(schema, allSchemas, options = {}) {
333
+ const { customTypes = /* @__PURE__ */ new Map() } = options;
268
334
  const tableName = toTableName(schema.name);
269
335
  const columns = [];
270
336
  const foreignKeys = [];
@@ -275,6 +341,16 @@ function schemaToBlueprint(schema, allSchemas) {
275
341
  }
276
342
  if (schema.properties) {
277
343
  for (const [propName, property] of Object.entries(schema.properties)) {
344
+ const expandedProps = expandCompoundType(propName, property, customTypes);
345
+ if (expandedProps) {
346
+ for (const { name: expandedName, property: expandedProp } of expandedProps) {
347
+ const columnMethod2 = propertyToColumnMethod(expandedName, expandedProp);
348
+ if (columnMethod2) {
349
+ columns.push(columnMethod2);
350
+ }
351
+ }
352
+ continue;
353
+ }
278
354
  const columnMethod = propertyToColumnMethod(propName, property);
279
355
  if (columnMethod) {
280
356
  columns.push(columnMethod);
@@ -300,36 +376,67 @@ function schemaToBlueprint(schema, allSchemas) {
300
376
  columns.push(generateSoftDeleteColumn());
301
377
  }
302
378
  if (schema.options?.indexes) {
379
+ const propToColName = (propName) => {
380
+ const colName = toColumnName(propName);
381
+ const prop = schema.properties?.[propName];
382
+ if (prop?.type === "Association") {
383
+ const assoc = prop;
384
+ if ((assoc.relation === "ManyToOne" || assoc.relation === "OneToOne") && !assoc.mappedBy) {
385
+ return colName + "_id";
386
+ }
387
+ }
388
+ return colName;
389
+ };
303
390
  for (const index of schema.options.indexes) {
304
391
  if (typeof index === "string") {
305
392
  indexes.push({
306
- columns: [toColumnName(index)],
393
+ columns: [propToColName(index)],
307
394
  unique: false
308
395
  });
309
396
  } else {
310
397
  indexes.push({
311
398
  name: index.name,
312
- columns: index.columns.map(toColumnName),
399
+ columns: index.columns.map(propToColName),
313
400
  unique: index.unique ?? false
314
401
  });
315
402
  }
316
403
  }
317
404
  }
318
405
  if (schema.options?.unique) {
406
+ const propToColName = (propName) => {
407
+ const colName = toColumnName(propName);
408
+ const prop = schema.properties?.[propName];
409
+ if (prop?.type === "Association") {
410
+ const assoc = prop;
411
+ if ((assoc.relation === "ManyToOne" || assoc.relation === "OneToOne") && !assoc.mappedBy) {
412
+ return colName + "_id";
413
+ }
414
+ }
415
+ return colName;
416
+ };
319
417
  const uniqueConstraints = Array.isArray(schema.options.unique[0]) ? schema.options.unique : [schema.options.unique];
320
418
  for (const constraint of uniqueConstraints) {
321
419
  indexes.push({
322
- columns: constraint.map(toColumnName),
420
+ columns: constraint.map(propToColName),
323
421
  unique: true
324
422
  });
325
423
  }
326
424
  }
425
+ const seenIndexes = /* @__PURE__ */ new Set();
426
+ const uniqueIndexes = indexes.filter((idx) => {
427
+ const key = idx.columns.join(",") + (idx.unique ? ":unique" : "");
428
+ if (seenIndexes.has(key)) {
429
+ return false;
430
+ }
431
+ seenIndexes.add(key);
432
+ return true;
433
+ });
327
434
  return {
328
435
  tableName,
329
436
  columns,
330
437
  primaryKey: ["id"],
331
438
  foreignKeys,
332
- indexes
439
+ indexes: uniqueIndexes
333
440
  };
334
441
  }
335
442
  function formatColumnMethod(column) {
@@ -650,7 +757,9 @@ function generateMigrations(schemas, options = {}) {
650
757
  const timestamp = options.timestamp ?? generateTimestamp();
651
758
  const offsetTimestamp = incrementTimestamp(timestamp, timestampOffset);
652
759
  timestampOffset++;
653
- const blueprint = schemaToBlueprint(schema, schemas);
760
+ const blueprint = schemaToBlueprint(schema, schemas, {
761
+ customTypes: options.customTypes
762
+ });
654
763
  const migration = generateCreateMigration(blueprint, {
655
764
  ...options,
656
765
  timestamp: offsetTimestamp
@@ -706,6 +815,288 @@ function getMigrationPath(migration, outputDir = "database/migrations") {
706
815
  return `${outputDir}/${migration.fileName}`;
707
816
  }
708
817
 
818
+ // src/migration/alter-generator.ts
819
+ var TYPE_METHOD_MAP2 = {
820
+ String: "string",
821
+ Int: "integer",
822
+ BigInt: "bigInteger",
823
+ Float: "double",
824
+ Decimal: "decimal",
825
+ Boolean: "boolean",
826
+ Text: "text",
827
+ LongText: "longText",
828
+ Date: "date",
829
+ Time: "time",
830
+ DateTime: "dateTime",
831
+ Timestamp: "timestamp",
832
+ Json: "json",
833
+ Email: "string",
834
+ Password: "string",
835
+ File: "string",
836
+ MultiFile: "json",
837
+ Enum: "enum",
838
+ Select: "string",
839
+ Lookup: "unsignedBigInteger"
840
+ };
841
+ function generateTimestamp2() {
842
+ const now = /* @__PURE__ */ new Date();
843
+ const year = now.getFullYear();
844
+ const month = String(now.getMonth() + 1).padStart(2, "0");
845
+ const day = String(now.getDate()).padStart(2, "0");
846
+ const hours = String(now.getHours()).padStart(2, "0");
847
+ const minutes = String(now.getMinutes()).padStart(2, "0");
848
+ const seconds = String(now.getSeconds()).padStart(2, "0");
849
+ return `${year}_${month}_${day}_${hours}${minutes}${seconds}`;
850
+ }
851
+ function formatAddColumn(columnName, prop) {
852
+ const snakeColumn = toColumnName(columnName);
853
+ const method = TYPE_METHOD_MAP2[prop.type] ?? "string";
854
+ let code;
855
+ if (prop.type === "Decimal") {
856
+ const precision = prop.precision ?? 8;
857
+ const scale = prop.scale ?? 2;
858
+ code = `$table->${method}('${snakeColumn}', ${precision}, ${scale})`;
859
+ } else {
860
+ code = `$table->${method}('${snakeColumn}')`;
861
+ }
862
+ if (prop.nullable) code += "->nullable()";
863
+ if (prop.unique) code += "->unique()";
864
+ if (prop.default !== void 0) {
865
+ const defaultValue = typeof prop.default === "string" ? `'${prop.default}'` : JSON.stringify(prop.default);
866
+ code += `->default(${defaultValue})`;
867
+ }
868
+ return code + ";";
869
+ }
870
+ function formatDropColumn(columnName) {
871
+ const snakeColumn = toColumnName(columnName);
872
+ return `$table->dropColumn('${snakeColumn}');`;
873
+ }
874
+ function formatRenameColumn(oldName, newName) {
875
+ const oldSnake = toColumnName(oldName);
876
+ const newSnake = toColumnName(newName);
877
+ return `$table->renameColumn('${oldSnake}', '${newSnake}');`;
878
+ }
879
+ function formatModifyColumn(columnName, _prevProp, currProp) {
880
+ const snakeColumn = toColumnName(columnName);
881
+ const method = TYPE_METHOD_MAP2[currProp.type] ?? "string";
882
+ let code;
883
+ if (currProp.type === "Decimal") {
884
+ const precision = currProp.precision ?? 8;
885
+ const scale = currProp.scale ?? 2;
886
+ code = `$table->${method}('${snakeColumn}', ${precision}, ${scale})`;
887
+ } else {
888
+ code = `$table->${method}('${snakeColumn}')`;
889
+ }
890
+ if (currProp.nullable) code += "->nullable()";
891
+ if (currProp.unique) code += "->unique()";
892
+ if (currProp.default !== void 0) {
893
+ const defaultValue = typeof currProp.default === "string" ? `'${currProp.default}'` : JSON.stringify(currProp.default);
894
+ code += `->default(${defaultValue})`;
895
+ }
896
+ return code + "->change();";
897
+ }
898
+ function formatAddIndex(columns, unique) {
899
+ const snakeColumns = columns.map(toColumnName);
900
+ const method = unique ? "unique" : "index";
901
+ const colsArg = snakeColumns.length === 1 ? `'${snakeColumns[0]}'` : `[${snakeColumns.map((c) => `'${c}'`).join(", ")}]`;
902
+ return `$table->${method}(${colsArg});`;
903
+ }
904
+ function formatDropIndex(tableName, columns, unique) {
905
+ const snakeColumns = columns.map(toColumnName);
906
+ const method = unique ? "dropUnique" : "dropIndex";
907
+ const suffix = unique ? "unique" : "index";
908
+ const indexName = `${tableName}_${snakeColumns.join("_")}_${suffix}`;
909
+ return `$table->${method}('${indexName}');`;
910
+ }
911
+ function generateAlterMigrationContent(tableName, change, options = {}) {
912
+ const upLines = [];
913
+ const downLines = [];
914
+ if (change.columnChanges) {
915
+ for (const col of change.columnChanges) {
916
+ if (col.changeType === "added" && col.currentDef) {
917
+ upLines.push(` ${formatAddColumn(col.column, col.currentDef)}`);
918
+ downLines.push(` ${formatDropColumn(col.column)}`);
919
+ } else if (col.changeType === "removed" && col.previousDef) {
920
+ upLines.push(` ${formatDropColumn(col.column)}`);
921
+ downLines.push(` ${formatAddColumn(col.column, col.previousDef)}`);
922
+ } else if (col.changeType === "modified" && col.previousDef && col.currentDef) {
923
+ upLines.push(` ${formatModifyColumn(col.column, col.previousDef, col.currentDef)}`);
924
+ downLines.push(` ${formatModifyColumn(col.column, col.currentDef, col.previousDef)}`);
925
+ } else if (col.changeType === "renamed" && col.previousColumn) {
926
+ upLines.push(` ${formatRenameColumn(col.previousColumn, col.column)}`);
927
+ downLines.push(` ${formatRenameColumn(col.column, col.previousColumn)}`);
928
+ if (col.modifications && col.modifications.length > 0 && col.previousDef && col.currentDef) {
929
+ upLines.push(` ${formatModifyColumn(col.column, col.previousDef, col.currentDef)}`);
930
+ downLines.push(` ${formatModifyColumn(col.column, col.currentDef, col.previousDef)}`);
931
+ }
932
+ }
933
+ }
934
+ }
935
+ if (change.indexChanges) {
936
+ for (const idx of change.indexChanges) {
937
+ if (idx.changeType === "added") {
938
+ upLines.push(` ${formatAddIndex(idx.index.columns, idx.index.unique)}`);
939
+ downLines.push(` ${formatDropIndex(tableName, idx.index.columns, idx.index.unique)}`);
940
+ } else {
941
+ upLines.push(` ${formatDropIndex(tableName, idx.index.columns, idx.index.unique)}`);
942
+ downLines.push(` ${formatAddIndex(idx.index.columns, idx.index.unique)}`);
943
+ }
944
+ }
945
+ }
946
+ if (change.optionChanges) {
947
+ if (change.optionChanges.timestamps) {
948
+ const { from, to } = change.optionChanges.timestamps;
949
+ if (to && !from) {
950
+ upLines.push(` $table->timestamps();`);
951
+ downLines.push(` $table->dropTimestamps();`);
952
+ } else if (from && !to) {
953
+ upLines.push(` $table->dropTimestamps();`);
954
+ downLines.push(` $table->timestamps();`);
955
+ }
956
+ }
957
+ if (change.optionChanges.softDelete) {
958
+ const { from, to } = change.optionChanges.softDelete;
959
+ if (to && !from) {
960
+ upLines.push(` $table->softDeletes();`);
961
+ downLines.push(` $table->dropSoftDeletes();`);
962
+ } else if (from && !to) {
963
+ upLines.push(` $table->dropSoftDeletes();`);
964
+ downLines.push(` $table->softDeletes();`);
965
+ }
966
+ }
967
+ }
968
+ const connection = options.connection ? `
969
+ protected $connection = '${options.connection}';
970
+ ` : "";
971
+ return `<?php
972
+
973
+ use Illuminate\\Database\\Migrations\\Migration;
974
+ use Illuminate\\Database\\Schema\\Blueprint;
975
+ use Illuminate\\Support\\Facades\\Schema;
976
+
977
+ return new class extends Migration
978
+ {${connection}
979
+ /**
980
+ * Run the migrations.
981
+ */
982
+ public function up(): void
983
+ {
984
+ Schema::table('${tableName}', function (Blueprint $table) {
985
+ ${upLines.join("\n")}
986
+ });
987
+ }
988
+
989
+ /**
990
+ * Reverse the migrations.
991
+ */
992
+ public function down(): void
993
+ {
994
+ Schema::table('${tableName}', function (Blueprint $table) {
995
+ ${downLines.join("\n")}
996
+ });
997
+ }
998
+ };
999
+ `;
1000
+ }
1001
+ function generateAlterMigration(change, options = {}) {
1002
+ if (change.changeType !== "modified") {
1003
+ return null;
1004
+ }
1005
+ const hasChanges = change.columnChanges && change.columnChanges.length > 0 || change.indexChanges && change.indexChanges.length > 0 || change.optionChanges && (change.optionChanges.timestamps || change.optionChanges.softDelete);
1006
+ if (!hasChanges) {
1007
+ return null;
1008
+ }
1009
+ const tableName = toTableName(change.schemaName);
1010
+ const timestamp = options.timestamp ?? generateTimestamp2();
1011
+ const fileName = `${timestamp}_update_${tableName}_table.php`;
1012
+ const content = generateAlterMigrationContent(tableName, change, options);
1013
+ return {
1014
+ fileName,
1015
+ className: `Update${change.schemaName}Table`,
1016
+ content,
1017
+ tables: [tableName],
1018
+ type: "alter"
1019
+ };
1020
+ }
1021
+ function generateDropTableMigration(schemaName, options = {}) {
1022
+ const tableName = toTableName(schemaName);
1023
+ const timestamp = options.timestamp ?? generateTimestamp2();
1024
+ const fileName = `${timestamp}_drop_${tableName}_table.php`;
1025
+ const connection = options.connection ? `
1026
+ protected $connection = '${options.connection}';
1027
+ ` : "";
1028
+ const content = `<?php
1029
+
1030
+ use Illuminate\\Database\\Migrations\\Migration;
1031
+ use Illuminate\\Database\\Schema\\Blueprint;
1032
+ use Illuminate\\Support\\Facades\\Schema;
1033
+
1034
+ return new class extends Migration
1035
+ {${connection}
1036
+ /**
1037
+ * Run the migrations.
1038
+ */
1039
+ public function up(): void
1040
+ {
1041
+ Schema::dropIfExists('${tableName}');
1042
+ }
1043
+
1044
+ /**
1045
+ * Reverse the migrations.
1046
+ */
1047
+ public function down(): void
1048
+ {
1049
+ // Cannot recreate table without full schema
1050
+ // Consider restoring from backup if needed
1051
+ }
1052
+ };
1053
+ `;
1054
+ return {
1055
+ fileName,
1056
+ className: `Drop${schemaName}Table`,
1057
+ content,
1058
+ tables: [tableName],
1059
+ type: "drop"
1060
+ };
1061
+ }
1062
+ function generateMigrationsFromChanges(changes, options = {}) {
1063
+ const migrations = [];
1064
+ let timestampOffset = 0;
1065
+ const getNextTimestamp = () => {
1066
+ const ts = options.timestamp ?? generateTimestamp2();
1067
+ const offset = timestampOffset++;
1068
+ if (offset === 0) return ts;
1069
+ const parts = ts.split("_");
1070
+ if (parts.length >= 4) {
1071
+ const timePart = parts[3] ?? "000000";
1072
+ const secs = parseInt(timePart.substring(4, 6), 10) + offset;
1073
+ const newSecs = String(secs % 60).padStart(2, "0");
1074
+ parts[3] = timePart.substring(0, 4) + newSecs;
1075
+ return parts.join("_");
1076
+ }
1077
+ return ts;
1078
+ };
1079
+ for (const change of changes) {
1080
+ if (change.changeType === "modified") {
1081
+ const migration = generateAlterMigration(change, {
1082
+ ...options,
1083
+ timestamp: getNextTimestamp()
1084
+ });
1085
+ if (migration) {
1086
+ migrations.push(migration);
1087
+ }
1088
+ } else if (change.changeType === "removed") {
1089
+ migrations.push(
1090
+ generateDropTableMigration(change.schemaName, {
1091
+ ...options,
1092
+ timestamp: getNextTimestamp()
1093
+ })
1094
+ );
1095
+ }
1096
+ }
1097
+ return migrations;
1098
+ }
1099
+
709
1100
  // src/utils.ts
710
1101
  function toSnakeCase(str) {
711
1102
  return str.replace(/([A-Z])/g, "_$1").replace(/^_/, "").toLowerCase();
@@ -1717,6 +2108,24 @@ function getFactoryPath(factory) {
1717
2108
  }
1718
2109
 
1719
2110
  // src/plugin.ts
2111
+ function getExistingMigrationTables(migrationsDir) {
2112
+ const existingTables = /* @__PURE__ */ new Set();
2113
+ if (!(0, import_node_fs.existsSync)(migrationsDir)) {
2114
+ return existingTables;
2115
+ }
2116
+ try {
2117
+ const files = (0, import_node_fs.readdirSync)(migrationsDir);
2118
+ const createMigrationPattern = /^\d{4}_\d{2}_\d{2}_\d{6}_create_(.+)_table\.php$/;
2119
+ for (const file of files) {
2120
+ const match = file.match(createMigrationPattern);
2121
+ if (match) {
2122
+ existingTables.add(match[1]);
2123
+ }
2124
+ }
2125
+ } catch {
2126
+ }
2127
+ return existingTables;
2128
+ }
1720
2129
  var LARAVEL_CONFIG_SCHEMA = {
1721
2130
  fields: [
1722
2131
  {
@@ -1800,18 +2209,81 @@ function laravelPlugin(options) {
1800
2209
  generate: async (ctx) => {
1801
2210
  const migrationOptions = {
1802
2211
  connection: resolved.connection,
1803
- timestamp: resolved.timestamp
2212
+ timestamp: resolved.timestamp,
2213
+ customTypes: ctx.customTypes
1804
2214
  };
1805
- const migrations = generateMigrations(ctx.schemas, migrationOptions);
1806
- return migrations.map((migration) => ({
1807
- path: getMigrationPath(migration, resolved.migrationsPath),
1808
- content: migration.content,
1809
- type: "migration",
1810
- metadata: {
1811
- tableName: migration.tables[0],
1812
- migrationType: migration.type
2215
+ const outputs = [];
2216
+ const migrationsDir = (0, import_node_path.join)(ctx.cwd, resolved.migrationsPath);
2217
+ const existingTables = getExistingMigrationTables(migrationsDir);
2218
+ if (ctx.changes !== void 0) {
2219
+ if (ctx.changes.length === 0) {
2220
+ return outputs;
1813
2221
  }
1814
- }));
2222
+ const addedSchemaNames = new Set(
2223
+ ctx.changes.filter((c) => c.changeType === "added").map((c) => c.schemaName)
2224
+ );
2225
+ if (addedSchemaNames.size > 0) {
2226
+ const addedSchemas = Object.fromEntries(
2227
+ Object.entries(ctx.schemas).filter(([name]) => addedSchemaNames.has(name))
2228
+ );
2229
+ const createMigrations = generateMigrations(addedSchemas, migrationOptions);
2230
+ for (const migration of createMigrations) {
2231
+ const tableName = migration.tables[0];
2232
+ if (existingTables.has(tableName)) {
2233
+ ctx.logger.debug(`Skipping CREATE for ${tableName} (already exists)`);
2234
+ continue;
2235
+ }
2236
+ outputs.push({
2237
+ path: getMigrationPath(migration, resolved.migrationsPath),
2238
+ content: migration.content,
2239
+ type: "migration",
2240
+ metadata: {
2241
+ tableName,
2242
+ migrationType: "create"
2243
+ }
2244
+ });
2245
+ }
2246
+ }
2247
+ const alterChanges = ctx.changes.filter(
2248
+ (c) => c.changeType === "modified" || c.changeType === "removed"
2249
+ );
2250
+ if (alterChanges.length > 0) {
2251
+ const alterMigrations = generateMigrationsFromChanges(
2252
+ alterChanges,
2253
+ migrationOptions
2254
+ );
2255
+ for (const migration of alterMigrations) {
2256
+ outputs.push({
2257
+ path: getMigrationPath(migration, resolved.migrationsPath),
2258
+ content: migration.content,
2259
+ type: "migration",
2260
+ metadata: {
2261
+ tableName: migration.tables[0],
2262
+ migrationType: migration.type
2263
+ }
2264
+ });
2265
+ }
2266
+ }
2267
+ } else {
2268
+ const migrations = generateMigrations(ctx.schemas, migrationOptions);
2269
+ for (const migration of migrations) {
2270
+ const tableName = migration.tables[0];
2271
+ if (migration.type === "create" && existingTables.has(tableName)) {
2272
+ ctx.logger.debug(`Skipping migration for ${tableName} (already exists)`);
2273
+ continue;
2274
+ }
2275
+ outputs.push({
2276
+ path: getMigrationPath(migration, resolved.migrationsPath),
2277
+ content: migration.content,
2278
+ type: "migration",
2279
+ metadata: {
2280
+ tableName,
2281
+ migrationType: migration.type
2282
+ }
2283
+ });
2284
+ }
2285
+ }
2286
+ return outputs;
1815
2287
  }
1816
2288
  };
1817
2289
  const modelGenerator = {