@famgia/omnify-cli 0.0.25 → 0.0.26

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/cli.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- import { existsSync as existsSync5 } from "fs";
5
- import { resolve as resolve7 } from "path";
4
+ import { existsSync as existsSync6 } from "fs";
5
+ import { resolve as resolve8 } from "path";
6
6
  import { Command } from "commander";
7
7
  import { OmnifyError as OmnifyError5 } from "@famgia/omnify-core";
8
8
 
@@ -670,8 +670,8 @@ function registerDiffCommand(program2) {
670
670
  }
671
671
 
672
672
  // src/commands/generate.ts
673
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
674
- import { resolve as resolve5, dirname as dirname4 } from "path";
673
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
674
+ import { resolve as resolve6, dirname as dirname4 } from "path";
675
675
  import {
676
676
  loadSchemas as loadSchemas3,
677
677
  validateSchemas as validateSchemas3,
@@ -694,6 +694,481 @@ import {
694
694
  getModelPath
695
695
  } from "@famgia/omnify-laravel";
696
696
  import { generateTypeScript } from "@famgia/omnify-typescript";
697
+
698
+ // src/guides/index.ts
699
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
700
+ import { resolve as resolve5 } from "path";
701
+ var CLAUDE_MD = `## Omnify
702
+
703
+ This project uses Omnify for schema-driven code generation.
704
+
705
+ **Documentation**: \`.claude/omnify/\`
706
+ - \`schema-guide.md\` - Schema format and property types
707
+ - \`laravel-guide.md\` - Laravel generator (if installed)
708
+ - \`typescript-guide.md\` - TypeScript generator (if installed)
709
+ - \`japan-guide.md\` - Japan plugin types (if installed)
710
+
711
+ **Commands**:
712
+ - \`npx omnify generate\` - Generate code from schemas
713
+ - \`npx omnify validate\` - Validate schemas
714
+ `;
715
+ var SCHEMA_GUIDE = `# Omnify Schema Guide
716
+
717
+ ## Schema File Format
718
+
719
+ Schemas are YAML files defining data models. Each file represents one entity.
720
+
721
+ \`\`\`yaml
722
+ name: User
723
+ displayName:
724
+ ja: \u30E6\u30FC\u30B6\u30FC
725
+ en: User
726
+ kind: object
727
+
728
+ properties:
729
+ email:
730
+ type: Email
731
+ unique: true
732
+ name:
733
+ type: String
734
+ bio:
735
+ type: Text
736
+ nullable: true
737
+
738
+ options:
739
+ timestamps: true
740
+ softDelete: true
741
+ \`\`\`
742
+
743
+ ## Property Types
744
+
745
+ ### String Types
746
+ - \`String\` - VARCHAR(255)
747
+ - \`Text\` - TEXT
748
+ - \`LongText\` - LONGTEXT
749
+ - \`Email\` - VARCHAR(255) for email addresses
750
+ - \`Password\` - VARCHAR(255), hidden in serialization
751
+
752
+ ### Numeric Types
753
+ - \`Int\` - INTEGER
754
+ - \`BigInt\` - BIGINT
755
+ - \`TinyInt\` - TINYINT
756
+ - \`Float\` - DOUBLE
757
+ - \`Decimal\` - DECIMAL(precision, scale)
758
+
759
+ ### Date/Time Types
760
+ - \`Date\` - DATE
761
+ - \`Time\` - TIME
762
+ - \`DateTime\` - DATETIME
763
+ - \`Timestamp\` - TIMESTAMP
764
+
765
+ ### Other Types
766
+ - \`Boolean\` - BOOLEAN
767
+ - \`Json\` - JSON
768
+ - \`Enum\` - ENUM with values
769
+ - \`EnumRef\` - Reference to enum schema
770
+
771
+ ## Property Options
772
+
773
+ \`\`\`yaml
774
+ propertyName:
775
+ type: String
776
+ nullable: true # Can be NULL
777
+ unique: true # Unique constraint
778
+ default: "value" # Default value
779
+ length: 100 # VARCHAR length
780
+ hidden: true # Hide from JSON output
781
+ fillable: false # Exclude from mass assignment
782
+ \`\`\`
783
+
784
+ ## Associations
785
+
786
+ \`\`\`yaml
787
+ # Many-to-One (belongsTo)
788
+ author:
789
+ type: Association
790
+ relation: ManyToOne
791
+ target: User
792
+ onDelete: CASCADE
793
+
794
+ # One-to-Many (hasMany)
795
+ posts:
796
+ type: Association
797
+ relation: OneToMany
798
+ target: Post
799
+
800
+ # Many-to-Many (belongsToMany)
801
+ tags:
802
+ type: Association
803
+ relation: ManyToMany
804
+ target: Tag
805
+ joinTable: post_tags
806
+
807
+ # Polymorphic
808
+ commentable:
809
+ type: Association
810
+ relation: MorphTo
811
+ \`\`\`
812
+
813
+ ## Indexes
814
+
815
+ \`\`\`yaml
816
+ indexes:
817
+ - columns: [status, published_at]
818
+ - columns: [email]
819
+ unique: true
820
+ \`\`\`
821
+
822
+ ## Schema Options
823
+
824
+ \`\`\`yaml
825
+ options:
826
+ timestamps: true # Add created_at, updated_at
827
+ softDelete: true # Add deleted_at
828
+ idType: BigInt # Primary key type (Int, BigInt, Uuid)
829
+ \`\`\`
830
+ `;
831
+ var LARAVEL_GUIDE = `# Laravel Generator Guide
832
+
833
+ ## Generated Files
834
+
835
+ ### Migrations
836
+ Located in \`database/migrations/omnify/\`
837
+ - Auto-generated from schema changes
838
+ - Handles column additions, modifications, removals
839
+ - Preserves manual migrations outside omnify folder
840
+
841
+ ### Models
842
+ Two-tier model structure:
843
+ - \`app/Models/OmnifyBase/*BaseModel.php\` - Auto-generated, DO NOT EDIT
844
+ - \`app/Models/*.php\` - User models, extend base models, safe to customize
845
+
846
+ ### Factories
847
+ Located in \`database/factories/\`
848
+ - Generated once, safe to customize
849
+ - Uses appropriate Faker methods for each type
850
+
851
+ ## Model Features
852
+
853
+ ### Fillable
854
+ All schema properties are mass-assignable by default.
855
+ Use \`fillable: false\` to exclude.
856
+
857
+ ### Hidden
858
+ Use \`hidden: true\` to exclude from JSON/array output.
859
+
860
+ \`\`\`yaml
861
+ password:
862
+ type: Password
863
+ hidden: true
864
+ \`\`\`
865
+
866
+ ### Casts
867
+ Auto-generated based on property types:
868
+ - \`Boolean\` \u2192 \`'boolean'\`
869
+ - \`Json\` \u2192 \`'array'\`
870
+ - \`Timestamp\` \u2192 \`'datetime'\`
871
+
872
+ ### Relationships
873
+ Generated from Association properties:
874
+ - \`ManyToOne\` \u2192 \`belongsTo()\`
875
+ - \`OneToMany\` \u2192 \`hasMany()\`
876
+ - \`ManyToMany\` \u2192 \`belongsToMany()\`
877
+ - \`MorphTo\` \u2192 \`morphTo()\`
878
+ - \`MorphMany\` \u2192 \`morphMany()\`
879
+
880
+ ## Commands
881
+
882
+ \`\`\`bash
883
+ # Generate migrations and models
884
+ npx omnify generate
885
+
886
+ # Force regeneration
887
+ npx omnify generate --force
888
+
889
+ # Validate schemas
890
+ npx omnify validate
891
+ \`\`\`
892
+ `;
893
+ var TYPESCRIPT_GUIDE = `# TypeScript Generator Guide
894
+
895
+ ## Generated Types
896
+
897
+ Types are generated in the configured output directory.
898
+
899
+ ### Interface Generation
900
+
901
+ Each schema generates a TypeScript interface:
902
+
903
+ \`\`\`typescript
904
+ export interface User {
905
+ id: number;
906
+ email: string;
907
+ name: string;
908
+ bio: string | null;
909
+ created_at: string;
910
+ updated_at: string;
911
+ }
912
+ \`\`\`
913
+
914
+ ### Type Mappings
915
+
916
+ | Omnify Type | TypeScript Type |
917
+ |-------------|-----------------|
918
+ | String, Text | string |
919
+ | Int, BigInt | number |
920
+ | Float, Decimal | number |
921
+ | Boolean | boolean |
922
+ | Date, DateTime | string |
923
+ | Json | Record<string, unknown> |
924
+ | Enum | union of literals |
925
+
926
+ ### Nullable Types
927
+
928
+ Nullable properties become \`T | null\`:
929
+
930
+ \`\`\`typescript
931
+ bio: string | null;
932
+ \`\`\`
933
+
934
+ ### Associations
935
+
936
+ Associations generate optional relation properties:
937
+
938
+ \`\`\`typescript
939
+ export interface Post {
940
+ id: number;
941
+ title: string;
942
+ author_id: number;
943
+ author?: User; // Optional relation
944
+ comments?: Comment[]; // Optional array relation
945
+ }
946
+ \`\`\`
947
+ `;
948
+ var JAPAN_GUIDE = `# Japan Plugin Types Guide
949
+
950
+ This project uses \`@famgia/omnify-japan\` plugin which provides Japan-specific types.
951
+
952
+ ## Available Types
953
+
954
+ ### Simple Types
955
+
956
+ #### JapanPhone
957
+ Japanese phone number format (e.g., \`090-1234-5678\`, \`03-1234-5678\`)
958
+ - SQL: \`VARCHAR(15)\`
959
+ - Accepts with or without hyphens
960
+
961
+ \`\`\`yaml
962
+ phone:
963
+ type: JapanPhone
964
+ \`\`\`
965
+
966
+ #### JapanPostalCode
967
+ Japanese postal code format (e.g., \`123-4567\`)
968
+ - SQL: \`VARCHAR(8)\`
969
+ - Accepts with or without hyphen
970
+
971
+ \`\`\`yaml
972
+ postal_code:
973
+ type: JapanPostalCode
974
+ nullable: true
975
+ \`\`\`
976
+
977
+ ### Compound Types
978
+
979
+ Compound types expand into multiple database columns automatically.
980
+
981
+ #### JapanName
982
+ Japanese name with kanji and kana variants.
983
+
984
+ **Expands to 4 columns:**
985
+ - \`{property}_lastname\` - VARCHAR(50) - Family name (\u59D3)
986
+ - \`{property}_firstname\` - VARCHAR(50) - Given name (\u540D)
987
+ - \`{property}_kana_lastname\` - VARCHAR(100) - Family name in katakana
988
+ - \`{property}_kana_firstname\` - VARCHAR(100) - Given name in katakana
989
+
990
+ **Accessors generated:**
991
+ - \`{property}_full_name\` - "\u59D3 \u540D" (space-separated)
992
+ - \`{property}_full_name_kana\` - "\u30BB\u30A4 \u30E1\u30A4" (space-separated)
993
+
994
+ \`\`\`yaml
995
+ name:
996
+ type: JapanName
997
+ displayName:
998
+ ja: \u6C0F\u540D
999
+ en: Full Name
1000
+ # Per-field overrides
1001
+ fields:
1002
+ KanaLastname:
1003
+ nullable: true
1004
+ hidden: true
1005
+ KanaFirstname:
1006
+ nullable: true
1007
+ hidden: true
1008
+ \`\`\`
1009
+
1010
+ #### JapanAddress
1011
+ Japanese address with postal code and prefecture ID.
1012
+
1013
+ **Expands to 5 columns:**
1014
+ - \`{property}_postal_code\` - VARCHAR(8) - Postal code (\u90F5\u4FBF\u756A\u53F7)
1015
+ - \`{property}_prefecture_id\` - TINYINT UNSIGNED - Prefecture ID 1-47 (\u90FD\u9053\u5E9C\u770C)
1016
+ - \`{property}_address1\` - VARCHAR(255) - City/Ward (\u5E02\u533A\u753A\u6751)
1017
+ - \`{property}_address2\` - VARCHAR(255) - Street address (\u4E01\u76EE\u756A\u5730\u53F7)
1018
+ - \`{property}_address3\` - VARCHAR(255) NULLABLE - Building name (\u30D3\u30EB\u30FB\u30DE\u30F3\u30B7\u30E7\u30F3\u540D)
1019
+
1020
+ **Accessors generated:**
1021
+ - \`{property}_full_address\` - Concatenation of address1 + address2 + address3
1022
+
1023
+ \`\`\`yaml
1024
+ address:
1025
+ type: JapanAddress
1026
+ displayName:
1027
+ ja: \u4F4F\u6240
1028
+ en: Address
1029
+ fields:
1030
+ Address3:
1031
+ nullable: true
1032
+ \`\`\`
1033
+
1034
+ **Prefecture IDs (JIS X 0401):**
1035
+ | ID | Prefecture | ID | Prefecture | ID | Prefecture |
1036
+ |----|-----------|----|-----------|----|-----------|
1037
+ | 1 | \u5317\u6D77\u9053 | 17 | \u77F3\u5DDD\u770C | 33 | \u5CA1\u5C71\u770C |
1038
+ | 2 | \u9752\u68EE\u770C | 18 | \u798F\u4E95\u770C | 34 | \u5E83\u5CF6\u770C |
1039
+ | 3 | \u5CA9\u624B\u770C | 19 | \u5C71\u68A8\u770C | 35 | \u5C71\u53E3\u770C |
1040
+ | 4 | \u5BAE\u57CE\u770C | 20 | \u9577\u91CE\u770C | 36 | \u5FB3\u5CF6\u770C |
1041
+ | 5 | \u79CB\u7530\u770C | 21 | \u5C90\u961C\u770C | 37 | \u9999\u5DDD\u770C |
1042
+ | 6 | \u5C71\u5F62\u770C | 22 | \u9759\u5CA1\u770C | 38 | \u611B\u5A9B\u770C |
1043
+ | 7 | \u798F\u5CF6\u770C | 23 | \u611B\u77E5\u770C | 39 | \u9AD8\u77E5\u770C |
1044
+ | 8 | \u8328\u57CE\u770C | 24 | \u4E09\u91CD\u770C | 40 | \u798F\u5CA1\u770C |
1045
+ | 9 | \u6803\u6728\u770C | 25 | \u6ECB\u8CC0\u770C | 41 | \u4F50\u8CC0\u770C |
1046
+ | 10 | \u7FA4\u99AC\u770C | 26 | \u4EAC\u90FD\u5E9C | 42 | \u9577\u5D0E\u770C |
1047
+ | 11 | \u57FC\u7389\u770C | 27 | \u5927\u962A\u5E9C | 43 | \u718A\u672C\u770C |
1048
+ | 12 | \u5343\u8449\u770C | 28 | \u5175\u5EAB\u770C | 44 | \u5927\u5206\u770C |
1049
+ | 13 | \u6771\u4EAC\u90FD | 29 | \u5948\u826F\u770C | 45 | \u5BAE\u5D0E\u770C |
1050
+ | 14 | \u795E\u5948\u5DDD\u770C | 30 | \u548C\u6B4C\u5C71\u770C | 46 | \u9E7F\u5150\u5CF6\u770C |
1051
+ | 15 | \u65B0\u6F5F\u770C | 31 | \u9CE5\u53D6\u770C | 47 | \u6C96\u7E04\u770C |
1052
+ | 16 | \u5BCC\u5C71\u770C | 32 | \u5CF6\u6839\u770C | | |
1053
+
1054
+ #### JapanBankAccount
1055
+ Japanese bank account information.
1056
+
1057
+ **Expands to 5 columns:**
1058
+ - \`{property}_bank_code\` - VARCHAR(4) - Bank code (\u9280\u884C\u30B3\u30FC\u30C9)
1059
+ - \`{property}_branch_code\` - VARCHAR(3) - Branch code (\u652F\u5E97\u30B3\u30FC\u30C9)
1060
+ - \`{property}_account_type\` - ENUM - Account type: 1=\u666E\u901A, 2=\u5F53\u5EA7, 4=\u8CAF\u84C4
1061
+ - \`{property}_account_number\` - VARCHAR(7) - Account number (\u53E3\u5EA7\u756A\u53F7)
1062
+ - \`{property}_account_holder\` - VARCHAR(100) - Account holder name (\u53E3\u5EA7\u540D\u7FA9)
1063
+
1064
+ \`\`\`yaml
1065
+ bank_account:
1066
+ type: JapanBankAccount
1067
+ \`\`\`
1068
+
1069
+ ## Per-field Overrides
1070
+
1071
+ All compound types support per-field overrides:
1072
+
1073
+ \`\`\`yaml
1074
+ name:
1075
+ type: JapanName
1076
+ fields:
1077
+ KanaLastname:
1078
+ nullable: true
1079
+ hidden: true
1080
+ KanaFirstname:
1081
+ nullable: true
1082
+ hidden: true
1083
+ \`\`\`
1084
+
1085
+ **Available overrides:**
1086
+ - \`nullable\` - Whether the field can be NULL
1087
+ - \`hidden\` - Exclude from JSON/array output
1088
+ - \`fillable\` - Control mass assignment
1089
+
1090
+ ## Factory Examples
1091
+
1092
+ \`\`\`php
1093
+ $faker = fake('ja_JP');
1094
+
1095
+ return [
1096
+ // JapanName
1097
+ 'name_lastname' => $faker->lastName(),
1098
+ 'name_firstname' => $faker->firstName(),
1099
+ 'name_kana_lastname' => $faker->lastKanaName(),
1100
+ 'name_kana_firstname' => $faker->firstKanaName(),
1101
+
1102
+ // JapanPhone
1103
+ 'phone' => $faker->phoneNumber(),
1104
+
1105
+ // JapanPostalCode
1106
+ 'postal_code' => $faker->postcode(),
1107
+
1108
+ // JapanAddress
1109
+ 'address_postal_code' => $faker->postcode(),
1110
+ 'address_prefecture_id' => $faker->numberBetween(1, 47),
1111
+ 'address_address1' => $faker->city(),
1112
+ 'address_address2' => $faker->streetAddress(),
1113
+ 'address_address3' => $faker->optional(0.5)->secondaryAddress(),
1114
+ ];
1115
+ \`\`\`
1116
+
1117
+ ## Model Accessors
1118
+
1119
+ \`\`\`php
1120
+ // JapanName accessors
1121
+ $customer->name_full_name; // "\u7530\u4E2D \u592A\u90CE"
1122
+ $customer->name_full_name_kana; // "\u30BF\u30CA\u30AB \u30BF\u30ED\u30A6"
1123
+
1124
+ // JapanAddress accessor
1125
+ $customer->address_full_address; // "\u5343\u4EE3\u7530\u533A\u4E38\u306E\u51851-1-1\u30D3\u30EB5F"
1126
+ \`\`\`
1127
+ `;
1128
+ function isJapanPlugin(plugin) {
1129
+ return plugin.name === "@famgia/omnify-japan";
1130
+ }
1131
+ function isLaravelPlugin(plugin) {
1132
+ return plugin.name === "@famgia/omnify-laravel";
1133
+ }
1134
+ function isTypeScriptPlugin(plugin) {
1135
+ return plugin.name === "@famgia/omnify-typescript";
1136
+ }
1137
+ function generateAIGuides(rootDir, plugins) {
1138
+ const guidesDir = resolve5(rootDir, ".claude/omnify");
1139
+ let filesWritten = 0;
1140
+ if (!existsSync3(guidesDir)) {
1141
+ mkdirSync2(guidesDir, { recursive: true });
1142
+ }
1143
+ const claudeMdPath = resolve5(rootDir, "CLAUDE.md");
1144
+ if (!existsSync3(claudeMdPath)) {
1145
+ writeFileSync2(claudeMdPath, CLAUDE_MD);
1146
+ filesWritten++;
1147
+ }
1148
+ const schemaGuidePath = resolve5(guidesDir, "schema-guide.md");
1149
+ writeFileSync2(schemaGuidePath, SCHEMA_GUIDE);
1150
+ filesWritten++;
1151
+ for (const plugin of plugins) {
1152
+ if (isLaravelPlugin(plugin)) {
1153
+ const laravelGuidePath = resolve5(guidesDir, "laravel-guide.md");
1154
+ writeFileSync2(laravelGuidePath, LARAVEL_GUIDE);
1155
+ filesWritten++;
1156
+ }
1157
+ if (isTypeScriptPlugin(plugin)) {
1158
+ const tsGuidePath = resolve5(guidesDir, "typescript-guide.md");
1159
+ writeFileSync2(tsGuidePath, TYPESCRIPT_GUIDE);
1160
+ filesWritten++;
1161
+ }
1162
+ if (isJapanPlugin(plugin)) {
1163
+ const japanGuidePath = resolve5(guidesDir, "japan-guide.md");
1164
+ writeFileSync2(japanGuidePath, JAPAN_GUIDE);
1165
+ filesWritten++;
1166
+ }
1167
+ }
1168
+ return filesWritten;
1169
+ }
1170
+
1171
+ // src/commands/generate.ts
697
1172
  function hasPluginGenerators(plugins) {
698
1173
  return plugins.some((p) => p.generators && p.generators.length > 0);
699
1174
  }
@@ -874,17 +1349,17 @@ function schemaChangeToVersionChange(change) {
874
1349
  function writeGeneratorOutputs(outputs, rootDir) {
875
1350
  const counts = { migrations: 0, types: 0, models: 0, factories: 0, other: 0 };
876
1351
  for (const output of outputs) {
877
- const filePath = resolve5(rootDir, output.path);
1352
+ const filePath = resolve6(rootDir, output.path);
878
1353
  const dir = dirname4(filePath);
879
- if (!existsSync3(dir)) {
880
- mkdirSync2(dir, { recursive: true });
1354
+ if (!existsSync4(dir)) {
1355
+ mkdirSync3(dir, { recursive: true });
881
1356
  logger.debug(`Created directory: ${dir}`);
882
1357
  }
883
- if (output.skipIfExists && existsSync3(filePath)) {
1358
+ if (output.skipIfExists && existsSync4(filePath)) {
884
1359
  logger.debug(`Skipped (exists): ${output.path}`);
885
1360
  continue;
886
1361
  }
887
- writeFileSync2(filePath, output.content);
1362
+ writeFileSync3(filePath, output.content);
888
1363
  logger.debug(`Created: ${output.path}`);
889
1364
  if (output.type === "migration") counts.migrations++;
890
1365
  else if (output.type === "type") counts.types++;
@@ -923,9 +1398,9 @@ function runDirectGeneration(schemas, config, rootDir, options, changes) {
923
1398
  let modelsGenerated = 0;
924
1399
  if (!options.typesOnly && config.output.laravel) {
925
1400
  logger.step("Generating Laravel migrations...");
926
- const migrationsDir = resolve5(rootDir, config.output.laravel.migrationsPath);
927
- if (!existsSync3(migrationsDir)) {
928
- mkdirSync2(migrationsDir, { recursive: true });
1401
+ const migrationsDir = resolve6(rootDir, config.output.laravel.migrationsPath);
1402
+ if (!existsSync4(migrationsDir)) {
1403
+ mkdirSync3(migrationsDir, { recursive: true });
929
1404
  logger.debug(`Created directory: ${migrationsDir}`);
930
1405
  }
931
1406
  const addedSchemaNames = new Set(
@@ -940,8 +1415,8 @@ function runDirectGeneration(schemas, config, rootDir, options, changes) {
940
1415
  );
941
1416
  const createMigrations = generateMigrations(addedSchemas);
942
1417
  for (const migration of createMigrations) {
943
- const filePath = resolve5(migrationsDir, migration.fileName);
944
- writeFileSync2(filePath, migration.content);
1418
+ const filePath = resolve6(migrationsDir, migration.fileName);
1419
+ writeFileSync3(filePath, migration.content);
945
1420
  logger.debug(`Created: ${migration.fileName}`);
946
1421
  migrationsGenerated++;
947
1422
  }
@@ -949,8 +1424,8 @@ function runDirectGeneration(schemas, config, rootDir, options, changes) {
949
1424
  if (alterChanges.length > 0) {
950
1425
  const alterMigrations = generateMigrationsFromChanges(alterChanges);
951
1426
  for (const migration of alterMigrations) {
952
- const filePath = resolve5(migrationsDir, migration.fileName);
953
- writeFileSync2(filePath, migration.content);
1427
+ const filePath = resolve6(migrationsDir, migration.fileName);
1428
+ writeFileSync3(filePath, migration.content);
954
1429
  logger.debug(`Created: ${migration.fileName}`);
955
1430
  migrationsGenerated++;
956
1431
  }
@@ -961,29 +1436,29 @@ function runDirectGeneration(schemas, config, rootDir, options, changes) {
961
1436
  logger.step("Generating Laravel models...");
962
1437
  const modelsPath = config.output.laravel.modelsPath;
963
1438
  const baseModelsPath = config.output.laravel.baseModelsPath ?? `${modelsPath}/OmnifyBase`;
964
- const modelsDir = resolve5(rootDir, modelsPath);
965
- const baseModelsDir = resolve5(rootDir, baseModelsPath);
966
- if (!existsSync3(modelsDir)) {
967
- mkdirSync2(modelsDir, { recursive: true });
1439
+ const modelsDir = resolve6(rootDir, modelsPath);
1440
+ const baseModelsDir = resolve6(rootDir, baseModelsPath);
1441
+ if (!existsSync4(modelsDir)) {
1442
+ mkdirSync3(modelsDir, { recursive: true });
968
1443
  }
969
- if (!existsSync3(baseModelsDir)) {
970
- mkdirSync2(baseModelsDir, { recursive: true });
1444
+ if (!existsSync4(baseModelsDir)) {
1445
+ mkdirSync3(baseModelsDir, { recursive: true });
971
1446
  }
972
1447
  const models = generateModels(schemas, {
973
1448
  modelPath: modelsPath,
974
1449
  baseModelPath: baseModelsPath
975
1450
  });
976
1451
  for (const model of models) {
977
- const filePath = resolve5(rootDir, getModelPath(model));
1452
+ const filePath = resolve6(rootDir, getModelPath(model));
978
1453
  const fileDir = dirname4(filePath);
979
- if (!existsSync3(fileDir)) {
980
- mkdirSync2(fileDir, { recursive: true });
1454
+ if (!existsSync4(fileDir)) {
1455
+ mkdirSync3(fileDir, { recursive: true });
981
1456
  }
982
- if (!model.overwrite && existsSync3(filePath)) {
1457
+ if (!model.overwrite && existsSync4(filePath)) {
983
1458
  logger.debug(`Skipped (exists): ${getModelPath(model)}`);
984
1459
  continue;
985
1460
  }
986
- writeFileSync2(filePath, model.content);
1461
+ writeFileSync3(filePath, model.content);
987
1462
  logger.debug(`Created: ${getModelPath(model)}`);
988
1463
  modelsGenerated++;
989
1464
  }
@@ -991,23 +1466,23 @@ function runDirectGeneration(schemas, config, rootDir, options, changes) {
991
1466
  }
992
1467
  if (!options.migrationsOnly && config.output.typescript) {
993
1468
  logger.step("Generating TypeScript types...");
994
- const typesDir = resolve5(rootDir, config.output.typescript.path);
995
- if (!existsSync3(typesDir)) {
996
- mkdirSync2(typesDir, { recursive: true });
1469
+ const typesDir = resolve6(rootDir, config.output.typescript.path);
1470
+ if (!existsSync4(typesDir)) {
1471
+ mkdirSync3(typesDir, { recursive: true });
997
1472
  logger.debug(`Created directory: ${typesDir}`);
998
1473
  }
999
1474
  const typeFiles = generateTypeScript(schemas);
1000
1475
  for (const file of typeFiles) {
1001
- const filePath = resolve5(typesDir, file.filePath);
1476
+ const filePath = resolve6(typesDir, file.filePath);
1002
1477
  const fileDir = dirname4(filePath);
1003
- if (!existsSync3(fileDir)) {
1004
- mkdirSync2(fileDir, { recursive: true });
1478
+ if (!existsSync4(fileDir)) {
1479
+ mkdirSync3(fileDir, { recursive: true });
1005
1480
  }
1006
- if (!file.overwrite && existsSync3(filePath)) {
1481
+ if (!file.overwrite && existsSync4(filePath)) {
1007
1482
  logger.debug(`Skipped (exists): ${file.filePath}`);
1008
1483
  continue;
1009
1484
  }
1010
- writeFileSync2(filePath, file.content);
1485
+ writeFileSync3(filePath, file.content);
1011
1486
  logger.debug(`Created: ${file.filePath}`);
1012
1487
  typesGenerated++;
1013
1488
  }
@@ -1022,7 +1497,7 @@ async function runGenerate(options) {
1022
1497
  const { config, configPath: configPath2 } = await loadConfig();
1023
1498
  const rootDir = configPath2 ? dirname4(configPath2) : process.cwd();
1024
1499
  validateConfig(config, rootDir);
1025
- const schemaPath = resolve5(rootDir, config.schemasDir);
1500
+ const schemaPath = resolve6(rootDir, config.schemasDir);
1026
1501
  logger.step(`Loading schemas from ${schemaPath}`);
1027
1502
  const schemas = await loadSchemas3(schemaPath);
1028
1503
  const schemaCount = Object.keys(schemas).length;
@@ -1052,7 +1527,7 @@ async function runGenerate(options) {
1052
1527
  process.exit(2);
1053
1528
  }
1054
1529
  logger.step("Checking for changes...");
1055
- const lockPath = resolve5(rootDir, config.lockFilePath);
1530
+ const lockPath = resolve6(rootDir, config.lockFilePath);
1056
1531
  const existingLock = await readLockFile(lockPath);
1057
1532
  const currentSnapshots = await buildSchemaSnapshots(schemas);
1058
1533
  const v2Lock = existingLock && isLockFileV2(existingLock) ? existingLock : null;
@@ -1126,6 +1601,14 @@ async function runGenerate(options) {
1126
1601
  } catch (versionError) {
1127
1602
  logger.debug(`Could not save version history: ${versionError.message}`);
1128
1603
  }
1604
+ try {
1605
+ const guidesWritten = generateAIGuides(rootDir, config.plugins);
1606
+ if (guidesWritten > 0) {
1607
+ logger.debug(`Updated ${guidesWritten} AI guide file(s)`);
1608
+ }
1609
+ } catch (guideError) {
1610
+ logger.debug(`Could not generate AI guides: ${guideError.message}`);
1611
+ }
1129
1612
  logger.newline();
1130
1613
  logger.success("Generation complete!");
1131
1614
  if (migrationsGenerated > 0 && config.output.laravel) {
@@ -1156,23 +1639,23 @@ function registerGenerateCommand(program2) {
1156
1639
  }
1157
1640
 
1158
1641
  // src/commands/reset.ts
1159
- import { existsSync as existsSync4, readdirSync, rmSync, statSync } from "fs";
1160
- import { resolve as resolve6, dirname as dirname5, join } from "path";
1642
+ import { existsSync as existsSync5, readdirSync, rmSync, statSync } from "fs";
1643
+ import { resolve as resolve7, dirname as dirname5, join } from "path";
1161
1644
  import { createInterface } from "readline";
1162
1645
  async function confirm2(message) {
1163
1646
  const rl = createInterface({
1164
1647
  input: process.stdin,
1165
1648
  output: process.stdout
1166
1649
  });
1167
- return new Promise((resolve8) => {
1650
+ return new Promise((resolve9) => {
1168
1651
  rl.question(`${message} (y/N) `, (answer) => {
1169
1652
  rl.close();
1170
- resolve8(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
1653
+ resolve9(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
1171
1654
  });
1172
1655
  });
1173
1656
  }
1174
1657
  function countFiles(dir) {
1175
- if (!existsSync4(dir)) return 0;
1658
+ if (!existsSync5(dir)) return 0;
1176
1659
  let count = 0;
1177
1660
  const entries = readdirSync(dir);
1178
1661
  for (const entry of entries) {
@@ -1187,7 +1670,7 @@ function countFiles(dir) {
1187
1670
  return count;
1188
1671
  }
1189
1672
  function deleteDir(dir, verbose) {
1190
- if (!existsSync4(dir)) return 0;
1673
+ if (!existsSync5(dir)) return 0;
1191
1674
  const count = countFiles(dir);
1192
1675
  rmSync(dir, { recursive: true, force: true });
1193
1676
  if (verbose) {
@@ -1196,7 +1679,7 @@ function deleteDir(dir, verbose) {
1196
1679
  return count;
1197
1680
  }
1198
1681
  function deleteFilesInDir(dir, pattern, verbose) {
1199
- if (!existsSync4(dir)) return 0;
1682
+ if (!existsSync5(dir)) return 0;
1200
1683
  let count = 0;
1201
1684
  const entries = readdirSync(dir);
1202
1685
  for (const entry of entries) {
@@ -1225,8 +1708,8 @@ async function runReset(options) {
1225
1708
  "src/Models/OmnifyBase"
1226
1709
  ];
1227
1710
  for (const relPath of omnifyBasePaths) {
1228
- const omnifyBasePath = resolve6(rootDir, relPath);
1229
- if (existsSync4(omnifyBasePath)) {
1711
+ const omnifyBasePath = resolve7(rootDir, relPath);
1712
+ if (existsSync5(omnifyBasePath)) {
1230
1713
  paths.push({ name: "OmnifyBase models", path: omnifyBasePath, type: "dir" });
1231
1714
  break;
1232
1715
  }
@@ -1236,8 +1719,8 @@ async function runReset(options) {
1236
1719
  "backend/database/migrations/omnify"
1237
1720
  ];
1238
1721
  for (const relPath of migrationPaths) {
1239
- const migrationsPath = resolve6(rootDir, relPath);
1240
- if (existsSync4(migrationsPath)) {
1722
+ const migrationsPath = resolve7(rootDir, relPath);
1723
+ if (existsSync5(migrationsPath)) {
1241
1724
  paths.push({
1242
1725
  name: "Omnify migrations",
1243
1726
  path: migrationsPath,
@@ -1249,15 +1732,15 @@ async function runReset(options) {
1249
1732
  }
1250
1733
  const laravelConfig = config.output.laravel;
1251
1734
  if (laravelConfig?.modelsPath) {
1252
- const modelsPath = resolve6(rootDir, laravelConfig.modelsPath);
1735
+ const modelsPath = resolve7(rootDir, laravelConfig.modelsPath);
1253
1736
  const omnifyBasePath = join(modelsPath, "OmnifyBase");
1254
- if (existsSync4(omnifyBasePath) && !paths.some((p) => p.path === omnifyBasePath)) {
1737
+ if (existsSync5(omnifyBasePath) && !paths.some((p) => p.path === omnifyBasePath)) {
1255
1738
  paths.push({ name: "OmnifyBase models", path: omnifyBasePath, type: "dir" });
1256
1739
  }
1257
1740
  }
1258
1741
  if (laravelConfig?.migrationsPath) {
1259
- const migrationsPath = resolve6(rootDir, laravelConfig.migrationsPath);
1260
- if (existsSync4(migrationsPath) && !paths.some((p) => p.path === migrationsPath)) {
1742
+ const migrationsPath = resolve7(rootDir, laravelConfig.migrationsPath);
1743
+ if (existsSync5(migrationsPath) && !paths.some((p) => p.path === migrationsPath)) {
1261
1744
  paths.push({
1262
1745
  name: "Omnify migrations",
1263
1746
  path: migrationsPath,
@@ -1266,12 +1749,12 @@ async function runReset(options) {
1266
1749
  });
1267
1750
  }
1268
1751
  }
1269
- const lockFilePath = resolve6(rootDir, config.lockFilePath);
1270
- if (existsSync4(lockFilePath)) {
1752
+ const lockFilePath = resolve7(rootDir, config.lockFilePath);
1753
+ if (existsSync5(lockFilePath)) {
1271
1754
  paths.push({ name: "Lock file", path: lockFilePath, type: "file" });
1272
1755
  }
1273
- const omnifyDir = resolve6(rootDir, ".omnify");
1274
- if (existsSync4(omnifyDir)) {
1756
+ const omnifyDir = resolve7(rootDir, ".omnify");
1757
+ if (existsSync5(omnifyDir)) {
1275
1758
  paths.push({ name: ".omnify directory", path: omnifyDir, type: "dir" });
1276
1759
  }
1277
1760
  if (paths.length === 0) {
@@ -1372,8 +1855,8 @@ process.on("unhandledRejection", (reason) => {
1372
1855
  var args = process.argv.slice(2);
1373
1856
  var firstArg = args[0];
1374
1857
  var hasCommand = firstArg !== void 0 && !firstArg.startsWith("-");
1375
- var configPath = resolve7(process.cwd(), "omnify.config.ts");
1376
- var hasConfig = existsSync5(configPath);
1858
+ var configPath = resolve8(process.cwd(), "omnify.config.ts");
1859
+ var hasConfig = existsSync6(configPath);
1377
1860
  if (!hasCommand && !hasConfig) {
1378
1861
  runInit({}).catch((error) => {
1379
1862
  if (error instanceof Error) {