@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/index.cjs CHANGED
@@ -121,9 +121,8 @@ function propertyToColumnMethod(propertyName, property) {
121
121
  if (baseProp.unique) {
122
122
  modifiers.push({ method: "unique" });
123
123
  }
124
- if (baseProp.default !== void 0) {
125
- const defaultValue = typeof baseProp.default === "string" ? baseProp.default : JSON.stringify(baseProp.default);
126
- modifiers.push({ method: "default", args: [defaultValue] });
124
+ if (baseProp.default !== void 0 && baseProp.default !== null) {
125
+ modifiers.push({ method: "default", args: [baseProp.default] });
127
126
  }
128
127
  if (baseProp.unsigned && (method === "integer" || method === "bigInteger")) {
129
128
  modifiers.push({ method: "unsigned" });
@@ -311,11 +310,18 @@ function schemaToBlueprint(schema, allSchemas) {
311
310
  }
312
311
  if (schema.options?.indexes) {
313
312
  for (const index of schema.options.indexes) {
314
- indexes.push({
315
- name: index.name,
316
- columns: index.columns.map(toColumnName),
317
- unique: index.unique ?? false
318
- });
313
+ if (typeof index === "string") {
314
+ indexes.push({
315
+ columns: [toColumnName(index)],
316
+ unique: false
317
+ });
318
+ } else {
319
+ indexes.push({
320
+ name: index.name,
321
+ columns: index.columns.map(toColumnName),
322
+ unique: index.unique ?? false
323
+ });
324
+ }
319
325
  }
320
326
  }
321
327
  if (schema.options?.unique) {
@@ -352,6 +358,12 @@ function formatColumnMethod(column) {
352
358
  if (typeof arg === "string") {
353
359
  return `'${arg}'`;
354
360
  }
361
+ if (typeof arg === "boolean") {
362
+ return arg ? "true" : "false";
363
+ }
364
+ if (typeof arg === "number") {
365
+ return String(arg);
366
+ }
355
367
  return String(arg);
356
368
  }).join(", ");
357
369
  code += `->${modifier.method}(${modArgs})`;
@@ -459,6 +471,7 @@ function generatePivotTableBlueprint(pivot) {
459
471
  args: [pivot.targetColumn],
460
472
  modifiers: []
461
473
  });
474
+ columns.push(...generateTimestampColumns());
462
475
  foreignKeys.push({
463
476
  columns: [pivot.sourceColumn],
464
477
  references: "id",
@@ -1033,6 +1046,1020 @@ function generateMigrationsFromChanges(changes, options = {}) {
1033
1046
  return migrations;
1034
1047
  }
1035
1048
 
1049
+ // src/plugin.ts
1050
+ var import_node_fs = require("fs");
1051
+ var import_node_path = require("path");
1052
+
1053
+ // src/utils.ts
1054
+ function toSnakeCase(str) {
1055
+ return str.replace(/([A-Z])/g, "_$1").replace(/^_/, "").toLowerCase();
1056
+ }
1057
+ function toPascalCase(str) {
1058
+ return str.replace(/[-_](.)/g, (_, c) => c.toUpperCase()).replace(/^(.)/, (_, c) => c.toUpperCase());
1059
+ }
1060
+ function toCamelCase(str) {
1061
+ const pascal = toPascalCase(str);
1062
+ return pascal.charAt(0).toLowerCase() + pascal.slice(1);
1063
+ }
1064
+ function pluralize(word) {
1065
+ if (word.endsWith("y") && !["ay", "ey", "iy", "oy", "uy"].some((v) => word.endsWith(v))) {
1066
+ return word.slice(0, -1) + "ies";
1067
+ }
1068
+ if (word.endsWith("s") || word.endsWith("x") || word.endsWith("z") || word.endsWith("ch") || word.endsWith("sh")) {
1069
+ return word + "es";
1070
+ }
1071
+ return word + "s";
1072
+ }
1073
+
1074
+ // src/model/generator.ts
1075
+ var DEFAULT_OPTIONS = {
1076
+ baseModelNamespace: "App\\Models\\OmnifyBase",
1077
+ modelNamespace: "App\\Models",
1078
+ baseModelClassName: "BaseModel",
1079
+ baseModelPath: "app/Models/OmnifyBase",
1080
+ modelPath: "app/Models"
1081
+ };
1082
+ function resolveOptions(options) {
1083
+ return {
1084
+ baseModelNamespace: options?.baseModelNamespace ?? DEFAULT_OPTIONS.baseModelNamespace,
1085
+ modelNamespace: options?.modelNamespace ?? DEFAULT_OPTIONS.modelNamespace,
1086
+ baseModelClassName: options?.baseModelClassName ?? DEFAULT_OPTIONS.baseModelClassName,
1087
+ baseModelPath: options?.baseModelPath ?? DEFAULT_OPTIONS.baseModelPath,
1088
+ modelPath: options?.modelPath ?? DEFAULT_OPTIONS.modelPath
1089
+ };
1090
+ }
1091
+ function getCastType(propDef) {
1092
+ switch (propDef.type) {
1093
+ case "Boolean":
1094
+ return "boolean";
1095
+ case "Int":
1096
+ case "BigInt":
1097
+ return "integer";
1098
+ case "Float":
1099
+ return "float";
1100
+ case "Decimal":
1101
+ return "decimal:" + (propDef.scale ?? 2);
1102
+ case "Json":
1103
+ return "array";
1104
+ case "Date":
1105
+ return "date";
1106
+ case "Timestamp":
1107
+ return "datetime";
1108
+ case "Password":
1109
+ return "hashed";
1110
+ default:
1111
+ return null;
1112
+ }
1113
+ }
1114
+ function isNullable(propDef) {
1115
+ return "nullable" in propDef && propDef.nullable === true;
1116
+ }
1117
+ function getPhpDocType(propDef, schemas) {
1118
+ const nullable = isNullable(propDef);
1119
+ switch (propDef.type) {
1120
+ case "String":
1121
+ case "Text":
1122
+ case "LongText":
1123
+ case "Email":
1124
+ case "Password":
1125
+ return "string" + (nullable ? "|null" : "");
1126
+ case "Int":
1127
+ case "BigInt":
1128
+ return "int" + (nullable ? "|null" : "");
1129
+ case "Float":
1130
+ case "Decimal":
1131
+ return "float" + (nullable ? "|null" : "");
1132
+ case "Boolean":
1133
+ return "bool" + (nullable ? "|null" : "");
1134
+ case "Date":
1135
+ case "Time":
1136
+ case "Timestamp":
1137
+ return "\\Carbon\\Carbon" + (nullable ? "|null" : "");
1138
+ case "Json":
1139
+ return "array" + (nullable ? "|null" : "");
1140
+ case "Enum":
1141
+ case "EnumRef":
1142
+ return "string" + (nullable ? "|null" : "");
1143
+ case "Association": {
1144
+ const assoc = propDef;
1145
+ if (assoc.target) {
1146
+ const className = toPascalCase(assoc.target);
1147
+ switch (assoc.relation) {
1148
+ case "OneToMany":
1149
+ case "ManyToMany":
1150
+ case "MorphMany":
1151
+ case "MorphToMany":
1152
+ case "MorphedByMany":
1153
+ return `\\Illuminate\\Database\\Eloquent\\Collection<${className}>`;
1154
+ default:
1155
+ return className + "|null";
1156
+ }
1157
+ }
1158
+ return "mixed";
1159
+ }
1160
+ default:
1161
+ return "mixed";
1162
+ }
1163
+ }
1164
+ function generateBaseModel(schemas, options, stubContent) {
1165
+ const modelMap = Object.values(schemas).filter((s) => s.kind !== "enum").map((s) => {
1166
+ const className = toPascalCase(s.name);
1167
+ return ` '${s.name}' => \\${options.modelNamespace}\\${className}::class,`;
1168
+ }).join("\n");
1169
+ const content = stubContent.replace(/\{\{BASE_MODEL_NAMESPACE\}\}/g, options.baseModelNamespace).replace(/\{\{BASE_MODEL_CLASS\}\}/g, options.baseModelClassName).replace(/\{\{MODEL_MAP\}\}/g, modelMap);
1170
+ return {
1171
+ path: `${options.baseModelPath}/${options.baseModelClassName}.php`,
1172
+ content,
1173
+ type: "base-model",
1174
+ overwrite: true,
1175
+ schemaName: "__base__"
1176
+ };
1177
+ }
1178
+ function generateEntityBaseModel(schema, schemas, options, stubContent, authStubContent) {
1179
+ const className = toPascalCase(schema.name);
1180
+ const tableName = schema.options?.tableName ?? pluralize(toSnakeCase(schema.name));
1181
+ const isAuth = schema.options?.authenticatable ?? false;
1182
+ const primaryKey = "id";
1183
+ const idType = schema.options?.idType ?? "BigInt";
1184
+ const isUuid = idType === "Uuid";
1185
+ const isStringKey = idType === "Uuid" || idType === "String";
1186
+ const imports = [];
1187
+ const traits = [];
1188
+ const fillable = [];
1189
+ const hidden = [];
1190
+ const appends = [];
1191
+ const casts = [];
1192
+ const relations = [];
1193
+ const docProperties = [];
1194
+ if (schema.options?.softDelete) {
1195
+ imports.push("use Illuminate\\Database\\Eloquent\\SoftDeletes;");
1196
+ traits.push(" use SoftDeletes;");
1197
+ }
1198
+ const properties = schema.properties ?? {};
1199
+ for (const [propName, propDef] of Object.entries(properties)) {
1200
+ const snakeName = toSnakeCase(propName);
1201
+ const phpType = getPhpDocType(propDef, schemas);
1202
+ docProperties.push(` * @property ${phpType} $${snakeName}`);
1203
+ if (propDef.type === "Association") {
1204
+ const assoc = propDef;
1205
+ if (assoc.target) {
1206
+ imports.push(`use ${options.modelNamespace}\\${toPascalCase(assoc.target)};`);
1207
+ }
1208
+ relations.push(generateRelation(propName, assoc, schema, schemas, options));
1209
+ if (assoc.relation === "ManyToOne" || assoc.relation === "OneToOne") {
1210
+ if (!assoc.mappedBy) {
1211
+ const fkName = toSnakeCase(propName) + "_id";
1212
+ fillable.push(` '${fkName}',`);
1213
+ docProperties.push(` * @property int|null $${fkName}`);
1214
+ }
1215
+ }
1216
+ } else if (propDef.type === "Password") {
1217
+ fillable.push(` '${snakeName}',`);
1218
+ hidden.push(` '${snakeName}',`);
1219
+ const cast = getCastType(propDef);
1220
+ if (cast) {
1221
+ casts.push(` '${snakeName}' => '${cast}',`);
1222
+ }
1223
+ } else if (propDef.type === "File") {
1224
+ const relMethod = generateFileRelation(propName, propDef);
1225
+ relations.push(relMethod);
1226
+ } else {
1227
+ fillable.push(` '${snakeName}',`);
1228
+ const cast = getCastType(propDef);
1229
+ if (cast) {
1230
+ casts.push(` '${snakeName}' => '${cast}',`);
1231
+ }
1232
+ }
1233
+ }
1234
+ const docComment = `/**
1235
+ * ${className}BaseModel
1236
+ *
1237
+ ${docProperties.join("\n")}
1238
+ */`;
1239
+ const stub = isAuth ? authStubContent : stubContent;
1240
+ const keyType = isStringKey ? ` /**
1241
+ * The "type" of the primary key ID.
1242
+ */
1243
+ protected $keyType = 'string';
1244
+
1245
+ ` : "";
1246
+ const incrementing = isUuid ? ` /**
1247
+ * Indicates if the IDs are auto-incrementing.
1248
+ */
1249
+ public $incrementing = false;
1250
+
1251
+ ` : "";
1252
+ if (isUuid) {
1253
+ imports.push("use Illuminate\\Database\\Eloquent\\Concerns\\HasUuids;");
1254
+ traits.push(" use HasUuids;");
1255
+ }
1256
+ 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"));
1257
+ return {
1258
+ path: `${options.baseModelPath}/${className}BaseModel.php`,
1259
+ content,
1260
+ type: "entity-base",
1261
+ overwrite: true,
1262
+ schemaName: schema.name
1263
+ };
1264
+ }
1265
+ function findInverseRelation(currentSchemaName, targetSchemaName, schemas) {
1266
+ const targetSchema = schemas[targetSchemaName];
1267
+ if (!targetSchema || !targetSchema.properties) {
1268
+ return null;
1269
+ }
1270
+ for (const [propName, propDef] of Object.entries(targetSchema.properties)) {
1271
+ if (propDef.type === "Association") {
1272
+ const assoc = propDef;
1273
+ if (assoc.relation === "ManyToOne" && assoc.target === currentSchemaName) {
1274
+ return propName;
1275
+ }
1276
+ }
1277
+ }
1278
+ return null;
1279
+ }
1280
+ function generateRelation(propName, assoc, schema, schemas, options) {
1281
+ const methodName = toCamelCase(propName);
1282
+ const targetClass = assoc.target ? toPascalCase(assoc.target) : "";
1283
+ const fkName = toSnakeCase(propName) + "_id";
1284
+ switch (assoc.relation) {
1285
+ case "ManyToOne":
1286
+ return ` /**
1287
+ * Get the ${propName} that owns this model.
1288
+ */
1289
+ public function ${methodName}(): BelongsTo
1290
+ {
1291
+ return $this->belongsTo(${targetClass}::class, '${fkName}');
1292
+ }`;
1293
+ case "OneToOne":
1294
+ if (assoc.mappedBy) {
1295
+ return ` /**
1296
+ * Get the ${propName} for this model.
1297
+ */
1298
+ public function ${methodName}(): HasOne
1299
+ {
1300
+ return $this->hasOne(${targetClass}::class, '${toSnakeCase(assoc.mappedBy)}_id');
1301
+ }`;
1302
+ }
1303
+ return ` /**
1304
+ * Get the ${propName} that owns this model.
1305
+ */
1306
+ public function ${methodName}(): BelongsTo
1307
+ {
1308
+ return $this->belongsTo(${targetClass}::class, '${fkName}');
1309
+ }`;
1310
+ case "OneToMany": {
1311
+ let foreignKey;
1312
+ if (assoc.inversedBy) {
1313
+ foreignKey = toSnakeCase(assoc.inversedBy) + "_id";
1314
+ } else if (assoc.target) {
1315
+ const inverseRelation = findInverseRelation(schema.name, assoc.target, schemas);
1316
+ if (inverseRelation) {
1317
+ foreignKey = toSnakeCase(inverseRelation) + "_id";
1318
+ } else {
1319
+ foreignKey = toSnakeCase(schema.name) + "_id";
1320
+ }
1321
+ } else {
1322
+ foreignKey = toSnakeCase(propName) + "_id";
1323
+ }
1324
+ return ` /**
1325
+ * Get the ${propName} for this model.
1326
+ */
1327
+ public function ${methodName}(): HasMany
1328
+ {
1329
+ return $this->hasMany(${targetClass}::class, '${foreignKey}');
1330
+ }`;
1331
+ }
1332
+ case "ManyToMany": {
1333
+ const pivotTable = assoc.joinTable ?? `${toSnakeCase(propName)}_pivot`;
1334
+ return ` /**
1335
+ * The ${propName} that belong to this model.
1336
+ */
1337
+ public function ${methodName}(): BelongsToMany
1338
+ {
1339
+ return $this->belongsToMany(${targetClass}::class, '${pivotTable}')
1340
+ ->withTimestamps();
1341
+ }`;
1342
+ }
1343
+ case "MorphTo":
1344
+ return ` /**
1345
+ * Get the parent ${propName} model.
1346
+ */
1347
+ public function ${methodName}(): MorphTo
1348
+ {
1349
+ return $this->morphTo('${methodName}');
1350
+ }`;
1351
+ case "MorphOne":
1352
+ return ` /**
1353
+ * Get the ${propName} for this model.
1354
+ */
1355
+ public function ${methodName}(): MorphOne
1356
+ {
1357
+ return $this->morphOne(${targetClass}::class, '${assoc.morphName ?? propName}');
1358
+ }`;
1359
+ case "MorphMany":
1360
+ return ` /**
1361
+ * Get the ${propName} for this model.
1362
+ */
1363
+ public function ${methodName}(): MorphMany
1364
+ {
1365
+ return $this->morphMany(${targetClass}::class, '${assoc.morphName ?? propName}');
1366
+ }`;
1367
+ default:
1368
+ return ` // TODO: Implement ${assoc.relation} relation for ${propName}`;
1369
+ }
1370
+ }
1371
+ function generateFileRelation(propName, propDef) {
1372
+ const methodName = toCamelCase(propName);
1373
+ const relationType = propDef.multiple ? "MorphMany" : "MorphOne";
1374
+ const relationMethod = propDef.multiple ? "morphMany" : "morphOne";
1375
+ return ` /**
1376
+ * Get the ${propName} file(s) for this model.
1377
+ */
1378
+ public function ${methodName}(): ${relationType}
1379
+ {
1380
+ return $this->${relationMethod}(FileUpload::class, 'uploadable')
1381
+ ->where('attribute_name', '${propName}');
1382
+ }`;
1383
+ }
1384
+ function generateEntityModel(schema, options, stubContent) {
1385
+ const className = toPascalCase(schema.name);
1386
+ const content = stubContent.replace(/\{\{BASE_MODEL_NAMESPACE\}\}/g, options.baseModelNamespace).replace(/\{\{MODEL_NAMESPACE\}\}/g, options.modelNamespace).replace(/\{\{CLASS_NAME\}\}/g, className);
1387
+ return {
1388
+ path: `${options.modelPath}/${className}.php`,
1389
+ content,
1390
+ type: "entity",
1391
+ overwrite: false,
1392
+ // Never overwrite user models
1393
+ schemaName: schema.name
1394
+ };
1395
+ }
1396
+ function getStubContent(stubName) {
1397
+ const stubs = {
1398
+ "base-model": `<?php
1399
+
1400
+ namespace {{BASE_MODEL_NAMESPACE}};
1401
+
1402
+ /**
1403
+ * Base model class for all Omnify-generated models.
1404
+ * Contains model mapping for polymorphic relations.
1405
+ *
1406
+ * DO NOT EDIT - This file is auto-generated by Omnify.
1407
+ * Any changes will be overwritten on next generation.
1408
+ *
1409
+ * @generated by @famgia/omnify-laravel
1410
+ */
1411
+
1412
+ use Illuminate\\Database\\Eloquent\\Model;
1413
+ use Illuminate\\Database\\Eloquent\\Relations\\Relation;
1414
+
1415
+ abstract class {{BASE_MODEL_CLASS}} extends Model
1416
+ {
1417
+ /**
1418
+ * Model class map for polymorphic relations.
1419
+ */
1420
+ protected static array $modelMap = [
1421
+ {{MODEL_MAP}}
1422
+ ];
1423
+
1424
+ /**
1425
+ * Boot the model and register morph map.
1426
+ */
1427
+ protected static function boot(): void
1428
+ {
1429
+ parent::boot();
1430
+
1431
+ // Register morph map for polymorphic relations
1432
+ Relation::enforceMorphMap(static::$modelMap);
1433
+ }
1434
+
1435
+ /**
1436
+ * Get the model class for a given morph type.
1437
+ */
1438
+ public static function getModelClass(string $morphType): ?string
1439
+ {
1440
+ return static::$modelMap[$morphType] ?? null;
1441
+ }
1442
+ }
1443
+ `,
1444
+ "entity-base": `<?php
1445
+
1446
+ namespace {{BASE_MODEL_NAMESPACE}};
1447
+
1448
+ /**
1449
+ * DO NOT EDIT - This file is auto-generated by Omnify.
1450
+ * Any changes will be overwritten on next generation.
1451
+ *
1452
+ * @generated by @famgia/omnify-laravel
1453
+ */
1454
+
1455
+ use Illuminate\\Database\\Eloquent\\Relations\\BelongsTo;
1456
+ use Illuminate\\Database\\Eloquent\\Relations\\HasMany;
1457
+ use Illuminate\\Database\\Eloquent\\Relations\\HasOne;
1458
+ use Illuminate\\Database\\Eloquent\\Relations\\BelongsToMany;
1459
+ use Illuminate\\Database\\Eloquent\\Relations\\MorphTo;
1460
+ use Illuminate\\Database\\Eloquent\\Relations\\MorphOne;
1461
+ use Illuminate\\Database\\Eloquent\\Relations\\MorphMany;
1462
+ use Illuminate\\Database\\Eloquent\\Relations\\MorphToMany;
1463
+ use Illuminate\\Database\\Eloquent\\Collection as EloquentCollection;
1464
+ {{IMPORTS}}
1465
+
1466
+ {{DOC_COMMENT}}
1467
+ class {{CLASS_NAME}}BaseModel extends {{BASE_MODEL_CLASS}}
1468
+ {
1469
+ {{TRAITS}}
1470
+ /**
1471
+ * The table associated with the model.
1472
+ */
1473
+ protected $table = '{{TABLE_NAME}}';
1474
+
1475
+ /**
1476
+ * The primary key for the model.
1477
+ */
1478
+ protected $primaryKey = '{{PRIMARY_KEY}}';
1479
+
1480
+ {{KEY_TYPE}}
1481
+ {{INCREMENTING}}
1482
+ /**
1483
+ * Indicates if the model should be timestamped.
1484
+ */
1485
+ public $timestamps = {{TIMESTAMPS}};
1486
+
1487
+ /**
1488
+ * The attributes that are mass assignable.
1489
+ */
1490
+ protected $fillable = [
1491
+ {{FILLABLE}}
1492
+ ];
1493
+
1494
+ /**
1495
+ * The attributes that should be hidden for serialization.
1496
+ */
1497
+ protected $hidden = [
1498
+ {{HIDDEN}}
1499
+ ];
1500
+
1501
+ /**
1502
+ * The accessors to append to the model's array form.
1503
+ */
1504
+ protected $appends = [
1505
+ {{APPENDS}}
1506
+ ];
1507
+
1508
+ /**
1509
+ * Get the attributes that should be cast.
1510
+ */
1511
+ protected function casts(): array
1512
+ {
1513
+ return [
1514
+ {{CASTS}}
1515
+ ];
1516
+ }
1517
+
1518
+ {{RELATIONS}}
1519
+ }
1520
+ `,
1521
+ "entity-base-auth": `<?php
1522
+
1523
+ namespace {{BASE_MODEL_NAMESPACE}};
1524
+
1525
+ /**
1526
+ * DO NOT EDIT - This file is auto-generated by Omnify.
1527
+ * Any changes will be overwritten on next generation.
1528
+ *
1529
+ * @generated by @famgia/omnify-laravel
1530
+ */
1531
+
1532
+ use Illuminate\\Foundation\\Auth\\User as Authenticatable;
1533
+ use Illuminate\\Database\\Eloquent\\Relations\\BelongsTo;
1534
+ use Illuminate\\Database\\Eloquent\\Relations\\HasMany;
1535
+ use Illuminate\\Database\\Eloquent\\Relations\\HasOne;
1536
+ use Illuminate\\Database\\Eloquent\\Relations\\BelongsToMany;
1537
+ use Illuminate\\Database\\Eloquent\\Relations\\MorphTo;
1538
+ use Illuminate\\Database\\Eloquent\\Relations\\MorphOne;
1539
+ use Illuminate\\Database\\Eloquent\\Relations\\MorphMany;
1540
+ use Illuminate\\Database\\Eloquent\\Relations\\MorphToMany;
1541
+ use Illuminate\\Database\\Eloquent\\Collection as EloquentCollection;
1542
+ use Illuminate\\Notifications\\Notifiable;
1543
+ {{IMPORTS}}
1544
+
1545
+ {{DOC_COMMENT}}
1546
+ class {{CLASS_NAME}}BaseModel extends Authenticatable
1547
+ {
1548
+ use Notifiable;
1549
+ {{TRAITS}}
1550
+ /**
1551
+ * The table associated with the model.
1552
+ */
1553
+ protected $table = '{{TABLE_NAME}}';
1554
+
1555
+ /**
1556
+ * The primary key for the model.
1557
+ */
1558
+ protected $primaryKey = '{{PRIMARY_KEY}}';
1559
+
1560
+ {{KEY_TYPE}}
1561
+ {{INCREMENTING}}
1562
+ /**
1563
+ * Indicates if the model should be timestamped.
1564
+ */
1565
+ public $timestamps = {{TIMESTAMPS}};
1566
+
1567
+ /**
1568
+ * The attributes that are mass assignable.
1569
+ */
1570
+ protected $fillable = [
1571
+ {{FILLABLE}}
1572
+ ];
1573
+
1574
+ /**
1575
+ * The attributes that should be hidden for serialization.
1576
+ */
1577
+ protected $hidden = [
1578
+ {{HIDDEN}}
1579
+ ];
1580
+
1581
+ /**
1582
+ * The accessors to append to the model's array form.
1583
+ */
1584
+ protected $appends = [
1585
+ {{APPENDS}}
1586
+ ];
1587
+
1588
+ /**
1589
+ * Get the attributes that should be cast.
1590
+ */
1591
+ protected function casts(): array
1592
+ {
1593
+ return [
1594
+ {{CASTS}}
1595
+ ];
1596
+ }
1597
+
1598
+ {{RELATIONS}}
1599
+ }
1600
+ `,
1601
+ "entity": `<?php
1602
+
1603
+ namespace {{MODEL_NAMESPACE}};
1604
+
1605
+ use {{BASE_MODEL_NAMESPACE}}\\{{CLASS_NAME}}BaseModel;
1606
+ use Illuminate\\Database\\Eloquent\\Factories\\HasFactory;
1607
+
1608
+ /**
1609
+ * {{CLASS_NAME}} Model
1610
+ *
1611
+ * This file is generated once and can be customized.
1612
+ * Add your custom methods and logic here.
1613
+ */
1614
+ class {{CLASS_NAME}} extends {{CLASS_NAME}}BaseModel
1615
+ {
1616
+ use HasFactory;
1617
+
1618
+ /**
1619
+ * Create a new model instance.
1620
+ */
1621
+ public function __construct(array $attributes = [])
1622
+ {
1623
+ parent::__construct($attributes);
1624
+ }
1625
+
1626
+ // Add your custom methods here
1627
+ }
1628
+ `,
1629
+ "service-provider": `<?php
1630
+
1631
+ namespace App\\Providers;
1632
+
1633
+ use Illuminate\\Database\\Eloquent\\Relations\\Relation;
1634
+ use Illuminate\\Support\\ServiceProvider;
1635
+
1636
+ /**
1637
+ * Omnify Service Provider
1638
+ *
1639
+ * DO NOT EDIT - This file is auto-generated by Omnify.
1640
+ * Any changes will be overwritten on next generation.
1641
+ *
1642
+ * - Loads Omnify migrations from database/migrations/omnify
1643
+ * - Registers morph map for polymorphic relationships
1644
+ *
1645
+ * @generated by @famgia/omnify-laravel
1646
+ */
1647
+ class OmnifyServiceProvider extends ServiceProvider
1648
+ {
1649
+ /**
1650
+ * Register any application services.
1651
+ */
1652
+ public function register(): void
1653
+ {
1654
+ //
1655
+ }
1656
+
1657
+ /**
1658
+ * Bootstrap any application services.
1659
+ */
1660
+ public function boot(): void
1661
+ {
1662
+ // Load Omnify migrations from custom directory
1663
+ $this->loadMigrationsFrom(database_path('migrations/omnify'));
1664
+
1665
+ // Register morph map for polymorphic relationships
1666
+ Relation::enforceMorphMap([
1667
+ {{MORPH_MAP}}
1668
+ ]);
1669
+ }
1670
+ }
1671
+ `
1672
+ };
1673
+ return stubs[stubName] ?? "";
1674
+ }
1675
+ function generateServiceProvider(schemas, options, stubContent) {
1676
+ const morphMap = Object.values(schemas).filter((s) => s.kind !== "enum").map((s) => {
1677
+ const className = toPascalCase(s.name);
1678
+ return ` '${s.name}' => \\${options.modelNamespace}\\${className}::class,`;
1679
+ }).join("\n");
1680
+ const content = stubContent.replace(/\{\{MORPH_MAP\}\}/g, morphMap);
1681
+ return {
1682
+ path: "app/Providers/OmnifyServiceProvider.php",
1683
+ content,
1684
+ type: "service-provider",
1685
+ overwrite: true,
1686
+ // Always overwrite to keep morph map in sync
1687
+ schemaName: "__service_provider__"
1688
+ };
1689
+ }
1690
+ function generateModels(schemas, options) {
1691
+ const resolved = resolveOptions(options);
1692
+ const models = [];
1693
+ models.push(generateBaseModel(schemas, resolved, getStubContent("base-model")));
1694
+ models.push(generateServiceProvider(schemas, resolved, getStubContent("service-provider")));
1695
+ for (const schema of Object.values(schemas)) {
1696
+ if (schema.kind === "enum") {
1697
+ continue;
1698
+ }
1699
+ models.push(generateEntityBaseModel(
1700
+ schema,
1701
+ schemas,
1702
+ resolved,
1703
+ getStubContent("entity-base"),
1704
+ getStubContent("entity-base-auth")
1705
+ ));
1706
+ models.push(generateEntityModel(schema, resolved, getStubContent("entity")));
1707
+ }
1708
+ return models;
1709
+ }
1710
+ function getModelPath(model) {
1711
+ return model.path;
1712
+ }
1713
+ function generateProviderRegistration(existingContent, laravelVersion) {
1714
+ const providerClass = "App\\Providers\\OmnifyServiceProvider::class";
1715
+ const providerLine = ` ${providerClass},`;
1716
+ if (existingContent && existingContent.includes("OmnifyServiceProvider")) {
1717
+ return {
1718
+ path: laravelVersion === "laravel11+" ? "bootstrap/providers.php" : "config/app.php",
1719
+ content: existingContent,
1720
+ laravelVersion,
1721
+ alreadyRegistered: true
1722
+ };
1723
+ }
1724
+ if (laravelVersion === "laravel11+") {
1725
+ if (existingContent) {
1726
+ const lines = existingContent.split("\n");
1727
+ const result = [];
1728
+ let inserted = false;
1729
+ for (let i = 0; i < lines.length; i++) {
1730
+ const line = lines[i];
1731
+ if (!inserted && line.trim() === "];") {
1732
+ result.push(providerLine);
1733
+ inserted = true;
1734
+ }
1735
+ result.push(line);
1736
+ }
1737
+ return {
1738
+ path: "bootstrap/providers.php",
1739
+ content: result.join("\n"),
1740
+ laravelVersion,
1741
+ alreadyRegistered: false
1742
+ };
1743
+ } else {
1744
+ return {
1745
+ path: "bootstrap/providers.php",
1746
+ content: `<?php
1747
+
1748
+ return [
1749
+ App\\Providers\\AppServiceProvider::class,
1750
+ ${providerLine}
1751
+ ];
1752
+ `,
1753
+ laravelVersion,
1754
+ alreadyRegistered: false
1755
+ };
1756
+ }
1757
+ } else {
1758
+ if (existingContent) {
1759
+ const providersSectionRegex = /'providers'\s*=>\s*\[[\s\S]*?\n(\s*)\]/m;
1760
+ const match = existingContent.match(providersSectionRegex);
1761
+ if (match) {
1762
+ const providersStart = existingContent.indexOf("'providers'");
1763
+ if (providersStart === -1) {
1764
+ return null;
1765
+ }
1766
+ let depth = 0;
1767
+ let foundStart = false;
1768
+ let insertPos = -1;
1769
+ for (let i = providersStart; i < existingContent.length; i++) {
1770
+ const char = existingContent[i];
1771
+ if (char === "[") {
1772
+ foundStart = true;
1773
+ depth++;
1774
+ } else if (char === "]") {
1775
+ depth--;
1776
+ if (foundStart && depth === 0) {
1777
+ insertPos = i;
1778
+ break;
1779
+ }
1780
+ }
1781
+ }
1782
+ if (insertPos !== -1) {
1783
+ const beforeClose = existingContent.substring(0, insertPos);
1784
+ const lastNewline = beforeClose.lastIndexOf("\n");
1785
+ const content = existingContent.substring(0, lastNewline + 1) + providerLine + "\n" + existingContent.substring(lastNewline + 1);
1786
+ return {
1787
+ path: "config/app.php",
1788
+ content,
1789
+ laravelVersion,
1790
+ alreadyRegistered: false
1791
+ };
1792
+ }
1793
+ }
1794
+ return null;
1795
+ } else {
1796
+ return null;
1797
+ }
1798
+ }
1799
+ }
1800
+
1801
+ // src/factory/generator.ts
1802
+ function resolveOptions2(options) {
1803
+ return {
1804
+ modelNamespace: options?.modelNamespace ?? "App\\Models",
1805
+ factoryPath: options?.factoryPath ?? "database/factories",
1806
+ fakerLocale: options?.fakerLocale ?? "en_US"
1807
+ };
1808
+ }
1809
+ function getStubContent2() {
1810
+ return `<?php
1811
+
1812
+ namespace Database\\Factories;
1813
+
1814
+ use {{MODEL_NAMESPACE}}\\{{MODEL_NAME}};
1815
+ use Illuminate\\Database\\Eloquent\\Factories\\Factory;
1816
+ {{IMPORTS}}
1817
+
1818
+ /**
1819
+ * @extends Factory<{{MODEL_NAME}}>
1820
+ */
1821
+ class {{MODEL_NAME}}Factory extends Factory
1822
+ {
1823
+ protected $model = {{MODEL_NAME}}::class;
1824
+
1825
+ /**
1826
+ * Define the model's default state.
1827
+ *
1828
+ * @return array<string, mixed>
1829
+ */
1830
+ public function definition(): array
1831
+ {
1832
+ return [
1833
+ {{ATTRIBUTES}}
1834
+ ];
1835
+ }
1836
+ }
1837
+ `;
1838
+ }
1839
+ function generateFakeData(propertyName, property, schema, schemas) {
1840
+ const type = property.type;
1841
+ if (["deleted_at", "created_at", "updated_at"].includes(propertyName)) {
1842
+ return null;
1843
+ }
1844
+ if (type === "Association") {
1845
+ return null;
1846
+ }
1847
+ switch (type) {
1848
+ case "String":
1849
+ return generateStringFake(propertyName, property);
1850
+ case "Email":
1851
+ return `'${propertyName}' => fake()->unique()->safeEmail(),`;
1852
+ case "Password":
1853
+ return `'${propertyName}' => bcrypt('password'),`;
1854
+ case "Int":
1855
+ case "BigInt":
1856
+ return generateIntFake(propertyName, property);
1857
+ case "Float":
1858
+ case "Decimal":
1859
+ return `'${propertyName}' => fake()->randomFloat(2, 1, 10000),`;
1860
+ case "Boolean":
1861
+ return `'${propertyName}' => fake()->boolean(),`;
1862
+ case "Text":
1863
+ return `'${propertyName}' => fake()->paragraphs(3, true),`;
1864
+ case "LongText":
1865
+ return `'${propertyName}' => fake()->paragraphs(5, true),`;
1866
+ case "Date":
1867
+ return `'${propertyName}' => fake()->date(),`;
1868
+ case "Time":
1869
+ return `'${propertyName}' => fake()->time(),`;
1870
+ case "Timestamp":
1871
+ case "DateTime":
1872
+ return `'${propertyName}' => fake()->dateTime(),`;
1873
+ case "Json":
1874
+ return `'${propertyName}' => [],`;
1875
+ case "Enum":
1876
+ return generateEnumFake(propertyName, property);
1877
+ case "EnumRef":
1878
+ return generateEnumRefFake(propertyName, property, schemas);
1879
+ default:
1880
+ return `'${propertyName}' => fake()->sentence(),`;
1881
+ }
1882
+ }
1883
+ function generateStringFake(propertyName, property) {
1884
+ if (propertyName === "slug") {
1885
+ return `'${propertyName}' => fake()->unique()->slug(),`;
1886
+ }
1887
+ if (propertyName === "uuid" || propertyName === "uid") {
1888
+ return `'${propertyName}' => (string) \\Illuminate\\Support\\Str::uuid(),`;
1889
+ }
1890
+ if (propertyName.includes("email")) {
1891
+ return `'${propertyName}' => fake()->unique()->safeEmail(),`;
1892
+ }
1893
+ if (propertyName.includes("phone")) {
1894
+ return `'${propertyName}' => fake()->phoneNumber(),`;
1895
+ }
1896
+ if (propertyName.includes("image") || propertyName.includes("photo") || propertyName.includes("avatar")) {
1897
+ return `'${propertyName}' => fake()->imageUrl(),`;
1898
+ }
1899
+ if (propertyName.includes("url") || propertyName.includes("website")) {
1900
+ return `'${propertyName}' => fake()->url(),`;
1901
+ }
1902
+ if (propertyName.includes("path") || propertyName.includes("file")) {
1903
+ return `'${propertyName}' => 'uploads/' . fake()->uuid() . '.jpg',`;
1904
+ }
1905
+ if (propertyName === "name" || propertyName === "title") {
1906
+ return `'${propertyName}' => fake()->sentence(3),`;
1907
+ }
1908
+ if (propertyName.includes("name")) {
1909
+ return `'${propertyName}' => fake()->name(),`;
1910
+ }
1911
+ if (propertyName.includes("address")) {
1912
+ return `'${propertyName}' => fake()->address(),`;
1913
+ }
1914
+ if (propertyName.includes("city")) {
1915
+ return `'${propertyName}' => fake()->city(),`;
1916
+ }
1917
+ if (propertyName.includes("country")) {
1918
+ return `'${propertyName}' => fake()->country(),`;
1919
+ }
1920
+ if (propertyName.includes("zip") || propertyName.includes("postal")) {
1921
+ return `'${propertyName}' => fake()->postcode(),`;
1922
+ }
1923
+ if (propertyName.includes("color")) {
1924
+ return `'${propertyName}' => fake()->hexColor(),`;
1925
+ }
1926
+ if (propertyName.includes("token") || propertyName.includes("secret") || propertyName.includes("key")) {
1927
+ return `'${propertyName}' => \\Illuminate\\Support\\Str::random(32),`;
1928
+ }
1929
+ if (propertyName.includes("code")) {
1930
+ return `'${propertyName}' => fake()->unique()->regexify('[A-Z0-9]{8}'),`;
1931
+ }
1932
+ const length = property.length;
1933
+ if (length && length <= 50) {
1934
+ return `'${propertyName}' => fake()->words(3, true),`;
1935
+ }
1936
+ return `'${propertyName}' => fake()->sentence(),`;
1937
+ }
1938
+ function generateIntFake(propertyName, property) {
1939
+ if (propertyName.includes("count") || propertyName.includes("quantity")) {
1940
+ return `'${propertyName}' => fake()->numberBetween(0, 100),`;
1941
+ }
1942
+ if (propertyName.includes("price") || propertyName.includes("amount") || propertyName.includes("cost")) {
1943
+ return `'${propertyName}' => fake()->numberBetween(100, 10000),`;
1944
+ }
1945
+ if (propertyName.includes("order") || propertyName.includes("sort") || propertyName.includes("position")) {
1946
+ return `'${propertyName}' => fake()->numberBetween(1, 100),`;
1947
+ }
1948
+ if (propertyName.includes("age")) {
1949
+ return `'${propertyName}' => fake()->numberBetween(18, 80),`;
1950
+ }
1951
+ if (propertyName.includes("year")) {
1952
+ return `'${propertyName}' => fake()->year(),`;
1953
+ }
1954
+ return `'${propertyName}' => fake()->numberBetween(1, 1000),`;
1955
+ }
1956
+ function generateEnumFake(propertyName, property) {
1957
+ const enumValues = property.enum;
1958
+ if (!enumValues || enumValues.length === 0) {
1959
+ return `'${propertyName}' => null,`;
1960
+ }
1961
+ const values = enumValues.map((v) => typeof v === "string" ? v : v.value);
1962
+ const valuesStr = values.map((v) => `'${v}'`).join(", ");
1963
+ return `'${propertyName}' => fake()->randomElement([${valuesStr}]),`;
1964
+ }
1965
+ function generateEnumRefFake(propertyName, property, schemas) {
1966
+ const enumName = property.enum;
1967
+ if (!enumName) {
1968
+ return `'${propertyName}' => null,`;
1969
+ }
1970
+ const enumSchema = schemas[enumName];
1971
+ if (!enumSchema || enumSchema.kind !== "enum" || !enumSchema.values) {
1972
+ return `'${propertyName}' => null,`;
1973
+ }
1974
+ const valuesStr = enumSchema.values.map((v) => `'${v}'`).join(", ");
1975
+ return `'${propertyName}' => fake()->randomElement([${valuesStr}]),`;
1976
+ }
1977
+ function generateAssociationFake(propertyName, property, schema, schemas, modelNamespace) {
1978
+ if (property.type !== "Association") {
1979
+ return null;
1980
+ }
1981
+ const relation = property.relation;
1982
+ const target = property.target;
1983
+ if (relation !== "ManyToOne" || !target) {
1984
+ return null;
1985
+ }
1986
+ const foreignKey = `${toSnakeCase(propertyName)}_id`;
1987
+ const isNullable2 = property.nullable ?? false;
1988
+ const targetSchema = schemas[target];
1989
+ if (!targetSchema) {
1990
+ return null;
1991
+ }
1992
+ let fake;
1993
+ if (isNullable2) {
1994
+ fake = `'${foreignKey}' => ${target}::query()->inRandomOrder()->first()?->id,`;
1995
+ } else {
1996
+ fake = `'${foreignKey}' => ${target}::query()->inRandomOrder()->first()?->id ?? ${target}::factory()->create()->id,`;
1997
+ }
1998
+ let importStatement;
1999
+ if (target !== schema.name) {
2000
+ importStatement = `use ${modelNamespace}\\${target};`;
2001
+ }
2002
+ return { fake, import: importStatement };
2003
+ }
2004
+ function generateFactory(schema, schemas, options, stubContent) {
2005
+ if (schema.kind === "enum") {
2006
+ return null;
2007
+ }
2008
+ const modelName = toPascalCase(schema.name);
2009
+ const factoryName = `${modelName}Factory`;
2010
+ const attributes = [];
2011
+ const imports = [];
2012
+ if (schema.properties) {
2013
+ for (const [propName, prop] of Object.entries(schema.properties)) {
2014
+ if (prop.type === "Association") {
2015
+ const assocResult = generateAssociationFake(propName, prop, schema, schemas, options.modelNamespace);
2016
+ if (assocResult) {
2017
+ attributes.push(assocResult.fake);
2018
+ if (assocResult.import) {
2019
+ imports.push(assocResult.import);
2020
+ }
2021
+ }
2022
+ continue;
2023
+ }
2024
+ const fake = generateFakeData(propName, prop, schema, schemas);
2025
+ if (fake) {
2026
+ attributes.push(fake);
2027
+ }
2028
+ }
2029
+ }
2030
+ let content = stubContent;
2031
+ content = content.replace(/\{\{MODEL_NAMESPACE\}\}/g, options.modelNamespace);
2032
+ content = content.replace(/\{\{MODEL_NAME\}\}/g, modelName);
2033
+ const uniqueImports = [...new Set(imports)];
2034
+ const importsStr = uniqueImports.length > 0 ? "\n" + uniqueImports.join("\n") : "";
2035
+ content = content.replace(/\{\{IMPORTS\}\}/g, importsStr);
2036
+ const attributesStr = attributes.length > 0 ? attributes.map((a) => ` ${a}`).join("\n") : "";
2037
+ content = content.replace(/\{\{ATTRIBUTES\}\}/g, attributesStr);
2038
+ return {
2039
+ name: factoryName,
2040
+ schemaName: schema.name,
2041
+ path: `${options.factoryPath}/${factoryName}.php`,
2042
+ content,
2043
+ overwrite: false
2044
+ // Factories should not overwrite existing files
2045
+ };
2046
+ }
2047
+ function generateFactories(schemas, options) {
2048
+ const resolved = resolveOptions2(options);
2049
+ const stubContent = getStubContent2();
2050
+ const factories = [];
2051
+ for (const schema of Object.values(schemas)) {
2052
+ const factory = generateFactory(schema, schemas, resolved, stubContent);
2053
+ if (factory) {
2054
+ factories.push(factory);
2055
+ }
2056
+ }
2057
+ return factories;
2058
+ }
2059
+ function getFactoryPath(factory) {
2060
+ return factory.path;
2061
+ }
2062
+
1036
2063
  // src/plugin.ts
1037
2064
  var LARAVEL_CONFIG_SCHEMA = {
1038
2065
  fields: [
@@ -1040,10 +2067,50 @@ var LARAVEL_CONFIG_SCHEMA = {
1040
2067
  key: "migrationsPath",
1041
2068
  type: "path",
1042
2069
  label: "Migrations Path",
1043
- description: "Directory for Laravel migration files",
1044
- default: "database/migrations",
2070
+ description: "Directory for Laravel migration files (loaded via OmnifyServiceProvider)",
2071
+ default: "database/migrations/omnify",
2072
+ group: "output"
2073
+ },
2074
+ {
2075
+ key: "modelsPath",
2076
+ type: "path",
2077
+ label: "Models Path",
2078
+ description: "Directory for user-editable model files",
2079
+ default: "app/Models",
1045
2080
  group: "output"
1046
2081
  },
2082
+ {
2083
+ key: "baseModelsPath",
2084
+ type: "path",
2085
+ label: "Base Models Path",
2086
+ description: "Directory for auto-generated base model files",
2087
+ default: "app/Models/OmnifyBase",
2088
+ group: "output"
2089
+ },
2090
+ {
2091
+ key: "generateModels",
2092
+ type: "boolean",
2093
+ label: "Generate Models",
2094
+ description: "Generate Eloquent model classes",
2095
+ default: true,
2096
+ group: "options"
2097
+ },
2098
+ {
2099
+ key: "factoriesPath",
2100
+ type: "path",
2101
+ label: "Factories Path",
2102
+ description: "Directory for Laravel factory files",
2103
+ default: "database/factories",
2104
+ group: "output"
2105
+ },
2106
+ {
2107
+ key: "generateFactories",
2108
+ type: "boolean",
2109
+ label: "Generate Factories",
2110
+ description: "Generate Laravel factory classes for testing",
2111
+ default: true,
2112
+ group: "options"
2113
+ },
1047
2114
  {
1048
2115
  key: "connection",
1049
2116
  type: "string",
@@ -1054,41 +2121,141 @@ var LARAVEL_CONFIG_SCHEMA = {
1054
2121
  }
1055
2122
  ]
1056
2123
  };
1057
- function resolveOptions(options) {
2124
+ function resolveOptions3(options) {
1058
2125
  return {
1059
- migrationsPath: options?.migrationsPath ?? "database/migrations",
2126
+ migrationsPath: options?.migrationsPath ?? "database/migrations/omnify",
2127
+ modelsPath: options?.modelsPath ?? "app/Models",
2128
+ baseModelsPath: options?.baseModelsPath ?? "app/Models/OmnifyBase",
2129
+ modelNamespace: options?.modelNamespace ?? "App\\Models",
2130
+ baseModelNamespace: options?.baseModelNamespace ?? "App\\Models\\OmnifyBase",
2131
+ generateModels: options?.generateModels ?? true,
2132
+ factoriesPath: options?.factoriesPath ?? "database/factories",
2133
+ generateFactories: options?.generateFactories ?? true,
2134
+ fakerLocale: options?.fakerLocale ?? "en_US",
1060
2135
  connection: options?.connection,
1061
2136
  timestamp: options?.timestamp
1062
2137
  };
1063
2138
  }
1064
2139
  function laravelPlugin(options) {
1065
- const resolved = resolveOptions(options);
2140
+ const resolved = resolveOptions3(options);
2141
+ const migrationGenerator = {
2142
+ name: "laravel-migrations",
2143
+ description: "Generate Laravel migration files",
2144
+ generate: async (ctx) => {
2145
+ const migrationOptions = {
2146
+ connection: resolved.connection,
2147
+ timestamp: resolved.timestamp
2148
+ };
2149
+ const migrations = generateMigrations(ctx.schemas, migrationOptions);
2150
+ return migrations.map((migration) => ({
2151
+ path: getMigrationPath(migration, resolved.migrationsPath),
2152
+ content: migration.content,
2153
+ type: "migration",
2154
+ metadata: {
2155
+ tableName: migration.tables[0],
2156
+ migrationType: migration.type
2157
+ }
2158
+ }));
2159
+ }
2160
+ };
2161
+ const modelGenerator = {
2162
+ name: "laravel-models",
2163
+ description: "Generate Eloquent model classes",
2164
+ generate: async (ctx) => {
2165
+ const modelOptions = {
2166
+ modelNamespace: resolved.modelNamespace,
2167
+ baseModelNamespace: resolved.baseModelNamespace,
2168
+ modelPath: resolved.modelsPath,
2169
+ baseModelPath: resolved.baseModelsPath
2170
+ };
2171
+ const models = generateModels(ctx.schemas, modelOptions);
2172
+ const outputs = models.map((model) => ({
2173
+ path: getModelPath(model),
2174
+ content: model.content,
2175
+ type: "model",
2176
+ // Skip writing user models if they already exist
2177
+ skipIfExists: !model.overwrite,
2178
+ metadata: {
2179
+ modelType: model.type,
2180
+ schemaName: model.schemaName
2181
+ }
2182
+ }));
2183
+ const bootstrapProvidersPath = (0, import_node_path.join)(ctx.cwd, "bootstrap/providers.php");
2184
+ const configAppPath = (0, import_node_path.join)(ctx.cwd, "config/app.php");
2185
+ let existingContent = null;
2186
+ let laravelVersion;
2187
+ if ((0, import_node_fs.existsSync)(bootstrapProvidersPath)) {
2188
+ laravelVersion = "laravel11+";
2189
+ try {
2190
+ existingContent = (0, import_node_fs.readFileSync)(bootstrapProvidersPath, "utf-8");
2191
+ } catch {
2192
+ existingContent = null;
2193
+ }
2194
+ } else if ((0, import_node_fs.existsSync)(configAppPath)) {
2195
+ laravelVersion = "laravel10-";
2196
+ try {
2197
+ existingContent = (0, import_node_fs.readFileSync)(configAppPath, "utf-8");
2198
+ } catch {
2199
+ existingContent = null;
2200
+ }
2201
+ } else {
2202
+ laravelVersion = "laravel11+";
2203
+ }
2204
+ const registration = generateProviderRegistration(existingContent, laravelVersion);
2205
+ if (registration && !registration.alreadyRegistered) {
2206
+ outputs.push({
2207
+ path: registration.path,
2208
+ content: registration.content,
2209
+ type: "other",
2210
+ skipIfExists: false,
2211
+ // We want to modify the file
2212
+ metadata: {
2213
+ registrationType: "provider-registration",
2214
+ laravelVersion: registration.laravelVersion
2215
+ }
2216
+ });
2217
+ ctx.logger.info(`OmnifyServiceProvider will be registered in ${registration.path}`);
2218
+ } else if (registration?.alreadyRegistered) {
2219
+ ctx.logger.info("OmnifyServiceProvider is already registered");
2220
+ }
2221
+ return outputs;
2222
+ }
2223
+ };
2224
+ const factoryGenerator = {
2225
+ name: "laravel-factories",
2226
+ description: "Generate Laravel factory classes for testing",
2227
+ generate: async (ctx) => {
2228
+ const factoryOptions = {
2229
+ modelNamespace: resolved.modelNamespace,
2230
+ factoryPath: resolved.factoriesPath,
2231
+ fakerLocale: resolved.fakerLocale
2232
+ };
2233
+ const factories = generateFactories(ctx.schemas, factoryOptions);
2234
+ return factories.map((factory) => ({
2235
+ path: getFactoryPath(factory),
2236
+ content: factory.content,
2237
+ type: "factory",
2238
+ // Skip writing factories if they already exist (allow customization)
2239
+ skipIfExists: !factory.overwrite,
2240
+ metadata: {
2241
+ factoryName: factory.name,
2242
+ schemaName: factory.schemaName
2243
+ }
2244
+ }));
2245
+ }
2246
+ };
2247
+ const generators = [migrationGenerator];
2248
+ if (resolved.generateModels) {
2249
+ generators.push(modelGenerator);
2250
+ }
2251
+ if (resolved.generateFactories) {
2252
+ generators.push(factoryGenerator);
2253
+ }
1066
2254
  return {
1067
2255
  name: "@famgia/omnify-laravel",
1068
- version: "0.0.13",
2256
+ version: "0.0.14",
1069
2257
  configSchema: LARAVEL_CONFIG_SCHEMA,
1070
- generators: [
1071
- {
1072
- name: "laravel-migrations",
1073
- description: "Generate Laravel migration files",
1074
- generate: async (ctx) => {
1075
- const migrationOptions = {
1076
- connection: resolved.connection,
1077
- timestamp: resolved.timestamp
1078
- };
1079
- const migrations = generateMigrations(ctx.schemas, migrationOptions);
1080
- return migrations.map((migration) => ({
1081
- path: getMigrationPath(migration, resolved.migrationsPath),
1082
- content: migration.content,
1083
- type: "migration",
1084
- metadata: {
1085
- tableName: migration.tables[0],
1086
- migrationType: migration.type
1087
- }
1088
- }));
1089
- }
1090
- }
1091
- ]
2258
+ generators
1092
2259
  };
1093
2260
  }
1094
2261
  // Annotate the CommonJS export names for ESM import in node: