@famgia/omnify-laravel 2.0.21 → 2.0.22

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.d.cts CHANGED
@@ -192,6 +192,17 @@ interface SchemaToBlueprintOptions {
192
192
  pluginEnums?: ReadonlyMap<string, PluginEnumDefinition>;
193
193
  /** Locale resolution options for displayName */
194
194
  locale?: LocaleResolutionOptions;
195
+ /**
196
+ * Set of FK constraints to exclude from this blueprint.
197
+ * Format: "schemaName.propertyName" (e.g., "Employee.department")
198
+ * Used for deferring circular FK dependencies to a separate migration.
199
+ */
200
+ excludeFKs?: Set<string>;
201
+ /**
202
+ * Current schema name (for FK exclusion matching).
203
+ * Set automatically when excludeFKs is provided.
204
+ */
205
+ schemaName?: string;
195
206
  }
196
207
  /**
197
208
  * Generates table blueprint from schema.
@@ -435,6 +446,8 @@ declare function generateProviderRegistration(existingContent: string | null, la
435
446
  interface FactoryGeneratorOptions {
436
447
  /** Model namespace */
437
448
  modelNamespace?: string;
449
+ /** Factory namespace (defaults to Database\Factories) */
450
+ factoryNamespace?: string;
438
451
  /** Factory output path */
439
452
  factoryPath?: string;
440
453
  /** Faker locale */
package/dist/index.d.ts CHANGED
@@ -192,6 +192,17 @@ interface SchemaToBlueprintOptions {
192
192
  pluginEnums?: ReadonlyMap<string, PluginEnumDefinition>;
193
193
  /** Locale resolution options for displayName */
194
194
  locale?: LocaleResolutionOptions;
195
+ /**
196
+ * Set of FK constraints to exclude from this blueprint.
197
+ * Format: "schemaName.propertyName" (e.g., "Employee.department")
198
+ * Used for deferring circular FK dependencies to a separate migration.
199
+ */
200
+ excludeFKs?: Set<string>;
201
+ /**
202
+ * Current schema name (for FK exclusion matching).
203
+ * Set automatically when excludeFKs is provided.
204
+ */
205
+ schemaName?: string;
195
206
  }
196
207
  /**
197
208
  * Generates table blueprint from schema.
@@ -435,6 +446,8 @@ declare function generateProviderRegistration(existingContent: string | null, la
435
446
  interface FactoryGeneratorOptions {
436
447
  /** Model namespace */
437
448
  modelNamespace?: string;
449
+ /** Factory namespace (defaults to Database\Factories) */
450
+ factoryNamespace?: string;
438
451
  /** Factory output path */
439
452
  factoryPath?: string;
440
453
  /** Faker locale */
package/dist/index.js CHANGED
@@ -29,7 +29,7 @@ import {
29
29
  schemaToBlueprint,
30
30
  toColumnName,
31
31
  toTableName
32
- } from "./chunk-U3NDJ6S6.js";
32
+ } from "./chunk-RSC5ATSV.js";
33
33
 
34
34
  // src/ai-guides/generator.ts
35
35
  import { existsSync, readdirSync, readFileSync, writeFileSync } from "fs";
package/dist/plugin.cjs CHANGED
@@ -455,7 +455,8 @@ function expandCompoundType(propName, property, customTypes, options = {}) {
455
455
  return expanded;
456
456
  }
457
457
  function schemaToBlueprint(schema, allSchemas, options = {}) {
458
- const { customTypes = /* @__PURE__ */ new Map(), pluginEnums = /* @__PURE__ */ new Map(), locale } = options;
458
+ const { customTypes = /* @__PURE__ */ new Map(), pluginEnums = /* @__PURE__ */ new Map(), locale, excludeFKs } = options;
459
+ const schemaName = options.schemaName ?? schema.name;
459
460
  const compoundOptions = { locale, pluginEnums };
460
461
  const tableName = schema.options?.tableName ?? toTableName(schema.name);
461
462
  const columns = [];
@@ -525,7 +526,10 @@ function schemaToBlueprint(schema, allSchemas, options = {}) {
525
526
  if (!explicitColumnNames.has(fkResult.column.name)) {
526
527
  columns.push(fkResult.column);
527
528
  }
528
- foreignKeys.push(fkResult.foreignKey);
529
+ const fkKey = `${schemaName}.${propName}`;
530
+ if (!excludeFKs?.has(fkKey)) {
531
+ foreignKeys.push(fkResult.foreignKey);
532
+ }
529
533
  indexes.push(fkResult.index);
530
534
  }
531
535
  const polyResult = generatePolymorphicColumns(propName, property, allSchemas);
@@ -1021,6 +1025,143 @@ function extractDependencies(schema) {
1021
1025
  }
1022
1026
  return deps;
1023
1027
  }
1028
+ function detectCircularDependencies(schemas) {
1029
+ const circularFKs = /* @__PURE__ */ new Set();
1030
+ const graph = /* @__PURE__ */ new Map();
1031
+ for (const schema of Object.values(schemas)) {
1032
+ if (schema.kind === "enum" || !schema.properties) continue;
1033
+ const refs = /* @__PURE__ */ new Map();
1034
+ for (const [propName, property] of Object.entries(schema.properties)) {
1035
+ if (property.type !== "Association") continue;
1036
+ const assocProp = property;
1037
+ if ((assocProp.relation === "ManyToOne" || assocProp.relation === "OneToOne") && !assocProp.mappedBy && assocProp.target && assocProp.target !== schema.name) {
1038
+ refs.set(assocProp.target, propName);
1039
+ }
1040
+ }
1041
+ if (refs.size > 0) {
1042
+ graph.set(schema.name, refs);
1043
+ }
1044
+ }
1045
+ for (const [schemaA, refsA] of graph) {
1046
+ for (const [targetB, propNameA] of refsA) {
1047
+ if (targetB === schemaA) continue;
1048
+ const refsB = graph.get(targetB);
1049
+ if (refsB && refsB.has(schemaA)) {
1050
+ if (schemaA > targetB) {
1051
+ circularFKs.add(`${schemaA}.${propNameA}`);
1052
+ } else {
1053
+ const propNameB = refsB.get(schemaA);
1054
+ circularFKs.add(`${targetB}.${propNameB}`);
1055
+ }
1056
+ }
1057
+ }
1058
+ }
1059
+ return circularFKs;
1060
+ }
1061
+ function extractDeferredFKs(schemas, circularFKs) {
1062
+ const deferred = [];
1063
+ for (const schema of Object.values(schemas)) {
1064
+ if (schema.kind === "enum" || !schema.properties) continue;
1065
+ const tableName = schema.options?.tableName ?? toTableName(schema.name);
1066
+ for (const [propName, property] of Object.entries(schema.properties)) {
1067
+ const key = `${schema.name}.${propName}`;
1068
+ if (!circularFKs.has(key)) continue;
1069
+ const assocProp = property;
1070
+ if (!assocProp.target) continue;
1071
+ const targetSchema = schemas[assocProp.target];
1072
+ const targetTable = targetSchema?.options?.tableName ?? toTableName(assocProp.target);
1073
+ const columnName = toColumnName(propName) + "_id";
1074
+ deferred.push({
1075
+ tableName,
1076
+ columnName,
1077
+ targetTable,
1078
+ onDelete: assocProp.onDelete,
1079
+ onUpdate: assocProp.onUpdate
1080
+ });
1081
+ }
1082
+ }
1083
+ return deferred;
1084
+ }
1085
+ function generateDeferredFKMigration(deferredFKs, options = {}) {
1086
+ if (deferredFKs.length === 0) return null;
1087
+ const timestamp = options.timestamp ?? generateTimestamp();
1088
+ const fileName = `${timestamp}_add_circular_foreign_keys.php`;
1089
+ const connection = options.connection ? `
1090
+ protected $connection = '${options.connection}';
1091
+ ` : "";
1092
+ const byTable = /* @__PURE__ */ new Map();
1093
+ for (const fk of deferredFKs) {
1094
+ const existing = byTable.get(fk.tableName) ?? [];
1095
+ existing.push(fk);
1096
+ byTable.set(fk.tableName, existing);
1097
+ }
1098
+ const upStatements = [];
1099
+ const downStatements = [];
1100
+ for (const [tableName, fks] of byTable) {
1101
+ const fkLines = fks.map((fk) => {
1102
+ let line = ` $table->foreign('${fk.columnName}')->references('id')->on('${fk.targetTable}')`;
1103
+ if (fk.onDelete) line += `->onDelete('${fk.onDelete}')`;
1104
+ if (fk.onUpdate) line += `->onUpdate('${fk.onUpdate}')`;
1105
+ return line + ";";
1106
+ });
1107
+ upStatements.push(` Schema::table('${tableName}', function (Blueprint $table) {
1108
+ ${fkLines.join("\n")}
1109
+ });`);
1110
+ const dropLines = fks.map(
1111
+ (fk) => ` $table->dropForeign(['${fk.columnName}']);`
1112
+ );
1113
+ downStatements.push(` Schema::table('${tableName}', function (Blueprint $table) {
1114
+ ${dropLines.join("\n")}
1115
+ });`);
1116
+ }
1117
+ const content = `<?php
1118
+
1119
+ /**
1120
+ * \u26A0\uFE0F DO NOT EDIT THIS FILE! \u26A0\uFE0F
1121
+ * \u3053\u306E\u30D5\u30A1\u30A4\u30EB\u3092\u7DE8\u96C6\u3057\u306A\u3044\u3067\u304F\u3060\u3055\u3044\uFF01
1122
+ * KH\xD4NG \u0110\u01AF\u1EE2C S\u1EECA FILE N\xC0Y!
1123
+ *
1124
+ * This file is AUTO-GENERATED by Omnify.
1125
+ * Any manual changes will be OVERWRITTEN on next generation.
1126
+ *
1127
+ * This migration adds foreign key constraints for circular dependencies.
1128
+ * These constraints are deferred to run after all tables are created.
1129
+ *
1130
+ * @generated by @famgia/omnify-laravel
1131
+ */
1132
+
1133
+ use Illuminate\\Database\\Migrations\\Migration;
1134
+ use Illuminate\\Database\\Schema\\Blueprint;
1135
+ use Illuminate\\Support\\Facades\\Schema;
1136
+
1137
+ return new class extends Migration
1138
+ {${connection}
1139
+ /**
1140
+ * Run the migrations.
1141
+ */
1142
+ public function up(): void
1143
+ {
1144
+ ${upStatements.join("\n\n")}
1145
+ }
1146
+
1147
+ /**
1148
+ * Reverse the migrations.
1149
+ */
1150
+ public function down(): void
1151
+ {
1152
+ ${downStatements.join("\n\n")}
1153
+ }
1154
+ };
1155
+ `;
1156
+ const tables = [...byTable.keys()];
1157
+ return {
1158
+ fileName,
1159
+ className: "AddCircularForeignKeys",
1160
+ content,
1161
+ tables,
1162
+ type: "alter"
1163
+ };
1164
+ }
1024
1165
  function topologicalSort(schemas) {
1025
1166
  const schemaList = Object.values(schemas).filter((s) => s.kind !== "enum");
1026
1167
  const sorted = [];
@@ -1054,6 +1195,7 @@ function generateMigrations(schemas, options = {}) {
1054
1195
  const migrations = [];
1055
1196
  const pivotTablesGenerated = /* @__PURE__ */ new Set();
1056
1197
  let timestampOffset = 0;
1198
+ const circularFKs = detectCircularDependencies(schemas);
1057
1199
  const sortedSchemas = topologicalSort(schemas);
1058
1200
  const baseTimestamp = options.timestamp ?? generateTimestamp();
1059
1201
  for (const schema of sortedSchemas) {
@@ -1062,7 +1204,9 @@ function generateMigrations(schemas, options = {}) {
1062
1204
  const blueprint = schemaToBlueprint(schema, schemas, {
1063
1205
  customTypes: options.customTypes,
1064
1206
  pluginEnums: options.pluginEnums,
1065
- locale: options.locale
1207
+ locale: options.locale,
1208
+ excludeFKs: circularFKs,
1209
+ schemaName: schema.name
1066
1210
  });
1067
1211
  const migration = generateCreateMigration(blueprint, {
1068
1212
  ...options,
@@ -1102,6 +1246,19 @@ function generateMigrations(schemas, options = {}) {
1102
1246
  });
1103
1247
  }
1104
1248
  }
1249
+ if (circularFKs.size > 0) {
1250
+ const deferredFKs = extractDeferredFKs(schemas, circularFKs);
1251
+ if (deferredFKs.length > 0) {
1252
+ const offsetTimestamp = incrementTimestamp(baseTimestamp, timestampOffset);
1253
+ const deferredMigration = generateDeferredFKMigration(deferredFKs, {
1254
+ ...options,
1255
+ timestamp: offsetTimestamp
1256
+ });
1257
+ if (deferredMigration) {
1258
+ migrations.push(deferredMigration);
1259
+ }
1260
+ }
1261
+ }
1105
1262
  return migrations;
1106
1263
  }
1107
1264
  function incrementTimestamp(timestamp, seconds) {
@@ -1181,8 +1338,16 @@ function generateTimestamp2() {
1181
1338
  const seconds = String(now.getSeconds()).padStart(2, "0");
1182
1339
  return `${year}_${month}_${day}_${hours}${minutes}${seconds}`;
1183
1340
  }
1341
+ function shouldSkipAssociationColumn(prop) {
1342
+ if (prop.type !== "Association") return false;
1343
+ if (isAssociationWithFkColumn(prop)) return false;
1344
+ return true;
1345
+ }
1184
1346
  function formatAddColumn(columnName, prop) {
1185
1347
  const lines = [];
1348
+ if (shouldSkipAssociationColumn(prop)) {
1349
+ return lines;
1350
+ }
1186
1351
  if (isAssociationWithFkColumn(prop)) {
1187
1352
  const fkColumn = getAssociationFkColumnName(columnName);
1188
1353
  let code2 = `$table->unsignedBigInteger('${fkColumn}')`;
@@ -1222,6 +1387,9 @@ function formatAddColumn(columnName, prop) {
1222
1387
  }
1223
1388
  function formatDropColumn(columnName, prop) {
1224
1389
  const lines = [];
1390
+ if (prop && shouldSkipAssociationColumn(prop)) {
1391
+ return lines;
1392
+ }
1225
1393
  if (prop && isAssociationWithFkColumn(prop)) {
1226
1394
  const fkColumn = getAssociationFkColumnName(columnName);
1227
1395
  lines.push(`$table->dropForeign(['${fkColumn}']);`);
@@ -2871,9 +3039,26 @@ ${providerLine}
2871
3039
  }
2872
3040
 
2873
3041
  // src/factory/generator.ts
3042
+ function deriveFactoryNamespace(modelNamespace) {
3043
+ if (modelNamespace === "App\\Models") {
3044
+ return "Database\\Factories";
3045
+ }
3046
+ if (modelNamespace.endsWith("\\Models")) {
3047
+ const base = modelNamespace.slice(0, -7);
3048
+ return `${base}\\Database\\Factories`;
3049
+ }
3050
+ const parts = modelNamespace.split("\\");
3051
+ if (parts.length > 1 && parts[parts.length - 1] === "Models") {
3052
+ parts.pop();
3053
+ return [...parts, "Database", "Factories"].join("\\");
3054
+ }
3055
+ return "Database\\Factories";
3056
+ }
2874
3057
  function resolveOptions2(options) {
3058
+ const modelNamespace = options?.modelNamespace ?? "App\\Models";
2875
3059
  return {
2876
- modelNamespace: options?.modelNamespace ?? "App\\Models",
3060
+ modelNamespace,
3061
+ factoryNamespace: options?.factoryNamespace ?? deriveFactoryNamespace(modelNamespace),
2877
3062
  factoryPath: options?.factoryPath ?? "database/factories",
2878
3063
  fakerLocale: options?.fakerLocale ?? "en_US",
2879
3064
  customTypes: options?.customTypes ?? /* @__PURE__ */ new Map(),
@@ -2886,16 +3071,18 @@ function resolveSchemaOptions2(schema, globalOptions) {
2886
3071
  return globalOptions;
2887
3072
  }
2888
3073
  const base = pkgOutput.base;
3074
+ const modelNamespace = pkgOutput.modelsNamespace;
2889
3075
  return {
2890
3076
  ...globalOptions,
2891
- modelNamespace: pkgOutput.modelsNamespace,
3077
+ modelNamespace,
3078
+ factoryNamespace: pkgOutput.factoriesNamespace ?? deriveFactoryNamespace(modelNamespace),
2892
3079
  factoryPath: `${base}/${pkgOutput.factoriesPath ?? "database/factories"}`
2893
3080
  };
2894
3081
  }
2895
3082
  function getStubContent2() {
2896
3083
  return `<?php
2897
3084
 
2898
- namespace Database\\Factories;
3085
+ namespace {{FACTORY_NAMESPACE}};
2899
3086
 
2900
3087
  use {{MODEL_NAMESPACE}}\\{{MODEL_NAME}};
2901
3088
  use Illuminate\\Database\\Eloquent\\Factories\\Factory;
@@ -3192,6 +3379,7 @@ function generateFactory(schema, schemas, options, stubContent) {
3192
3379
  }
3193
3380
  }
3194
3381
  let content = stubContent;
3382
+ content = content.replace(/\{\{FACTORY_NAMESPACE\}\}/g, options.factoryNamespace);
3195
3383
  content = content.replace(/\{\{MODEL_NAMESPACE\}\}/g, options.modelNamespace);
3196
3384
  content = content.replace(/\{\{MODEL_NAME\}\}/g, modelName);
3197
3385
  const uniqueImports = [...new Set(imports)];
@@ -4813,9 +5001,10 @@ function laravelPlugin(options) {
4813
5001
  name: "laravel-migrations",
4814
5002
  description: "Generate Laravel migration files",
4815
5003
  generate: async (ctx) => {
5004
+ const baseTimestamp = resolved.timestamp ?? generateTimestamp();
4816
5005
  const migrationOptions = {
4817
5006
  connection: resolved.connection,
4818
- timestamp: resolved.timestamp,
5007
+ timestamp: baseTimestamp,
4819
5008
  customTypes: ctx.customTypes,
4820
5009
  pluginEnums: ctx.pluginEnums
4821
5010
  };
@@ -4837,6 +5026,7 @@ function laravelPlugin(options) {
4837
5026
  if (ctx.changes.length === 0) {
4838
5027
  return outputs;
4839
5028
  }
5029
+ let timestampOffset = 0;
4840
5030
  const addedSchemaNames = new Set(
4841
5031
  ctx.changes.filter((c) => c.changeType === "added").map((c) => c.schemaName)
4842
5032
  );
@@ -4861,15 +5051,17 @@ function laravelPlugin(options) {
4861
5051
  schemaName: migration.schemaName
4862
5052
  }
4863
5053
  });
5054
+ timestampOffset++;
4864
5055
  }
4865
5056
  }
4866
5057
  const alterChanges = ctx.changes.filter(
4867
5058
  (c) => c.changeType === "modified" || c.changeType === "removed"
4868
5059
  );
4869
5060
  if (alterChanges.length > 0) {
5061
+ const alterTimestamp = timestampOffset > 0 ? incrementTimestamp(baseTimestamp, timestampOffset) : baseTimestamp;
4870
5062
  const alterMigrations = generateMigrationsFromChanges(
4871
5063
  alterChanges,
4872
- migrationOptions
5064
+ { ...migrationOptions, timestamp: alterTimestamp }
4873
5065
  );
4874
5066
  for (const migration of alterMigrations) {
4875
5067
  outputs.push({