@famgia/omnify-laravel 0.0.14 → 0.0.16

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" });
@@ -237,11 +238,18 @@ function generateForeignKey(propertyName, property, allSchemas) {
237
238
  } else if (targetPkType === "String") {
238
239
  method = "string";
239
240
  }
241
+ const modifiers = [];
242
+ if (assocProp.nullable || assocProp.relation === "ManyToOne") {
243
+ modifiers.push({ method: "nullable" });
244
+ }
245
+ if (assocProp.default !== void 0 && assocProp.default !== null) {
246
+ modifiers.push({ method: "default", args: [assocProp.default] });
247
+ }
240
248
  const column = {
241
249
  name: columnName,
242
250
  method,
243
251
  args: [columnName],
244
- modifiers: assocProp.relation === "ManyToOne" ? [{ method: "nullable" }] : []
252
+ modifiers
245
253
  };
246
254
  const foreignKey = {
247
255
  columns: [columnName],
@@ -341,6 +349,12 @@ function formatColumnMethod(column) {
341
349
  if (typeof arg === "string") {
342
350
  return `'${arg}'`;
343
351
  }
352
+ if (typeof arg === "boolean") {
353
+ return arg ? "true" : "false";
354
+ }
355
+ if (typeof arg === "number") {
356
+ return String(arg);
357
+ }
344
358
  return String(arg);
345
359
  }).join(", ");
346
360
  code += `->${modifier.method}(${modArgs})`;
@@ -448,6 +462,7 @@ function generatePivotTableBlueprint(pivot) {
448
462
  args: [pivot.targetColumn],
449
463
  modifiers: []
450
464
  });
465
+ columns.push(...generateTimestampColumns());
451
466
  foreignKeys.push({
452
467
  columns: [pivot.sourceColumn],
453
468
  references: "id",
@@ -846,7 +861,7 @@ function generateEntityBaseModel(schema, schemas, options, stubContent, authStub
846
861
  if (assoc.target) {
847
862
  imports.push(`use ${options.modelNamespace}\\${toPascalCase(assoc.target)};`);
848
863
  }
849
- relations.push(generateRelation(propName, assoc, options));
864
+ relations.push(generateRelation(propName, assoc, schema, schemas, options));
850
865
  if (assoc.relation === "ManyToOne" || assoc.relation === "OneToOne") {
851
866
  if (!assoc.mappedBy) {
852
867
  const fkName = toSnakeCase(propName) + "_id";
@@ -903,7 +918,22 @@ ${docProperties.join("\n")}
903
918
  schemaName: schema.name
904
919
  };
905
920
  }
906
- function generateRelation(propName, assoc, options) {
921
+ function findInverseRelation(currentSchemaName, targetSchemaName, schemas) {
922
+ const targetSchema = schemas[targetSchemaName];
923
+ if (!targetSchema || !targetSchema.properties) {
924
+ return null;
925
+ }
926
+ for (const [propName, propDef] of Object.entries(targetSchema.properties)) {
927
+ if (propDef.type === "Association") {
928
+ const assoc = propDef;
929
+ if (assoc.relation === "ManyToOne" && assoc.target === currentSchemaName) {
930
+ return propName;
931
+ }
932
+ }
933
+ }
934
+ return null;
935
+ }
936
+ function generateRelation(propName, assoc, schema, schemas, options) {
907
937
  const methodName = toCamelCase(propName);
908
938
  const targetClass = assoc.target ? toPascalCase(assoc.target) : "";
909
939
  const fkName = toSnakeCase(propName) + "_id";
@@ -933,14 +963,28 @@ function generateRelation(propName, assoc, options) {
933
963
  {
934
964
  return $this->belongsTo(${targetClass}::class, '${fkName}');
935
965
  }`;
936
- case "OneToMany":
966
+ case "OneToMany": {
967
+ let foreignKey;
968
+ if (assoc.inversedBy) {
969
+ foreignKey = toSnakeCase(assoc.inversedBy) + "_id";
970
+ } else if (assoc.target) {
971
+ const inverseRelation = findInverseRelation(schema.name, assoc.target, schemas);
972
+ if (inverseRelation) {
973
+ foreignKey = toSnakeCase(inverseRelation) + "_id";
974
+ } else {
975
+ foreignKey = toSnakeCase(schema.name) + "_id";
976
+ }
977
+ } else {
978
+ foreignKey = toSnakeCase(propName) + "_id";
979
+ }
937
980
  return ` /**
938
981
  * Get the ${propName} for this model.
939
982
  */
940
983
  public function ${methodName}(): HasMany
941
984
  {
942
- return $this->hasMany(${targetClass}::class, '${toSnakeCase(assoc.inversedBy ?? propName)}_id');
985
+ return $this->hasMany(${targetClass}::class, '${foreignKey}');
943
986
  }`;
987
+ }
944
988
  case "ManyToMany": {
945
989
  const pivotTable = assoc.joinTable ?? `${toSnakeCase(propName)}_pivot`;
946
990
  return ` /**
@@ -1237,14 +1281,73 @@ class {{CLASS_NAME}} extends {{CLASS_NAME}}BaseModel
1237
1281
 
1238
1282
  // Add your custom methods here
1239
1283
  }
1284
+ `,
1285
+ "service-provider": `<?php
1286
+
1287
+ namespace App\\Providers;
1288
+
1289
+ use Illuminate\\Database\\Eloquent\\Relations\\Relation;
1290
+ use Illuminate\\Support\\ServiceProvider;
1291
+
1292
+ /**
1293
+ * Omnify Service Provider
1294
+ *
1295
+ * DO NOT EDIT - This file is auto-generated by Omnify.
1296
+ * Any changes will be overwritten on next generation.
1297
+ *
1298
+ * - Loads Omnify migrations from database/migrations/omnify
1299
+ * - Registers morph map for polymorphic relationships
1300
+ *
1301
+ * @generated by @famgia/omnify-laravel
1302
+ */
1303
+ class OmnifyServiceProvider extends ServiceProvider
1304
+ {
1305
+ /**
1306
+ * Register any application services.
1307
+ */
1308
+ public function register(): void
1309
+ {
1310
+ //
1311
+ }
1312
+
1313
+ /**
1314
+ * Bootstrap any application services.
1315
+ */
1316
+ public function boot(): void
1317
+ {
1318
+ // Load Omnify migrations from custom directory
1319
+ $this->loadMigrationsFrom(database_path('migrations/omnify'));
1320
+
1321
+ // Register morph map for polymorphic relationships
1322
+ Relation::enforceMorphMap([
1323
+ {{MORPH_MAP}}
1324
+ ]);
1325
+ }
1326
+ }
1240
1327
  `
1241
1328
  };
1242
1329
  return stubs[stubName] ?? "";
1243
1330
  }
1331
+ function generateServiceProvider(schemas, options, stubContent) {
1332
+ const morphMap = Object.values(schemas).filter((s) => s.kind !== "enum").map((s) => {
1333
+ const className = toPascalCase(s.name);
1334
+ return ` '${s.name}' => \\${options.modelNamespace}\\${className}::class,`;
1335
+ }).join("\n");
1336
+ const content = stubContent.replace(/\{\{MORPH_MAP\}\}/g, morphMap);
1337
+ return {
1338
+ path: "app/Providers/OmnifyServiceProvider.php",
1339
+ content,
1340
+ type: "service-provider",
1341
+ overwrite: true,
1342
+ // Always overwrite to keep morph map in sync
1343
+ schemaName: "__service_provider__"
1344
+ };
1345
+ }
1244
1346
  function generateModels(schemas, options) {
1245
1347
  const resolved = resolveOptions(options);
1246
1348
  const models = [];
1247
1349
  models.push(generateBaseModel(schemas, resolved, getStubContent("base-model")));
1350
+ models.push(generateServiceProvider(schemas, resolved, getStubContent("service-provider")));
1248
1351
  for (const schema of Object.values(schemas)) {
1249
1352
  if (schema.kind === "enum") {
1250
1353
  continue;
@@ -1263,6 +1366,355 @@ function generateModels(schemas, options) {
1263
1366
  function getModelPath(model) {
1264
1367
  return model.path;
1265
1368
  }
1369
+ function generateProviderRegistration(existingContent, laravelVersion) {
1370
+ const providerClass = "App\\Providers\\OmnifyServiceProvider::class";
1371
+ const providerLine = ` ${providerClass},`;
1372
+ if (existingContent && existingContent.includes("OmnifyServiceProvider")) {
1373
+ return {
1374
+ path: laravelVersion === "laravel11+" ? "bootstrap/providers.php" : "config/app.php",
1375
+ content: existingContent,
1376
+ laravelVersion,
1377
+ alreadyRegistered: true
1378
+ };
1379
+ }
1380
+ if (laravelVersion === "laravel11+") {
1381
+ if (existingContent) {
1382
+ const lines = existingContent.split("\n");
1383
+ const result = [];
1384
+ let inserted = false;
1385
+ for (let i = 0; i < lines.length; i++) {
1386
+ const line = lines[i];
1387
+ if (!inserted && line.trim() === "];") {
1388
+ result.push(providerLine);
1389
+ inserted = true;
1390
+ }
1391
+ result.push(line);
1392
+ }
1393
+ return {
1394
+ path: "bootstrap/providers.php",
1395
+ content: result.join("\n"),
1396
+ laravelVersion,
1397
+ alreadyRegistered: false
1398
+ };
1399
+ } else {
1400
+ return {
1401
+ path: "bootstrap/providers.php",
1402
+ content: `<?php
1403
+
1404
+ return [
1405
+ App\\Providers\\AppServiceProvider::class,
1406
+ ${providerLine}
1407
+ ];
1408
+ `,
1409
+ laravelVersion,
1410
+ alreadyRegistered: false
1411
+ };
1412
+ }
1413
+ } else {
1414
+ if (existingContent) {
1415
+ const providersSectionRegex = /'providers'\s*=>\s*\[[\s\S]*?\n(\s*)\]/m;
1416
+ const match = existingContent.match(providersSectionRegex);
1417
+ if (match) {
1418
+ const providersStart = existingContent.indexOf("'providers'");
1419
+ if (providersStart === -1) {
1420
+ return null;
1421
+ }
1422
+ let depth = 0;
1423
+ let foundStart = false;
1424
+ let insertPos = -1;
1425
+ for (let i = providersStart; i < existingContent.length; i++) {
1426
+ const char = existingContent[i];
1427
+ if (char === "[") {
1428
+ foundStart = true;
1429
+ depth++;
1430
+ } else if (char === "]") {
1431
+ depth--;
1432
+ if (foundStart && depth === 0) {
1433
+ insertPos = i;
1434
+ break;
1435
+ }
1436
+ }
1437
+ }
1438
+ if (insertPos !== -1) {
1439
+ const beforeClose = existingContent.substring(0, insertPos);
1440
+ const lastNewline = beforeClose.lastIndexOf("\n");
1441
+ const content = existingContent.substring(0, lastNewline + 1) + providerLine + "\n" + existingContent.substring(lastNewline + 1);
1442
+ return {
1443
+ path: "config/app.php",
1444
+ content,
1445
+ laravelVersion,
1446
+ alreadyRegistered: false
1447
+ };
1448
+ }
1449
+ }
1450
+ return null;
1451
+ } else {
1452
+ return null;
1453
+ }
1454
+ }
1455
+ }
1456
+
1457
+ // src/factory/generator.ts
1458
+ function resolveOptions2(options) {
1459
+ return {
1460
+ modelNamespace: options?.modelNamespace ?? "App\\Models",
1461
+ factoryPath: options?.factoryPath ?? "database/factories",
1462
+ fakerLocale: options?.fakerLocale ?? "en_US"
1463
+ };
1464
+ }
1465
+ function getStubContent2() {
1466
+ return `<?php
1467
+
1468
+ namespace Database\\Factories;
1469
+
1470
+ use {{MODEL_NAMESPACE}}\\{{MODEL_NAME}};
1471
+ use Illuminate\\Database\\Eloquent\\Factories\\Factory;
1472
+ {{IMPORTS}}
1473
+
1474
+ /**
1475
+ * @extends Factory<{{MODEL_NAME}}>
1476
+ */
1477
+ class {{MODEL_NAME}}Factory extends Factory
1478
+ {
1479
+ protected $model = {{MODEL_NAME}}::class;
1480
+
1481
+ /**
1482
+ * Define the model's default state.
1483
+ *
1484
+ * @return array<string, mixed>
1485
+ */
1486
+ public function definition(): array
1487
+ {
1488
+ return [
1489
+ {{ATTRIBUTES}}
1490
+ ];
1491
+ }
1492
+ }
1493
+ `;
1494
+ }
1495
+ function generateFakeData(propertyName, property, schema, schemas) {
1496
+ const type = property.type;
1497
+ if (["deleted_at", "created_at", "updated_at"].includes(propertyName)) {
1498
+ return null;
1499
+ }
1500
+ if (type === "Association") {
1501
+ return null;
1502
+ }
1503
+ switch (type) {
1504
+ case "String":
1505
+ return generateStringFake(propertyName, property);
1506
+ case "Email":
1507
+ return `'${propertyName}' => fake()->unique()->safeEmail(),`;
1508
+ case "Password":
1509
+ return `'${propertyName}' => bcrypt('password'),`;
1510
+ case "Int":
1511
+ case "BigInt":
1512
+ return generateIntFake(propertyName, property);
1513
+ case "Float":
1514
+ case "Decimal":
1515
+ return `'${propertyName}' => fake()->randomFloat(2, 1, 10000),`;
1516
+ case "Boolean":
1517
+ return `'${propertyName}' => fake()->boolean(),`;
1518
+ case "Text":
1519
+ return `'${propertyName}' => fake()->paragraphs(3, true),`;
1520
+ case "LongText":
1521
+ return `'${propertyName}' => fake()->paragraphs(5, true),`;
1522
+ case "Date":
1523
+ return `'${propertyName}' => fake()->date(),`;
1524
+ case "Time":
1525
+ return `'${propertyName}' => fake()->time(),`;
1526
+ case "Timestamp":
1527
+ case "DateTime":
1528
+ return `'${propertyName}' => fake()->dateTime(),`;
1529
+ case "Json":
1530
+ return `'${propertyName}' => [],`;
1531
+ case "Enum":
1532
+ return generateEnumFake(propertyName, property);
1533
+ case "EnumRef":
1534
+ return generateEnumRefFake(propertyName, property, schemas);
1535
+ default:
1536
+ return `'${propertyName}' => fake()->sentence(),`;
1537
+ }
1538
+ }
1539
+ function generateStringFake(propertyName, property) {
1540
+ if (propertyName === "slug") {
1541
+ return `'${propertyName}' => fake()->unique()->slug(),`;
1542
+ }
1543
+ if (propertyName === "uuid" || propertyName === "uid") {
1544
+ return `'${propertyName}' => (string) \\Illuminate\\Support\\Str::uuid(),`;
1545
+ }
1546
+ if (propertyName.includes("email")) {
1547
+ return `'${propertyName}' => fake()->unique()->safeEmail(),`;
1548
+ }
1549
+ if (propertyName.includes("phone")) {
1550
+ return `'${propertyName}' => fake()->phoneNumber(),`;
1551
+ }
1552
+ if (propertyName.includes("image") || propertyName.includes("photo") || propertyName.includes("avatar")) {
1553
+ return `'${propertyName}' => fake()->imageUrl(),`;
1554
+ }
1555
+ if (propertyName.includes("url") || propertyName.includes("website")) {
1556
+ return `'${propertyName}' => fake()->url(),`;
1557
+ }
1558
+ if (propertyName.includes("path") || propertyName.includes("file")) {
1559
+ return `'${propertyName}' => 'uploads/' . fake()->uuid() . '.jpg',`;
1560
+ }
1561
+ if (propertyName === "name" || propertyName === "title") {
1562
+ return `'${propertyName}' => fake()->sentence(3),`;
1563
+ }
1564
+ if (propertyName.includes("name")) {
1565
+ return `'${propertyName}' => fake()->name(),`;
1566
+ }
1567
+ if (propertyName.includes("address")) {
1568
+ return `'${propertyName}' => fake()->address(),`;
1569
+ }
1570
+ if (propertyName.includes("city")) {
1571
+ return `'${propertyName}' => fake()->city(),`;
1572
+ }
1573
+ if (propertyName.includes("country")) {
1574
+ return `'${propertyName}' => fake()->country(),`;
1575
+ }
1576
+ if (propertyName.includes("zip") || propertyName.includes("postal")) {
1577
+ return `'${propertyName}' => fake()->postcode(),`;
1578
+ }
1579
+ if (propertyName.includes("color")) {
1580
+ return `'${propertyName}' => fake()->hexColor(),`;
1581
+ }
1582
+ if (propertyName.includes("token") || propertyName.includes("secret") || propertyName.includes("key")) {
1583
+ return `'${propertyName}' => \\Illuminate\\Support\\Str::random(32),`;
1584
+ }
1585
+ if (propertyName.includes("code")) {
1586
+ return `'${propertyName}' => fake()->unique()->regexify('[A-Z0-9]{8}'),`;
1587
+ }
1588
+ const length = property.length;
1589
+ if (length && length <= 50) {
1590
+ return `'${propertyName}' => fake()->words(3, true),`;
1591
+ }
1592
+ return `'${propertyName}' => fake()->sentence(),`;
1593
+ }
1594
+ function generateIntFake(propertyName, property) {
1595
+ if (propertyName.includes("count") || propertyName.includes("quantity")) {
1596
+ return `'${propertyName}' => fake()->numberBetween(0, 100),`;
1597
+ }
1598
+ if (propertyName.includes("price") || propertyName.includes("amount") || propertyName.includes("cost")) {
1599
+ return `'${propertyName}' => fake()->numberBetween(100, 10000),`;
1600
+ }
1601
+ if (propertyName.includes("order") || propertyName.includes("sort") || propertyName.includes("position")) {
1602
+ return `'${propertyName}' => fake()->numberBetween(1, 100),`;
1603
+ }
1604
+ if (propertyName.includes("age")) {
1605
+ return `'${propertyName}' => fake()->numberBetween(18, 80),`;
1606
+ }
1607
+ if (propertyName.includes("year")) {
1608
+ return `'${propertyName}' => fake()->year(),`;
1609
+ }
1610
+ return `'${propertyName}' => fake()->numberBetween(1, 1000),`;
1611
+ }
1612
+ function generateEnumFake(propertyName, property) {
1613
+ const enumValues = property.enum;
1614
+ if (!enumValues || enumValues.length === 0) {
1615
+ return `'${propertyName}' => null,`;
1616
+ }
1617
+ const values = enumValues.map((v) => typeof v === "string" ? v : v.value);
1618
+ const valuesStr = values.map((v) => `'${v}'`).join(", ");
1619
+ return `'${propertyName}' => fake()->randomElement([${valuesStr}]),`;
1620
+ }
1621
+ function generateEnumRefFake(propertyName, property, schemas) {
1622
+ const enumName = property.enum;
1623
+ if (!enumName) {
1624
+ return `'${propertyName}' => null,`;
1625
+ }
1626
+ const enumSchema = schemas[enumName];
1627
+ if (!enumSchema || enumSchema.kind !== "enum" || !enumSchema.values) {
1628
+ return `'${propertyName}' => null,`;
1629
+ }
1630
+ const valuesStr = enumSchema.values.map((v) => `'${v}'`).join(", ");
1631
+ return `'${propertyName}' => fake()->randomElement([${valuesStr}]),`;
1632
+ }
1633
+ function generateAssociationFake(propertyName, property, schema, schemas, modelNamespace) {
1634
+ if (property.type !== "Association") {
1635
+ return null;
1636
+ }
1637
+ const relation = property.relation;
1638
+ const target = property.target;
1639
+ if (relation !== "ManyToOne" || !target) {
1640
+ return null;
1641
+ }
1642
+ const foreignKey = `${toSnakeCase(propertyName)}_id`;
1643
+ const isNullable2 = property.nullable ?? false;
1644
+ const targetSchema = schemas[target];
1645
+ if (!targetSchema) {
1646
+ return null;
1647
+ }
1648
+ let fake;
1649
+ if (isNullable2) {
1650
+ fake = `'${foreignKey}' => ${target}::query()->inRandomOrder()->first()?->id,`;
1651
+ } else {
1652
+ fake = `'${foreignKey}' => ${target}::query()->inRandomOrder()->first()?->id ?? ${target}::factory()->create()->id,`;
1653
+ }
1654
+ let importStatement;
1655
+ if (target !== schema.name) {
1656
+ importStatement = `use ${modelNamespace}\\${target};`;
1657
+ }
1658
+ return { fake, import: importStatement };
1659
+ }
1660
+ function generateFactory(schema, schemas, options, stubContent) {
1661
+ if (schema.kind === "enum") {
1662
+ return null;
1663
+ }
1664
+ const modelName = toPascalCase(schema.name);
1665
+ const factoryName = `${modelName}Factory`;
1666
+ const attributes = [];
1667
+ const imports = [];
1668
+ if (schema.properties) {
1669
+ for (const [propName, prop] of Object.entries(schema.properties)) {
1670
+ if (prop.type === "Association") {
1671
+ const assocResult = generateAssociationFake(propName, prop, schema, schemas, options.modelNamespace);
1672
+ if (assocResult) {
1673
+ attributes.push(assocResult.fake);
1674
+ if (assocResult.import) {
1675
+ imports.push(assocResult.import);
1676
+ }
1677
+ }
1678
+ continue;
1679
+ }
1680
+ const fake = generateFakeData(propName, prop, schema, schemas);
1681
+ if (fake) {
1682
+ attributes.push(fake);
1683
+ }
1684
+ }
1685
+ }
1686
+ let content = stubContent;
1687
+ content = content.replace(/\{\{MODEL_NAMESPACE\}\}/g, options.modelNamespace);
1688
+ content = content.replace(/\{\{MODEL_NAME\}\}/g, modelName);
1689
+ const uniqueImports = [...new Set(imports)];
1690
+ const importsStr = uniqueImports.length > 0 ? "\n" + uniqueImports.join("\n") : "";
1691
+ content = content.replace(/\{\{IMPORTS\}\}/g, importsStr);
1692
+ const attributesStr = attributes.length > 0 ? attributes.map((a) => ` ${a}`).join("\n") : "";
1693
+ content = content.replace(/\{\{ATTRIBUTES\}\}/g, attributesStr);
1694
+ return {
1695
+ name: factoryName,
1696
+ schemaName: schema.name,
1697
+ path: `${options.factoryPath}/${factoryName}.php`,
1698
+ content,
1699
+ overwrite: false
1700
+ // Factories should not overwrite existing files
1701
+ };
1702
+ }
1703
+ function generateFactories(schemas, options) {
1704
+ const resolved = resolveOptions2(options);
1705
+ const stubContent = getStubContent2();
1706
+ const factories = [];
1707
+ for (const schema of Object.values(schemas)) {
1708
+ const factory = generateFactory(schema, schemas, resolved, stubContent);
1709
+ if (factory) {
1710
+ factories.push(factory);
1711
+ }
1712
+ }
1713
+ return factories;
1714
+ }
1715
+ function getFactoryPath(factory) {
1716
+ return factory.path;
1717
+ }
1266
1718
 
1267
1719
  // src/plugin.ts
1268
1720
  var LARAVEL_CONFIG_SCHEMA = {
@@ -1271,8 +1723,8 @@ var LARAVEL_CONFIG_SCHEMA = {
1271
1723
  key: "migrationsPath",
1272
1724
  type: "path",
1273
1725
  label: "Migrations Path",
1274
- description: "Directory for Laravel migration files",
1275
- default: "database/migrations",
1726
+ description: "Directory for Laravel migration files (loaded via OmnifyServiceProvider)",
1727
+ default: "database/migrations/omnify",
1276
1728
  group: "output"
1277
1729
  },
1278
1730
  {
@@ -1299,6 +1751,22 @@ var LARAVEL_CONFIG_SCHEMA = {
1299
1751
  default: true,
1300
1752
  group: "options"
1301
1753
  },
1754
+ {
1755
+ key: "factoriesPath",
1756
+ type: "path",
1757
+ label: "Factories Path",
1758
+ description: "Directory for Laravel factory files",
1759
+ default: "database/factories",
1760
+ group: "output"
1761
+ },
1762
+ {
1763
+ key: "generateFactories",
1764
+ type: "boolean",
1765
+ label: "Generate Factories",
1766
+ description: "Generate Laravel factory classes for testing",
1767
+ default: true,
1768
+ group: "options"
1769
+ },
1302
1770
  {
1303
1771
  key: "connection",
1304
1772
  type: "string",
@@ -1309,20 +1777,23 @@ var LARAVEL_CONFIG_SCHEMA = {
1309
1777
  }
1310
1778
  ]
1311
1779
  };
1312
- function resolveOptions2(options) {
1780
+ function resolveOptions3(options) {
1313
1781
  return {
1314
- migrationsPath: options?.migrationsPath ?? "database/migrations",
1782
+ migrationsPath: options?.migrationsPath ?? "database/migrations/omnify",
1315
1783
  modelsPath: options?.modelsPath ?? "app/Models",
1316
1784
  baseModelsPath: options?.baseModelsPath ?? "app/Models/OmnifyBase",
1317
1785
  modelNamespace: options?.modelNamespace ?? "App\\Models",
1318
1786
  baseModelNamespace: options?.baseModelNamespace ?? "App\\Models\\OmnifyBase",
1319
1787
  generateModels: options?.generateModels ?? true,
1788
+ factoriesPath: options?.factoriesPath ?? "database/factories",
1789
+ generateFactories: options?.generateFactories ?? true,
1790
+ fakerLocale: options?.fakerLocale ?? "en_US",
1320
1791
  connection: options?.connection,
1321
1792
  timestamp: options?.timestamp
1322
1793
  };
1323
1794
  }
1324
1795
  function laravelPlugin(options) {
1325
- const resolved = resolveOptions2(options);
1796
+ const resolved = resolveOptions3(options);
1326
1797
  const migrationGenerator = {
1327
1798
  name: "laravel-migrations",
1328
1799
  description: "Generate Laravel migration files",
@@ -1354,7 +1825,7 @@ function laravelPlugin(options) {
1354
1825
  baseModelPath: resolved.baseModelsPath
1355
1826
  };
1356
1827
  const models = generateModels(ctx.schemas, modelOptions);
1357
- return models.map((model) => ({
1828
+ const outputs = models.map((model) => ({
1358
1829
  path: getModelPath(model),
1359
1830
  content: model.content,
1360
1831
  type: "model",
@@ -1365,13 +1836,82 @@ function laravelPlugin(options) {
1365
1836
  schemaName: model.schemaName
1366
1837
  }
1367
1838
  }));
1839
+ const bootstrapProvidersPath = (0, import_node_path.join)(ctx.cwd, "bootstrap/providers.php");
1840
+ const configAppPath = (0, import_node_path.join)(ctx.cwd, "config/app.php");
1841
+ let existingContent = null;
1842
+ let laravelVersion;
1843
+ if ((0, import_node_fs.existsSync)(bootstrapProvidersPath)) {
1844
+ laravelVersion = "laravel11+";
1845
+ try {
1846
+ existingContent = (0, import_node_fs.readFileSync)(bootstrapProvidersPath, "utf-8");
1847
+ } catch {
1848
+ existingContent = null;
1849
+ }
1850
+ } else if ((0, import_node_fs.existsSync)(configAppPath)) {
1851
+ laravelVersion = "laravel10-";
1852
+ try {
1853
+ existingContent = (0, import_node_fs.readFileSync)(configAppPath, "utf-8");
1854
+ } catch {
1855
+ existingContent = null;
1856
+ }
1857
+ } else {
1858
+ laravelVersion = "laravel11+";
1859
+ }
1860
+ const registration = generateProviderRegistration(existingContent, laravelVersion);
1861
+ if (registration && !registration.alreadyRegistered) {
1862
+ outputs.push({
1863
+ path: registration.path,
1864
+ content: registration.content,
1865
+ type: "other",
1866
+ skipIfExists: false,
1867
+ // We want to modify the file
1868
+ metadata: {
1869
+ registrationType: "provider-registration",
1870
+ laravelVersion: registration.laravelVersion
1871
+ }
1872
+ });
1873
+ ctx.logger.info(`OmnifyServiceProvider will be registered in ${registration.path}`);
1874
+ } else if (registration?.alreadyRegistered) {
1875
+ ctx.logger.info("OmnifyServiceProvider is already registered");
1876
+ }
1877
+ return outputs;
1878
+ }
1879
+ };
1880
+ const factoryGenerator = {
1881
+ name: "laravel-factories",
1882
+ description: "Generate Laravel factory classes for testing",
1883
+ generate: async (ctx) => {
1884
+ const factoryOptions = {
1885
+ modelNamespace: resolved.modelNamespace,
1886
+ factoryPath: resolved.factoriesPath,
1887
+ fakerLocale: resolved.fakerLocale
1888
+ };
1889
+ const factories = generateFactories(ctx.schemas, factoryOptions);
1890
+ return factories.map((factory) => ({
1891
+ path: getFactoryPath(factory),
1892
+ content: factory.content,
1893
+ type: "factory",
1894
+ // Skip writing factories if they already exist (allow customization)
1895
+ skipIfExists: !factory.overwrite,
1896
+ metadata: {
1897
+ factoryName: factory.name,
1898
+ schemaName: factory.schemaName
1899
+ }
1900
+ }));
1368
1901
  }
1369
1902
  };
1903
+ const generators = [migrationGenerator];
1904
+ if (resolved.generateModels) {
1905
+ generators.push(modelGenerator);
1906
+ }
1907
+ if (resolved.generateFactories) {
1908
+ generators.push(factoryGenerator);
1909
+ }
1370
1910
  return {
1371
1911
  name: "@famgia/omnify-laravel",
1372
1912
  version: "0.0.14",
1373
1913
  configSchema: LARAVEL_CONFIG_SCHEMA,
1374
- generators: resolved.generateModels ? [migrationGenerator, modelGenerator] : [migrationGenerator]
1914
+ generators
1375
1915
  };
1376
1916
  }
1377
1917
  // Annotate the CommonJS export names for ESM import in node: