@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.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" });
@@ -359,6 +358,12 @@ function formatColumnMethod(column) {
359
358
  if (typeof arg === "string") {
360
359
  return `'${arg}'`;
361
360
  }
361
+ if (typeof arg === "boolean") {
362
+ return arg ? "true" : "false";
363
+ }
364
+ if (typeof arg === "number") {
365
+ return String(arg);
366
+ }
362
367
  return String(arg);
363
368
  }).join(", ");
364
369
  code += `->${modifier.method}(${modArgs})`;
@@ -466,6 +471,7 @@ function generatePivotTableBlueprint(pivot) {
466
471
  args: [pivot.targetColumn],
467
472
  modifiers: []
468
473
  });
474
+ columns.push(...generateTimestampColumns());
469
475
  foreignKeys.push({
470
476
  columns: [pivot.sourceColumn],
471
477
  references: "id",
@@ -1040,6 +1046,10 @@ function generateMigrationsFromChanges(changes, options = {}) {
1040
1046
  return migrations;
1041
1047
  }
1042
1048
 
1049
+ // src/plugin.ts
1050
+ var import_node_fs = require("fs");
1051
+ var import_node_path = require("path");
1052
+
1043
1053
  // src/utils.ts
1044
1054
  function toSnakeCase(str) {
1045
1055
  return str.replace(/([A-Z])/g, "_$1").replace(/^_/, "").toLowerCase();
@@ -1195,7 +1205,7 @@ function generateEntityBaseModel(schema, schemas, options, stubContent, authStub
1195
1205
  if (assoc.target) {
1196
1206
  imports.push(`use ${options.modelNamespace}\\${toPascalCase(assoc.target)};`);
1197
1207
  }
1198
- relations.push(generateRelation(propName, assoc, options));
1208
+ relations.push(generateRelation(propName, assoc, schema, schemas, options));
1199
1209
  if (assoc.relation === "ManyToOne" || assoc.relation === "OneToOne") {
1200
1210
  if (!assoc.mappedBy) {
1201
1211
  const fkName = toSnakeCase(propName) + "_id";
@@ -1252,7 +1262,22 @@ ${docProperties.join("\n")}
1252
1262
  schemaName: schema.name
1253
1263
  };
1254
1264
  }
1255
- function generateRelation(propName, assoc, options) {
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) {
1256
1281
  const methodName = toCamelCase(propName);
1257
1282
  const targetClass = assoc.target ? toPascalCase(assoc.target) : "";
1258
1283
  const fkName = toSnakeCase(propName) + "_id";
@@ -1282,14 +1307,28 @@ function generateRelation(propName, assoc, options) {
1282
1307
  {
1283
1308
  return $this->belongsTo(${targetClass}::class, '${fkName}');
1284
1309
  }`;
1285
- case "OneToMany":
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
+ }
1286
1324
  return ` /**
1287
1325
  * Get the ${propName} for this model.
1288
1326
  */
1289
1327
  public function ${methodName}(): HasMany
1290
1328
  {
1291
- return $this->hasMany(${targetClass}::class, '${toSnakeCase(assoc.inversedBy ?? propName)}_id');
1329
+ return $this->hasMany(${targetClass}::class, '${foreignKey}');
1292
1330
  }`;
1331
+ }
1293
1332
  case "ManyToMany": {
1294
1333
  const pivotTable = assoc.joinTable ?? `${toSnakeCase(propName)}_pivot`;
1295
1334
  return ` /**
@@ -1586,14 +1625,73 @@ class {{CLASS_NAME}} extends {{CLASS_NAME}}BaseModel
1586
1625
 
1587
1626
  // Add your custom methods here
1588
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
+ }
1589
1671
  `
1590
1672
  };
1591
1673
  return stubs[stubName] ?? "";
1592
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
+ }
1593
1690
  function generateModels(schemas, options) {
1594
1691
  const resolved = resolveOptions(options);
1595
1692
  const models = [];
1596
1693
  models.push(generateBaseModel(schemas, resolved, getStubContent("base-model")));
1694
+ models.push(generateServiceProvider(schemas, resolved, getStubContent("service-provider")));
1597
1695
  for (const schema of Object.values(schemas)) {
1598
1696
  if (schema.kind === "enum") {
1599
1697
  continue;
@@ -1612,6 +1710,355 @@ function generateModels(schemas, options) {
1612
1710
  function getModelPath(model) {
1613
1711
  return model.path;
1614
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
+ }
1615
2062
 
1616
2063
  // src/plugin.ts
1617
2064
  var LARAVEL_CONFIG_SCHEMA = {
@@ -1620,8 +2067,8 @@ var LARAVEL_CONFIG_SCHEMA = {
1620
2067
  key: "migrationsPath",
1621
2068
  type: "path",
1622
2069
  label: "Migrations Path",
1623
- description: "Directory for Laravel migration files",
1624
- default: "database/migrations",
2070
+ description: "Directory for Laravel migration files (loaded via OmnifyServiceProvider)",
2071
+ default: "database/migrations/omnify",
1625
2072
  group: "output"
1626
2073
  },
1627
2074
  {
@@ -1648,6 +2095,22 @@ var LARAVEL_CONFIG_SCHEMA = {
1648
2095
  default: true,
1649
2096
  group: "options"
1650
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
+ },
1651
2114
  {
1652
2115
  key: "connection",
1653
2116
  type: "string",
@@ -1658,20 +2121,23 @@ var LARAVEL_CONFIG_SCHEMA = {
1658
2121
  }
1659
2122
  ]
1660
2123
  };
1661
- function resolveOptions2(options) {
2124
+ function resolveOptions3(options) {
1662
2125
  return {
1663
- migrationsPath: options?.migrationsPath ?? "database/migrations",
2126
+ migrationsPath: options?.migrationsPath ?? "database/migrations/omnify",
1664
2127
  modelsPath: options?.modelsPath ?? "app/Models",
1665
2128
  baseModelsPath: options?.baseModelsPath ?? "app/Models/OmnifyBase",
1666
2129
  modelNamespace: options?.modelNamespace ?? "App\\Models",
1667
2130
  baseModelNamespace: options?.baseModelNamespace ?? "App\\Models\\OmnifyBase",
1668
2131
  generateModels: options?.generateModels ?? true,
2132
+ factoriesPath: options?.factoriesPath ?? "database/factories",
2133
+ generateFactories: options?.generateFactories ?? true,
2134
+ fakerLocale: options?.fakerLocale ?? "en_US",
1669
2135
  connection: options?.connection,
1670
2136
  timestamp: options?.timestamp
1671
2137
  };
1672
2138
  }
1673
2139
  function laravelPlugin(options) {
1674
- const resolved = resolveOptions2(options);
2140
+ const resolved = resolveOptions3(options);
1675
2141
  const migrationGenerator = {
1676
2142
  name: "laravel-migrations",
1677
2143
  description: "Generate Laravel migration files",
@@ -1703,7 +2169,7 @@ function laravelPlugin(options) {
1703
2169
  baseModelPath: resolved.baseModelsPath
1704
2170
  };
1705
2171
  const models = generateModels(ctx.schemas, modelOptions);
1706
- return models.map((model) => ({
2172
+ const outputs = models.map((model) => ({
1707
2173
  path: getModelPath(model),
1708
2174
  content: model.content,
1709
2175
  type: "model",
@@ -1714,13 +2180,82 @@ function laravelPlugin(options) {
1714
2180
  schemaName: model.schemaName
1715
2181
  }
1716
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
+ }));
1717
2245
  }
1718
2246
  };
2247
+ const generators = [migrationGenerator];
2248
+ if (resolved.generateModels) {
2249
+ generators.push(modelGenerator);
2250
+ }
2251
+ if (resolved.generateFactories) {
2252
+ generators.push(factoryGenerator);
2253
+ }
1719
2254
  return {
1720
2255
  name: "@famgia/omnify-laravel",
1721
2256
  version: "0.0.14",
1722
2257
  configSchema: LARAVEL_CONFIG_SCHEMA,
1723
- generators: resolved.generateModels ? [migrationGenerator, modelGenerator] : [migrationGenerator]
2258
+ generators
1724
2259
  };
1725
2260
  }
1726
2261
  // Annotate the CommonJS export names for ESM import in node: