@famgia/omnify-laravel 0.0.14 → 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.js CHANGED
@@ -19,7 +19,7 @@ import {
19
19
  schemaToBlueprint,
20
20
  toColumnName,
21
21
  toTableName
22
- } from "./chunk-UVF7W2J2.js";
22
+ } from "./chunk-REDFZUQY.js";
23
23
  export {
24
24
  formatColumnMethod,
25
25
  formatForeignKey,
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" });
@@ -341,6 +342,12 @@ function formatColumnMethod(column) {
341
342
  if (typeof arg === "string") {
342
343
  return `'${arg}'`;
343
344
  }
345
+ if (typeof arg === "boolean") {
346
+ return arg ? "true" : "false";
347
+ }
348
+ if (typeof arg === "number") {
349
+ return String(arg);
350
+ }
344
351
  return String(arg);
345
352
  }).join(", ");
346
353
  code += `->${modifier.method}(${modArgs})`;
@@ -448,6 +455,7 @@ function generatePivotTableBlueprint(pivot) {
448
455
  args: [pivot.targetColumn],
449
456
  modifiers: []
450
457
  });
458
+ columns.push(...generateTimestampColumns());
451
459
  foreignKeys.push({
452
460
  columns: [pivot.sourceColumn],
453
461
  references: "id",
@@ -846,7 +854,7 @@ function generateEntityBaseModel(schema, schemas, options, stubContent, authStub
846
854
  if (assoc.target) {
847
855
  imports.push(`use ${options.modelNamespace}\\${toPascalCase(assoc.target)};`);
848
856
  }
849
- relations.push(generateRelation(propName, assoc, options));
857
+ relations.push(generateRelation(propName, assoc, schema, schemas, options));
850
858
  if (assoc.relation === "ManyToOne" || assoc.relation === "OneToOne") {
851
859
  if (!assoc.mappedBy) {
852
860
  const fkName = toSnakeCase(propName) + "_id";
@@ -903,7 +911,22 @@ ${docProperties.join("\n")}
903
911
  schemaName: schema.name
904
912
  };
905
913
  }
906
- function generateRelation(propName, assoc, options) {
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) {
907
930
  const methodName = toCamelCase(propName);
908
931
  const targetClass = assoc.target ? toPascalCase(assoc.target) : "";
909
932
  const fkName = toSnakeCase(propName) + "_id";
@@ -933,14 +956,28 @@ function generateRelation(propName, assoc, options) {
933
956
  {
934
957
  return $this->belongsTo(${targetClass}::class, '${fkName}');
935
958
  }`;
936
- case "OneToMany":
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
+ }
937
973
  return ` /**
938
974
  * Get the ${propName} for this model.
939
975
  */
940
976
  public function ${methodName}(): HasMany
941
977
  {
942
- return $this->hasMany(${targetClass}::class, '${toSnakeCase(assoc.inversedBy ?? propName)}_id');
978
+ return $this->hasMany(${targetClass}::class, '${foreignKey}');
943
979
  }`;
980
+ }
944
981
  case "ManyToMany": {
945
982
  const pivotTable = assoc.joinTable ?? `${toSnakeCase(propName)}_pivot`;
946
983
  return ` /**
@@ -1237,14 +1274,73 @@ class {{CLASS_NAME}} extends {{CLASS_NAME}}BaseModel
1237
1274
 
1238
1275
  // Add your custom methods here
1239
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
+ }
1240
1320
  `
1241
1321
  };
1242
1322
  return stubs[stubName] ?? "";
1243
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
+ }
1244
1339
  function generateModels(schemas, options) {
1245
1340
  const resolved = resolveOptions(options);
1246
1341
  const models = [];
1247
1342
  models.push(generateBaseModel(schemas, resolved, getStubContent("base-model")));
1343
+ models.push(generateServiceProvider(schemas, resolved, getStubContent("service-provider")));
1248
1344
  for (const schema of Object.values(schemas)) {
1249
1345
  if (schema.kind === "enum") {
1250
1346
  continue;
@@ -1263,6 +1359,355 @@ function generateModels(schemas, options) {
1263
1359
  function getModelPath(model) {
1264
1360
  return model.path;
1265
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
+ }
1266
1711
 
1267
1712
  // src/plugin.ts
1268
1713
  var LARAVEL_CONFIG_SCHEMA = {
@@ -1271,8 +1716,8 @@ var LARAVEL_CONFIG_SCHEMA = {
1271
1716
  key: "migrationsPath",
1272
1717
  type: "path",
1273
1718
  label: "Migrations Path",
1274
- description: "Directory for Laravel migration files",
1275
- default: "database/migrations",
1719
+ description: "Directory for Laravel migration files (loaded via OmnifyServiceProvider)",
1720
+ default: "database/migrations/omnify",
1276
1721
  group: "output"
1277
1722
  },
1278
1723
  {
@@ -1299,6 +1744,22 @@ var LARAVEL_CONFIG_SCHEMA = {
1299
1744
  default: true,
1300
1745
  group: "options"
1301
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
+ },
1302
1763
  {
1303
1764
  key: "connection",
1304
1765
  type: "string",
@@ -1309,20 +1770,23 @@ var LARAVEL_CONFIG_SCHEMA = {
1309
1770
  }
1310
1771
  ]
1311
1772
  };
1312
- function resolveOptions2(options) {
1773
+ function resolveOptions3(options) {
1313
1774
  return {
1314
- migrationsPath: options?.migrationsPath ?? "database/migrations",
1775
+ migrationsPath: options?.migrationsPath ?? "database/migrations/omnify",
1315
1776
  modelsPath: options?.modelsPath ?? "app/Models",
1316
1777
  baseModelsPath: options?.baseModelsPath ?? "app/Models/OmnifyBase",
1317
1778
  modelNamespace: options?.modelNamespace ?? "App\\Models",
1318
1779
  baseModelNamespace: options?.baseModelNamespace ?? "App\\Models\\OmnifyBase",
1319
1780
  generateModels: options?.generateModels ?? true,
1781
+ factoriesPath: options?.factoriesPath ?? "database/factories",
1782
+ generateFactories: options?.generateFactories ?? true,
1783
+ fakerLocale: options?.fakerLocale ?? "en_US",
1320
1784
  connection: options?.connection,
1321
1785
  timestamp: options?.timestamp
1322
1786
  };
1323
1787
  }
1324
1788
  function laravelPlugin(options) {
1325
- const resolved = resolveOptions2(options);
1789
+ const resolved = resolveOptions3(options);
1326
1790
  const migrationGenerator = {
1327
1791
  name: "laravel-migrations",
1328
1792
  description: "Generate Laravel migration files",
@@ -1354,7 +1818,7 @@ function laravelPlugin(options) {
1354
1818
  baseModelPath: resolved.baseModelsPath
1355
1819
  };
1356
1820
  const models = generateModels(ctx.schemas, modelOptions);
1357
- return models.map((model) => ({
1821
+ const outputs = models.map((model) => ({
1358
1822
  path: getModelPath(model),
1359
1823
  content: model.content,
1360
1824
  type: "model",
@@ -1365,13 +1829,82 @@ function laravelPlugin(options) {
1365
1829
  schemaName: model.schemaName
1366
1830
  }
1367
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
+ }));
1368
1894
  }
1369
1895
  };
1896
+ const generators = [migrationGenerator];
1897
+ if (resolved.generateModels) {
1898
+ generators.push(modelGenerator);
1899
+ }
1900
+ if (resolved.generateFactories) {
1901
+ generators.push(factoryGenerator);
1902
+ }
1370
1903
  return {
1371
1904
  name: "@famgia/omnify-laravel",
1372
1905
  version: "0.0.14",
1373
1906
  configSchema: LARAVEL_CONFIG_SCHEMA,
1374
- generators: resolved.generateModels ? [migrationGenerator, modelGenerator] : [migrationGenerator]
1907
+ generators
1375
1908
  };
1376
1909
  }
1377
1910
  // Annotate the CommonJS export names for ESM import in node: