@famgia/omnify-cli 0.0.24 → 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
  }
@@ -763,7 +1238,12 @@ function propertyToVersionSnapshot(prop) {
763
1238
  ...prop.mappedBy !== void 0 && { mappedBy: prop.mappedBy },
764
1239
  ...prop.inversedBy !== void 0 && { inversedBy: prop.inversedBy },
765
1240
  ...prop.joinTable !== void 0 && { joinTable: prop.joinTable },
766
- ...prop.owning !== void 0 && { owning: prop.owning }
1241
+ ...prop.owning !== void 0 && { owning: prop.owning },
1242
+ // Laravel-specific properties
1243
+ ...prop.hidden !== void 0 && { hidden: prop.hidden },
1244
+ ...prop.fillable !== void 0 && { fillable: prop.fillable },
1245
+ // Per-field overrides for compound types
1246
+ ...prop.fields !== void 0 && { fields: prop.fields }
767
1247
  };
768
1248
  }
769
1249
  function schemasToVersionSnapshot(schemas) {
@@ -869,17 +1349,17 @@ function schemaChangeToVersionChange(change) {
869
1349
  function writeGeneratorOutputs(outputs, rootDir) {
870
1350
  const counts = { migrations: 0, types: 0, models: 0, factories: 0, other: 0 };
871
1351
  for (const output of outputs) {
872
- const filePath = resolve5(rootDir, output.path);
1352
+ const filePath = resolve6(rootDir, output.path);
873
1353
  const dir = dirname4(filePath);
874
- if (!existsSync3(dir)) {
875
- mkdirSync2(dir, { recursive: true });
1354
+ if (!existsSync4(dir)) {
1355
+ mkdirSync3(dir, { recursive: true });
876
1356
  logger.debug(`Created directory: ${dir}`);
877
1357
  }
878
- if (output.skipIfExists && existsSync3(filePath)) {
1358
+ if (output.skipIfExists && existsSync4(filePath)) {
879
1359
  logger.debug(`Skipped (exists): ${output.path}`);
880
1360
  continue;
881
1361
  }
882
- writeFileSync2(filePath, output.content);
1362
+ writeFileSync3(filePath, output.content);
883
1363
  logger.debug(`Created: ${output.path}`);
884
1364
  if (output.type === "migration") counts.migrations++;
885
1365
  else if (output.type === "type") counts.types++;
@@ -918,9 +1398,9 @@ function runDirectGeneration(schemas, config, rootDir, options, changes) {
918
1398
  let modelsGenerated = 0;
919
1399
  if (!options.typesOnly && config.output.laravel) {
920
1400
  logger.step("Generating Laravel migrations...");
921
- const migrationsDir = resolve5(rootDir, config.output.laravel.migrationsPath);
922
- if (!existsSync3(migrationsDir)) {
923
- mkdirSync2(migrationsDir, { recursive: true });
1401
+ const migrationsDir = resolve6(rootDir, config.output.laravel.migrationsPath);
1402
+ if (!existsSync4(migrationsDir)) {
1403
+ mkdirSync3(migrationsDir, { recursive: true });
924
1404
  logger.debug(`Created directory: ${migrationsDir}`);
925
1405
  }
926
1406
  const addedSchemaNames = new Set(
@@ -935,8 +1415,8 @@ function runDirectGeneration(schemas, config, rootDir, options, changes) {
935
1415
  );
936
1416
  const createMigrations = generateMigrations(addedSchemas);
937
1417
  for (const migration of createMigrations) {
938
- const filePath = resolve5(migrationsDir, migration.fileName);
939
- writeFileSync2(filePath, migration.content);
1418
+ const filePath = resolve6(migrationsDir, migration.fileName);
1419
+ writeFileSync3(filePath, migration.content);
940
1420
  logger.debug(`Created: ${migration.fileName}`);
941
1421
  migrationsGenerated++;
942
1422
  }
@@ -944,8 +1424,8 @@ function runDirectGeneration(schemas, config, rootDir, options, changes) {
944
1424
  if (alterChanges.length > 0) {
945
1425
  const alterMigrations = generateMigrationsFromChanges(alterChanges);
946
1426
  for (const migration of alterMigrations) {
947
- const filePath = resolve5(migrationsDir, migration.fileName);
948
- writeFileSync2(filePath, migration.content);
1427
+ const filePath = resolve6(migrationsDir, migration.fileName);
1428
+ writeFileSync3(filePath, migration.content);
949
1429
  logger.debug(`Created: ${migration.fileName}`);
950
1430
  migrationsGenerated++;
951
1431
  }
@@ -956,29 +1436,29 @@ function runDirectGeneration(schemas, config, rootDir, options, changes) {
956
1436
  logger.step("Generating Laravel models...");
957
1437
  const modelsPath = config.output.laravel.modelsPath;
958
1438
  const baseModelsPath = config.output.laravel.baseModelsPath ?? `${modelsPath}/OmnifyBase`;
959
- const modelsDir = resolve5(rootDir, modelsPath);
960
- const baseModelsDir = resolve5(rootDir, baseModelsPath);
961
- if (!existsSync3(modelsDir)) {
962
- 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 });
963
1443
  }
964
- if (!existsSync3(baseModelsDir)) {
965
- mkdirSync2(baseModelsDir, { recursive: true });
1444
+ if (!existsSync4(baseModelsDir)) {
1445
+ mkdirSync3(baseModelsDir, { recursive: true });
966
1446
  }
967
1447
  const models = generateModels(schemas, {
968
1448
  modelPath: modelsPath,
969
1449
  baseModelPath: baseModelsPath
970
1450
  });
971
1451
  for (const model of models) {
972
- const filePath = resolve5(rootDir, getModelPath(model));
1452
+ const filePath = resolve6(rootDir, getModelPath(model));
973
1453
  const fileDir = dirname4(filePath);
974
- if (!existsSync3(fileDir)) {
975
- mkdirSync2(fileDir, { recursive: true });
1454
+ if (!existsSync4(fileDir)) {
1455
+ mkdirSync3(fileDir, { recursive: true });
976
1456
  }
977
- if (!model.overwrite && existsSync3(filePath)) {
1457
+ if (!model.overwrite && existsSync4(filePath)) {
978
1458
  logger.debug(`Skipped (exists): ${getModelPath(model)}`);
979
1459
  continue;
980
1460
  }
981
- writeFileSync2(filePath, model.content);
1461
+ writeFileSync3(filePath, model.content);
982
1462
  logger.debug(`Created: ${getModelPath(model)}`);
983
1463
  modelsGenerated++;
984
1464
  }
@@ -986,23 +1466,23 @@ function runDirectGeneration(schemas, config, rootDir, options, changes) {
986
1466
  }
987
1467
  if (!options.migrationsOnly && config.output.typescript) {
988
1468
  logger.step("Generating TypeScript types...");
989
- const typesDir = resolve5(rootDir, config.output.typescript.path);
990
- if (!existsSync3(typesDir)) {
991
- mkdirSync2(typesDir, { recursive: true });
1469
+ const typesDir = resolve6(rootDir, config.output.typescript.path);
1470
+ if (!existsSync4(typesDir)) {
1471
+ mkdirSync3(typesDir, { recursive: true });
992
1472
  logger.debug(`Created directory: ${typesDir}`);
993
1473
  }
994
1474
  const typeFiles = generateTypeScript(schemas);
995
1475
  for (const file of typeFiles) {
996
- const filePath = resolve5(typesDir, file.filePath);
1476
+ const filePath = resolve6(typesDir, file.filePath);
997
1477
  const fileDir = dirname4(filePath);
998
- if (!existsSync3(fileDir)) {
999
- mkdirSync2(fileDir, { recursive: true });
1478
+ if (!existsSync4(fileDir)) {
1479
+ mkdirSync3(fileDir, { recursive: true });
1000
1480
  }
1001
- if (!file.overwrite && existsSync3(filePath)) {
1481
+ if (!file.overwrite && existsSync4(filePath)) {
1002
1482
  logger.debug(`Skipped (exists): ${file.filePath}`);
1003
1483
  continue;
1004
1484
  }
1005
- writeFileSync2(filePath, file.content);
1485
+ writeFileSync3(filePath, file.content);
1006
1486
  logger.debug(`Created: ${file.filePath}`);
1007
1487
  typesGenerated++;
1008
1488
  }
@@ -1017,7 +1497,7 @@ async function runGenerate(options) {
1017
1497
  const { config, configPath: configPath2 } = await loadConfig();
1018
1498
  const rootDir = configPath2 ? dirname4(configPath2) : process.cwd();
1019
1499
  validateConfig(config, rootDir);
1020
- const schemaPath = resolve5(rootDir, config.schemasDir);
1500
+ const schemaPath = resolve6(rootDir, config.schemasDir);
1021
1501
  logger.step(`Loading schemas from ${schemaPath}`);
1022
1502
  const schemas = await loadSchemas3(schemaPath);
1023
1503
  const schemaCount = Object.keys(schemas).length;
@@ -1047,7 +1527,7 @@ async function runGenerate(options) {
1047
1527
  process.exit(2);
1048
1528
  }
1049
1529
  logger.step("Checking for changes...");
1050
- const lockPath = resolve5(rootDir, config.lockFilePath);
1530
+ const lockPath = resolve6(rootDir, config.lockFilePath);
1051
1531
  const existingLock = await readLockFile(lockPath);
1052
1532
  const currentSnapshots = await buildSchemaSnapshots(schemas);
1053
1533
  const v2Lock = existingLock && isLockFileV2(existingLock) ? existingLock : null;
@@ -1121,6 +1601,14 @@ async function runGenerate(options) {
1121
1601
  } catch (versionError) {
1122
1602
  logger.debug(`Could not save version history: ${versionError.message}`);
1123
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
+ }
1124
1612
  logger.newline();
1125
1613
  logger.success("Generation complete!");
1126
1614
  if (migrationsGenerated > 0 && config.output.laravel) {
@@ -1151,23 +1639,23 @@ function registerGenerateCommand(program2) {
1151
1639
  }
1152
1640
 
1153
1641
  // src/commands/reset.ts
1154
- import { existsSync as existsSync4, readdirSync, rmSync, statSync } from "fs";
1155
- 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";
1156
1644
  import { createInterface } from "readline";
1157
1645
  async function confirm2(message) {
1158
1646
  const rl = createInterface({
1159
1647
  input: process.stdin,
1160
1648
  output: process.stdout
1161
1649
  });
1162
- return new Promise((resolve8) => {
1650
+ return new Promise((resolve9) => {
1163
1651
  rl.question(`${message} (y/N) `, (answer) => {
1164
1652
  rl.close();
1165
- resolve8(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
1653
+ resolve9(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
1166
1654
  });
1167
1655
  });
1168
1656
  }
1169
1657
  function countFiles(dir) {
1170
- if (!existsSync4(dir)) return 0;
1658
+ if (!existsSync5(dir)) return 0;
1171
1659
  let count = 0;
1172
1660
  const entries = readdirSync(dir);
1173
1661
  for (const entry of entries) {
@@ -1182,7 +1670,7 @@ function countFiles(dir) {
1182
1670
  return count;
1183
1671
  }
1184
1672
  function deleteDir(dir, verbose) {
1185
- if (!existsSync4(dir)) return 0;
1673
+ if (!existsSync5(dir)) return 0;
1186
1674
  const count = countFiles(dir);
1187
1675
  rmSync(dir, { recursive: true, force: true });
1188
1676
  if (verbose) {
@@ -1191,7 +1679,7 @@ function deleteDir(dir, verbose) {
1191
1679
  return count;
1192
1680
  }
1193
1681
  function deleteFilesInDir(dir, pattern, verbose) {
1194
- if (!existsSync4(dir)) return 0;
1682
+ if (!existsSync5(dir)) return 0;
1195
1683
  let count = 0;
1196
1684
  const entries = readdirSync(dir);
1197
1685
  for (const entry of entries) {
@@ -1220,8 +1708,8 @@ async function runReset(options) {
1220
1708
  "src/Models/OmnifyBase"
1221
1709
  ];
1222
1710
  for (const relPath of omnifyBasePaths) {
1223
- const omnifyBasePath = resolve6(rootDir, relPath);
1224
- if (existsSync4(omnifyBasePath)) {
1711
+ const omnifyBasePath = resolve7(rootDir, relPath);
1712
+ if (existsSync5(omnifyBasePath)) {
1225
1713
  paths.push({ name: "OmnifyBase models", path: omnifyBasePath, type: "dir" });
1226
1714
  break;
1227
1715
  }
@@ -1231,8 +1719,8 @@ async function runReset(options) {
1231
1719
  "backend/database/migrations/omnify"
1232
1720
  ];
1233
1721
  for (const relPath of migrationPaths) {
1234
- const migrationsPath = resolve6(rootDir, relPath);
1235
- if (existsSync4(migrationsPath)) {
1722
+ const migrationsPath = resolve7(rootDir, relPath);
1723
+ if (existsSync5(migrationsPath)) {
1236
1724
  paths.push({
1237
1725
  name: "Omnify migrations",
1238
1726
  path: migrationsPath,
@@ -1244,15 +1732,15 @@ async function runReset(options) {
1244
1732
  }
1245
1733
  const laravelConfig = config.output.laravel;
1246
1734
  if (laravelConfig?.modelsPath) {
1247
- const modelsPath = resolve6(rootDir, laravelConfig.modelsPath);
1735
+ const modelsPath = resolve7(rootDir, laravelConfig.modelsPath);
1248
1736
  const omnifyBasePath = join(modelsPath, "OmnifyBase");
1249
- if (existsSync4(omnifyBasePath) && !paths.some((p) => p.path === omnifyBasePath)) {
1737
+ if (existsSync5(omnifyBasePath) && !paths.some((p) => p.path === omnifyBasePath)) {
1250
1738
  paths.push({ name: "OmnifyBase models", path: omnifyBasePath, type: "dir" });
1251
1739
  }
1252
1740
  }
1253
1741
  if (laravelConfig?.migrationsPath) {
1254
- const migrationsPath = resolve6(rootDir, laravelConfig.migrationsPath);
1255
- 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)) {
1256
1744
  paths.push({
1257
1745
  name: "Omnify migrations",
1258
1746
  path: migrationsPath,
@@ -1261,12 +1749,12 @@ async function runReset(options) {
1261
1749
  });
1262
1750
  }
1263
1751
  }
1264
- const lockFilePath = resolve6(rootDir, config.lockFilePath);
1265
- if (existsSync4(lockFilePath)) {
1752
+ const lockFilePath = resolve7(rootDir, config.lockFilePath);
1753
+ if (existsSync5(lockFilePath)) {
1266
1754
  paths.push({ name: "Lock file", path: lockFilePath, type: "file" });
1267
1755
  }
1268
- const omnifyDir = resolve6(rootDir, ".omnify");
1269
- if (existsSync4(omnifyDir)) {
1756
+ const omnifyDir = resolve7(rootDir, ".omnify");
1757
+ if (existsSync5(omnifyDir)) {
1270
1758
  paths.push({ name: ".omnify directory", path: omnifyDir, type: "dir" });
1271
1759
  }
1272
1760
  if (paths.length === 0) {
@@ -1367,8 +1855,8 @@ process.on("unhandledRejection", (reason) => {
1367
1855
  var args = process.argv.slice(2);
1368
1856
  var firstArg = args[0];
1369
1857
  var hasCommand = firstArg !== void 0 && !firstArg.startsWith("-");
1370
- var configPath = resolve7(process.cwd(), "omnify.config.ts");
1371
- var hasConfig = existsSync5(configPath);
1858
+ var configPath = resolve8(process.cwd(), "omnify.config.ts");
1859
+ var hasConfig = existsSync6(configPath);
1372
1860
  if (!hasCommand && !hasConfig) {
1373
1861
  runInit({}).catch((error) => {
1374
1862
  if (error instanceof Error) {