@famgia/omnify-cli 0.0.84 → 0.0.86

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -122,11 +122,14 @@ async function resolveConfig(userConfig, configPath) {
122
122
  migrationsPath: userConfig.output?.laravel?.migrationsPath ?? "database/migrations"
123
123
  };
124
124
  const laravel = buildLaravelConfig(laravelConfig, userConfig.output?.laravel);
125
+ const tsConfig = userConfig.output?.typescript;
125
126
  const typescript = {
126
- path: userConfig.output?.typescript?.path ?? "types",
127
- singleFile: userConfig.output?.typescript?.singleFile ?? true,
128
- generateEnums: userConfig.output?.typescript?.generateEnums ?? true,
129
- generateRelationships: userConfig.output?.typescript?.generateRelationships ?? true
127
+ path: tsConfig?.path ?? "types",
128
+ schemasDir: tsConfig?.schemasDir ?? "schemas",
129
+ enumDir: tsConfig?.enumDir ?? "enum",
130
+ singleFile: tsConfig?.singleFile ?? true,
131
+ generateEnums: tsConfig?.generateEnums ?? true,
132
+ generateRelationships: tsConfig?.generateRelationships ?? true
130
133
  };
131
134
  const result = {
132
135
  schemasDir: userConfig.schemasDir ?? "./schemas",
@@ -724,475 +727,24 @@ var CLAUDE_MD = `## Omnify
724
727
  This project uses Omnify for schema-driven code generation.
725
728
 
726
729
  **Documentation**: \`.claude/omnify/\`
727
- - \`schema-guide.md\` - Schema format and property types
728
- - \`laravel-guide.md\` - Laravel generator (if installed)
729
- - \`typescript-guide.md\` - TypeScript generator (if installed)
730
- - \`japan-guide.md\` - Japan plugin types (if installed)
730
+ - \`guides/omnify/\` - Omnify tool docs (schema syntax, config)
731
+ - \`guides/laravel/\` - Laravel implementation guides
732
+ - \`guides/react/\` - React implementation guides
733
+ - \`checklists/\` - Development checklists
734
+ - \`workflows/\` - Workflow guides
735
+ - \`agents/\` - AI agent prompts
731
736
 
732
737
  **Commands**:
733
738
  - \`npx omnify generate\` - Generate code from schemas
734
739
  - \`npx omnify validate\` - Validate schemas
735
740
  `;
736
- var SCHEMA_GUIDE = `# Omnify Schema Guide
737
-
738
- ## Schema File Format
739
-
740
- Schemas are YAML files defining data models. Each file represents one entity.
741
-
742
- \`\`\`yaml
743
- name: User
744
- displayName:
745
- ja: \u30E6\u30FC\u30B6\u30FC
746
- en: User
747
- kind: object
748
-
749
- properties:
750
- email:
751
- type: Email
752
- unique: true
753
- name:
754
- type: String
755
- bio:
756
- type: Text
757
- nullable: true
758
-
759
- options:
760
- timestamps: true
761
- softDelete: true
762
- \`\`\`
763
-
764
- ## Property Types
765
-
766
- ### String Types
767
- - \`String\` - VARCHAR(255)
768
- - \`Text\` - TEXT
769
- - \`LongText\` - LONGTEXT
770
- - \`Email\` - VARCHAR(255) for email addresses
771
- - \`Password\` - VARCHAR(255), hidden in serialization
772
-
773
- ### Numeric Types
774
- - \`Int\` - INTEGER
775
- - \`BigInt\` - BIGINT
776
- - \`TinyInt\` - TINYINT
777
- - \`Float\` - DOUBLE
778
- - \`Decimal\` - DECIMAL(precision, scale)
779
-
780
- ### Date/Time Types
781
- - \`Date\` - DATE
782
- - \`Time\` - TIME
783
- - \`DateTime\` - DATETIME
784
- - \`Timestamp\` - TIMESTAMP
785
-
786
- ### Other Types
787
- - \`Boolean\` - BOOLEAN
788
- - \`Json\` - JSON
789
- - \`Enum\` - ENUM with values
790
- - \`EnumRef\` - Reference to enum schema
791
-
792
- ## Property Options
793
-
794
- \`\`\`yaml
795
- propertyName:
796
- type: String
797
- nullable: true # Can be NULL
798
- unique: true # Unique constraint
799
- default: "value" # Default value
800
- length: 100 # VARCHAR length
801
- hidden: true # Hide from JSON output
802
- fillable: false # Exclude from mass assignment
803
- \`\`\`
804
-
805
- ## Associations
806
-
807
- \`\`\`yaml
808
- # Many-to-One (belongsTo)
809
- author:
810
- type: Association
811
- relation: ManyToOne
812
- target: User
813
- onDelete: CASCADE
814
-
815
- # One-to-Many (hasMany)
816
- posts:
817
- type: Association
818
- relation: OneToMany
819
- target: Post
820
-
821
- # Many-to-Many (belongsToMany)
822
- tags:
823
- type: Association
824
- relation: ManyToMany
825
- target: Tag
826
- joinTable: post_tags
827
-
828
- # Polymorphic
829
- commentable:
830
- type: Association
831
- relation: MorphTo
832
- \`\`\`
833
-
834
- ## Indexes
835
-
836
- \`\`\`yaml
837
- indexes:
838
- - columns: [status, published_at]
839
- - columns: [email]
840
- unique: true
841
- \`\`\`
842
-
843
- ## Schema Options
844
-
845
- \`\`\`yaml
846
- options:
847
- timestamps: true # Add created_at, updated_at
848
- softDelete: true # Add deleted_at
849
- idType: BigInt # Primary key type (Int, BigInt, Uuid)
850
- \`\`\`
851
- `;
852
- var LARAVEL_GUIDE = `# Laravel Generator Guide
853
-
854
- ## Generated Files
855
-
856
- ### Migrations
857
- Located in \`database/migrations/omnify/\`
858
- - Auto-generated from schema changes
859
- - Handles column additions, modifications, removals
860
- - Preserves manual migrations outside omnify folder
861
-
862
- ### Models
863
- Two-tier model structure:
864
- - \`app/Models/OmnifyBase/*BaseModel.php\` - Auto-generated, DO NOT EDIT
865
- - \`app/Models/*.php\` - User models, extend base models, safe to customize
866
-
867
- ### Factories
868
- Located in \`database/factories/\`
869
- - Generated once, safe to customize
870
- - Uses appropriate Faker methods for each type
871
-
872
- ## Model Features
873
-
874
- ### Fillable
875
- All schema properties are mass-assignable by default.
876
- Use \`fillable: false\` to exclude.
877
-
878
- ### Hidden
879
- Use \`hidden: true\` to exclude from JSON/array output.
880
-
881
- \`\`\`yaml
882
- password:
883
- type: Password
884
- hidden: true
885
- \`\`\`
886
-
887
- ### Casts
888
- Auto-generated based on property types:
889
- - \`Boolean\` \u2192 \`'boolean'\`
890
- - \`Json\` \u2192 \`'array'\`
891
- - \`Timestamp\` \u2192 \`'datetime'\`
892
-
893
- ### Relationships
894
- Generated from Association properties:
895
- - \`ManyToOne\` \u2192 \`belongsTo()\`
896
- - \`OneToMany\` \u2192 \`hasMany()\`
897
- - \`ManyToMany\` \u2192 \`belongsToMany()\`
898
- - \`MorphTo\` \u2192 \`morphTo()\`
899
- - \`MorphMany\` \u2192 \`morphMany()\`
900
-
901
- ## Commands
902
-
903
- \`\`\`bash
904
- # Generate migrations and models
905
- npx omnify generate
906
-
907
- # Force regeneration
908
- npx omnify generate --force
909
-
910
- # Validate schemas
911
- npx omnify validate
912
- \`\`\`
913
- `;
914
- var TYPESCRIPT_GUIDE = `# TypeScript Generator Guide
915
-
916
- ## Generated Types
917
-
918
- Types are generated in the configured output directory.
919
-
920
- ### Interface Generation
921
-
922
- Each schema generates a TypeScript interface:
923
-
924
- \`\`\`typescript
925
- export interface User {
926
- id: number;
927
- email: string;
928
- name: string;
929
- bio: string | null;
930
- created_at: string;
931
- updated_at: string;
932
- }
933
- \`\`\`
934
-
935
- ### Type Mappings
936
-
937
- | Omnify Type | TypeScript Type |
938
- |-------------|-----------------|
939
- | String, Text | string |
940
- | Int, BigInt | number |
941
- | Float, Decimal | number |
942
- | Boolean | boolean |
943
- | Date, DateTime | string |
944
- | Json | Record<string, unknown> |
945
- | Enum | union of literals |
946
-
947
- ### Nullable Types
948
-
949
- Nullable properties become \`T | null\`:
950
-
951
- \`\`\`typescript
952
- bio: string | null;
953
- \`\`\`
954
-
955
- ### Associations
956
-
957
- Associations generate optional relation properties:
958
-
959
- \`\`\`typescript
960
- export interface Post {
961
- id: number;
962
- title: string;
963
- author_id: number;
964
- author?: User; // Optional relation
965
- comments?: Comment[]; // Optional array relation
966
- }
967
- \`\`\`
968
- `;
969
- var JAPAN_GUIDE = `# Japan Plugin Types Guide
970
-
971
- This project uses \`@famgia/omnify-japan\` plugin which provides Japan-specific types.
972
-
973
- ## Available Types
974
-
975
- ### Simple Types
976
-
977
- #### JapanesePhone
978
- Japanese phone number format (e.g., \`090-1234-5678\`, \`03-1234-5678\`)
979
- - SQL: \`VARCHAR(15)\`
980
- - Accepts with or without hyphens
981
-
982
- \`\`\`yaml
983
- phone:
984
- type: JapanesePhone
985
- \`\`\`
986
-
987
- #### JapanesePostalCode
988
- Japanese postal code format (e.g., \`123-4567\`)
989
- - SQL: \`VARCHAR(8)\`
990
- - Accepts with or without hyphen
991
-
992
- \`\`\`yaml
993
- postal_code:
994
- type: JapanesePostalCode
995
- nullable: true
996
- \`\`\`
997
-
998
- ### Compound Types
999
-
1000
- Compound types expand into multiple database columns automatically.
1001
-
1002
- #### JapaneseName
1003
- Japanese name with kanji and kana variants.
1004
-
1005
- **Expands to 4 columns:**
1006
- - \`{property}_lastname\` - VARCHAR(50) - Family name (\u59D3)
1007
- - \`{property}_firstname\` - VARCHAR(50) - Given name (\u540D)
1008
- - \`{property}_kana_lastname\` - VARCHAR(100) - Family name in katakana
1009
- - \`{property}_kana_firstname\` - VARCHAR(100) - Given name in katakana
1010
-
1011
- **Accessors generated:**
1012
- - \`{property}_full_name\` - "\u59D3 \u540D" (space-separated)
1013
- - \`{property}_full_name_kana\` - "\u30BB\u30A4 \u30E1\u30A4" (space-separated)
1014
-
1015
- \`\`\`yaml
1016
- name:
1017
- type: JapaneseName
1018
- displayName:
1019
- ja: \u6C0F\u540D
1020
- en: Full Name
1021
- # Per-field overrides
1022
- fields:
1023
- KanaLastname:
1024
- nullable: true
1025
- hidden: true
1026
- KanaFirstname:
1027
- nullable: true
1028
- hidden: true
1029
- \`\`\`
1030
-
1031
- #### JapaneseAddress
1032
- Japanese address with postal code and prefecture ID.
1033
-
1034
- **Expands to 5 columns:**
1035
- - \`{property}_postal_code\` - VARCHAR(8) - Postal code (\u90F5\u4FBF\u756A\u53F7)
1036
- - \`{property}_prefecture_id\` - TINYINT UNSIGNED - Prefecture ID 1-47 (\u90FD\u9053\u5E9C\u770C)
1037
- - \`{property}_address1\` - VARCHAR(255) - City/Ward (\u5E02\u533A\u753A\u6751)
1038
- - \`{property}_address2\` - VARCHAR(255) - Street address (\u4E01\u76EE\u756A\u5730\u53F7)
1039
- - \`{property}_address3\` - VARCHAR(255) NULLABLE - Building name (\u30D3\u30EB\u30FB\u30DE\u30F3\u30B7\u30E7\u30F3\u540D)
1040
-
1041
- **Accessors generated:**
1042
- - \`{property}_full_address\` - Concatenation of address1 + address2 + address3
1043
-
1044
- \`\`\`yaml
1045
- address:
1046
- type: JapaneseAddress
1047
- displayName:
1048
- ja: \u4F4F\u6240
1049
- en: Address
1050
- fields:
1051
- Address3:
1052
- nullable: true
1053
- \`\`\`
1054
-
1055
- **Prefecture IDs (JIS X 0401):**
1056
- | ID | Prefecture | ID | Prefecture | ID | Prefecture |
1057
- |----|-----------|----|-----------|----|-----------|
1058
- | 1 | \u5317\u6D77\u9053 | 17 | \u77F3\u5DDD\u770C | 33 | \u5CA1\u5C71\u770C |
1059
- | 2 | \u9752\u68EE\u770C | 18 | \u798F\u4E95\u770C | 34 | \u5E83\u5CF6\u770C |
1060
- | 3 | \u5CA9\u624B\u770C | 19 | \u5C71\u68A8\u770C | 35 | \u5C71\u53E3\u770C |
1061
- | 4 | \u5BAE\u57CE\u770C | 20 | \u9577\u91CE\u770C | 36 | \u5FB3\u5CF6\u770C |
1062
- | 5 | \u79CB\u7530\u770C | 21 | \u5C90\u961C\u770C | 37 | \u9999\u5DDD\u770C |
1063
- | 6 | \u5C71\u5F62\u770C | 22 | \u9759\u5CA1\u770C | 38 | \u611B\u5A9B\u770C |
1064
- | 7 | \u798F\u5CF6\u770C | 23 | \u611B\u77E5\u770C | 39 | \u9AD8\u77E5\u770C |
1065
- | 8 | \u8328\u57CE\u770C | 24 | \u4E09\u91CD\u770C | 40 | \u798F\u5CA1\u770C |
1066
- | 9 | \u6803\u6728\u770C | 25 | \u6ECB\u8CC0\u770C | 41 | \u4F50\u8CC0\u770C |
1067
- | 10 | \u7FA4\u99AC\u770C | 26 | \u4EAC\u90FD\u5E9C | 42 | \u9577\u5D0E\u770C |
1068
- | 11 | \u57FC\u7389\u770C | 27 | \u5927\u962A\u5E9C | 43 | \u718A\u672C\u770C |
1069
- | 12 | \u5343\u8449\u770C | 28 | \u5175\u5EAB\u770C | 44 | \u5927\u5206\u770C |
1070
- | 13 | \u6771\u4EAC\u90FD | 29 | \u5948\u826F\u770C | 45 | \u5BAE\u5D0E\u770C |
1071
- | 14 | \u795E\u5948\u5DDD\u770C | 30 | \u548C\u6B4C\u5C71\u770C | 46 | \u9E7F\u5150\u5CF6\u770C |
1072
- | 15 | \u65B0\u6F5F\u770C | 31 | \u9CE5\u53D6\u770C | 47 | \u6C96\u7E04\u770C |
1073
- | 16 | \u5BCC\u5C71\u770C | 32 | \u5CF6\u6839\u770C | | |
1074
-
1075
- #### JapaneseBankAccount
1076
- Japanese bank account information.
1077
-
1078
- **Expands to 5 columns:**
1079
- - \`{property}_bank_code\` - VARCHAR(4) - Bank code (\u9280\u884C\u30B3\u30FC\u30C9)
1080
- - \`{property}_branch_code\` - VARCHAR(3) - Branch code (\u652F\u5E97\u30B3\u30FC\u30C9)
1081
- - \`{property}_account_type\` - ENUM - Account type: 1=\u666E\u901A, 2=\u5F53\u5EA7, 4=\u8CAF\u84C4
1082
- - \`{property}_account_number\` - VARCHAR(7) - Account number (\u53E3\u5EA7\u756A\u53F7)
1083
- - \`{property}_account_holder\` - VARCHAR(100) - Account holder name (\u53E3\u5EA7\u540D\u7FA9)
1084
-
1085
- \`\`\`yaml
1086
- bank_account:
1087
- type: JapaneseBankAccount
1088
- \`\`\`
1089
-
1090
- ## Per-field Overrides
1091
-
1092
- All compound types support per-field overrides:
1093
-
1094
- \`\`\`yaml
1095
- name:
1096
- type: JapaneseName
1097
- fields:
1098
- Lastname:
1099
- length: 100 # Override default VARCHAR length
1100
- Firstname:
1101
- length: 100
1102
- KanaLastname:
1103
- length: 200
1104
- nullable: true
1105
- hidden: true
1106
- KanaFirstname:
1107
- length: 200
1108
- nullable: true
1109
- hidden: true
1110
- \`\`\`
1111
-
1112
- **Available overrides:**
1113
- - \`length\` - VARCHAR length (override default)
1114
- - \`nullable\` - Whether the field can be NULL
1115
- - \`hidden\` - Exclude from JSON/array output
1116
- - \`fillable\` - Control mass assignment
1117
-
1118
- ## Factory Examples
1119
-
1120
- \`\`\`php
1121
- $faker = fake('ja_JP');
1122
-
1123
- return [
1124
- // JapaneseName
1125
- 'name_lastname' => $faker->lastName(),
1126
- 'name_firstname' => $faker->firstName(),
1127
- 'name_kana_lastname' => $faker->lastKanaName(),
1128
- 'name_kana_firstname' => $faker->firstKanaName(),
1129
-
1130
- // JapanesePhone
1131
- 'phone' => $faker->phoneNumber(),
1132
-
1133
- // JapanesePostalCode
1134
- 'postal_code' => $faker->postcode(),
1135
-
1136
- // JapaneseAddress
1137
- 'address_postal_code' => $faker->postcode(),
1138
- 'address_prefecture_id' => $faker->numberBetween(1, 47),
1139
- 'address_address1' => $faker->city(),
1140
- 'address_address2' => $faker->streetAddress(),
1141
- 'address_address3' => $faker->optional(0.5)->secondaryAddress(),
1142
- ];
1143
- \`\`\`
1144
-
1145
- ## Model Accessors
1146
-
1147
- \`\`\`php
1148
- // JapaneseName accessors
1149
- $customer->name_full_name; // "\u7530\u4E2D \u592A\u90CE"
1150
- $customer->name_full_name_kana; // "\u30BF\u30CA\u30AB \u30BF\u30ED\u30A6"
1151
-
1152
- // JapaneseAddress accessor
1153
- $customer->address_full_address; // "\u5343\u4EE3\u7530\u533A\u4E38\u306E\u51851-1-1\u30D3\u30EB5F"
1154
- \`\`\`
1155
- `;
1156
- function isJapanPlugin(plugin) {
1157
- return plugin.name === "@famgia/omnify-japan";
1158
- }
1159
- function isLaravelPlugin(plugin) {
1160
- return plugin.name === "@famgia/omnify-laravel";
1161
- }
1162
- function isTypeScriptPlugin(plugin) {
1163
- return plugin.name === "@famgia/omnify-typescript";
1164
- }
1165
- function generateAIGuides(rootDir, plugins) {
1166
- const guidesDir = (0, import_node_path5.resolve)(rootDir, ".claude/omnify");
741
+ function generateAIGuides(rootDir, _plugins) {
1167
742
  let filesWritten = 0;
1168
- if (!(0, import_node_fs3.existsSync)(guidesDir)) {
1169
- (0, import_node_fs3.mkdirSync)(guidesDir, { recursive: true });
1170
- }
1171
743
  const claudeMdPath = (0, import_node_path5.resolve)(rootDir, "CLAUDE.md");
1172
744
  if (!(0, import_node_fs3.existsSync)(claudeMdPath)) {
1173
745
  (0, import_node_fs3.writeFileSync)(claudeMdPath, CLAUDE_MD);
1174
746
  filesWritten++;
1175
747
  }
1176
- const schemaGuidePath = (0, import_node_path5.resolve)(guidesDir, "schema-guide.md");
1177
- (0, import_node_fs3.writeFileSync)(schemaGuidePath, SCHEMA_GUIDE);
1178
- filesWritten++;
1179
- for (const plugin of plugins) {
1180
- if (isLaravelPlugin(plugin)) {
1181
- const laravelGuidePath = (0, import_node_path5.resolve)(guidesDir, "laravel-guide.md");
1182
- (0, import_node_fs3.writeFileSync)(laravelGuidePath, LARAVEL_GUIDE);
1183
- filesWritten++;
1184
- }
1185
- if (isTypeScriptPlugin(plugin)) {
1186
- const tsGuidePath = (0, import_node_path5.resolve)(guidesDir, "typescript-guide.md");
1187
- (0, import_node_fs3.writeFileSync)(tsGuidePath, TYPESCRIPT_GUIDE);
1188
- filesWritten++;
1189
- }
1190
- if (isJapanPlugin(plugin)) {
1191
- const japanGuidePath = (0, import_node_path5.resolve)(guidesDir, "japan-guide.md");
1192
- (0, import_node_fs3.writeFileSync)(japanGuidePath, JAPAN_GUIDE);
1193
- filesWritten++;
1194
- }
1195
- }
1196
748
  return filesWritten;
1197
749
  }
1198
750
 
@@ -1451,6 +1003,14 @@ function runDirectGeneration(schemas, config, rootDir, options, changes) {
1451
1003
  }
1452
1004
  }
1453
1005
  }
1006
+ const pluginEnumsMap = /* @__PURE__ */ new Map();
1007
+ for (const plugin of config.plugins) {
1008
+ if (plugin.enums) {
1009
+ for (const enumDef of plugin.enums) {
1010
+ pluginEnumsMap.set(enumDef.name, enumDef);
1011
+ }
1012
+ }
1013
+ }
1454
1014
  if (!options.typesOnly && config.output.laravel) {
1455
1015
  logger.step("Generating Laravel migrations...");
1456
1016
  const migrationsDir = (0, import_node_path6.resolve)(rootDir, config.output.laravel.migrationsPath);
@@ -1556,22 +1116,32 @@ function runDirectGeneration(schemas, config, rootDir, options, changes) {
1556
1116
  }
1557
1117
  if (!options.migrationsOnly && config.output.typescript) {
1558
1118
  logger.step("Generating TypeScript types...");
1559
- const typesDir = (0, import_node_path6.resolve)(rootDir, config.output.typescript.path);
1560
- if (!(0, import_node_fs4.existsSync)(typesDir)) {
1561
- (0, import_node_fs4.mkdirSync)(typesDir, { recursive: true });
1562
- logger.debug(`Created directory: ${typesDir}`);
1119
+ const tsConfig = config.output.typescript;
1120
+ const basePath = (0, import_node_path6.resolve)(rootDir, tsConfig.path);
1121
+ const schemasDir = (0, import_node_path6.resolve)(basePath, tsConfig.schemasDir ?? "schemas");
1122
+ const enumDir = (0, import_node_path6.resolve)(basePath, tsConfig.enumDir ?? "enum");
1123
+ const enumImportPrefix = (0, import_node_path6.relative)(schemasDir, enumDir).replace(/\\/g, "/");
1124
+ if (!(0, import_node_fs4.existsSync)(schemasDir)) {
1125
+ (0, import_node_fs4.mkdirSync)(schemasDir, { recursive: true });
1126
+ logger.debug(`Created directory: ${schemasDir}`);
1127
+ }
1128
+ if (!(0, import_node_fs4.existsSync)(enumDir)) {
1129
+ (0, import_node_fs4.mkdirSync)(enumDir, { recursive: true });
1130
+ logger.debug(`Created directory: ${enumDir}`);
1563
1131
  }
1564
1132
  const isMultiLocale = config.locale && config.locale.locales && config.locale.locales.length > 1;
1565
- const typescriptConfig = config.output.typescript;
1566
1133
  const typeFiles = (0, import_omnify_typescript.generateTypeScript)(schemas, {
1567
1134
  customTypes: customTypesMap,
1135
+ pluginEnums: pluginEnumsMap,
1568
1136
  localeConfig: config.locale,
1569
1137
  multiLocale: isMultiLocale,
1570
- generateRules: typescriptConfig.generateRules ?? true,
1571
- validationTemplates: typescriptConfig.validationTemplates
1138
+ generateRules: tsConfig.generateRules ?? true,
1139
+ validationTemplates: tsConfig.validationTemplates,
1140
+ enumImportPrefix
1572
1141
  });
1573
1142
  for (const file of typeFiles) {
1574
- const filePath = (0, import_node_path6.resolve)(typesDir, file.filePath);
1143
+ const outputDir = file.category === "enum" ? enumDir : schemasDir;
1144
+ const filePath = (0, import_node_path6.resolve)(outputDir, file.filePath);
1575
1145
  const fileDir = (0, import_node_path6.dirname)(filePath);
1576
1146
  if (!(0, import_node_fs4.existsSync)(fileDir)) {
1577
1147
  (0, import_node_fs4.mkdirSync)(fileDir, { recursive: true });
@@ -1585,6 +1155,13 @@ function runDirectGeneration(schemas, config, rootDir, options, changes) {
1585
1155
  typesGenerated++;
1586
1156
  }
1587
1157
  logger.success(`Generated ${typesGenerated} TypeScript file(s)`);
1158
+ const stubsResult = (0, import_omnify_typescript.copyStubs)({
1159
+ targetDir: basePath,
1160
+ skipIfExists: true
1161
+ });
1162
+ if (stubsResult.copied.length > 0) {
1163
+ logger.success(`Generated ${stubsResult.copied.length} React stub(s)`);
1164
+ }
1588
1165
  }
1589
1166
  return { migrations: migrationsGenerated, types: typesGenerated, models: modelsGenerated, factories: factoriesGenerated };
1590
1167
  }
@@ -1631,7 +1208,9 @@ async function runGenerate(options) {
1631
1208
  const v2Lock = existingLock && (0, import_omnify_atlas2.isLockFileV2)(existingLock) ? existingLock : null;
1632
1209
  const comparison = (0, import_omnify_atlas2.compareSchemasDeep)(currentSnapshots, v2Lock);
1633
1210
  const skipMigrations = !comparison.hasChanges && !options.force;
1634
- if (skipMigrations && !config.output.laravel?.modelsPath) {
1211
+ const pluginsHaveGenerators = config.plugins.some((p) => p.generators && p.generators.length > 0);
1212
+ const hasTypescriptOutput = !!config.output.typescript;
1213
+ if (skipMigrations && !config.output.laravel?.modelsPath && !pluginsHaveGenerators && !hasTypescriptOutput) {
1635
1214
  logger.success("No changes to generate");
1636
1215
  return;
1637
1216
  }
@@ -1654,6 +1233,14 @@ async function runGenerate(options) {
1654
1233
  }
1655
1234
  }
1656
1235
  }
1236
+ const pluginEnumsMap = /* @__PURE__ */ new Map();
1237
+ for (const plugin of config.plugins) {
1238
+ if (plugin.enums) {
1239
+ for (const enumDef of plugin.enums) {
1240
+ pluginEnumsMap.set(enumDef.name, enumDef);
1241
+ }
1242
+ }
1243
+ }
1657
1244
  if (usePlugins) {
1658
1245
  logger.step("Running plugin generators...");
1659
1246
  const counts = await runPluginGeneration(
@@ -1682,22 +1269,32 @@ async function runGenerate(options) {
1682
1269
  }
1683
1270
  if (!options.migrationsOnly && config.output.typescript && typesGenerated === 0) {
1684
1271
  logger.step("Generating TypeScript types...");
1685
- const typesDir = (0, import_node_path6.resolve)(rootDir, config.output.typescript.path);
1686
- if (!(0, import_node_fs4.existsSync)(typesDir)) {
1687
- (0, import_node_fs4.mkdirSync)(typesDir, { recursive: true });
1688
- logger.debug(`Created directory: ${typesDir}`);
1272
+ const tsConfig2 = config.output.typescript;
1273
+ const basePath2 = (0, import_node_path6.resolve)(rootDir, tsConfig2.path);
1274
+ const schemasDir2 = (0, import_node_path6.resolve)(basePath2, tsConfig2.schemasDir ?? "schemas");
1275
+ const enumDir2 = (0, import_node_path6.resolve)(basePath2, tsConfig2.enumDir ?? "enum");
1276
+ const enumImportPrefix2 = (0, import_node_path6.relative)(schemasDir2, enumDir2).replace(/\\/g, "/");
1277
+ if (!(0, import_node_fs4.existsSync)(schemasDir2)) {
1278
+ (0, import_node_fs4.mkdirSync)(schemasDir2, { recursive: true });
1279
+ logger.debug(`Created directory: ${schemasDir2}`);
1280
+ }
1281
+ if (!(0, import_node_fs4.existsSync)(enumDir2)) {
1282
+ (0, import_node_fs4.mkdirSync)(enumDir2, { recursive: true });
1283
+ logger.debug(`Created directory: ${enumDir2}`);
1689
1284
  }
1690
1285
  const isMultiLocale = config.locale && config.locale.locales && config.locale.locales.length > 1;
1691
- const typescriptConfig = config.output.typescript;
1692
1286
  const typeFiles = (0, import_omnify_typescript.generateTypeScript)(schemas, {
1693
1287
  customTypes: customTypesMap,
1288
+ pluginEnums: pluginEnumsMap,
1694
1289
  localeConfig: config.locale,
1695
1290
  multiLocale: isMultiLocale,
1696
- generateRules: typescriptConfig.generateRules ?? true,
1697
- validationTemplates: typescriptConfig.validationTemplates
1291
+ generateRules: tsConfig2.generateRules ?? true,
1292
+ validationTemplates: tsConfig2.validationTemplates,
1293
+ enumImportPrefix: enumImportPrefix2
1698
1294
  });
1699
1295
  for (const file of typeFiles) {
1700
- const filePath = (0, import_node_path6.resolve)(typesDir, file.filePath);
1296
+ const outputDir2 = file.category === "enum" ? enumDir2 : schemasDir2;
1297
+ const filePath = (0, import_node_path6.resolve)(outputDir2, file.filePath);
1701
1298
  const fileDir = (0, import_node_path6.dirname)(filePath);
1702
1299
  if (!(0, import_node_fs4.existsSync)(fileDir)) {
1703
1300
  (0, import_node_fs4.mkdirSync)(fileDir, { recursive: true });
@@ -1711,6 +1308,22 @@ async function runGenerate(options) {
1711
1308
  typesGenerated++;
1712
1309
  }
1713
1310
  logger.success(`Generated ${typesGenerated} TypeScript file(s)`);
1311
+ const stubsResult2 = (0, import_omnify_typescript.copyStubs)({
1312
+ targetDir: basePath2,
1313
+ skipIfExists: true
1314
+ });
1315
+ if (stubsResult2.copied.length > 0) {
1316
+ logger.success(`Generated ${stubsResult2.copied.length} React stub(s)`);
1317
+ }
1318
+ if ((0, import_omnify_typescript.shouldGenerateAIGuides)(rootDir)) {
1319
+ const tsAIResult = (0, import_omnify_typescript.generateAIGuides)(rootDir, {
1320
+ typescriptPath: tsConfig2.path
1321
+ });
1322
+ const tsClaudeTotal = tsAIResult.claudeGuides + tsAIResult.claudeChecklists;
1323
+ if (tsClaudeTotal > 0 || tsAIResult.cursorRules > 0) {
1324
+ logger.debug(`Generated ${tsClaudeTotal} React Claude files, ${tsAIResult.cursorRules} Cursor rules`);
1325
+ }
1326
+ }
1714
1327
  }
1715
1328
  } else {
1716
1329
  const counts = runDirectGeneration(schemas, config, rootDir, options, comparison.changes);