@famgia/omnify-laravel 0.0.13 → 0.0.15

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
@@ -24,6 +24,8 @@ __export(plugin_exports, {
24
24
  laravelPlugin: () => laravelPlugin
25
25
  });
26
26
  module.exports = __toCommonJS(plugin_exports);
27
+ var import_node_fs = require("fs");
28
+ var import_node_path = require("path");
27
29
 
28
30
  // src/migration/schema-builder.ts
29
31
  var TYPE_METHOD_MAP = {
@@ -103,9 +105,8 @@ function propertyToColumnMethod(propertyName, property) {
103
105
  if (baseProp.unique) {
104
106
  modifiers.push({ method: "unique" });
105
107
  }
106
- if (baseProp.default !== void 0) {
107
- const defaultValue = typeof baseProp.default === "string" ? baseProp.default : JSON.stringify(baseProp.default);
108
- modifiers.push({ method: "default", args: [defaultValue] });
108
+ if (baseProp.default !== void 0 && baseProp.default !== null) {
109
+ modifiers.push({ method: "default", args: [baseProp.default] });
109
110
  }
110
111
  if (baseProp.unsigned && (method === "integer" || method === "bigInteger")) {
111
112
  modifiers.push({ method: "unsigned" });
@@ -293,11 +294,18 @@ function schemaToBlueprint(schema, allSchemas) {
293
294
  }
294
295
  if (schema.options?.indexes) {
295
296
  for (const index of schema.options.indexes) {
296
- indexes.push({
297
- name: index.name,
298
- columns: index.columns.map(toColumnName),
299
- unique: index.unique ?? false
300
- });
297
+ if (typeof index === "string") {
298
+ indexes.push({
299
+ columns: [toColumnName(index)],
300
+ unique: false
301
+ });
302
+ } else {
303
+ indexes.push({
304
+ name: index.name,
305
+ columns: index.columns.map(toColumnName),
306
+ unique: index.unique ?? false
307
+ });
308
+ }
301
309
  }
302
310
  }
303
311
  if (schema.options?.unique) {
@@ -334,6 +342,12 @@ function formatColumnMethod(column) {
334
342
  if (typeof arg === "string") {
335
343
  return `'${arg}'`;
336
344
  }
345
+ if (typeof arg === "boolean") {
346
+ return arg ? "true" : "false";
347
+ }
348
+ if (typeof arg === "number") {
349
+ return String(arg);
350
+ }
337
351
  return String(arg);
338
352
  }).join(", ");
339
353
  code += `->${modifier.method}(${modArgs})`;
@@ -441,6 +455,7 @@ function generatePivotTableBlueprint(pivot) {
441
455
  args: [pivot.targetColumn],
442
456
  modifiers: []
443
457
  });
458
+ columns.push(...generateTimestampColumns());
444
459
  foreignKeys.push({
445
460
  columns: [pivot.sourceColumn],
446
461
  references: "id",
@@ -684,6 +699,1016 @@ function getMigrationPath(migration, outputDir = "database/migrations") {
684
699
  return `${outputDir}/${migration.fileName}`;
685
700
  }
686
701
 
702
+ // src/utils.ts
703
+ function toSnakeCase(str) {
704
+ return str.replace(/([A-Z])/g, "_$1").replace(/^_/, "").toLowerCase();
705
+ }
706
+ function toPascalCase(str) {
707
+ return str.replace(/[-_](.)/g, (_, c) => c.toUpperCase()).replace(/^(.)/, (_, c) => c.toUpperCase());
708
+ }
709
+ function toCamelCase(str) {
710
+ const pascal = toPascalCase(str);
711
+ return pascal.charAt(0).toLowerCase() + pascal.slice(1);
712
+ }
713
+ function pluralize(word) {
714
+ if (word.endsWith("y") && !["ay", "ey", "iy", "oy", "uy"].some((v) => word.endsWith(v))) {
715
+ return word.slice(0, -1) + "ies";
716
+ }
717
+ if (word.endsWith("s") || word.endsWith("x") || word.endsWith("z") || word.endsWith("ch") || word.endsWith("sh")) {
718
+ return word + "es";
719
+ }
720
+ return word + "s";
721
+ }
722
+
723
+ // src/model/generator.ts
724
+ var DEFAULT_OPTIONS = {
725
+ baseModelNamespace: "App\\Models\\OmnifyBase",
726
+ modelNamespace: "App\\Models",
727
+ baseModelClassName: "BaseModel",
728
+ baseModelPath: "app/Models/OmnifyBase",
729
+ modelPath: "app/Models"
730
+ };
731
+ function resolveOptions(options) {
732
+ return {
733
+ baseModelNamespace: options?.baseModelNamespace ?? DEFAULT_OPTIONS.baseModelNamespace,
734
+ modelNamespace: options?.modelNamespace ?? DEFAULT_OPTIONS.modelNamespace,
735
+ baseModelClassName: options?.baseModelClassName ?? DEFAULT_OPTIONS.baseModelClassName,
736
+ baseModelPath: options?.baseModelPath ?? DEFAULT_OPTIONS.baseModelPath,
737
+ modelPath: options?.modelPath ?? DEFAULT_OPTIONS.modelPath
738
+ };
739
+ }
740
+ function getCastType(propDef) {
741
+ switch (propDef.type) {
742
+ case "Boolean":
743
+ return "boolean";
744
+ case "Int":
745
+ case "BigInt":
746
+ return "integer";
747
+ case "Float":
748
+ return "float";
749
+ case "Decimal":
750
+ return "decimal:" + (propDef.scale ?? 2);
751
+ case "Json":
752
+ return "array";
753
+ case "Date":
754
+ return "date";
755
+ case "Timestamp":
756
+ return "datetime";
757
+ case "Password":
758
+ return "hashed";
759
+ default:
760
+ return null;
761
+ }
762
+ }
763
+ function isNullable(propDef) {
764
+ return "nullable" in propDef && propDef.nullable === true;
765
+ }
766
+ function getPhpDocType(propDef, schemas) {
767
+ const nullable = isNullable(propDef);
768
+ switch (propDef.type) {
769
+ case "String":
770
+ case "Text":
771
+ case "LongText":
772
+ case "Email":
773
+ case "Password":
774
+ return "string" + (nullable ? "|null" : "");
775
+ case "Int":
776
+ case "BigInt":
777
+ return "int" + (nullable ? "|null" : "");
778
+ case "Float":
779
+ case "Decimal":
780
+ return "float" + (nullable ? "|null" : "");
781
+ case "Boolean":
782
+ return "bool" + (nullable ? "|null" : "");
783
+ case "Date":
784
+ case "Time":
785
+ case "Timestamp":
786
+ return "\\Carbon\\Carbon" + (nullable ? "|null" : "");
787
+ case "Json":
788
+ return "array" + (nullable ? "|null" : "");
789
+ case "Enum":
790
+ case "EnumRef":
791
+ return "string" + (nullable ? "|null" : "");
792
+ case "Association": {
793
+ const assoc = propDef;
794
+ if (assoc.target) {
795
+ const className = toPascalCase(assoc.target);
796
+ switch (assoc.relation) {
797
+ case "OneToMany":
798
+ case "ManyToMany":
799
+ case "MorphMany":
800
+ case "MorphToMany":
801
+ case "MorphedByMany":
802
+ return `\\Illuminate\\Database\\Eloquent\\Collection<${className}>`;
803
+ default:
804
+ return className + "|null";
805
+ }
806
+ }
807
+ return "mixed";
808
+ }
809
+ default:
810
+ return "mixed";
811
+ }
812
+ }
813
+ function generateBaseModel(schemas, options, stubContent) {
814
+ const modelMap = Object.values(schemas).filter((s) => s.kind !== "enum").map((s) => {
815
+ const className = toPascalCase(s.name);
816
+ return ` '${s.name}' => \\${options.modelNamespace}\\${className}::class,`;
817
+ }).join("\n");
818
+ const content = stubContent.replace(/\{\{BASE_MODEL_NAMESPACE\}\}/g, options.baseModelNamespace).replace(/\{\{BASE_MODEL_CLASS\}\}/g, options.baseModelClassName).replace(/\{\{MODEL_MAP\}\}/g, modelMap);
819
+ return {
820
+ path: `${options.baseModelPath}/${options.baseModelClassName}.php`,
821
+ content,
822
+ type: "base-model",
823
+ overwrite: true,
824
+ schemaName: "__base__"
825
+ };
826
+ }
827
+ function generateEntityBaseModel(schema, schemas, options, stubContent, authStubContent) {
828
+ const className = toPascalCase(schema.name);
829
+ const tableName = schema.options?.tableName ?? pluralize(toSnakeCase(schema.name));
830
+ const isAuth = schema.options?.authenticatable ?? false;
831
+ const primaryKey = "id";
832
+ const idType = schema.options?.idType ?? "BigInt";
833
+ const isUuid = idType === "Uuid";
834
+ const isStringKey = idType === "Uuid" || idType === "String";
835
+ const imports = [];
836
+ const traits = [];
837
+ const fillable = [];
838
+ const hidden = [];
839
+ const appends = [];
840
+ const casts = [];
841
+ const relations = [];
842
+ const docProperties = [];
843
+ if (schema.options?.softDelete) {
844
+ imports.push("use Illuminate\\Database\\Eloquent\\SoftDeletes;");
845
+ traits.push(" use SoftDeletes;");
846
+ }
847
+ const properties = schema.properties ?? {};
848
+ for (const [propName, propDef] of Object.entries(properties)) {
849
+ const snakeName = toSnakeCase(propName);
850
+ const phpType = getPhpDocType(propDef, schemas);
851
+ docProperties.push(` * @property ${phpType} $${snakeName}`);
852
+ if (propDef.type === "Association") {
853
+ const assoc = propDef;
854
+ if (assoc.target) {
855
+ imports.push(`use ${options.modelNamespace}\\${toPascalCase(assoc.target)};`);
856
+ }
857
+ relations.push(generateRelation(propName, assoc, schema, schemas, options));
858
+ if (assoc.relation === "ManyToOne" || assoc.relation === "OneToOne") {
859
+ if (!assoc.mappedBy) {
860
+ const fkName = toSnakeCase(propName) + "_id";
861
+ fillable.push(` '${fkName}',`);
862
+ docProperties.push(` * @property int|null $${fkName}`);
863
+ }
864
+ }
865
+ } else if (propDef.type === "Password") {
866
+ fillable.push(` '${snakeName}',`);
867
+ hidden.push(` '${snakeName}',`);
868
+ const cast = getCastType(propDef);
869
+ if (cast) {
870
+ casts.push(` '${snakeName}' => '${cast}',`);
871
+ }
872
+ } else if (propDef.type === "File") {
873
+ const relMethod = generateFileRelation(propName, propDef);
874
+ relations.push(relMethod);
875
+ } else {
876
+ fillable.push(` '${snakeName}',`);
877
+ const cast = getCastType(propDef);
878
+ if (cast) {
879
+ casts.push(` '${snakeName}' => '${cast}',`);
880
+ }
881
+ }
882
+ }
883
+ const docComment = `/**
884
+ * ${className}BaseModel
885
+ *
886
+ ${docProperties.join("\n")}
887
+ */`;
888
+ const stub = isAuth ? authStubContent : stubContent;
889
+ const keyType = isStringKey ? ` /**
890
+ * The "type" of the primary key ID.
891
+ */
892
+ protected $keyType = 'string';
893
+
894
+ ` : "";
895
+ const incrementing = isUuid ? ` /**
896
+ * Indicates if the IDs are auto-incrementing.
897
+ */
898
+ public $incrementing = false;
899
+
900
+ ` : "";
901
+ if (isUuid) {
902
+ imports.push("use Illuminate\\Database\\Eloquent\\Concerns\\HasUuids;");
903
+ traits.push(" use HasUuids;");
904
+ }
905
+ const content = stub.replace(/\{\{BASE_MODEL_NAMESPACE\}\}/g, options.baseModelNamespace).replace(/\{\{BASE_MODEL_CLASS\}\}/g, options.baseModelClassName).replace(/\{\{CLASS_NAME\}\}/g, className).replace(/\{\{TABLE_NAME\}\}/g, tableName).replace(/\{\{PRIMARY_KEY\}\}/g, primaryKey).replace(/\{\{KEY_TYPE\}\}/g, keyType).replace(/\{\{INCREMENTING\}\}/g, incrementing).replace(/\{\{TIMESTAMPS\}\}/g, schema.options?.timestamps !== false ? "true" : "false").replace(/\{\{IMPORTS\}\}/g, [...new Set(imports)].sort().join("\n")).replace(/\{\{TRAITS\}\}/g, traits.join("\n")).replace(/\{\{DOC_COMMENT\}\}/g, docComment).replace(/\{\{FILLABLE\}\}/g, fillable.join("\n")).replace(/\{\{HIDDEN\}\}/g, hidden.join("\n")).replace(/\{\{APPENDS\}\}/g, appends.join("\n")).replace(/\{\{CASTS\}\}/g, casts.join("\n")).replace(/\{\{RELATIONS\}\}/g, relations.join("\n\n"));
906
+ return {
907
+ path: `${options.baseModelPath}/${className}BaseModel.php`,
908
+ content,
909
+ type: "entity-base",
910
+ overwrite: true,
911
+ schemaName: schema.name
912
+ };
913
+ }
914
+ function findInverseRelation(currentSchemaName, targetSchemaName, schemas) {
915
+ const targetSchema = schemas[targetSchemaName];
916
+ if (!targetSchema || !targetSchema.properties) {
917
+ return null;
918
+ }
919
+ for (const [propName, propDef] of Object.entries(targetSchema.properties)) {
920
+ if (propDef.type === "Association") {
921
+ const assoc = propDef;
922
+ if (assoc.relation === "ManyToOne" && assoc.target === currentSchemaName) {
923
+ return propName;
924
+ }
925
+ }
926
+ }
927
+ return null;
928
+ }
929
+ function generateRelation(propName, assoc, schema, schemas, options) {
930
+ const methodName = toCamelCase(propName);
931
+ const targetClass = assoc.target ? toPascalCase(assoc.target) : "";
932
+ const fkName = toSnakeCase(propName) + "_id";
933
+ switch (assoc.relation) {
934
+ case "ManyToOne":
935
+ return ` /**
936
+ * Get the ${propName} that owns this model.
937
+ */
938
+ public function ${methodName}(): BelongsTo
939
+ {
940
+ return $this->belongsTo(${targetClass}::class, '${fkName}');
941
+ }`;
942
+ case "OneToOne":
943
+ if (assoc.mappedBy) {
944
+ return ` /**
945
+ * Get the ${propName} for this model.
946
+ */
947
+ public function ${methodName}(): HasOne
948
+ {
949
+ return $this->hasOne(${targetClass}::class, '${toSnakeCase(assoc.mappedBy)}_id');
950
+ }`;
951
+ }
952
+ return ` /**
953
+ * Get the ${propName} that owns this model.
954
+ */
955
+ public function ${methodName}(): BelongsTo
956
+ {
957
+ return $this->belongsTo(${targetClass}::class, '${fkName}');
958
+ }`;
959
+ case "OneToMany": {
960
+ let foreignKey;
961
+ if (assoc.inversedBy) {
962
+ foreignKey = toSnakeCase(assoc.inversedBy) + "_id";
963
+ } else if (assoc.target) {
964
+ const inverseRelation = findInverseRelation(schema.name, assoc.target, schemas);
965
+ if (inverseRelation) {
966
+ foreignKey = toSnakeCase(inverseRelation) + "_id";
967
+ } else {
968
+ foreignKey = toSnakeCase(schema.name) + "_id";
969
+ }
970
+ } else {
971
+ foreignKey = toSnakeCase(propName) + "_id";
972
+ }
973
+ return ` /**
974
+ * Get the ${propName} for this model.
975
+ */
976
+ public function ${methodName}(): HasMany
977
+ {
978
+ return $this->hasMany(${targetClass}::class, '${foreignKey}');
979
+ }`;
980
+ }
981
+ case "ManyToMany": {
982
+ const pivotTable = assoc.joinTable ?? `${toSnakeCase(propName)}_pivot`;
983
+ return ` /**
984
+ * The ${propName} that belong to this model.
985
+ */
986
+ public function ${methodName}(): BelongsToMany
987
+ {
988
+ return $this->belongsToMany(${targetClass}::class, '${pivotTable}')
989
+ ->withTimestamps();
990
+ }`;
991
+ }
992
+ case "MorphTo":
993
+ return ` /**
994
+ * Get the parent ${propName} model.
995
+ */
996
+ public function ${methodName}(): MorphTo
997
+ {
998
+ return $this->morphTo('${methodName}');
999
+ }`;
1000
+ case "MorphOne":
1001
+ return ` /**
1002
+ * Get the ${propName} for this model.
1003
+ */
1004
+ public function ${methodName}(): MorphOne
1005
+ {
1006
+ return $this->morphOne(${targetClass}::class, '${assoc.morphName ?? propName}');
1007
+ }`;
1008
+ case "MorphMany":
1009
+ return ` /**
1010
+ * Get the ${propName} for this model.
1011
+ */
1012
+ public function ${methodName}(): MorphMany
1013
+ {
1014
+ return $this->morphMany(${targetClass}::class, '${assoc.morphName ?? propName}');
1015
+ }`;
1016
+ default:
1017
+ return ` // TODO: Implement ${assoc.relation} relation for ${propName}`;
1018
+ }
1019
+ }
1020
+ function generateFileRelation(propName, propDef) {
1021
+ const methodName = toCamelCase(propName);
1022
+ const relationType = propDef.multiple ? "MorphMany" : "MorphOne";
1023
+ const relationMethod = propDef.multiple ? "morphMany" : "morphOne";
1024
+ return ` /**
1025
+ * Get the ${propName} file(s) for this model.
1026
+ */
1027
+ public function ${methodName}(): ${relationType}
1028
+ {
1029
+ return $this->${relationMethod}(FileUpload::class, 'uploadable')
1030
+ ->where('attribute_name', '${propName}');
1031
+ }`;
1032
+ }
1033
+ function generateEntityModel(schema, options, stubContent) {
1034
+ const className = toPascalCase(schema.name);
1035
+ const content = stubContent.replace(/\{\{BASE_MODEL_NAMESPACE\}\}/g, options.baseModelNamespace).replace(/\{\{MODEL_NAMESPACE\}\}/g, options.modelNamespace).replace(/\{\{CLASS_NAME\}\}/g, className);
1036
+ return {
1037
+ path: `${options.modelPath}/${className}.php`,
1038
+ content,
1039
+ type: "entity",
1040
+ overwrite: false,
1041
+ // Never overwrite user models
1042
+ schemaName: schema.name
1043
+ };
1044
+ }
1045
+ function getStubContent(stubName) {
1046
+ const stubs = {
1047
+ "base-model": `<?php
1048
+
1049
+ namespace {{BASE_MODEL_NAMESPACE}};
1050
+
1051
+ /**
1052
+ * Base model class for all Omnify-generated models.
1053
+ * Contains model mapping for polymorphic relations.
1054
+ *
1055
+ * DO NOT EDIT - This file is auto-generated by Omnify.
1056
+ * Any changes will be overwritten on next generation.
1057
+ *
1058
+ * @generated by @famgia/omnify-laravel
1059
+ */
1060
+
1061
+ use Illuminate\\Database\\Eloquent\\Model;
1062
+ use Illuminate\\Database\\Eloquent\\Relations\\Relation;
1063
+
1064
+ abstract class {{BASE_MODEL_CLASS}} extends Model
1065
+ {
1066
+ /**
1067
+ * Model class map for polymorphic relations.
1068
+ */
1069
+ protected static array $modelMap = [
1070
+ {{MODEL_MAP}}
1071
+ ];
1072
+
1073
+ /**
1074
+ * Boot the model and register morph map.
1075
+ */
1076
+ protected static function boot(): void
1077
+ {
1078
+ parent::boot();
1079
+
1080
+ // Register morph map for polymorphic relations
1081
+ Relation::enforceMorphMap(static::$modelMap);
1082
+ }
1083
+
1084
+ /**
1085
+ * Get the model class for a given morph type.
1086
+ */
1087
+ public static function getModelClass(string $morphType): ?string
1088
+ {
1089
+ return static::$modelMap[$morphType] ?? null;
1090
+ }
1091
+ }
1092
+ `,
1093
+ "entity-base": `<?php
1094
+
1095
+ namespace {{BASE_MODEL_NAMESPACE}};
1096
+
1097
+ /**
1098
+ * DO NOT EDIT - This file is auto-generated by Omnify.
1099
+ * Any changes will be overwritten on next generation.
1100
+ *
1101
+ * @generated by @famgia/omnify-laravel
1102
+ */
1103
+
1104
+ use Illuminate\\Database\\Eloquent\\Relations\\BelongsTo;
1105
+ use Illuminate\\Database\\Eloquent\\Relations\\HasMany;
1106
+ use Illuminate\\Database\\Eloquent\\Relations\\HasOne;
1107
+ use Illuminate\\Database\\Eloquent\\Relations\\BelongsToMany;
1108
+ use Illuminate\\Database\\Eloquent\\Relations\\MorphTo;
1109
+ use Illuminate\\Database\\Eloquent\\Relations\\MorphOne;
1110
+ use Illuminate\\Database\\Eloquent\\Relations\\MorphMany;
1111
+ use Illuminate\\Database\\Eloquent\\Relations\\MorphToMany;
1112
+ use Illuminate\\Database\\Eloquent\\Collection as EloquentCollection;
1113
+ {{IMPORTS}}
1114
+
1115
+ {{DOC_COMMENT}}
1116
+ class {{CLASS_NAME}}BaseModel extends {{BASE_MODEL_CLASS}}
1117
+ {
1118
+ {{TRAITS}}
1119
+ /**
1120
+ * The table associated with the model.
1121
+ */
1122
+ protected $table = '{{TABLE_NAME}}';
1123
+
1124
+ /**
1125
+ * The primary key for the model.
1126
+ */
1127
+ protected $primaryKey = '{{PRIMARY_KEY}}';
1128
+
1129
+ {{KEY_TYPE}}
1130
+ {{INCREMENTING}}
1131
+ /**
1132
+ * Indicates if the model should be timestamped.
1133
+ */
1134
+ public $timestamps = {{TIMESTAMPS}};
1135
+
1136
+ /**
1137
+ * The attributes that are mass assignable.
1138
+ */
1139
+ protected $fillable = [
1140
+ {{FILLABLE}}
1141
+ ];
1142
+
1143
+ /**
1144
+ * The attributes that should be hidden for serialization.
1145
+ */
1146
+ protected $hidden = [
1147
+ {{HIDDEN}}
1148
+ ];
1149
+
1150
+ /**
1151
+ * The accessors to append to the model's array form.
1152
+ */
1153
+ protected $appends = [
1154
+ {{APPENDS}}
1155
+ ];
1156
+
1157
+ /**
1158
+ * Get the attributes that should be cast.
1159
+ */
1160
+ protected function casts(): array
1161
+ {
1162
+ return [
1163
+ {{CASTS}}
1164
+ ];
1165
+ }
1166
+
1167
+ {{RELATIONS}}
1168
+ }
1169
+ `,
1170
+ "entity-base-auth": `<?php
1171
+
1172
+ namespace {{BASE_MODEL_NAMESPACE}};
1173
+
1174
+ /**
1175
+ * DO NOT EDIT - This file is auto-generated by Omnify.
1176
+ * Any changes will be overwritten on next generation.
1177
+ *
1178
+ * @generated by @famgia/omnify-laravel
1179
+ */
1180
+
1181
+ use Illuminate\\Foundation\\Auth\\User as Authenticatable;
1182
+ use Illuminate\\Database\\Eloquent\\Relations\\BelongsTo;
1183
+ use Illuminate\\Database\\Eloquent\\Relations\\HasMany;
1184
+ use Illuminate\\Database\\Eloquent\\Relations\\HasOne;
1185
+ use Illuminate\\Database\\Eloquent\\Relations\\BelongsToMany;
1186
+ use Illuminate\\Database\\Eloquent\\Relations\\MorphTo;
1187
+ use Illuminate\\Database\\Eloquent\\Relations\\MorphOne;
1188
+ use Illuminate\\Database\\Eloquent\\Relations\\MorphMany;
1189
+ use Illuminate\\Database\\Eloquent\\Relations\\MorphToMany;
1190
+ use Illuminate\\Database\\Eloquent\\Collection as EloquentCollection;
1191
+ use Illuminate\\Notifications\\Notifiable;
1192
+ {{IMPORTS}}
1193
+
1194
+ {{DOC_COMMENT}}
1195
+ class {{CLASS_NAME}}BaseModel extends Authenticatable
1196
+ {
1197
+ use Notifiable;
1198
+ {{TRAITS}}
1199
+ /**
1200
+ * The table associated with the model.
1201
+ */
1202
+ protected $table = '{{TABLE_NAME}}';
1203
+
1204
+ /**
1205
+ * The primary key for the model.
1206
+ */
1207
+ protected $primaryKey = '{{PRIMARY_KEY}}';
1208
+
1209
+ {{KEY_TYPE}}
1210
+ {{INCREMENTING}}
1211
+ /**
1212
+ * Indicates if the model should be timestamped.
1213
+ */
1214
+ public $timestamps = {{TIMESTAMPS}};
1215
+
1216
+ /**
1217
+ * The attributes that are mass assignable.
1218
+ */
1219
+ protected $fillable = [
1220
+ {{FILLABLE}}
1221
+ ];
1222
+
1223
+ /**
1224
+ * The attributes that should be hidden for serialization.
1225
+ */
1226
+ protected $hidden = [
1227
+ {{HIDDEN}}
1228
+ ];
1229
+
1230
+ /**
1231
+ * The accessors to append to the model's array form.
1232
+ */
1233
+ protected $appends = [
1234
+ {{APPENDS}}
1235
+ ];
1236
+
1237
+ /**
1238
+ * Get the attributes that should be cast.
1239
+ */
1240
+ protected function casts(): array
1241
+ {
1242
+ return [
1243
+ {{CASTS}}
1244
+ ];
1245
+ }
1246
+
1247
+ {{RELATIONS}}
1248
+ }
1249
+ `,
1250
+ "entity": `<?php
1251
+
1252
+ namespace {{MODEL_NAMESPACE}};
1253
+
1254
+ use {{BASE_MODEL_NAMESPACE}}\\{{CLASS_NAME}}BaseModel;
1255
+ use Illuminate\\Database\\Eloquent\\Factories\\HasFactory;
1256
+
1257
+ /**
1258
+ * {{CLASS_NAME}} Model
1259
+ *
1260
+ * This file is generated once and can be customized.
1261
+ * Add your custom methods and logic here.
1262
+ */
1263
+ class {{CLASS_NAME}} extends {{CLASS_NAME}}BaseModel
1264
+ {
1265
+ use HasFactory;
1266
+
1267
+ /**
1268
+ * Create a new model instance.
1269
+ */
1270
+ public function __construct(array $attributes = [])
1271
+ {
1272
+ parent::__construct($attributes);
1273
+ }
1274
+
1275
+ // Add your custom methods here
1276
+ }
1277
+ `,
1278
+ "service-provider": `<?php
1279
+
1280
+ namespace App\\Providers;
1281
+
1282
+ use Illuminate\\Database\\Eloquent\\Relations\\Relation;
1283
+ use Illuminate\\Support\\ServiceProvider;
1284
+
1285
+ /**
1286
+ * Omnify Service Provider
1287
+ *
1288
+ * DO NOT EDIT - This file is auto-generated by Omnify.
1289
+ * Any changes will be overwritten on next generation.
1290
+ *
1291
+ * - Loads Omnify migrations from database/migrations/omnify
1292
+ * - Registers morph map for polymorphic relationships
1293
+ *
1294
+ * @generated by @famgia/omnify-laravel
1295
+ */
1296
+ class OmnifyServiceProvider extends ServiceProvider
1297
+ {
1298
+ /**
1299
+ * Register any application services.
1300
+ */
1301
+ public function register(): void
1302
+ {
1303
+ //
1304
+ }
1305
+
1306
+ /**
1307
+ * Bootstrap any application services.
1308
+ */
1309
+ public function boot(): void
1310
+ {
1311
+ // Load Omnify migrations from custom directory
1312
+ $this->loadMigrationsFrom(database_path('migrations/omnify'));
1313
+
1314
+ // Register morph map for polymorphic relationships
1315
+ Relation::enforceMorphMap([
1316
+ {{MORPH_MAP}}
1317
+ ]);
1318
+ }
1319
+ }
1320
+ `
1321
+ };
1322
+ return stubs[stubName] ?? "";
1323
+ }
1324
+ function generateServiceProvider(schemas, options, stubContent) {
1325
+ const morphMap = Object.values(schemas).filter((s) => s.kind !== "enum").map((s) => {
1326
+ const className = toPascalCase(s.name);
1327
+ return ` '${s.name}' => \\${options.modelNamespace}\\${className}::class,`;
1328
+ }).join("\n");
1329
+ const content = stubContent.replace(/\{\{MORPH_MAP\}\}/g, morphMap);
1330
+ return {
1331
+ path: "app/Providers/OmnifyServiceProvider.php",
1332
+ content,
1333
+ type: "service-provider",
1334
+ overwrite: true,
1335
+ // Always overwrite to keep morph map in sync
1336
+ schemaName: "__service_provider__"
1337
+ };
1338
+ }
1339
+ function generateModels(schemas, options) {
1340
+ const resolved = resolveOptions(options);
1341
+ const models = [];
1342
+ models.push(generateBaseModel(schemas, resolved, getStubContent("base-model")));
1343
+ models.push(generateServiceProvider(schemas, resolved, getStubContent("service-provider")));
1344
+ for (const schema of Object.values(schemas)) {
1345
+ if (schema.kind === "enum") {
1346
+ continue;
1347
+ }
1348
+ models.push(generateEntityBaseModel(
1349
+ schema,
1350
+ schemas,
1351
+ resolved,
1352
+ getStubContent("entity-base"),
1353
+ getStubContent("entity-base-auth")
1354
+ ));
1355
+ models.push(generateEntityModel(schema, resolved, getStubContent("entity")));
1356
+ }
1357
+ return models;
1358
+ }
1359
+ function getModelPath(model) {
1360
+ return model.path;
1361
+ }
1362
+ function generateProviderRegistration(existingContent, laravelVersion) {
1363
+ const providerClass = "App\\Providers\\OmnifyServiceProvider::class";
1364
+ const providerLine = ` ${providerClass},`;
1365
+ if (existingContent && existingContent.includes("OmnifyServiceProvider")) {
1366
+ return {
1367
+ path: laravelVersion === "laravel11+" ? "bootstrap/providers.php" : "config/app.php",
1368
+ content: existingContent,
1369
+ laravelVersion,
1370
+ alreadyRegistered: true
1371
+ };
1372
+ }
1373
+ if (laravelVersion === "laravel11+") {
1374
+ if (existingContent) {
1375
+ const lines = existingContent.split("\n");
1376
+ const result = [];
1377
+ let inserted = false;
1378
+ for (let i = 0; i < lines.length; i++) {
1379
+ const line = lines[i];
1380
+ if (!inserted && line.trim() === "];") {
1381
+ result.push(providerLine);
1382
+ inserted = true;
1383
+ }
1384
+ result.push(line);
1385
+ }
1386
+ return {
1387
+ path: "bootstrap/providers.php",
1388
+ content: result.join("\n"),
1389
+ laravelVersion,
1390
+ alreadyRegistered: false
1391
+ };
1392
+ } else {
1393
+ return {
1394
+ path: "bootstrap/providers.php",
1395
+ content: `<?php
1396
+
1397
+ return [
1398
+ App\\Providers\\AppServiceProvider::class,
1399
+ ${providerLine}
1400
+ ];
1401
+ `,
1402
+ laravelVersion,
1403
+ alreadyRegistered: false
1404
+ };
1405
+ }
1406
+ } else {
1407
+ if (existingContent) {
1408
+ const providersSectionRegex = /'providers'\s*=>\s*\[[\s\S]*?\n(\s*)\]/m;
1409
+ const match = existingContent.match(providersSectionRegex);
1410
+ if (match) {
1411
+ const providersStart = existingContent.indexOf("'providers'");
1412
+ if (providersStart === -1) {
1413
+ return null;
1414
+ }
1415
+ let depth = 0;
1416
+ let foundStart = false;
1417
+ let insertPos = -1;
1418
+ for (let i = providersStart; i < existingContent.length; i++) {
1419
+ const char = existingContent[i];
1420
+ if (char === "[") {
1421
+ foundStart = true;
1422
+ depth++;
1423
+ } else if (char === "]") {
1424
+ depth--;
1425
+ if (foundStart && depth === 0) {
1426
+ insertPos = i;
1427
+ break;
1428
+ }
1429
+ }
1430
+ }
1431
+ if (insertPos !== -1) {
1432
+ const beforeClose = existingContent.substring(0, insertPos);
1433
+ const lastNewline = beforeClose.lastIndexOf("\n");
1434
+ const content = existingContent.substring(0, lastNewline + 1) + providerLine + "\n" + existingContent.substring(lastNewline + 1);
1435
+ return {
1436
+ path: "config/app.php",
1437
+ content,
1438
+ laravelVersion,
1439
+ alreadyRegistered: false
1440
+ };
1441
+ }
1442
+ }
1443
+ return null;
1444
+ } else {
1445
+ return null;
1446
+ }
1447
+ }
1448
+ }
1449
+
1450
+ // src/factory/generator.ts
1451
+ function resolveOptions2(options) {
1452
+ return {
1453
+ modelNamespace: options?.modelNamespace ?? "App\\Models",
1454
+ factoryPath: options?.factoryPath ?? "database/factories",
1455
+ fakerLocale: options?.fakerLocale ?? "en_US"
1456
+ };
1457
+ }
1458
+ function getStubContent2() {
1459
+ return `<?php
1460
+
1461
+ namespace Database\\Factories;
1462
+
1463
+ use {{MODEL_NAMESPACE}}\\{{MODEL_NAME}};
1464
+ use Illuminate\\Database\\Eloquent\\Factories\\Factory;
1465
+ {{IMPORTS}}
1466
+
1467
+ /**
1468
+ * @extends Factory<{{MODEL_NAME}}>
1469
+ */
1470
+ class {{MODEL_NAME}}Factory extends Factory
1471
+ {
1472
+ protected $model = {{MODEL_NAME}}::class;
1473
+
1474
+ /**
1475
+ * Define the model's default state.
1476
+ *
1477
+ * @return array<string, mixed>
1478
+ */
1479
+ public function definition(): array
1480
+ {
1481
+ return [
1482
+ {{ATTRIBUTES}}
1483
+ ];
1484
+ }
1485
+ }
1486
+ `;
1487
+ }
1488
+ function generateFakeData(propertyName, property, schema, schemas) {
1489
+ const type = property.type;
1490
+ if (["deleted_at", "created_at", "updated_at"].includes(propertyName)) {
1491
+ return null;
1492
+ }
1493
+ if (type === "Association") {
1494
+ return null;
1495
+ }
1496
+ switch (type) {
1497
+ case "String":
1498
+ return generateStringFake(propertyName, property);
1499
+ case "Email":
1500
+ return `'${propertyName}' => fake()->unique()->safeEmail(),`;
1501
+ case "Password":
1502
+ return `'${propertyName}' => bcrypt('password'),`;
1503
+ case "Int":
1504
+ case "BigInt":
1505
+ return generateIntFake(propertyName, property);
1506
+ case "Float":
1507
+ case "Decimal":
1508
+ return `'${propertyName}' => fake()->randomFloat(2, 1, 10000),`;
1509
+ case "Boolean":
1510
+ return `'${propertyName}' => fake()->boolean(),`;
1511
+ case "Text":
1512
+ return `'${propertyName}' => fake()->paragraphs(3, true),`;
1513
+ case "LongText":
1514
+ return `'${propertyName}' => fake()->paragraphs(5, true),`;
1515
+ case "Date":
1516
+ return `'${propertyName}' => fake()->date(),`;
1517
+ case "Time":
1518
+ return `'${propertyName}' => fake()->time(),`;
1519
+ case "Timestamp":
1520
+ case "DateTime":
1521
+ return `'${propertyName}' => fake()->dateTime(),`;
1522
+ case "Json":
1523
+ return `'${propertyName}' => [],`;
1524
+ case "Enum":
1525
+ return generateEnumFake(propertyName, property);
1526
+ case "EnumRef":
1527
+ return generateEnumRefFake(propertyName, property, schemas);
1528
+ default:
1529
+ return `'${propertyName}' => fake()->sentence(),`;
1530
+ }
1531
+ }
1532
+ function generateStringFake(propertyName, property) {
1533
+ if (propertyName === "slug") {
1534
+ return `'${propertyName}' => fake()->unique()->slug(),`;
1535
+ }
1536
+ if (propertyName === "uuid" || propertyName === "uid") {
1537
+ return `'${propertyName}' => (string) \\Illuminate\\Support\\Str::uuid(),`;
1538
+ }
1539
+ if (propertyName.includes("email")) {
1540
+ return `'${propertyName}' => fake()->unique()->safeEmail(),`;
1541
+ }
1542
+ if (propertyName.includes("phone")) {
1543
+ return `'${propertyName}' => fake()->phoneNumber(),`;
1544
+ }
1545
+ if (propertyName.includes("image") || propertyName.includes("photo") || propertyName.includes("avatar")) {
1546
+ return `'${propertyName}' => fake()->imageUrl(),`;
1547
+ }
1548
+ if (propertyName.includes("url") || propertyName.includes("website")) {
1549
+ return `'${propertyName}' => fake()->url(),`;
1550
+ }
1551
+ if (propertyName.includes("path") || propertyName.includes("file")) {
1552
+ return `'${propertyName}' => 'uploads/' . fake()->uuid() . '.jpg',`;
1553
+ }
1554
+ if (propertyName === "name" || propertyName === "title") {
1555
+ return `'${propertyName}' => fake()->sentence(3),`;
1556
+ }
1557
+ if (propertyName.includes("name")) {
1558
+ return `'${propertyName}' => fake()->name(),`;
1559
+ }
1560
+ if (propertyName.includes("address")) {
1561
+ return `'${propertyName}' => fake()->address(),`;
1562
+ }
1563
+ if (propertyName.includes("city")) {
1564
+ return `'${propertyName}' => fake()->city(),`;
1565
+ }
1566
+ if (propertyName.includes("country")) {
1567
+ return `'${propertyName}' => fake()->country(),`;
1568
+ }
1569
+ if (propertyName.includes("zip") || propertyName.includes("postal")) {
1570
+ return `'${propertyName}' => fake()->postcode(),`;
1571
+ }
1572
+ if (propertyName.includes("color")) {
1573
+ return `'${propertyName}' => fake()->hexColor(),`;
1574
+ }
1575
+ if (propertyName.includes("token") || propertyName.includes("secret") || propertyName.includes("key")) {
1576
+ return `'${propertyName}' => \\Illuminate\\Support\\Str::random(32),`;
1577
+ }
1578
+ if (propertyName.includes("code")) {
1579
+ return `'${propertyName}' => fake()->unique()->regexify('[A-Z0-9]{8}'),`;
1580
+ }
1581
+ const length = property.length;
1582
+ if (length && length <= 50) {
1583
+ return `'${propertyName}' => fake()->words(3, true),`;
1584
+ }
1585
+ return `'${propertyName}' => fake()->sentence(),`;
1586
+ }
1587
+ function generateIntFake(propertyName, property) {
1588
+ if (propertyName.includes("count") || propertyName.includes("quantity")) {
1589
+ return `'${propertyName}' => fake()->numberBetween(0, 100),`;
1590
+ }
1591
+ if (propertyName.includes("price") || propertyName.includes("amount") || propertyName.includes("cost")) {
1592
+ return `'${propertyName}' => fake()->numberBetween(100, 10000),`;
1593
+ }
1594
+ if (propertyName.includes("order") || propertyName.includes("sort") || propertyName.includes("position")) {
1595
+ return `'${propertyName}' => fake()->numberBetween(1, 100),`;
1596
+ }
1597
+ if (propertyName.includes("age")) {
1598
+ return `'${propertyName}' => fake()->numberBetween(18, 80),`;
1599
+ }
1600
+ if (propertyName.includes("year")) {
1601
+ return `'${propertyName}' => fake()->year(),`;
1602
+ }
1603
+ return `'${propertyName}' => fake()->numberBetween(1, 1000),`;
1604
+ }
1605
+ function generateEnumFake(propertyName, property) {
1606
+ const enumValues = property.enum;
1607
+ if (!enumValues || enumValues.length === 0) {
1608
+ return `'${propertyName}' => null,`;
1609
+ }
1610
+ const values = enumValues.map((v) => typeof v === "string" ? v : v.value);
1611
+ const valuesStr = values.map((v) => `'${v}'`).join(", ");
1612
+ return `'${propertyName}' => fake()->randomElement([${valuesStr}]),`;
1613
+ }
1614
+ function generateEnumRefFake(propertyName, property, schemas) {
1615
+ const enumName = property.enum;
1616
+ if (!enumName) {
1617
+ return `'${propertyName}' => null,`;
1618
+ }
1619
+ const enumSchema = schemas[enumName];
1620
+ if (!enumSchema || enumSchema.kind !== "enum" || !enumSchema.values) {
1621
+ return `'${propertyName}' => null,`;
1622
+ }
1623
+ const valuesStr = enumSchema.values.map((v) => `'${v}'`).join(", ");
1624
+ return `'${propertyName}' => fake()->randomElement([${valuesStr}]),`;
1625
+ }
1626
+ function generateAssociationFake(propertyName, property, schema, schemas, modelNamespace) {
1627
+ if (property.type !== "Association") {
1628
+ return null;
1629
+ }
1630
+ const relation = property.relation;
1631
+ const target = property.target;
1632
+ if (relation !== "ManyToOne" || !target) {
1633
+ return null;
1634
+ }
1635
+ const foreignKey = `${toSnakeCase(propertyName)}_id`;
1636
+ const isNullable2 = property.nullable ?? false;
1637
+ const targetSchema = schemas[target];
1638
+ if (!targetSchema) {
1639
+ return null;
1640
+ }
1641
+ let fake;
1642
+ if (isNullable2) {
1643
+ fake = `'${foreignKey}' => ${target}::query()->inRandomOrder()->first()?->id,`;
1644
+ } else {
1645
+ fake = `'${foreignKey}' => ${target}::query()->inRandomOrder()->first()?->id ?? ${target}::factory()->create()->id,`;
1646
+ }
1647
+ let importStatement;
1648
+ if (target !== schema.name) {
1649
+ importStatement = `use ${modelNamespace}\\${target};`;
1650
+ }
1651
+ return { fake, import: importStatement };
1652
+ }
1653
+ function generateFactory(schema, schemas, options, stubContent) {
1654
+ if (schema.kind === "enum") {
1655
+ return null;
1656
+ }
1657
+ const modelName = toPascalCase(schema.name);
1658
+ const factoryName = `${modelName}Factory`;
1659
+ const attributes = [];
1660
+ const imports = [];
1661
+ if (schema.properties) {
1662
+ for (const [propName, prop] of Object.entries(schema.properties)) {
1663
+ if (prop.type === "Association") {
1664
+ const assocResult = generateAssociationFake(propName, prop, schema, schemas, options.modelNamespace);
1665
+ if (assocResult) {
1666
+ attributes.push(assocResult.fake);
1667
+ if (assocResult.import) {
1668
+ imports.push(assocResult.import);
1669
+ }
1670
+ }
1671
+ continue;
1672
+ }
1673
+ const fake = generateFakeData(propName, prop, schema, schemas);
1674
+ if (fake) {
1675
+ attributes.push(fake);
1676
+ }
1677
+ }
1678
+ }
1679
+ let content = stubContent;
1680
+ content = content.replace(/\{\{MODEL_NAMESPACE\}\}/g, options.modelNamespace);
1681
+ content = content.replace(/\{\{MODEL_NAME\}\}/g, modelName);
1682
+ const uniqueImports = [...new Set(imports)];
1683
+ const importsStr = uniqueImports.length > 0 ? "\n" + uniqueImports.join("\n") : "";
1684
+ content = content.replace(/\{\{IMPORTS\}\}/g, importsStr);
1685
+ const attributesStr = attributes.length > 0 ? attributes.map((a) => ` ${a}`).join("\n") : "";
1686
+ content = content.replace(/\{\{ATTRIBUTES\}\}/g, attributesStr);
1687
+ return {
1688
+ name: factoryName,
1689
+ schemaName: schema.name,
1690
+ path: `${options.factoryPath}/${factoryName}.php`,
1691
+ content,
1692
+ overwrite: false
1693
+ // Factories should not overwrite existing files
1694
+ };
1695
+ }
1696
+ function generateFactories(schemas, options) {
1697
+ const resolved = resolveOptions2(options);
1698
+ const stubContent = getStubContent2();
1699
+ const factories = [];
1700
+ for (const schema of Object.values(schemas)) {
1701
+ const factory = generateFactory(schema, schemas, resolved, stubContent);
1702
+ if (factory) {
1703
+ factories.push(factory);
1704
+ }
1705
+ }
1706
+ return factories;
1707
+ }
1708
+ function getFactoryPath(factory) {
1709
+ return factory.path;
1710
+ }
1711
+
687
1712
  // src/plugin.ts
688
1713
  var LARAVEL_CONFIG_SCHEMA = {
689
1714
  fields: [
@@ -691,10 +1716,50 @@ var LARAVEL_CONFIG_SCHEMA = {
691
1716
  key: "migrationsPath",
692
1717
  type: "path",
693
1718
  label: "Migrations Path",
694
- description: "Directory for Laravel migration files",
695
- default: "database/migrations",
1719
+ description: "Directory for Laravel migration files (loaded via OmnifyServiceProvider)",
1720
+ default: "database/migrations/omnify",
1721
+ group: "output"
1722
+ },
1723
+ {
1724
+ key: "modelsPath",
1725
+ type: "path",
1726
+ label: "Models Path",
1727
+ description: "Directory for user-editable model files",
1728
+ default: "app/Models",
696
1729
  group: "output"
697
1730
  },
1731
+ {
1732
+ key: "baseModelsPath",
1733
+ type: "path",
1734
+ label: "Base Models Path",
1735
+ description: "Directory for auto-generated base model files",
1736
+ default: "app/Models/OmnifyBase",
1737
+ group: "output"
1738
+ },
1739
+ {
1740
+ key: "generateModels",
1741
+ type: "boolean",
1742
+ label: "Generate Models",
1743
+ description: "Generate Eloquent model classes",
1744
+ default: true,
1745
+ group: "options"
1746
+ },
1747
+ {
1748
+ key: "factoriesPath",
1749
+ type: "path",
1750
+ label: "Factories Path",
1751
+ description: "Directory for Laravel factory files",
1752
+ default: "database/factories",
1753
+ group: "output"
1754
+ },
1755
+ {
1756
+ key: "generateFactories",
1757
+ type: "boolean",
1758
+ label: "Generate Factories",
1759
+ description: "Generate Laravel factory classes for testing",
1760
+ default: true,
1761
+ group: "options"
1762
+ },
698
1763
  {
699
1764
  key: "connection",
700
1765
  type: "string",
@@ -705,41 +1770,141 @@ var LARAVEL_CONFIG_SCHEMA = {
705
1770
  }
706
1771
  ]
707
1772
  };
708
- function resolveOptions(options) {
1773
+ function resolveOptions3(options) {
709
1774
  return {
710
- migrationsPath: options?.migrationsPath ?? "database/migrations",
1775
+ migrationsPath: options?.migrationsPath ?? "database/migrations/omnify",
1776
+ modelsPath: options?.modelsPath ?? "app/Models",
1777
+ baseModelsPath: options?.baseModelsPath ?? "app/Models/OmnifyBase",
1778
+ modelNamespace: options?.modelNamespace ?? "App\\Models",
1779
+ baseModelNamespace: options?.baseModelNamespace ?? "App\\Models\\OmnifyBase",
1780
+ generateModels: options?.generateModels ?? true,
1781
+ factoriesPath: options?.factoriesPath ?? "database/factories",
1782
+ generateFactories: options?.generateFactories ?? true,
1783
+ fakerLocale: options?.fakerLocale ?? "en_US",
711
1784
  connection: options?.connection,
712
1785
  timestamp: options?.timestamp
713
1786
  };
714
1787
  }
715
1788
  function laravelPlugin(options) {
716
- const resolved = resolveOptions(options);
1789
+ const resolved = resolveOptions3(options);
1790
+ const migrationGenerator = {
1791
+ name: "laravel-migrations",
1792
+ description: "Generate Laravel migration files",
1793
+ generate: async (ctx) => {
1794
+ const migrationOptions = {
1795
+ connection: resolved.connection,
1796
+ timestamp: resolved.timestamp
1797
+ };
1798
+ const migrations = generateMigrations(ctx.schemas, migrationOptions);
1799
+ return migrations.map((migration) => ({
1800
+ path: getMigrationPath(migration, resolved.migrationsPath),
1801
+ content: migration.content,
1802
+ type: "migration",
1803
+ metadata: {
1804
+ tableName: migration.tables[0],
1805
+ migrationType: migration.type
1806
+ }
1807
+ }));
1808
+ }
1809
+ };
1810
+ const modelGenerator = {
1811
+ name: "laravel-models",
1812
+ description: "Generate Eloquent model classes",
1813
+ generate: async (ctx) => {
1814
+ const modelOptions = {
1815
+ modelNamespace: resolved.modelNamespace,
1816
+ baseModelNamespace: resolved.baseModelNamespace,
1817
+ modelPath: resolved.modelsPath,
1818
+ baseModelPath: resolved.baseModelsPath
1819
+ };
1820
+ const models = generateModels(ctx.schemas, modelOptions);
1821
+ const outputs = models.map((model) => ({
1822
+ path: getModelPath(model),
1823
+ content: model.content,
1824
+ type: "model",
1825
+ // Skip writing user models if they already exist
1826
+ skipIfExists: !model.overwrite,
1827
+ metadata: {
1828
+ modelType: model.type,
1829
+ schemaName: model.schemaName
1830
+ }
1831
+ }));
1832
+ const bootstrapProvidersPath = (0, import_node_path.join)(ctx.cwd, "bootstrap/providers.php");
1833
+ const configAppPath = (0, import_node_path.join)(ctx.cwd, "config/app.php");
1834
+ let existingContent = null;
1835
+ let laravelVersion;
1836
+ if ((0, import_node_fs.existsSync)(bootstrapProvidersPath)) {
1837
+ laravelVersion = "laravel11+";
1838
+ try {
1839
+ existingContent = (0, import_node_fs.readFileSync)(bootstrapProvidersPath, "utf-8");
1840
+ } catch {
1841
+ existingContent = null;
1842
+ }
1843
+ } else if ((0, import_node_fs.existsSync)(configAppPath)) {
1844
+ laravelVersion = "laravel10-";
1845
+ try {
1846
+ existingContent = (0, import_node_fs.readFileSync)(configAppPath, "utf-8");
1847
+ } catch {
1848
+ existingContent = null;
1849
+ }
1850
+ } else {
1851
+ laravelVersion = "laravel11+";
1852
+ }
1853
+ const registration = generateProviderRegistration(existingContent, laravelVersion);
1854
+ if (registration && !registration.alreadyRegistered) {
1855
+ outputs.push({
1856
+ path: registration.path,
1857
+ content: registration.content,
1858
+ type: "other",
1859
+ skipIfExists: false,
1860
+ // We want to modify the file
1861
+ metadata: {
1862
+ registrationType: "provider-registration",
1863
+ laravelVersion: registration.laravelVersion
1864
+ }
1865
+ });
1866
+ ctx.logger.info(`OmnifyServiceProvider will be registered in ${registration.path}`);
1867
+ } else if (registration?.alreadyRegistered) {
1868
+ ctx.logger.info("OmnifyServiceProvider is already registered");
1869
+ }
1870
+ return outputs;
1871
+ }
1872
+ };
1873
+ const factoryGenerator = {
1874
+ name: "laravel-factories",
1875
+ description: "Generate Laravel factory classes for testing",
1876
+ generate: async (ctx) => {
1877
+ const factoryOptions = {
1878
+ modelNamespace: resolved.modelNamespace,
1879
+ factoryPath: resolved.factoriesPath,
1880
+ fakerLocale: resolved.fakerLocale
1881
+ };
1882
+ const factories = generateFactories(ctx.schemas, factoryOptions);
1883
+ return factories.map((factory) => ({
1884
+ path: getFactoryPath(factory),
1885
+ content: factory.content,
1886
+ type: "factory",
1887
+ // Skip writing factories if they already exist (allow customization)
1888
+ skipIfExists: !factory.overwrite,
1889
+ metadata: {
1890
+ factoryName: factory.name,
1891
+ schemaName: factory.schemaName
1892
+ }
1893
+ }));
1894
+ }
1895
+ };
1896
+ const generators = [migrationGenerator];
1897
+ if (resolved.generateModels) {
1898
+ generators.push(modelGenerator);
1899
+ }
1900
+ if (resolved.generateFactories) {
1901
+ generators.push(factoryGenerator);
1902
+ }
717
1903
  return {
718
1904
  name: "@famgia/omnify-laravel",
719
- version: "0.0.13",
1905
+ version: "0.0.14",
720
1906
  configSchema: LARAVEL_CONFIG_SCHEMA,
721
- generators: [
722
- {
723
- name: "laravel-migrations",
724
- description: "Generate Laravel migration files",
725
- generate: async (ctx) => {
726
- const migrationOptions = {
727
- connection: resolved.connection,
728
- timestamp: resolved.timestamp
729
- };
730
- const migrations = generateMigrations(ctx.schemas, migrationOptions);
731
- return migrations.map((migration) => ({
732
- path: getMigrationPath(migration, resolved.migrationsPath),
733
- content: migration.content,
734
- type: "migration",
735
- metadata: {
736
- tableName: migration.tables[0],
737
- migrationType: migration.type
738
- }
739
- }));
740
- }
741
- }
742
- ]
1907
+ generators
743
1908
  };
744
1909
  }
745
1910
  // Annotate the CommonJS export names for ESM import in node: