@famgia/omnify-cli 0.0.85 → 0.0.87
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 +109 -494
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +92 -479
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +105 -492
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
package/dist/cli.js
CHANGED
|
@@ -440,11 +440,14 @@ async function resolveConfig(userConfig, configPath2) {
|
|
|
440
440
|
migrationsPath: userConfig.output?.laravel?.migrationsPath ?? "database/migrations"
|
|
441
441
|
};
|
|
442
442
|
const laravel = buildLaravelConfig(laravelConfig, userConfig.output?.laravel);
|
|
443
|
+
const tsConfig = userConfig.output?.typescript;
|
|
443
444
|
const typescript = {
|
|
444
|
-
path:
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
445
|
+
path: tsConfig?.path ?? "types",
|
|
446
|
+
schemasDir: tsConfig?.schemasDir ?? "schemas",
|
|
447
|
+
enumDir: tsConfig?.enumDir ?? "enum",
|
|
448
|
+
singleFile: tsConfig?.singleFile ?? true,
|
|
449
|
+
generateEnums: tsConfig?.generateEnums ?? true,
|
|
450
|
+
generateRelationships: tsConfig?.generateRelationships ?? true
|
|
448
451
|
};
|
|
449
452
|
const result = {
|
|
450
453
|
schemasDir: userConfig.schemasDir ?? "./schemas",
|
|
@@ -676,8 +679,8 @@ function registerDiffCommand(program2) {
|
|
|
676
679
|
}
|
|
677
680
|
|
|
678
681
|
// src/commands/generate.ts
|
|
679
|
-
import { existsSync as existsSync4, mkdirSync as
|
|
680
|
-
import { resolve as resolve6, dirname as dirname4 } from "path";
|
|
682
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync2, writeFileSync as writeFileSync3, readdirSync } from "fs";
|
|
683
|
+
import { resolve as resolve6, dirname as dirname4, relative } from "path";
|
|
681
684
|
import {
|
|
682
685
|
loadSchemas as loadSchemas3,
|
|
683
686
|
validateSchemas as validateSchemas3,
|
|
@@ -701,485 +704,34 @@ import {
|
|
|
701
704
|
generateFactories,
|
|
702
705
|
getFactoryPath
|
|
703
706
|
} from "@famgia/omnify-laravel";
|
|
704
|
-
import { generateTypeScript } from "@famgia/omnify-typescript";
|
|
707
|
+
import { generateTypeScript, copyStubs, generateAIGuides as generateTypescriptAIGuides, shouldGenerateAIGuides as shouldGenerateTypescriptAIGuides } from "@famgia/omnify-typescript";
|
|
705
708
|
|
|
706
709
|
// src/guides/index.ts
|
|
707
|
-
import { existsSync as existsSync3,
|
|
710
|
+
import { existsSync as existsSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
708
711
|
import { resolve as resolve5 } from "path";
|
|
709
712
|
var CLAUDE_MD = `## Omnify
|
|
710
713
|
|
|
711
714
|
This project uses Omnify for schema-driven code generation.
|
|
712
715
|
|
|
713
716
|
**Documentation**: \`.claude/omnify/\`
|
|
714
|
-
- \`
|
|
715
|
-
- \`laravel
|
|
716
|
-
- \`
|
|
717
|
-
- \`
|
|
717
|
+
- \`guides/omnify/\` - Omnify tool docs (schema syntax, config)
|
|
718
|
+
- \`guides/laravel/\` - Laravel implementation guides
|
|
719
|
+
- \`guides/react/\` - React implementation guides
|
|
720
|
+
- \`checklists/\` - Development checklists
|
|
721
|
+
- \`workflows/\` - Workflow guides
|
|
722
|
+
- \`agents/\` - AI agent prompts
|
|
718
723
|
|
|
719
724
|
**Commands**:
|
|
720
725
|
- \`npx omnify generate\` - Generate code from schemas
|
|
721
726
|
- \`npx omnify validate\` - Validate schemas
|
|
722
727
|
`;
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
## Schema File Format
|
|
726
|
-
|
|
727
|
-
Schemas are YAML files defining data models. Each file represents one entity.
|
|
728
|
-
|
|
729
|
-
\`\`\`yaml
|
|
730
|
-
name: User
|
|
731
|
-
displayName:
|
|
732
|
-
ja: \u30E6\u30FC\u30B6\u30FC
|
|
733
|
-
en: User
|
|
734
|
-
kind: object
|
|
735
|
-
|
|
736
|
-
properties:
|
|
737
|
-
email:
|
|
738
|
-
type: Email
|
|
739
|
-
unique: true
|
|
740
|
-
name:
|
|
741
|
-
type: String
|
|
742
|
-
bio:
|
|
743
|
-
type: Text
|
|
744
|
-
nullable: true
|
|
745
|
-
|
|
746
|
-
options:
|
|
747
|
-
timestamps: true
|
|
748
|
-
softDelete: true
|
|
749
|
-
\`\`\`
|
|
750
|
-
|
|
751
|
-
## Property Types
|
|
752
|
-
|
|
753
|
-
### String Types
|
|
754
|
-
- \`String\` - VARCHAR(255)
|
|
755
|
-
- \`Text\` - TEXT
|
|
756
|
-
- \`LongText\` - LONGTEXT
|
|
757
|
-
- \`Email\` - VARCHAR(255) for email addresses
|
|
758
|
-
- \`Password\` - VARCHAR(255), hidden in serialization
|
|
759
|
-
|
|
760
|
-
### Numeric Types
|
|
761
|
-
- \`Int\` - INTEGER
|
|
762
|
-
- \`BigInt\` - BIGINT
|
|
763
|
-
- \`TinyInt\` - TINYINT
|
|
764
|
-
- \`Float\` - DOUBLE
|
|
765
|
-
- \`Decimal\` - DECIMAL(precision, scale)
|
|
766
|
-
|
|
767
|
-
### Date/Time Types
|
|
768
|
-
- \`Date\` - DATE
|
|
769
|
-
- \`Time\` - TIME
|
|
770
|
-
- \`DateTime\` - DATETIME
|
|
771
|
-
- \`Timestamp\` - TIMESTAMP
|
|
772
|
-
|
|
773
|
-
### Other Types
|
|
774
|
-
- \`Boolean\` - BOOLEAN
|
|
775
|
-
- \`Json\` - JSON
|
|
776
|
-
- \`Enum\` - ENUM with values
|
|
777
|
-
- \`EnumRef\` - Reference to enum schema
|
|
778
|
-
|
|
779
|
-
## Property Options
|
|
780
|
-
|
|
781
|
-
\`\`\`yaml
|
|
782
|
-
propertyName:
|
|
783
|
-
type: String
|
|
784
|
-
nullable: true # Can be NULL
|
|
785
|
-
unique: true # Unique constraint
|
|
786
|
-
default: "value" # Default value
|
|
787
|
-
length: 100 # VARCHAR length
|
|
788
|
-
hidden: true # Hide from JSON output
|
|
789
|
-
fillable: false # Exclude from mass assignment
|
|
790
|
-
\`\`\`
|
|
791
|
-
|
|
792
|
-
## Associations
|
|
793
|
-
|
|
794
|
-
\`\`\`yaml
|
|
795
|
-
# Many-to-One (belongsTo)
|
|
796
|
-
author:
|
|
797
|
-
type: Association
|
|
798
|
-
relation: ManyToOne
|
|
799
|
-
target: User
|
|
800
|
-
onDelete: CASCADE
|
|
801
|
-
|
|
802
|
-
# One-to-Many (hasMany)
|
|
803
|
-
posts:
|
|
804
|
-
type: Association
|
|
805
|
-
relation: OneToMany
|
|
806
|
-
target: Post
|
|
807
|
-
|
|
808
|
-
# Many-to-Many (belongsToMany)
|
|
809
|
-
tags:
|
|
810
|
-
type: Association
|
|
811
|
-
relation: ManyToMany
|
|
812
|
-
target: Tag
|
|
813
|
-
joinTable: post_tags
|
|
814
|
-
|
|
815
|
-
# Polymorphic
|
|
816
|
-
commentable:
|
|
817
|
-
type: Association
|
|
818
|
-
relation: MorphTo
|
|
819
|
-
\`\`\`
|
|
820
|
-
|
|
821
|
-
## Indexes
|
|
822
|
-
|
|
823
|
-
\`\`\`yaml
|
|
824
|
-
indexes:
|
|
825
|
-
- columns: [status, published_at]
|
|
826
|
-
- columns: [email]
|
|
827
|
-
unique: true
|
|
828
|
-
\`\`\`
|
|
829
|
-
|
|
830
|
-
## Schema Options
|
|
831
|
-
|
|
832
|
-
\`\`\`yaml
|
|
833
|
-
options:
|
|
834
|
-
timestamps: true # Add created_at, updated_at
|
|
835
|
-
softDelete: true # Add deleted_at
|
|
836
|
-
idType: BigInt # Primary key type (Int, BigInt, Uuid)
|
|
837
|
-
\`\`\`
|
|
838
|
-
`;
|
|
839
|
-
var LARAVEL_GUIDE = `# Laravel Generator Guide
|
|
840
|
-
|
|
841
|
-
## Generated Files
|
|
842
|
-
|
|
843
|
-
### Migrations
|
|
844
|
-
Located in \`database/migrations/omnify/\`
|
|
845
|
-
- Auto-generated from schema changes
|
|
846
|
-
- Handles column additions, modifications, removals
|
|
847
|
-
- Preserves manual migrations outside omnify folder
|
|
848
|
-
|
|
849
|
-
### Models
|
|
850
|
-
Two-tier model structure:
|
|
851
|
-
- \`app/Models/OmnifyBase/*BaseModel.php\` - Auto-generated, DO NOT EDIT
|
|
852
|
-
- \`app/Models/*.php\` - User models, extend base models, safe to customize
|
|
853
|
-
|
|
854
|
-
### Factories
|
|
855
|
-
Located in \`database/factories/\`
|
|
856
|
-
- Generated once, safe to customize
|
|
857
|
-
- Uses appropriate Faker methods for each type
|
|
858
|
-
|
|
859
|
-
## Model Features
|
|
860
|
-
|
|
861
|
-
### Fillable
|
|
862
|
-
All schema properties are mass-assignable by default.
|
|
863
|
-
Use \`fillable: false\` to exclude.
|
|
864
|
-
|
|
865
|
-
### Hidden
|
|
866
|
-
Use \`hidden: true\` to exclude from JSON/array output.
|
|
867
|
-
|
|
868
|
-
\`\`\`yaml
|
|
869
|
-
password:
|
|
870
|
-
type: Password
|
|
871
|
-
hidden: true
|
|
872
|
-
\`\`\`
|
|
873
|
-
|
|
874
|
-
### Casts
|
|
875
|
-
Auto-generated based on property types:
|
|
876
|
-
- \`Boolean\` \u2192 \`'boolean'\`
|
|
877
|
-
- \`Json\` \u2192 \`'array'\`
|
|
878
|
-
- \`Timestamp\` \u2192 \`'datetime'\`
|
|
879
|
-
|
|
880
|
-
### Relationships
|
|
881
|
-
Generated from Association properties:
|
|
882
|
-
- \`ManyToOne\` \u2192 \`belongsTo()\`
|
|
883
|
-
- \`OneToMany\` \u2192 \`hasMany()\`
|
|
884
|
-
- \`ManyToMany\` \u2192 \`belongsToMany()\`
|
|
885
|
-
- \`MorphTo\` \u2192 \`morphTo()\`
|
|
886
|
-
- \`MorphMany\` \u2192 \`morphMany()\`
|
|
887
|
-
|
|
888
|
-
## Commands
|
|
889
|
-
|
|
890
|
-
\`\`\`bash
|
|
891
|
-
# Generate migrations and models
|
|
892
|
-
npx omnify generate
|
|
893
|
-
|
|
894
|
-
# Force regeneration
|
|
895
|
-
npx omnify generate --force
|
|
896
|
-
|
|
897
|
-
# Validate schemas
|
|
898
|
-
npx omnify validate
|
|
899
|
-
\`\`\`
|
|
900
|
-
`;
|
|
901
|
-
var TYPESCRIPT_GUIDE = `# TypeScript Generator Guide
|
|
902
|
-
|
|
903
|
-
## Generated Types
|
|
904
|
-
|
|
905
|
-
Types are generated in the configured output directory.
|
|
906
|
-
|
|
907
|
-
### Interface Generation
|
|
908
|
-
|
|
909
|
-
Each schema generates a TypeScript interface:
|
|
910
|
-
|
|
911
|
-
\`\`\`typescript
|
|
912
|
-
export interface User {
|
|
913
|
-
id: number;
|
|
914
|
-
email: string;
|
|
915
|
-
name: string;
|
|
916
|
-
bio: string | null;
|
|
917
|
-
created_at: string;
|
|
918
|
-
updated_at: string;
|
|
919
|
-
}
|
|
920
|
-
\`\`\`
|
|
921
|
-
|
|
922
|
-
### Type Mappings
|
|
923
|
-
|
|
924
|
-
| Omnify Type | TypeScript Type |
|
|
925
|
-
|-------------|-----------------|
|
|
926
|
-
| String, Text | string |
|
|
927
|
-
| Int, BigInt | number |
|
|
928
|
-
| Float, Decimal | number |
|
|
929
|
-
| Boolean | boolean |
|
|
930
|
-
| Date, DateTime | string |
|
|
931
|
-
| Json | Record<string, unknown> |
|
|
932
|
-
| Enum | union of literals |
|
|
933
|
-
|
|
934
|
-
### Nullable Types
|
|
935
|
-
|
|
936
|
-
Nullable properties become \`T | null\`:
|
|
937
|
-
|
|
938
|
-
\`\`\`typescript
|
|
939
|
-
bio: string | null;
|
|
940
|
-
\`\`\`
|
|
941
|
-
|
|
942
|
-
### Associations
|
|
943
|
-
|
|
944
|
-
Associations generate optional relation properties:
|
|
945
|
-
|
|
946
|
-
\`\`\`typescript
|
|
947
|
-
export interface Post {
|
|
948
|
-
id: number;
|
|
949
|
-
title: string;
|
|
950
|
-
author_id: number;
|
|
951
|
-
author?: User; // Optional relation
|
|
952
|
-
comments?: Comment[]; // Optional array relation
|
|
953
|
-
}
|
|
954
|
-
\`\`\`
|
|
955
|
-
`;
|
|
956
|
-
var JAPAN_GUIDE = `# Japan Plugin Types Guide
|
|
957
|
-
|
|
958
|
-
This project uses \`@famgia/omnify-japan\` plugin which provides Japan-specific types.
|
|
959
|
-
|
|
960
|
-
## Available Types
|
|
961
|
-
|
|
962
|
-
### Simple Types
|
|
963
|
-
|
|
964
|
-
#### JapanesePhone
|
|
965
|
-
Japanese phone number format (e.g., \`090-1234-5678\`, \`03-1234-5678\`)
|
|
966
|
-
- SQL: \`VARCHAR(15)\`
|
|
967
|
-
- Accepts with or without hyphens
|
|
968
|
-
|
|
969
|
-
\`\`\`yaml
|
|
970
|
-
phone:
|
|
971
|
-
type: JapanesePhone
|
|
972
|
-
\`\`\`
|
|
973
|
-
|
|
974
|
-
#### JapanesePostalCode
|
|
975
|
-
Japanese postal code format (e.g., \`123-4567\`)
|
|
976
|
-
- SQL: \`VARCHAR(8)\`
|
|
977
|
-
- Accepts with or without hyphen
|
|
978
|
-
|
|
979
|
-
\`\`\`yaml
|
|
980
|
-
postal_code:
|
|
981
|
-
type: JapanesePostalCode
|
|
982
|
-
nullable: true
|
|
983
|
-
\`\`\`
|
|
984
|
-
|
|
985
|
-
### Compound Types
|
|
986
|
-
|
|
987
|
-
Compound types expand into multiple database columns automatically.
|
|
988
|
-
|
|
989
|
-
#### JapaneseName
|
|
990
|
-
Japanese name with kanji and kana variants.
|
|
991
|
-
|
|
992
|
-
**Expands to 4 columns:**
|
|
993
|
-
- \`{property}_lastname\` - VARCHAR(50) - Family name (\u59D3)
|
|
994
|
-
- \`{property}_firstname\` - VARCHAR(50) - Given name (\u540D)
|
|
995
|
-
- \`{property}_kana_lastname\` - VARCHAR(100) - Family name in katakana
|
|
996
|
-
- \`{property}_kana_firstname\` - VARCHAR(100) - Given name in katakana
|
|
997
|
-
|
|
998
|
-
**Accessors generated:**
|
|
999
|
-
- \`{property}_full_name\` - "\u59D3 \u540D" (space-separated)
|
|
1000
|
-
- \`{property}_full_name_kana\` - "\u30BB\u30A4 \u30E1\u30A4" (space-separated)
|
|
1001
|
-
|
|
1002
|
-
\`\`\`yaml
|
|
1003
|
-
name:
|
|
1004
|
-
type: JapaneseName
|
|
1005
|
-
displayName:
|
|
1006
|
-
ja: \u6C0F\u540D
|
|
1007
|
-
en: Full Name
|
|
1008
|
-
# Per-field overrides
|
|
1009
|
-
fields:
|
|
1010
|
-
KanaLastname:
|
|
1011
|
-
nullable: true
|
|
1012
|
-
hidden: true
|
|
1013
|
-
KanaFirstname:
|
|
1014
|
-
nullable: true
|
|
1015
|
-
hidden: true
|
|
1016
|
-
\`\`\`
|
|
1017
|
-
|
|
1018
|
-
#### JapaneseAddress
|
|
1019
|
-
Japanese address with postal code and prefecture ID.
|
|
1020
|
-
|
|
1021
|
-
**Expands to 5 columns:**
|
|
1022
|
-
- \`{property}_postal_code\` - VARCHAR(8) - Postal code (\u90F5\u4FBF\u756A\u53F7)
|
|
1023
|
-
- \`{property}_prefecture_id\` - TINYINT UNSIGNED - Prefecture ID 1-47 (\u90FD\u9053\u5E9C\u770C)
|
|
1024
|
-
- \`{property}_address1\` - VARCHAR(255) - City/Ward (\u5E02\u533A\u753A\u6751)
|
|
1025
|
-
- \`{property}_address2\` - VARCHAR(255) - Street address (\u4E01\u76EE\u756A\u5730\u53F7)
|
|
1026
|
-
- \`{property}_address3\` - VARCHAR(255) NULLABLE - Building name (\u30D3\u30EB\u30FB\u30DE\u30F3\u30B7\u30E7\u30F3\u540D)
|
|
1027
|
-
|
|
1028
|
-
**Accessors generated:**
|
|
1029
|
-
- \`{property}_full_address\` - Concatenation of address1 + address2 + address3
|
|
1030
|
-
|
|
1031
|
-
\`\`\`yaml
|
|
1032
|
-
address:
|
|
1033
|
-
type: JapaneseAddress
|
|
1034
|
-
displayName:
|
|
1035
|
-
ja: \u4F4F\u6240
|
|
1036
|
-
en: Address
|
|
1037
|
-
fields:
|
|
1038
|
-
Address3:
|
|
1039
|
-
nullable: true
|
|
1040
|
-
\`\`\`
|
|
1041
|
-
|
|
1042
|
-
**Prefecture IDs (JIS X 0401):**
|
|
1043
|
-
| ID | Prefecture | ID | Prefecture | ID | Prefecture |
|
|
1044
|
-
|----|-----------|----|-----------|----|-----------|
|
|
1045
|
-
| 1 | \u5317\u6D77\u9053 | 17 | \u77F3\u5DDD\u770C | 33 | \u5CA1\u5C71\u770C |
|
|
1046
|
-
| 2 | \u9752\u68EE\u770C | 18 | \u798F\u4E95\u770C | 34 | \u5E83\u5CF6\u770C |
|
|
1047
|
-
| 3 | \u5CA9\u624B\u770C | 19 | \u5C71\u68A8\u770C | 35 | \u5C71\u53E3\u770C |
|
|
1048
|
-
| 4 | \u5BAE\u57CE\u770C | 20 | \u9577\u91CE\u770C | 36 | \u5FB3\u5CF6\u770C |
|
|
1049
|
-
| 5 | \u79CB\u7530\u770C | 21 | \u5C90\u961C\u770C | 37 | \u9999\u5DDD\u770C |
|
|
1050
|
-
| 6 | \u5C71\u5F62\u770C | 22 | \u9759\u5CA1\u770C | 38 | \u611B\u5A9B\u770C |
|
|
1051
|
-
| 7 | \u798F\u5CF6\u770C | 23 | \u611B\u77E5\u770C | 39 | \u9AD8\u77E5\u770C |
|
|
1052
|
-
| 8 | \u8328\u57CE\u770C | 24 | \u4E09\u91CD\u770C | 40 | \u798F\u5CA1\u770C |
|
|
1053
|
-
| 9 | \u6803\u6728\u770C | 25 | \u6ECB\u8CC0\u770C | 41 | \u4F50\u8CC0\u770C |
|
|
1054
|
-
| 10 | \u7FA4\u99AC\u770C | 26 | \u4EAC\u90FD\u5E9C | 42 | \u9577\u5D0E\u770C |
|
|
1055
|
-
| 11 | \u57FC\u7389\u770C | 27 | \u5927\u962A\u5E9C | 43 | \u718A\u672C\u770C |
|
|
1056
|
-
| 12 | \u5343\u8449\u770C | 28 | \u5175\u5EAB\u770C | 44 | \u5927\u5206\u770C |
|
|
1057
|
-
| 13 | \u6771\u4EAC\u90FD | 29 | \u5948\u826F\u770C | 45 | \u5BAE\u5D0E\u770C |
|
|
1058
|
-
| 14 | \u795E\u5948\u5DDD\u770C | 30 | \u548C\u6B4C\u5C71\u770C | 46 | \u9E7F\u5150\u5CF6\u770C |
|
|
1059
|
-
| 15 | \u65B0\u6F5F\u770C | 31 | \u9CE5\u53D6\u770C | 47 | \u6C96\u7E04\u770C |
|
|
1060
|
-
| 16 | \u5BCC\u5C71\u770C | 32 | \u5CF6\u6839\u770C | | |
|
|
1061
|
-
|
|
1062
|
-
#### JapaneseBankAccount
|
|
1063
|
-
Japanese bank account information.
|
|
1064
|
-
|
|
1065
|
-
**Expands to 5 columns:**
|
|
1066
|
-
- \`{property}_bank_code\` - VARCHAR(4) - Bank code (\u9280\u884C\u30B3\u30FC\u30C9)
|
|
1067
|
-
- \`{property}_branch_code\` - VARCHAR(3) - Branch code (\u652F\u5E97\u30B3\u30FC\u30C9)
|
|
1068
|
-
- \`{property}_account_type\` - ENUM - Account type: 1=\u666E\u901A, 2=\u5F53\u5EA7, 4=\u8CAF\u84C4
|
|
1069
|
-
- \`{property}_account_number\` - VARCHAR(7) - Account number (\u53E3\u5EA7\u756A\u53F7)
|
|
1070
|
-
- \`{property}_account_holder\` - VARCHAR(100) - Account holder name (\u53E3\u5EA7\u540D\u7FA9)
|
|
1071
|
-
|
|
1072
|
-
\`\`\`yaml
|
|
1073
|
-
bank_account:
|
|
1074
|
-
type: JapaneseBankAccount
|
|
1075
|
-
\`\`\`
|
|
1076
|
-
|
|
1077
|
-
## Per-field Overrides
|
|
1078
|
-
|
|
1079
|
-
All compound types support per-field overrides:
|
|
1080
|
-
|
|
1081
|
-
\`\`\`yaml
|
|
1082
|
-
name:
|
|
1083
|
-
type: JapaneseName
|
|
1084
|
-
fields:
|
|
1085
|
-
Lastname:
|
|
1086
|
-
length: 100 # Override default VARCHAR length
|
|
1087
|
-
Firstname:
|
|
1088
|
-
length: 100
|
|
1089
|
-
KanaLastname:
|
|
1090
|
-
length: 200
|
|
1091
|
-
nullable: true
|
|
1092
|
-
hidden: true
|
|
1093
|
-
KanaFirstname:
|
|
1094
|
-
length: 200
|
|
1095
|
-
nullable: true
|
|
1096
|
-
hidden: true
|
|
1097
|
-
\`\`\`
|
|
1098
|
-
|
|
1099
|
-
**Available overrides:**
|
|
1100
|
-
- \`length\` - VARCHAR length (override default)
|
|
1101
|
-
- \`nullable\` - Whether the field can be NULL
|
|
1102
|
-
- \`hidden\` - Exclude from JSON/array output
|
|
1103
|
-
- \`fillable\` - Control mass assignment
|
|
1104
|
-
|
|
1105
|
-
## Factory Examples
|
|
1106
|
-
|
|
1107
|
-
\`\`\`php
|
|
1108
|
-
$faker = fake('ja_JP');
|
|
1109
|
-
|
|
1110
|
-
return [
|
|
1111
|
-
// JapaneseName
|
|
1112
|
-
'name_lastname' => $faker->lastName(),
|
|
1113
|
-
'name_firstname' => $faker->firstName(),
|
|
1114
|
-
'name_kana_lastname' => $faker->lastKanaName(),
|
|
1115
|
-
'name_kana_firstname' => $faker->firstKanaName(),
|
|
1116
|
-
|
|
1117
|
-
// JapanesePhone
|
|
1118
|
-
'phone' => $faker->phoneNumber(),
|
|
1119
|
-
|
|
1120
|
-
// JapanesePostalCode
|
|
1121
|
-
'postal_code' => $faker->postcode(),
|
|
1122
|
-
|
|
1123
|
-
// JapaneseAddress
|
|
1124
|
-
'address_postal_code' => $faker->postcode(),
|
|
1125
|
-
'address_prefecture_id' => $faker->numberBetween(1, 47),
|
|
1126
|
-
'address_address1' => $faker->city(),
|
|
1127
|
-
'address_address2' => $faker->streetAddress(),
|
|
1128
|
-
'address_address3' => $faker->optional(0.5)->secondaryAddress(),
|
|
1129
|
-
];
|
|
1130
|
-
\`\`\`
|
|
1131
|
-
|
|
1132
|
-
## Model Accessors
|
|
1133
|
-
|
|
1134
|
-
\`\`\`php
|
|
1135
|
-
// JapaneseName accessors
|
|
1136
|
-
$customer->name_full_name; // "\u7530\u4E2D \u592A\u90CE"
|
|
1137
|
-
$customer->name_full_name_kana; // "\u30BF\u30CA\u30AB \u30BF\u30ED\u30A6"
|
|
1138
|
-
|
|
1139
|
-
// JapaneseAddress accessor
|
|
1140
|
-
$customer->address_full_address; // "\u5343\u4EE3\u7530\u533A\u4E38\u306E\u51851-1-1\u30D3\u30EB5F"
|
|
1141
|
-
\`\`\`
|
|
1142
|
-
`;
|
|
1143
|
-
function isJapanPlugin(plugin) {
|
|
1144
|
-
return plugin.name === "@famgia/omnify-japan";
|
|
1145
|
-
}
|
|
1146
|
-
function isLaravelPlugin(plugin) {
|
|
1147
|
-
return plugin.name === "@famgia/omnify-laravel";
|
|
1148
|
-
}
|
|
1149
|
-
function isTypeScriptPlugin(plugin) {
|
|
1150
|
-
return plugin.name === "@famgia/omnify-typescript";
|
|
1151
|
-
}
|
|
1152
|
-
function generateAIGuides(rootDir, plugins) {
|
|
1153
|
-
const guidesDir = resolve5(rootDir, ".claude/omnify");
|
|
728
|
+
function generateAIGuides(rootDir, _plugins) {
|
|
1154
729
|
let filesWritten = 0;
|
|
1155
|
-
if (!existsSync3(guidesDir)) {
|
|
1156
|
-
mkdirSync2(guidesDir, { recursive: true });
|
|
1157
|
-
}
|
|
1158
730
|
const claudeMdPath = resolve5(rootDir, "CLAUDE.md");
|
|
1159
731
|
if (!existsSync3(claudeMdPath)) {
|
|
1160
732
|
writeFileSync2(claudeMdPath, CLAUDE_MD);
|
|
1161
733
|
filesWritten++;
|
|
1162
734
|
}
|
|
1163
|
-
const schemaGuidePath = resolve5(guidesDir, "schema-guide.md");
|
|
1164
|
-
writeFileSync2(schemaGuidePath, SCHEMA_GUIDE);
|
|
1165
|
-
filesWritten++;
|
|
1166
|
-
for (const plugin of plugins) {
|
|
1167
|
-
if (isLaravelPlugin(plugin)) {
|
|
1168
|
-
const laravelGuidePath = resolve5(guidesDir, "laravel-guide.md");
|
|
1169
|
-
writeFileSync2(laravelGuidePath, LARAVEL_GUIDE);
|
|
1170
|
-
filesWritten++;
|
|
1171
|
-
}
|
|
1172
|
-
if (isTypeScriptPlugin(plugin)) {
|
|
1173
|
-
const tsGuidePath = resolve5(guidesDir, "typescript-guide.md");
|
|
1174
|
-
writeFileSync2(tsGuidePath, TYPESCRIPT_GUIDE);
|
|
1175
|
-
filesWritten++;
|
|
1176
|
-
}
|
|
1177
|
-
if (isJapanPlugin(plugin)) {
|
|
1178
|
-
const japanGuidePath = resolve5(guidesDir, "japan-guide.md");
|
|
1179
|
-
writeFileSync2(japanGuidePath, JAPAN_GUIDE);
|
|
1180
|
-
filesWritten++;
|
|
1181
|
-
}
|
|
1182
|
-
}
|
|
1183
735
|
return filesWritten;
|
|
1184
736
|
}
|
|
1185
737
|
|
|
@@ -1385,7 +937,7 @@ function writeGeneratorOutputs(outputs, rootDir) {
|
|
|
1385
937
|
const filePath = resolve6(rootDir, output.path);
|
|
1386
938
|
const dir = dirname4(filePath);
|
|
1387
939
|
if (!existsSync4(dir)) {
|
|
1388
|
-
|
|
940
|
+
mkdirSync2(dir, { recursive: true });
|
|
1389
941
|
logger.debug(`Created directory: ${dir}`);
|
|
1390
942
|
}
|
|
1391
943
|
if (output.skipIfExists && existsSync4(filePath)) {
|
|
@@ -1438,11 +990,19 @@ function runDirectGeneration(schemas, config, rootDir, options, changes) {
|
|
|
1438
990
|
}
|
|
1439
991
|
}
|
|
1440
992
|
}
|
|
993
|
+
const pluginEnumsMap = /* @__PURE__ */ new Map();
|
|
994
|
+
for (const plugin of config.plugins) {
|
|
995
|
+
if (plugin.enums) {
|
|
996
|
+
for (const enumDef of plugin.enums) {
|
|
997
|
+
pluginEnumsMap.set(enumDef.name, enumDef);
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1441
1001
|
if (!options.typesOnly && config.output.laravel) {
|
|
1442
1002
|
logger.step("Generating Laravel migrations...");
|
|
1443
1003
|
const migrationsDir = resolve6(rootDir, config.output.laravel.migrationsPath);
|
|
1444
1004
|
if (!existsSync4(migrationsDir)) {
|
|
1445
|
-
|
|
1005
|
+
mkdirSync2(migrationsDir, { recursive: true });
|
|
1446
1006
|
logger.debug(`Created directory: ${migrationsDir}`);
|
|
1447
1007
|
}
|
|
1448
1008
|
const addedSchemaNames = new Set(
|
|
@@ -1487,10 +1047,10 @@ function runDirectGeneration(schemas, config, rootDir, options, changes) {
|
|
|
1487
1047
|
const modelsDir = resolve6(rootDir, modelsPath);
|
|
1488
1048
|
const baseModelsDir = resolve6(rootDir, baseModelsPath);
|
|
1489
1049
|
if (!existsSync4(modelsDir)) {
|
|
1490
|
-
|
|
1050
|
+
mkdirSync2(modelsDir, { recursive: true });
|
|
1491
1051
|
}
|
|
1492
1052
|
if (!existsSync4(baseModelsDir)) {
|
|
1493
|
-
|
|
1053
|
+
mkdirSync2(baseModelsDir, { recursive: true });
|
|
1494
1054
|
}
|
|
1495
1055
|
const providersPath = config.output.laravel.providersPath ?? "app/Providers";
|
|
1496
1056
|
const models = generateModels(schemas, {
|
|
@@ -1503,7 +1063,7 @@ function runDirectGeneration(schemas, config, rootDir, options, changes) {
|
|
|
1503
1063
|
const filePath = resolve6(rootDir, getModelPath(model));
|
|
1504
1064
|
const fileDir = dirname4(filePath);
|
|
1505
1065
|
if (!existsSync4(fileDir)) {
|
|
1506
|
-
|
|
1066
|
+
mkdirSync2(fileDir, { recursive: true });
|
|
1507
1067
|
}
|
|
1508
1068
|
if (!model.overwrite && existsSync4(filePath)) {
|
|
1509
1069
|
logger.debug(`Skipped (exists): ${getModelPath(model)}`);
|
|
@@ -1520,7 +1080,7 @@ function runDirectGeneration(schemas, config, rootDir, options, changes) {
|
|
|
1520
1080
|
const factoriesPath = config.output.laravel.factoriesPath;
|
|
1521
1081
|
const factoriesDir = resolve6(rootDir, factoriesPath);
|
|
1522
1082
|
if (!existsSync4(factoriesDir)) {
|
|
1523
|
-
|
|
1083
|
+
mkdirSync2(factoriesDir, { recursive: true });
|
|
1524
1084
|
}
|
|
1525
1085
|
const factories = generateFactories(schemas, {
|
|
1526
1086
|
factoryPath: factoriesPath
|
|
@@ -1529,7 +1089,7 @@ function runDirectGeneration(schemas, config, rootDir, options, changes) {
|
|
|
1529
1089
|
const filePath = resolve6(rootDir, getFactoryPath(factory));
|
|
1530
1090
|
const fileDir = dirname4(filePath);
|
|
1531
1091
|
if (!existsSync4(fileDir)) {
|
|
1532
|
-
|
|
1092
|
+
mkdirSync2(fileDir, { recursive: true });
|
|
1533
1093
|
}
|
|
1534
1094
|
if (!factory.overwrite && existsSync4(filePath)) {
|
|
1535
1095
|
logger.debug(`Skipped (exists): ${getFactoryPath(factory)}`);
|
|
@@ -1543,25 +1103,35 @@ function runDirectGeneration(schemas, config, rootDir, options, changes) {
|
|
|
1543
1103
|
}
|
|
1544
1104
|
if (!options.migrationsOnly && config.output.typescript) {
|
|
1545
1105
|
logger.step("Generating TypeScript types...");
|
|
1546
|
-
const
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1106
|
+
const tsConfig = config.output.typescript;
|
|
1107
|
+
const basePath = resolve6(rootDir, tsConfig.path);
|
|
1108
|
+
const schemasDir = resolve6(basePath, tsConfig.schemasDir ?? "schemas");
|
|
1109
|
+
const enumDir = resolve6(basePath, tsConfig.enumDir ?? "enum");
|
|
1110
|
+
const enumImportPrefix = relative(schemasDir, enumDir).replace(/\\/g, "/");
|
|
1111
|
+
if (!existsSync4(schemasDir)) {
|
|
1112
|
+
mkdirSync2(schemasDir, { recursive: true });
|
|
1113
|
+
logger.debug(`Created directory: ${schemasDir}`);
|
|
1114
|
+
}
|
|
1115
|
+
if (!existsSync4(enumDir)) {
|
|
1116
|
+
mkdirSync2(enumDir, { recursive: true });
|
|
1117
|
+
logger.debug(`Created directory: ${enumDir}`);
|
|
1550
1118
|
}
|
|
1551
1119
|
const isMultiLocale = config.locale && config.locale.locales && config.locale.locales.length > 1;
|
|
1552
|
-
const typescriptConfig = config.output.typescript;
|
|
1553
1120
|
const typeFiles = generateTypeScript(schemas, {
|
|
1554
1121
|
customTypes: customTypesMap,
|
|
1122
|
+
pluginEnums: pluginEnumsMap,
|
|
1555
1123
|
localeConfig: config.locale,
|
|
1556
1124
|
multiLocale: isMultiLocale,
|
|
1557
|
-
generateRules:
|
|
1558
|
-
validationTemplates:
|
|
1125
|
+
generateRules: tsConfig.generateRules ?? true,
|
|
1126
|
+
validationTemplates: tsConfig.validationTemplates,
|
|
1127
|
+
enumImportPrefix
|
|
1559
1128
|
});
|
|
1560
1129
|
for (const file of typeFiles) {
|
|
1561
|
-
const
|
|
1130
|
+
const outputDir = file.category === "enum" ? enumDir : schemasDir;
|
|
1131
|
+
const filePath = resolve6(outputDir, file.filePath);
|
|
1562
1132
|
const fileDir = dirname4(filePath);
|
|
1563
1133
|
if (!existsSync4(fileDir)) {
|
|
1564
|
-
|
|
1134
|
+
mkdirSync2(fileDir, { recursive: true });
|
|
1565
1135
|
}
|
|
1566
1136
|
if (!file.overwrite && existsSync4(filePath)) {
|
|
1567
1137
|
logger.debug(`Skipped (exists): ${file.filePath}`);
|
|
@@ -1572,6 +1142,13 @@ function runDirectGeneration(schemas, config, rootDir, options, changes) {
|
|
|
1572
1142
|
typesGenerated++;
|
|
1573
1143
|
}
|
|
1574
1144
|
logger.success(`Generated ${typesGenerated} TypeScript file(s)`);
|
|
1145
|
+
const stubsResult = copyStubs({
|
|
1146
|
+
targetDir: basePath,
|
|
1147
|
+
skipIfExists: true
|
|
1148
|
+
});
|
|
1149
|
+
if (stubsResult.copied.length > 0) {
|
|
1150
|
+
logger.success(`Generated ${stubsResult.copied.length} React stub(s)`);
|
|
1151
|
+
}
|
|
1575
1152
|
}
|
|
1576
1153
|
return { migrations: migrationsGenerated, types: typesGenerated, models: modelsGenerated, factories: factoriesGenerated };
|
|
1577
1154
|
}
|
|
@@ -1618,7 +1195,9 @@ async function runGenerate(options) {
|
|
|
1618
1195
|
const v2Lock = existingLock && isLockFileV2(existingLock) ? existingLock : null;
|
|
1619
1196
|
const comparison = compareSchemasDeep(currentSnapshots, v2Lock);
|
|
1620
1197
|
const skipMigrations = !comparison.hasChanges && !options.force;
|
|
1621
|
-
|
|
1198
|
+
const pluginsHaveGenerators = config.plugins.some((p) => p.generators && p.generators.length > 0);
|
|
1199
|
+
const hasTypescriptOutput = !!config.output.typescript;
|
|
1200
|
+
if (skipMigrations && !config.output.laravel?.modelsPath && !pluginsHaveGenerators && !hasTypescriptOutput) {
|
|
1622
1201
|
logger.success("No changes to generate");
|
|
1623
1202
|
return;
|
|
1624
1203
|
}
|
|
@@ -1641,6 +1220,14 @@ async function runGenerate(options) {
|
|
|
1641
1220
|
}
|
|
1642
1221
|
}
|
|
1643
1222
|
}
|
|
1223
|
+
const pluginEnumsMap = /* @__PURE__ */ new Map();
|
|
1224
|
+
for (const plugin of config.plugins) {
|
|
1225
|
+
if (plugin.enums) {
|
|
1226
|
+
for (const enumDef of plugin.enums) {
|
|
1227
|
+
pluginEnumsMap.set(enumDef.name, enumDef);
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1644
1231
|
if (usePlugins) {
|
|
1645
1232
|
logger.step("Running plugin generators...");
|
|
1646
1233
|
const counts = await runPluginGeneration(
|
|
@@ -1669,25 +1256,35 @@ async function runGenerate(options) {
|
|
|
1669
1256
|
}
|
|
1670
1257
|
if (!options.migrationsOnly && config.output.typescript && typesGenerated === 0) {
|
|
1671
1258
|
logger.step("Generating TypeScript types...");
|
|
1672
|
-
const
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1259
|
+
const tsConfig2 = config.output.typescript;
|
|
1260
|
+
const basePath2 = resolve6(rootDir, tsConfig2.path);
|
|
1261
|
+
const schemasDir2 = resolve6(basePath2, tsConfig2.schemasDir ?? "schemas");
|
|
1262
|
+
const enumDir2 = resolve6(basePath2, tsConfig2.enumDir ?? "enum");
|
|
1263
|
+
const enumImportPrefix2 = relative(schemasDir2, enumDir2).replace(/\\/g, "/");
|
|
1264
|
+
if (!existsSync4(schemasDir2)) {
|
|
1265
|
+
mkdirSync2(schemasDir2, { recursive: true });
|
|
1266
|
+
logger.debug(`Created directory: ${schemasDir2}`);
|
|
1267
|
+
}
|
|
1268
|
+
if (!existsSync4(enumDir2)) {
|
|
1269
|
+
mkdirSync2(enumDir2, { recursive: true });
|
|
1270
|
+
logger.debug(`Created directory: ${enumDir2}`);
|
|
1676
1271
|
}
|
|
1677
1272
|
const isMultiLocale = config.locale && config.locale.locales && config.locale.locales.length > 1;
|
|
1678
|
-
const typescriptConfig = config.output.typescript;
|
|
1679
1273
|
const typeFiles = generateTypeScript(schemas, {
|
|
1680
1274
|
customTypes: customTypesMap,
|
|
1275
|
+
pluginEnums: pluginEnumsMap,
|
|
1681
1276
|
localeConfig: config.locale,
|
|
1682
1277
|
multiLocale: isMultiLocale,
|
|
1683
|
-
generateRules:
|
|
1684
|
-
validationTemplates:
|
|
1278
|
+
generateRules: tsConfig2.generateRules ?? true,
|
|
1279
|
+
validationTemplates: tsConfig2.validationTemplates,
|
|
1280
|
+
enumImportPrefix: enumImportPrefix2
|
|
1685
1281
|
});
|
|
1686
1282
|
for (const file of typeFiles) {
|
|
1687
|
-
const
|
|
1283
|
+
const outputDir2 = file.category === "enum" ? enumDir2 : schemasDir2;
|
|
1284
|
+
const filePath = resolve6(outputDir2, file.filePath);
|
|
1688
1285
|
const fileDir = dirname4(filePath);
|
|
1689
1286
|
if (!existsSync4(fileDir)) {
|
|
1690
|
-
|
|
1287
|
+
mkdirSync2(fileDir, { recursive: true });
|
|
1691
1288
|
}
|
|
1692
1289
|
if (!file.overwrite && existsSync4(filePath)) {
|
|
1693
1290
|
logger.debug(`Skipped (exists): ${file.filePath}`);
|
|
@@ -1698,6 +1295,22 @@ async function runGenerate(options) {
|
|
|
1698
1295
|
typesGenerated++;
|
|
1699
1296
|
}
|
|
1700
1297
|
logger.success(`Generated ${typesGenerated} TypeScript file(s)`);
|
|
1298
|
+
const stubsResult2 = copyStubs({
|
|
1299
|
+
targetDir: basePath2,
|
|
1300
|
+
skipIfExists: true
|
|
1301
|
+
});
|
|
1302
|
+
if (stubsResult2.copied.length > 0) {
|
|
1303
|
+
logger.success(`Generated ${stubsResult2.copied.length} React stub(s)`);
|
|
1304
|
+
}
|
|
1305
|
+
if (shouldGenerateTypescriptAIGuides(rootDir)) {
|
|
1306
|
+
const tsAIResult = generateTypescriptAIGuides(rootDir, {
|
|
1307
|
+
typescriptPath: tsConfig2.path
|
|
1308
|
+
});
|
|
1309
|
+
const tsClaudeTotal = tsAIResult.claudeGuides + tsAIResult.claudeChecklists;
|
|
1310
|
+
if (tsClaudeTotal > 0 || tsAIResult.cursorRules > 0) {
|
|
1311
|
+
logger.debug(`Generated ${tsClaudeTotal} React Claude files, ${tsAIResult.cursorRules} Cursor rules`);
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1701
1314
|
}
|
|
1702
1315
|
} else {
|
|
1703
1316
|
const counts = runDirectGeneration(schemas, config, rootDir, options, comparison.changes);
|
|
@@ -1882,6 +1495,8 @@ async function runReset(options) {
|
|
|
1882
1495
|
const typescriptPath = config.output.typescript?.path;
|
|
1883
1496
|
const tsBasePath = typescriptPath ? resolve7(rootDir, typescriptPath) : null;
|
|
1884
1497
|
const commonTsPaths = [
|
|
1498
|
+
"resources/ts/omnify",
|
|
1499
|
+
"resources/ts/omnify/schemas",
|
|
1885
1500
|
"resources/ts/types/models",
|
|
1886
1501
|
"frontend/src/types/model",
|
|
1887
1502
|
"frontend/src/types/models",
|
|
@@ -1898,14 +1513,14 @@ async function runReset(options) {
|
|
|
1898
1513
|
}
|
|
1899
1514
|
}
|
|
1900
1515
|
if (foundTsPath && existsSync5(foundTsPath)) {
|
|
1901
|
-
const autoGeneratedDirs = ["base", "enum", "rules"];
|
|
1516
|
+
const autoGeneratedDirs = ["base", "enum", "rules", "hooks", "lib"];
|
|
1902
1517
|
for (const subDir of autoGeneratedDirs) {
|
|
1903
1518
|
const subPath = join(foundTsPath, subDir);
|
|
1904
1519
|
if (existsSync5(subPath)) {
|
|
1905
1520
|
paths.push({ name: `TypeScript ${subDir}`, path: subPath, type: "dir" });
|
|
1906
1521
|
}
|
|
1907
1522
|
}
|
|
1908
|
-
const autoGeneratedFiles = ["common.ts", "index.ts"];
|
|
1523
|
+
const autoGeneratedFiles = ["common.ts", "index.ts", "i18n.ts"];
|
|
1909
1524
|
for (const fileName of autoGeneratedFiles) {
|
|
1910
1525
|
const filePath = join(foundTsPath, fileName);
|
|
1911
1526
|
if (existsSync5(filePath)) {
|