@famgia/omnify-cli 0.0.25 → 0.0.27

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
@@ -663,8 +663,8 @@ function registerDiffCommand(program) {
663
663
  }
664
664
 
665
665
  // src/commands/generate.ts
666
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
667
- import { resolve as resolve5, dirname as dirname4 } from "path";
666
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
667
+ import { resolve as resolve6, dirname as dirname4 } from "path";
668
668
  import {
669
669
  loadSchemas as loadSchemas3,
670
670
  validateSchemas as validateSchemas3,
@@ -687,6 +687,481 @@ import {
687
687
  getModelPath
688
688
  } from "@famgia/omnify-laravel";
689
689
  import { generateTypeScript } from "@famgia/omnify-typescript";
690
+
691
+ // src/guides/index.ts
692
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
693
+ import { resolve as resolve5 } from "path";
694
+ var CLAUDE_MD = `## Omnify
695
+
696
+ This project uses Omnify for schema-driven code generation.
697
+
698
+ **Documentation**: \`.claude/omnify/\`
699
+ - \`schema-guide.md\` - Schema format and property types
700
+ - \`laravel-guide.md\` - Laravel generator (if installed)
701
+ - \`typescript-guide.md\` - TypeScript generator (if installed)
702
+ - \`japan-guide.md\` - Japan plugin types (if installed)
703
+
704
+ **Commands**:
705
+ - \`npx omnify generate\` - Generate code from schemas
706
+ - \`npx omnify validate\` - Validate schemas
707
+ `;
708
+ var SCHEMA_GUIDE = `# Omnify Schema Guide
709
+
710
+ ## Schema File Format
711
+
712
+ Schemas are YAML files defining data models. Each file represents one entity.
713
+
714
+ \`\`\`yaml
715
+ name: User
716
+ displayName:
717
+ ja: \u30E6\u30FC\u30B6\u30FC
718
+ en: User
719
+ kind: object
720
+
721
+ properties:
722
+ email:
723
+ type: Email
724
+ unique: true
725
+ name:
726
+ type: String
727
+ bio:
728
+ type: Text
729
+ nullable: true
730
+
731
+ options:
732
+ timestamps: true
733
+ softDelete: true
734
+ \`\`\`
735
+
736
+ ## Property Types
737
+
738
+ ### String Types
739
+ - \`String\` - VARCHAR(255)
740
+ - \`Text\` - TEXT
741
+ - \`LongText\` - LONGTEXT
742
+ - \`Email\` - VARCHAR(255) for email addresses
743
+ - \`Password\` - VARCHAR(255), hidden in serialization
744
+
745
+ ### Numeric Types
746
+ - \`Int\` - INTEGER
747
+ - \`BigInt\` - BIGINT
748
+ - \`TinyInt\` - TINYINT
749
+ - \`Float\` - DOUBLE
750
+ - \`Decimal\` - DECIMAL(precision, scale)
751
+
752
+ ### Date/Time Types
753
+ - \`Date\` - DATE
754
+ - \`Time\` - TIME
755
+ - \`DateTime\` - DATETIME
756
+ - \`Timestamp\` - TIMESTAMP
757
+
758
+ ### Other Types
759
+ - \`Boolean\` - BOOLEAN
760
+ - \`Json\` - JSON
761
+ - \`Enum\` - ENUM with values
762
+ - \`EnumRef\` - Reference to enum schema
763
+
764
+ ## Property Options
765
+
766
+ \`\`\`yaml
767
+ propertyName:
768
+ type: String
769
+ nullable: true # Can be NULL
770
+ unique: true # Unique constraint
771
+ default: "value" # Default value
772
+ length: 100 # VARCHAR length
773
+ hidden: true # Hide from JSON output
774
+ fillable: false # Exclude from mass assignment
775
+ \`\`\`
776
+
777
+ ## Associations
778
+
779
+ \`\`\`yaml
780
+ # Many-to-One (belongsTo)
781
+ author:
782
+ type: Association
783
+ relation: ManyToOne
784
+ target: User
785
+ onDelete: CASCADE
786
+
787
+ # One-to-Many (hasMany)
788
+ posts:
789
+ type: Association
790
+ relation: OneToMany
791
+ target: Post
792
+
793
+ # Many-to-Many (belongsToMany)
794
+ tags:
795
+ type: Association
796
+ relation: ManyToMany
797
+ target: Tag
798
+ joinTable: post_tags
799
+
800
+ # Polymorphic
801
+ commentable:
802
+ type: Association
803
+ relation: MorphTo
804
+ \`\`\`
805
+
806
+ ## Indexes
807
+
808
+ \`\`\`yaml
809
+ indexes:
810
+ - columns: [status, published_at]
811
+ - columns: [email]
812
+ unique: true
813
+ \`\`\`
814
+
815
+ ## Schema Options
816
+
817
+ \`\`\`yaml
818
+ options:
819
+ timestamps: true # Add created_at, updated_at
820
+ softDelete: true # Add deleted_at
821
+ idType: BigInt # Primary key type (Int, BigInt, Uuid)
822
+ \`\`\`
823
+ `;
824
+ var LARAVEL_GUIDE = `# Laravel Generator Guide
825
+
826
+ ## Generated Files
827
+
828
+ ### Migrations
829
+ Located in \`database/migrations/omnify/\`
830
+ - Auto-generated from schema changes
831
+ - Handles column additions, modifications, removals
832
+ - Preserves manual migrations outside omnify folder
833
+
834
+ ### Models
835
+ Two-tier model structure:
836
+ - \`app/Models/OmnifyBase/*BaseModel.php\` - Auto-generated, DO NOT EDIT
837
+ - \`app/Models/*.php\` - User models, extend base models, safe to customize
838
+
839
+ ### Factories
840
+ Located in \`database/factories/\`
841
+ - Generated once, safe to customize
842
+ - Uses appropriate Faker methods for each type
843
+
844
+ ## Model Features
845
+
846
+ ### Fillable
847
+ All schema properties are mass-assignable by default.
848
+ Use \`fillable: false\` to exclude.
849
+
850
+ ### Hidden
851
+ Use \`hidden: true\` to exclude from JSON/array output.
852
+
853
+ \`\`\`yaml
854
+ password:
855
+ type: Password
856
+ hidden: true
857
+ \`\`\`
858
+
859
+ ### Casts
860
+ Auto-generated based on property types:
861
+ - \`Boolean\` \u2192 \`'boolean'\`
862
+ - \`Json\` \u2192 \`'array'\`
863
+ - \`Timestamp\` \u2192 \`'datetime'\`
864
+
865
+ ### Relationships
866
+ Generated from Association properties:
867
+ - \`ManyToOne\` \u2192 \`belongsTo()\`
868
+ - \`OneToMany\` \u2192 \`hasMany()\`
869
+ - \`ManyToMany\` \u2192 \`belongsToMany()\`
870
+ - \`MorphTo\` \u2192 \`morphTo()\`
871
+ - \`MorphMany\` \u2192 \`morphMany()\`
872
+
873
+ ## Commands
874
+
875
+ \`\`\`bash
876
+ # Generate migrations and models
877
+ npx omnify generate
878
+
879
+ # Force regeneration
880
+ npx omnify generate --force
881
+
882
+ # Validate schemas
883
+ npx omnify validate
884
+ \`\`\`
885
+ `;
886
+ var TYPESCRIPT_GUIDE = `# TypeScript Generator Guide
887
+
888
+ ## Generated Types
889
+
890
+ Types are generated in the configured output directory.
891
+
892
+ ### Interface Generation
893
+
894
+ Each schema generates a TypeScript interface:
895
+
896
+ \`\`\`typescript
897
+ export interface User {
898
+ id: number;
899
+ email: string;
900
+ name: string;
901
+ bio: string | null;
902
+ created_at: string;
903
+ updated_at: string;
904
+ }
905
+ \`\`\`
906
+
907
+ ### Type Mappings
908
+
909
+ | Omnify Type | TypeScript Type |
910
+ |-------------|-----------------|
911
+ | String, Text | string |
912
+ | Int, BigInt | number |
913
+ | Float, Decimal | number |
914
+ | Boolean | boolean |
915
+ | Date, DateTime | string |
916
+ | Json | Record<string, unknown> |
917
+ | Enum | union of literals |
918
+
919
+ ### Nullable Types
920
+
921
+ Nullable properties become \`T | null\`:
922
+
923
+ \`\`\`typescript
924
+ bio: string | null;
925
+ \`\`\`
926
+
927
+ ### Associations
928
+
929
+ Associations generate optional relation properties:
930
+
931
+ \`\`\`typescript
932
+ export interface Post {
933
+ id: number;
934
+ title: string;
935
+ author_id: number;
936
+ author?: User; // Optional relation
937
+ comments?: Comment[]; // Optional array relation
938
+ }
939
+ \`\`\`
940
+ `;
941
+ var JAPAN_GUIDE = `# Japan Plugin Types Guide
942
+
943
+ This project uses \`@famgia/omnify-japan\` plugin which provides Japan-specific types.
944
+
945
+ ## Available Types
946
+
947
+ ### Simple Types
948
+
949
+ #### JapanPhone
950
+ Japanese phone number format (e.g., \`090-1234-5678\`, \`03-1234-5678\`)
951
+ - SQL: \`VARCHAR(15)\`
952
+ - Accepts with or without hyphens
953
+
954
+ \`\`\`yaml
955
+ phone:
956
+ type: JapanPhone
957
+ \`\`\`
958
+
959
+ #### JapanPostalCode
960
+ Japanese postal code format (e.g., \`123-4567\`)
961
+ - SQL: \`VARCHAR(8)\`
962
+ - Accepts with or without hyphen
963
+
964
+ \`\`\`yaml
965
+ postal_code:
966
+ type: JapanPostalCode
967
+ nullable: true
968
+ \`\`\`
969
+
970
+ ### Compound Types
971
+
972
+ Compound types expand into multiple database columns automatically.
973
+
974
+ #### JapanName
975
+ Japanese name with kanji and kana variants.
976
+
977
+ **Expands to 4 columns:**
978
+ - \`{property}_lastname\` - VARCHAR(50) - Family name (\u59D3)
979
+ - \`{property}_firstname\` - VARCHAR(50) - Given name (\u540D)
980
+ - \`{property}_kana_lastname\` - VARCHAR(100) - Family name in katakana
981
+ - \`{property}_kana_firstname\` - VARCHAR(100) - Given name in katakana
982
+
983
+ **Accessors generated:**
984
+ - \`{property}_full_name\` - "\u59D3 \u540D" (space-separated)
985
+ - \`{property}_full_name_kana\` - "\u30BB\u30A4 \u30E1\u30A4" (space-separated)
986
+
987
+ \`\`\`yaml
988
+ name:
989
+ type: JapanName
990
+ displayName:
991
+ ja: \u6C0F\u540D
992
+ en: Full Name
993
+ # Per-field overrides
994
+ fields:
995
+ KanaLastname:
996
+ nullable: true
997
+ hidden: true
998
+ KanaFirstname:
999
+ nullable: true
1000
+ hidden: true
1001
+ \`\`\`
1002
+
1003
+ #### JapanAddress
1004
+ Japanese address with postal code and prefecture ID.
1005
+
1006
+ **Expands to 5 columns:**
1007
+ - \`{property}_postal_code\` - VARCHAR(8) - Postal code (\u90F5\u4FBF\u756A\u53F7)
1008
+ - \`{property}_prefecture_id\` - TINYINT UNSIGNED - Prefecture ID 1-47 (\u90FD\u9053\u5E9C\u770C)
1009
+ - \`{property}_address1\` - VARCHAR(255) - City/Ward (\u5E02\u533A\u753A\u6751)
1010
+ - \`{property}_address2\` - VARCHAR(255) - Street address (\u4E01\u76EE\u756A\u5730\u53F7)
1011
+ - \`{property}_address3\` - VARCHAR(255) NULLABLE - Building name (\u30D3\u30EB\u30FB\u30DE\u30F3\u30B7\u30E7\u30F3\u540D)
1012
+
1013
+ **Accessors generated:**
1014
+ - \`{property}_full_address\` - Concatenation of address1 + address2 + address3
1015
+
1016
+ \`\`\`yaml
1017
+ address:
1018
+ type: JapanAddress
1019
+ displayName:
1020
+ ja: \u4F4F\u6240
1021
+ en: Address
1022
+ fields:
1023
+ Address3:
1024
+ nullable: true
1025
+ \`\`\`
1026
+
1027
+ **Prefecture IDs (JIS X 0401):**
1028
+ | ID | Prefecture | ID | Prefecture | ID | Prefecture |
1029
+ |----|-----------|----|-----------|----|-----------|
1030
+ | 1 | \u5317\u6D77\u9053 | 17 | \u77F3\u5DDD\u770C | 33 | \u5CA1\u5C71\u770C |
1031
+ | 2 | \u9752\u68EE\u770C | 18 | \u798F\u4E95\u770C | 34 | \u5E83\u5CF6\u770C |
1032
+ | 3 | \u5CA9\u624B\u770C | 19 | \u5C71\u68A8\u770C | 35 | \u5C71\u53E3\u770C |
1033
+ | 4 | \u5BAE\u57CE\u770C | 20 | \u9577\u91CE\u770C | 36 | \u5FB3\u5CF6\u770C |
1034
+ | 5 | \u79CB\u7530\u770C | 21 | \u5C90\u961C\u770C | 37 | \u9999\u5DDD\u770C |
1035
+ | 6 | \u5C71\u5F62\u770C | 22 | \u9759\u5CA1\u770C | 38 | \u611B\u5A9B\u770C |
1036
+ | 7 | \u798F\u5CF6\u770C | 23 | \u611B\u77E5\u770C | 39 | \u9AD8\u77E5\u770C |
1037
+ | 8 | \u8328\u57CE\u770C | 24 | \u4E09\u91CD\u770C | 40 | \u798F\u5CA1\u770C |
1038
+ | 9 | \u6803\u6728\u770C | 25 | \u6ECB\u8CC0\u770C | 41 | \u4F50\u8CC0\u770C |
1039
+ | 10 | \u7FA4\u99AC\u770C | 26 | \u4EAC\u90FD\u5E9C | 42 | \u9577\u5D0E\u770C |
1040
+ | 11 | \u57FC\u7389\u770C | 27 | \u5927\u962A\u5E9C | 43 | \u718A\u672C\u770C |
1041
+ | 12 | \u5343\u8449\u770C | 28 | \u5175\u5EAB\u770C | 44 | \u5927\u5206\u770C |
1042
+ | 13 | \u6771\u4EAC\u90FD | 29 | \u5948\u826F\u770C | 45 | \u5BAE\u5D0E\u770C |
1043
+ | 14 | \u795E\u5948\u5DDD\u770C | 30 | \u548C\u6B4C\u5C71\u770C | 46 | \u9E7F\u5150\u5CF6\u770C |
1044
+ | 15 | \u65B0\u6F5F\u770C | 31 | \u9CE5\u53D6\u770C | 47 | \u6C96\u7E04\u770C |
1045
+ | 16 | \u5BCC\u5C71\u770C | 32 | \u5CF6\u6839\u770C | | |
1046
+
1047
+ #### JapanBankAccount
1048
+ Japanese bank account information.
1049
+
1050
+ **Expands to 5 columns:**
1051
+ - \`{property}_bank_code\` - VARCHAR(4) - Bank code (\u9280\u884C\u30B3\u30FC\u30C9)
1052
+ - \`{property}_branch_code\` - VARCHAR(3) - Branch code (\u652F\u5E97\u30B3\u30FC\u30C9)
1053
+ - \`{property}_account_type\` - ENUM - Account type: 1=\u666E\u901A, 2=\u5F53\u5EA7, 4=\u8CAF\u84C4
1054
+ - \`{property}_account_number\` - VARCHAR(7) - Account number (\u53E3\u5EA7\u756A\u53F7)
1055
+ - \`{property}_account_holder\` - VARCHAR(100) - Account holder name (\u53E3\u5EA7\u540D\u7FA9)
1056
+
1057
+ \`\`\`yaml
1058
+ bank_account:
1059
+ type: JapanBankAccount
1060
+ \`\`\`
1061
+
1062
+ ## Per-field Overrides
1063
+
1064
+ All compound types support per-field overrides:
1065
+
1066
+ \`\`\`yaml
1067
+ name:
1068
+ type: JapanName
1069
+ fields:
1070
+ KanaLastname:
1071
+ nullable: true
1072
+ hidden: true
1073
+ KanaFirstname:
1074
+ nullable: true
1075
+ hidden: true
1076
+ \`\`\`
1077
+
1078
+ **Available overrides:**
1079
+ - \`nullable\` - Whether the field can be NULL
1080
+ - \`hidden\` - Exclude from JSON/array output
1081
+ - \`fillable\` - Control mass assignment
1082
+
1083
+ ## Factory Examples
1084
+
1085
+ \`\`\`php
1086
+ $faker = fake('ja_JP');
1087
+
1088
+ return [
1089
+ // JapanName
1090
+ 'name_lastname' => $faker->lastName(),
1091
+ 'name_firstname' => $faker->firstName(),
1092
+ 'name_kana_lastname' => $faker->lastKanaName(),
1093
+ 'name_kana_firstname' => $faker->firstKanaName(),
1094
+
1095
+ // JapanPhone
1096
+ 'phone' => $faker->phoneNumber(),
1097
+
1098
+ // JapanPostalCode
1099
+ 'postal_code' => $faker->postcode(),
1100
+
1101
+ // JapanAddress
1102
+ 'address_postal_code' => $faker->postcode(),
1103
+ 'address_prefecture_id' => $faker->numberBetween(1, 47),
1104
+ 'address_address1' => $faker->city(),
1105
+ 'address_address2' => $faker->streetAddress(),
1106
+ 'address_address3' => $faker->optional(0.5)->secondaryAddress(),
1107
+ ];
1108
+ \`\`\`
1109
+
1110
+ ## Model Accessors
1111
+
1112
+ \`\`\`php
1113
+ // JapanName accessors
1114
+ $customer->name_full_name; // "\u7530\u4E2D \u592A\u90CE"
1115
+ $customer->name_full_name_kana; // "\u30BF\u30CA\u30AB \u30BF\u30ED\u30A6"
1116
+
1117
+ // JapanAddress accessor
1118
+ $customer->address_full_address; // "\u5343\u4EE3\u7530\u533A\u4E38\u306E\u51851-1-1\u30D3\u30EB5F"
1119
+ \`\`\`
1120
+ `;
1121
+ function isJapanPlugin(plugin) {
1122
+ return plugin.name === "@famgia/omnify-japan";
1123
+ }
1124
+ function isLaravelPlugin(plugin) {
1125
+ return plugin.name === "@famgia/omnify-laravel";
1126
+ }
1127
+ function isTypeScriptPlugin(plugin) {
1128
+ return plugin.name === "@famgia/omnify-typescript";
1129
+ }
1130
+ function generateAIGuides(rootDir, plugins) {
1131
+ const guidesDir = resolve5(rootDir, ".claude/omnify");
1132
+ let filesWritten = 0;
1133
+ if (!existsSync3(guidesDir)) {
1134
+ mkdirSync2(guidesDir, { recursive: true });
1135
+ }
1136
+ const claudeMdPath = resolve5(rootDir, "CLAUDE.md");
1137
+ if (!existsSync3(claudeMdPath)) {
1138
+ writeFileSync2(claudeMdPath, CLAUDE_MD);
1139
+ filesWritten++;
1140
+ }
1141
+ const schemaGuidePath = resolve5(guidesDir, "schema-guide.md");
1142
+ writeFileSync2(schemaGuidePath, SCHEMA_GUIDE);
1143
+ filesWritten++;
1144
+ for (const plugin of plugins) {
1145
+ if (isLaravelPlugin(plugin)) {
1146
+ const laravelGuidePath = resolve5(guidesDir, "laravel-guide.md");
1147
+ writeFileSync2(laravelGuidePath, LARAVEL_GUIDE);
1148
+ filesWritten++;
1149
+ }
1150
+ if (isTypeScriptPlugin(plugin)) {
1151
+ const tsGuidePath = resolve5(guidesDir, "typescript-guide.md");
1152
+ writeFileSync2(tsGuidePath, TYPESCRIPT_GUIDE);
1153
+ filesWritten++;
1154
+ }
1155
+ if (isJapanPlugin(plugin)) {
1156
+ const japanGuidePath = resolve5(guidesDir, "japan-guide.md");
1157
+ writeFileSync2(japanGuidePath, JAPAN_GUIDE);
1158
+ filesWritten++;
1159
+ }
1160
+ }
1161
+ return filesWritten;
1162
+ }
1163
+
1164
+ // src/commands/generate.ts
690
1165
  function hasPluginGenerators(plugins) {
691
1166
  return plugins.some((p) => p.generators && p.generators.length > 0);
692
1167
  }
@@ -867,17 +1342,17 @@ function schemaChangeToVersionChange(change) {
867
1342
  function writeGeneratorOutputs(outputs, rootDir) {
868
1343
  const counts = { migrations: 0, types: 0, models: 0, factories: 0, other: 0 };
869
1344
  for (const output of outputs) {
870
- const filePath = resolve5(rootDir, output.path);
1345
+ const filePath = resolve6(rootDir, output.path);
871
1346
  const dir = dirname4(filePath);
872
- if (!existsSync3(dir)) {
873
- mkdirSync2(dir, { recursive: true });
1347
+ if (!existsSync4(dir)) {
1348
+ mkdirSync3(dir, { recursive: true });
874
1349
  logger.debug(`Created directory: ${dir}`);
875
1350
  }
876
- if (output.skipIfExists && existsSync3(filePath)) {
1351
+ if (output.skipIfExists && existsSync4(filePath)) {
877
1352
  logger.debug(`Skipped (exists): ${output.path}`);
878
1353
  continue;
879
1354
  }
880
- writeFileSync2(filePath, output.content);
1355
+ writeFileSync3(filePath, output.content);
881
1356
  logger.debug(`Created: ${output.path}`);
882
1357
  if (output.type === "migration") counts.migrations++;
883
1358
  else if (output.type === "type") counts.types++;
@@ -916,9 +1391,9 @@ function runDirectGeneration(schemas, config, rootDir, options, changes) {
916
1391
  let modelsGenerated = 0;
917
1392
  if (!options.typesOnly && config.output.laravel) {
918
1393
  logger.step("Generating Laravel migrations...");
919
- const migrationsDir = resolve5(rootDir, config.output.laravel.migrationsPath);
920
- if (!existsSync3(migrationsDir)) {
921
- mkdirSync2(migrationsDir, { recursive: true });
1394
+ const migrationsDir = resolve6(rootDir, config.output.laravel.migrationsPath);
1395
+ if (!existsSync4(migrationsDir)) {
1396
+ mkdirSync3(migrationsDir, { recursive: true });
922
1397
  logger.debug(`Created directory: ${migrationsDir}`);
923
1398
  }
924
1399
  const addedSchemaNames = new Set(
@@ -933,8 +1408,8 @@ function runDirectGeneration(schemas, config, rootDir, options, changes) {
933
1408
  );
934
1409
  const createMigrations = generateMigrations(addedSchemas);
935
1410
  for (const migration of createMigrations) {
936
- const filePath = resolve5(migrationsDir, migration.fileName);
937
- writeFileSync2(filePath, migration.content);
1411
+ const filePath = resolve6(migrationsDir, migration.fileName);
1412
+ writeFileSync3(filePath, migration.content);
938
1413
  logger.debug(`Created: ${migration.fileName}`);
939
1414
  migrationsGenerated++;
940
1415
  }
@@ -942,8 +1417,8 @@ function runDirectGeneration(schemas, config, rootDir, options, changes) {
942
1417
  if (alterChanges.length > 0) {
943
1418
  const alterMigrations = generateMigrationsFromChanges(alterChanges);
944
1419
  for (const migration of alterMigrations) {
945
- const filePath = resolve5(migrationsDir, migration.fileName);
946
- writeFileSync2(filePath, migration.content);
1420
+ const filePath = resolve6(migrationsDir, migration.fileName);
1421
+ writeFileSync3(filePath, migration.content);
947
1422
  logger.debug(`Created: ${migration.fileName}`);
948
1423
  migrationsGenerated++;
949
1424
  }
@@ -954,29 +1429,29 @@ function runDirectGeneration(schemas, config, rootDir, options, changes) {
954
1429
  logger.step("Generating Laravel models...");
955
1430
  const modelsPath = config.output.laravel.modelsPath;
956
1431
  const baseModelsPath = config.output.laravel.baseModelsPath ?? `${modelsPath}/OmnifyBase`;
957
- const modelsDir = resolve5(rootDir, modelsPath);
958
- const baseModelsDir = resolve5(rootDir, baseModelsPath);
959
- if (!existsSync3(modelsDir)) {
960
- mkdirSync2(modelsDir, { recursive: true });
1432
+ const modelsDir = resolve6(rootDir, modelsPath);
1433
+ const baseModelsDir = resolve6(rootDir, baseModelsPath);
1434
+ if (!existsSync4(modelsDir)) {
1435
+ mkdirSync3(modelsDir, { recursive: true });
961
1436
  }
962
- if (!existsSync3(baseModelsDir)) {
963
- mkdirSync2(baseModelsDir, { recursive: true });
1437
+ if (!existsSync4(baseModelsDir)) {
1438
+ mkdirSync3(baseModelsDir, { recursive: true });
964
1439
  }
965
1440
  const models = generateModels(schemas, {
966
1441
  modelPath: modelsPath,
967
1442
  baseModelPath: baseModelsPath
968
1443
  });
969
1444
  for (const model of models) {
970
- const filePath = resolve5(rootDir, getModelPath(model));
1445
+ const filePath = resolve6(rootDir, getModelPath(model));
971
1446
  const fileDir = dirname4(filePath);
972
- if (!existsSync3(fileDir)) {
973
- mkdirSync2(fileDir, { recursive: true });
1447
+ if (!existsSync4(fileDir)) {
1448
+ mkdirSync3(fileDir, { recursive: true });
974
1449
  }
975
- if (!model.overwrite && existsSync3(filePath)) {
1450
+ if (!model.overwrite && existsSync4(filePath)) {
976
1451
  logger.debug(`Skipped (exists): ${getModelPath(model)}`);
977
1452
  continue;
978
1453
  }
979
- writeFileSync2(filePath, model.content);
1454
+ writeFileSync3(filePath, model.content);
980
1455
  logger.debug(`Created: ${getModelPath(model)}`);
981
1456
  modelsGenerated++;
982
1457
  }
@@ -984,23 +1459,23 @@ function runDirectGeneration(schemas, config, rootDir, options, changes) {
984
1459
  }
985
1460
  if (!options.migrationsOnly && config.output.typescript) {
986
1461
  logger.step("Generating TypeScript types...");
987
- const typesDir = resolve5(rootDir, config.output.typescript.path);
988
- if (!existsSync3(typesDir)) {
989
- mkdirSync2(typesDir, { recursive: true });
1462
+ const typesDir = resolve6(rootDir, config.output.typescript.path);
1463
+ if (!existsSync4(typesDir)) {
1464
+ mkdirSync3(typesDir, { recursive: true });
990
1465
  logger.debug(`Created directory: ${typesDir}`);
991
1466
  }
992
1467
  const typeFiles = generateTypeScript(schemas);
993
1468
  for (const file of typeFiles) {
994
- const filePath = resolve5(typesDir, file.filePath);
1469
+ const filePath = resolve6(typesDir, file.filePath);
995
1470
  const fileDir = dirname4(filePath);
996
- if (!existsSync3(fileDir)) {
997
- mkdirSync2(fileDir, { recursive: true });
1471
+ if (!existsSync4(fileDir)) {
1472
+ mkdirSync3(fileDir, { recursive: true });
998
1473
  }
999
- if (!file.overwrite && existsSync3(filePath)) {
1474
+ if (!file.overwrite && existsSync4(filePath)) {
1000
1475
  logger.debug(`Skipped (exists): ${file.filePath}`);
1001
1476
  continue;
1002
1477
  }
1003
- writeFileSync2(filePath, file.content);
1478
+ writeFileSync3(filePath, file.content);
1004
1479
  logger.debug(`Created: ${file.filePath}`);
1005
1480
  typesGenerated++;
1006
1481
  }
@@ -1015,7 +1490,7 @@ async function runGenerate(options) {
1015
1490
  const { config, configPath } = await loadConfig();
1016
1491
  const rootDir = configPath ? dirname4(configPath) : process.cwd();
1017
1492
  validateConfig(config, rootDir);
1018
- const schemaPath = resolve5(rootDir, config.schemasDir);
1493
+ const schemaPath = resolve6(rootDir, config.schemasDir);
1019
1494
  logger.step(`Loading schemas from ${schemaPath}`);
1020
1495
  const schemas = await loadSchemas3(schemaPath);
1021
1496
  const schemaCount = Object.keys(schemas).length;
@@ -1045,7 +1520,7 @@ async function runGenerate(options) {
1045
1520
  process.exit(2);
1046
1521
  }
1047
1522
  logger.step("Checking for changes...");
1048
- const lockPath = resolve5(rootDir, config.lockFilePath);
1523
+ const lockPath = resolve6(rootDir, config.lockFilePath);
1049
1524
  const existingLock = await readLockFile(lockPath);
1050
1525
  const currentSnapshots = await buildSchemaSnapshots(schemas);
1051
1526
  const v2Lock = existingLock && isLockFileV2(existingLock) ? existingLock : null;
@@ -1119,6 +1594,14 @@ async function runGenerate(options) {
1119
1594
  } catch (versionError) {
1120
1595
  logger.debug(`Could not save version history: ${versionError.message}`);
1121
1596
  }
1597
+ try {
1598
+ const guidesWritten = generateAIGuides(rootDir, config.plugins);
1599
+ if (guidesWritten > 0) {
1600
+ logger.debug(`Updated ${guidesWritten} AI guide file(s)`);
1601
+ }
1602
+ } catch (guideError) {
1603
+ logger.debug(`Could not generate AI guides: ${guideError.message}`);
1604
+ }
1122
1605
  logger.newline();
1123
1606
  logger.success("Generation complete!");
1124
1607
  if (migrationsGenerated > 0 && config.output.laravel) {