@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 +547 -59
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +528 -40
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +524 -36
- package/dist/index.js.map +1 -1
- package/package.json +13 -13
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
|
|
5
|
-
import { resolve as
|
|
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
|
|
674
|
-
import { resolve as
|
|
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 =
|
|
1352
|
+
const filePath = resolve6(rootDir, output.path);
|
|
873
1353
|
const dir = dirname4(filePath);
|
|
874
|
-
if (!
|
|
875
|
-
|
|
1354
|
+
if (!existsSync4(dir)) {
|
|
1355
|
+
mkdirSync3(dir, { recursive: true });
|
|
876
1356
|
logger.debug(`Created directory: ${dir}`);
|
|
877
1357
|
}
|
|
878
|
-
if (output.skipIfExists &&
|
|
1358
|
+
if (output.skipIfExists && existsSync4(filePath)) {
|
|
879
1359
|
logger.debug(`Skipped (exists): ${output.path}`);
|
|
880
1360
|
continue;
|
|
881
1361
|
}
|
|
882
|
-
|
|
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 =
|
|
922
|
-
if (!
|
|
923
|
-
|
|
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 =
|
|
939
|
-
|
|
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 =
|
|
948
|
-
|
|
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 =
|
|
960
|
-
const baseModelsDir =
|
|
961
|
-
if (!
|
|
962
|
-
|
|
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 (!
|
|
965
|
-
|
|
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 =
|
|
1452
|
+
const filePath = resolve6(rootDir, getModelPath(model));
|
|
973
1453
|
const fileDir = dirname4(filePath);
|
|
974
|
-
if (!
|
|
975
|
-
|
|
1454
|
+
if (!existsSync4(fileDir)) {
|
|
1455
|
+
mkdirSync3(fileDir, { recursive: true });
|
|
976
1456
|
}
|
|
977
|
-
if (!model.overwrite &&
|
|
1457
|
+
if (!model.overwrite && existsSync4(filePath)) {
|
|
978
1458
|
logger.debug(`Skipped (exists): ${getModelPath(model)}`);
|
|
979
1459
|
continue;
|
|
980
1460
|
}
|
|
981
|
-
|
|
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 =
|
|
990
|
-
if (!
|
|
991
|
-
|
|
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 =
|
|
1476
|
+
const filePath = resolve6(typesDir, file.filePath);
|
|
997
1477
|
const fileDir = dirname4(filePath);
|
|
998
|
-
if (!
|
|
999
|
-
|
|
1478
|
+
if (!existsSync4(fileDir)) {
|
|
1479
|
+
mkdirSync3(fileDir, { recursive: true });
|
|
1000
1480
|
}
|
|
1001
|
-
if (!file.overwrite &&
|
|
1481
|
+
if (!file.overwrite && existsSync4(filePath)) {
|
|
1002
1482
|
logger.debug(`Skipped (exists): ${file.filePath}`);
|
|
1003
1483
|
continue;
|
|
1004
1484
|
}
|
|
1005
|
-
|
|
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 =
|
|
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 =
|
|
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
|
|
1155
|
-
import { resolve as
|
|
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((
|
|
1650
|
+
return new Promise((resolve9) => {
|
|
1163
1651
|
rl.question(`${message} (y/N) `, (answer) => {
|
|
1164
1652
|
rl.close();
|
|
1165
|
-
|
|
1653
|
+
resolve9(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
|
|
1166
1654
|
});
|
|
1167
1655
|
});
|
|
1168
1656
|
}
|
|
1169
1657
|
function countFiles(dir) {
|
|
1170
|
-
if (!
|
|
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 (!
|
|
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 (!
|
|
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 =
|
|
1224
|
-
if (
|
|
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 =
|
|
1235
|
-
if (
|
|
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 =
|
|
1735
|
+
const modelsPath = resolve7(rootDir, laravelConfig.modelsPath);
|
|
1248
1736
|
const omnifyBasePath = join(modelsPath, "OmnifyBase");
|
|
1249
|
-
if (
|
|
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 =
|
|
1255
|
-
if (
|
|
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 =
|
|
1265
|
-
if (
|
|
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 =
|
|
1269
|
-
if (
|
|
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 =
|
|
1371
|
-
var hasConfig =
|
|
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) {
|