@famgia/omnify-laravel 0.0.17 → 0.0.19

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/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,
@@ -245,6 +250,9 @@ function generateForeignKey(propertyName, property, allSchemas) {
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);
@@ -681,7 +757,9 @@ function generateMigrations(schemas, options = {}) {
681
757
  const timestamp = options.timestamp ?? generateTimestamp();
682
758
  const offsetTimestamp = incrementTimestamp(timestamp, timestampOffset);
683
759
  timestampOffset++;
684
- const blueprint = schemaToBlueprint(schema, schemas);
760
+ const blueprint = schemaToBlueprint(schema, schemas, {
761
+ customTypes: options.customTypes
762
+ });
685
763
  const migration = generateCreateMigration(blueprint, {
686
764
  ...options,
687
765
  timestamp: offsetTimestamp
@@ -737,6 +815,291 @@ function getMigrationPath(migration, outputDir = "database/migrations") {
737
815
  return `${outputDir}/${migration.fileName}`;
738
816
  }
739
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
+
1100
+ // src/model/generator.ts
1101
+ var import_omnify_types = require("@famgia/omnify-types");
1102
+
740
1103
  // src/utils.ts
741
1104
  function toSnakeCase(str) {
742
1105
  return str.replace(/([A-Z])/g, "_$1").replace(/^_/, "").toLowerCase();
@@ -766,6 +1129,45 @@ var DEFAULT_OPTIONS = {
766
1129
  baseModelPath: "app/Models/OmnifyBase",
767
1130
  modelPath: "app/Models"
768
1131
  };
1132
+ function generateLocalizedDisplayNames(displayName, indent = " ") {
1133
+ if (displayName === void 0) {
1134
+ return "";
1135
+ }
1136
+ if (typeof displayName === "string") {
1137
+ return `${indent}'en' => '${escapePhpString(displayName)}',`;
1138
+ }
1139
+ if ((0, import_omnify_types.isLocaleMap)(displayName)) {
1140
+ const entries = Object.entries(displayName).map(([locale, value]) => `${indent}'${locale}' => '${escapePhpString(value)}',`).join("\n");
1141
+ return entries;
1142
+ }
1143
+ return "";
1144
+ }
1145
+ function generatePropertyLocalizedDisplayNames(schema, indent = " ") {
1146
+ const properties = schema.properties ?? {};
1147
+ const entries = [];
1148
+ for (const [propName, propDef] of Object.entries(properties)) {
1149
+ const snakeName = toSnakeCase(propName);
1150
+ const displayName = propDef.displayName;
1151
+ if (displayName === void 0) {
1152
+ continue;
1153
+ }
1154
+ const innerIndent = indent + " ";
1155
+ if (typeof displayName === "string") {
1156
+ entries.push(`${indent}'${snakeName}' => [
1157
+ ${innerIndent}'en' => '${escapePhpString(displayName)}',
1158
+ ${indent}],`);
1159
+ } else if ((0, import_omnify_types.isLocaleMap)(displayName)) {
1160
+ const localeEntries = Object.entries(displayName).map(([locale, value]) => `${innerIndent}'${locale}' => '${escapePhpString(value)}',`).join("\n");
1161
+ entries.push(`${indent}'${snakeName}' => [
1162
+ ${localeEntries}
1163
+ ${indent}],`);
1164
+ }
1165
+ }
1166
+ return entries.join("\n");
1167
+ }
1168
+ function escapePhpString(str) {
1169
+ return str.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
1170
+ }
769
1171
  function resolveOptions(options) {
770
1172
  return {
771
1173
  baseModelNamespace: options?.baseModelNamespace ?? DEFAULT_OPTIONS.baseModelNamespace,
@@ -1148,11 +1550,14 @@ use Illuminate\\Database\\Eloquent\\Relations\\MorphOne;
1148
1550
  use Illuminate\\Database\\Eloquent\\Relations\\MorphMany;
1149
1551
  use Illuminate\\Database\\Eloquent\\Relations\\MorphToMany;
1150
1552
  use Illuminate\\Database\\Eloquent\\Collection as EloquentCollection;
1553
+ use {{BASE_MODEL_NAMESPACE}}\\Traits\\HasLocalizedDisplayName;
1554
+ use {{BASE_MODEL_NAMESPACE}}\\Locales\\{{CLASS_NAME}}Locales;
1151
1555
  {{IMPORTS}}
1152
1556
 
1153
1557
  {{DOC_COMMENT}}
1154
1558
  class {{CLASS_NAME}}BaseModel extends {{BASE_MODEL_CLASS}}
1155
1559
  {
1560
+ use HasLocalizedDisplayName;
1156
1561
  {{TRAITS}}
1157
1562
  /**
1158
1563
  * The table associated with the model.
@@ -1171,6 +1576,20 @@ class {{CLASS_NAME}}BaseModel extends {{BASE_MODEL_CLASS}}
1171
1576
  */
1172
1577
  public $timestamps = {{TIMESTAMPS}};
1173
1578
 
1579
+ /**
1580
+ * Localized display names for this model.
1581
+ *
1582
+ * @var array<string, string>
1583
+ */
1584
+ protected static array $localizedDisplayNames = {{CLASS_NAME}}Locales::DISPLAY_NAMES;
1585
+
1586
+ /**
1587
+ * Localized display names for properties.
1588
+ *
1589
+ * @var array<string, array<string, string>>
1590
+ */
1591
+ protected static array $localizedPropertyDisplayNames = {{CLASS_NAME}}Locales::PROPERTY_DISPLAY_NAMES;
1592
+
1174
1593
  /**
1175
1594
  * The attributes that are mass assignable.
1176
1595
  */
@@ -1227,12 +1646,15 @@ use Illuminate\\Database\\Eloquent\\Relations\\MorphMany;
1227
1646
  use Illuminate\\Database\\Eloquent\\Relations\\MorphToMany;
1228
1647
  use Illuminate\\Database\\Eloquent\\Collection as EloquentCollection;
1229
1648
  use Illuminate\\Notifications\\Notifiable;
1649
+ use {{BASE_MODEL_NAMESPACE}}\\Traits\\HasLocalizedDisplayName;
1650
+ use {{BASE_MODEL_NAMESPACE}}\\Locales\\{{CLASS_NAME}}Locales;
1230
1651
  {{IMPORTS}}
1231
1652
 
1232
1653
  {{DOC_COMMENT}}
1233
1654
  class {{CLASS_NAME}}BaseModel extends Authenticatable
1234
1655
  {
1235
1656
  use Notifiable;
1657
+ use HasLocalizedDisplayName;
1236
1658
  {{TRAITS}}
1237
1659
  /**
1238
1660
  * The table associated with the model.
@@ -1251,6 +1673,20 @@ class {{CLASS_NAME}}BaseModel extends Authenticatable
1251
1673
  */
1252
1674
  public $timestamps = {{TIMESTAMPS}};
1253
1675
 
1676
+ /**
1677
+ * Localized display names for this model.
1678
+ *
1679
+ * @var array<string, string>
1680
+ */
1681
+ protected static array $localizedDisplayNames = {{CLASS_NAME}}Locales::DISPLAY_NAMES;
1682
+
1683
+ /**
1684
+ * Localized display names for properties.
1685
+ *
1686
+ * @var array<string, array<string, string>>
1687
+ */
1688
+ protected static array $localizedPropertyDisplayNames = {{CLASS_NAME}}Locales::PROPERTY_DISPLAY_NAMES;
1689
+
1254
1690
  /**
1255
1691
  * The attributes that are mass assignable.
1256
1692
  */
@@ -1355,6 +1791,132 @@ class OmnifyServiceProvider extends ServiceProvider
1355
1791
  ]);
1356
1792
  }
1357
1793
  }
1794
+ `,
1795
+ "has-localized-display-name": `<?php
1796
+
1797
+ namespace {{BASE_MODEL_NAMESPACE}}\\Traits;
1798
+
1799
+ /**
1800
+ * Trait for localized display names.
1801
+ * Uses Laravel's app()->getLocale() for locale resolution.
1802
+ *
1803
+ * DO NOT EDIT - This file is auto-generated by Omnify.
1804
+ * Any changes will be overwritten on next generation.
1805
+ *
1806
+ * @generated by @famgia/omnify-laravel
1807
+ */
1808
+ trait HasLocalizedDisplayName
1809
+ {
1810
+ /**
1811
+ * Get the localized display name for this model.
1812
+ *
1813
+ * @param string|null $locale Locale code (defaults to app locale)
1814
+ * @return string
1815
+ */
1816
+ public static function displayName(?string $locale = null): string
1817
+ {
1818
+ $locale = $locale ?? app()->getLocale();
1819
+ $displayNames = static::$localizedDisplayNames ?? [];
1820
+
1821
+ return $displayNames[$locale]
1822
+ ?? $displayNames[config('app.fallback_locale', 'en')]
1823
+ ?? $displayNames[array_key_first($displayNames) ?? 'en']
1824
+ ?? class_basename(static::class);
1825
+ }
1826
+
1827
+ /**
1828
+ * Get all localized display names for this model.
1829
+ *
1830
+ * @return array<string, string>
1831
+ */
1832
+ public static function allDisplayNames(): array
1833
+ {
1834
+ return static::$localizedDisplayNames ?? [];
1835
+ }
1836
+
1837
+ /**
1838
+ * Get the localized display name for a property.
1839
+ *
1840
+ * @param string $property Property name
1841
+ * @param string|null $locale Locale code (defaults to app locale)
1842
+ * @return string
1843
+ */
1844
+ public static function propertyDisplayName(string $property, ?string $locale = null): string
1845
+ {
1846
+ $locale = $locale ?? app()->getLocale();
1847
+ $displayNames = static::$localizedPropertyDisplayNames[$property] ?? [];
1848
+
1849
+ return $displayNames[$locale]
1850
+ ?? $displayNames[config('app.fallback_locale', 'en')]
1851
+ ?? $displayNames[array_key_first($displayNames) ?? 'en']
1852
+ ?? $property;
1853
+ }
1854
+
1855
+ /**
1856
+ * Get all localized display names for a property.
1857
+ *
1858
+ * @param string $property Property name
1859
+ * @return array<string, string>
1860
+ */
1861
+ public static function allPropertyDisplayNames(string $property): array
1862
+ {
1863
+ return static::$localizedPropertyDisplayNames[$property] ?? [];
1864
+ }
1865
+
1866
+ /**
1867
+ * Get all property display names for a given locale.
1868
+ *
1869
+ * @param string|null $locale Locale code (defaults to app locale)
1870
+ * @return array<string, string>
1871
+ */
1872
+ public static function allPropertyDisplayNamesForLocale(?string $locale = null): array
1873
+ {
1874
+ $locale = $locale ?? app()->getLocale();
1875
+ $result = [];
1876
+
1877
+ foreach (static::$localizedPropertyDisplayNames ?? [] as $property => $displayNames) {
1878
+ $result[$property] = $displayNames[$locale]
1879
+ ?? $displayNames[config('app.fallback_locale', 'en')]
1880
+ ?? $displayNames[array_key_first($displayNames) ?? 'en']
1881
+ ?? $property;
1882
+ }
1883
+
1884
+ return $result;
1885
+ }
1886
+ }
1887
+ `,
1888
+ "locales": `<?php
1889
+
1890
+ namespace {{BASE_MODEL_NAMESPACE}}\\Locales;
1891
+
1892
+ /**
1893
+ * Localized display names for {{CLASS_NAME}}.
1894
+ *
1895
+ * DO NOT EDIT - This file is auto-generated by Omnify.
1896
+ * Any changes will be overwritten on next generation.
1897
+ *
1898
+ * @generated by @famgia/omnify-laravel
1899
+ */
1900
+ class {{CLASS_NAME}}Locales
1901
+ {
1902
+ /**
1903
+ * Localized display names for the model.
1904
+ *
1905
+ * @var array<string, string>
1906
+ */
1907
+ public const DISPLAY_NAMES = [
1908
+ {{LOCALIZED_DISPLAY_NAMES}}
1909
+ ];
1910
+
1911
+ /**
1912
+ * Localized display names for properties.
1913
+ *
1914
+ * @var array<string, array<string, string>>
1915
+ */
1916
+ public const PROPERTY_DISPLAY_NAMES = [
1917
+ {{LOCALIZED_PROPERTY_DISPLAY_NAMES}}
1918
+ ];
1919
+ }
1358
1920
  `
1359
1921
  };
1360
1922
  return stubs[stubName] ?? "";
@@ -1374,15 +1936,42 @@ function generateServiceProvider(schemas, options, stubContent) {
1374
1936
  schemaName: "__service_provider__"
1375
1937
  };
1376
1938
  }
1939
+ function generateLocalizedDisplayNameTrait(options, stubContent) {
1940
+ const content = stubContent.replace(/\{\{BASE_MODEL_NAMESPACE\}\}/g, options.baseModelNamespace);
1941
+ return {
1942
+ path: `${options.baseModelPath}/Traits/HasLocalizedDisplayName.php`,
1943
+ content,
1944
+ type: "trait",
1945
+ overwrite: true,
1946
+ // Always overwrite trait
1947
+ schemaName: "__trait__"
1948
+ };
1949
+ }
1950
+ function generateLocalesClass(schema, options, stubContent) {
1951
+ const className = toPascalCase(schema.name);
1952
+ const localizedDisplayNames = generateLocalizedDisplayNames(schema.displayName);
1953
+ const localizedPropertyDisplayNames = generatePropertyLocalizedDisplayNames(schema);
1954
+ const content = stubContent.replace(/\{\{BASE_MODEL_NAMESPACE\}\}/g, options.baseModelNamespace).replace(/\{\{CLASS_NAME\}\}/g, className).replace(/\{\{LOCALIZED_DISPLAY_NAMES\}\}/g, localizedDisplayNames).replace(/\{\{LOCALIZED_PROPERTY_DISPLAY_NAMES\}\}/g, localizedPropertyDisplayNames);
1955
+ return {
1956
+ path: `${options.baseModelPath}/Locales/${className}Locales.php`,
1957
+ content,
1958
+ type: "locales",
1959
+ overwrite: true,
1960
+ // Always overwrite locales
1961
+ schemaName: schema.name
1962
+ };
1963
+ }
1377
1964
  function generateModels(schemas, options) {
1378
1965
  const resolved = resolveOptions(options);
1379
1966
  const models = [];
1380
1967
  models.push(generateBaseModel(schemas, resolved, getStubContent("base-model")));
1968
+ models.push(generateLocalizedDisplayNameTrait(resolved, getStubContent("has-localized-display-name")));
1381
1969
  models.push(generateServiceProvider(schemas, resolved, getStubContent("service-provider")));
1382
1970
  for (const schema of Object.values(schemas)) {
1383
1971
  if (schema.kind === "enum") {
1384
1972
  continue;
1385
1973
  }
1974
+ models.push(generateLocalesClass(schema, resolved, getStubContent("locales")));
1386
1975
  models.push(generateEntityBaseModel(
1387
1976
  schema,
1388
1977
  schemas,
@@ -1748,6 +2337,24 @@ function getFactoryPath(factory) {
1748
2337
  }
1749
2338
 
1750
2339
  // src/plugin.ts
2340
+ function getExistingMigrationTables(migrationsDir) {
2341
+ const existingTables = /* @__PURE__ */ new Set();
2342
+ if (!(0, import_node_fs.existsSync)(migrationsDir)) {
2343
+ return existingTables;
2344
+ }
2345
+ try {
2346
+ const files = (0, import_node_fs.readdirSync)(migrationsDir);
2347
+ const createMigrationPattern = /^\d{4}_\d{2}_\d{2}_\d{6}_create_(.+)_table\.php$/;
2348
+ for (const file of files) {
2349
+ const match = file.match(createMigrationPattern);
2350
+ if (match) {
2351
+ existingTables.add(match[1]);
2352
+ }
2353
+ }
2354
+ } catch {
2355
+ }
2356
+ return existingTables;
2357
+ }
1751
2358
  var LARAVEL_CONFIG_SCHEMA = {
1752
2359
  fields: [
1753
2360
  {
@@ -1831,18 +2438,81 @@ function laravelPlugin(options) {
1831
2438
  generate: async (ctx) => {
1832
2439
  const migrationOptions = {
1833
2440
  connection: resolved.connection,
1834
- timestamp: resolved.timestamp
2441
+ timestamp: resolved.timestamp,
2442
+ customTypes: ctx.customTypes
1835
2443
  };
1836
- const migrations = generateMigrations(ctx.schemas, migrationOptions);
1837
- return migrations.map((migration) => ({
1838
- path: getMigrationPath(migration, resolved.migrationsPath),
1839
- content: migration.content,
1840
- type: "migration",
1841
- metadata: {
1842
- tableName: migration.tables[0],
1843
- migrationType: migration.type
2444
+ const outputs = [];
2445
+ const migrationsDir = (0, import_node_path.join)(ctx.cwd, resolved.migrationsPath);
2446
+ const existingTables = getExistingMigrationTables(migrationsDir);
2447
+ if (ctx.changes !== void 0) {
2448
+ if (ctx.changes.length === 0) {
2449
+ return outputs;
1844
2450
  }
1845
- }));
2451
+ const addedSchemaNames = new Set(
2452
+ ctx.changes.filter((c) => c.changeType === "added").map((c) => c.schemaName)
2453
+ );
2454
+ if (addedSchemaNames.size > 0) {
2455
+ const addedSchemas = Object.fromEntries(
2456
+ Object.entries(ctx.schemas).filter(([name]) => addedSchemaNames.has(name))
2457
+ );
2458
+ const createMigrations = generateMigrations(addedSchemas, migrationOptions);
2459
+ for (const migration of createMigrations) {
2460
+ const tableName = migration.tables[0];
2461
+ if (existingTables.has(tableName)) {
2462
+ ctx.logger.debug(`Skipping CREATE for ${tableName} (already exists)`);
2463
+ continue;
2464
+ }
2465
+ outputs.push({
2466
+ path: getMigrationPath(migration, resolved.migrationsPath),
2467
+ content: migration.content,
2468
+ type: "migration",
2469
+ metadata: {
2470
+ tableName,
2471
+ migrationType: "create"
2472
+ }
2473
+ });
2474
+ }
2475
+ }
2476
+ const alterChanges = ctx.changes.filter(
2477
+ (c) => c.changeType === "modified" || c.changeType === "removed"
2478
+ );
2479
+ if (alterChanges.length > 0) {
2480
+ const alterMigrations = generateMigrationsFromChanges(
2481
+ alterChanges,
2482
+ migrationOptions
2483
+ );
2484
+ for (const migration of alterMigrations) {
2485
+ outputs.push({
2486
+ path: getMigrationPath(migration, resolved.migrationsPath),
2487
+ content: migration.content,
2488
+ type: "migration",
2489
+ metadata: {
2490
+ tableName: migration.tables[0],
2491
+ migrationType: migration.type
2492
+ }
2493
+ });
2494
+ }
2495
+ }
2496
+ } else {
2497
+ const migrations = generateMigrations(ctx.schemas, migrationOptions);
2498
+ for (const migration of migrations) {
2499
+ const tableName = migration.tables[0];
2500
+ if (migration.type === "create" && existingTables.has(tableName)) {
2501
+ ctx.logger.debug(`Skipping migration for ${tableName} (already exists)`);
2502
+ continue;
2503
+ }
2504
+ outputs.push({
2505
+ path: getMigrationPath(migration, resolved.migrationsPath),
2506
+ content: migration.content,
2507
+ type: "migration",
2508
+ metadata: {
2509
+ tableName,
2510
+ migrationType: migration.type
2511
+ }
2512
+ });
2513
+ }
2514
+ }
2515
+ return outputs;
1846
2516
  }
1847
2517
  };
1848
2518
  const modelGenerator = {