@famgia/omnify-typescript 0.0.28 → 0.0.29

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.
@@ -0,0 +1,401 @@
1
+ # Omnify + Ant Design Integration Guide
2
+
3
+ This guide shows how to use Omnify-generated validation rules with Ant Design Forms.
4
+
5
+ ## Generated Files
6
+
7
+ Omnify generates validation rules in `rules/` directory:
8
+ - `rules/{Model}.rules.ts` - Validation rules for each model
9
+
10
+ ## File Structure
11
+
12
+ ```typescript
13
+ // rules/User.rules.ts
14
+
15
+ // Display name for model (multi-locale)
16
+ export const UserDisplayName: LocaleMap = {
17
+ ja: 'ユーザー',
18
+ en: 'User',
19
+ };
20
+
21
+ // Display names for properties (multi-locale)
22
+ export const UserPropertyDisplayNames: Record<string, LocaleMap> = {
23
+ name: { ja: '名前', en: 'Name' },
24
+ email: { ja: 'メールアドレス', en: 'Email' },
25
+ };
26
+
27
+ // Validation rules with multi-locale messages
28
+ export const UserRules: Record<string, ValidationRule[]> = {
29
+ name: [
30
+ { required: true, message: { ja: '名前は必須です', en: 'Name is required' } },
31
+ { max: 100, message: { ja: '名前は100文字以内で入力してください', en: 'Name must be at most 100 characters' } },
32
+ ],
33
+ email: [
34
+ { required: true, message: { ja: 'メールアドレスは必須です', en: 'Email is required' } },
35
+ { type: 'email', message: { ja: 'メールアドレスの形式が正しくありません', en: 'Email is not a valid email address' } },
36
+ ],
37
+ };
38
+
39
+ // Helper functions
40
+ export function getUserRules(locale: string): Record<string, Rule[]>;
41
+ export function getUserDisplayName(locale: string): string;
42
+ export function getUserPropertyDisplayName(property: string, locale: string): string;
43
+ ```
44
+
45
+ ## Basic Usage
46
+
47
+ ```tsx
48
+ import { Form, Input, Button } from 'antd';
49
+ import {
50
+ getUserRules,
51
+ getUserDisplayName,
52
+ getUserPropertyDisplayName
53
+ } from '@/types/model/rules/User.rules';
54
+
55
+ interface UserFormProps {
56
+ locale?: string;
57
+ onSubmit: (values: User) => void;
58
+ }
59
+
60
+ export function UserForm({ locale = 'ja', onSubmit }: UserFormProps) {
61
+ const [form] = Form.useForm();
62
+ const rules = getUserRules(locale);
63
+
64
+ return (
65
+ <Form
66
+ form={form}
67
+ layout="vertical"
68
+ onFinish={onSubmit}
69
+ >
70
+ <Form.Item
71
+ name="name"
72
+ label={getUserPropertyDisplayName('name', locale)}
73
+ rules={rules.name}
74
+ >
75
+ <Input placeholder={getUserPropertyDisplayName('name', locale)} />
76
+ </Form.Item>
77
+
78
+ <Form.Item
79
+ name="email"
80
+ label={getUserPropertyDisplayName('email', locale)}
81
+ rules={rules.email}
82
+ >
83
+ <Input type="email" />
84
+ </Form.Item>
85
+
86
+ <Form.Item>
87
+ <Button type="primary" htmlType="submit">
88
+ {locale === 'ja' ? '送信' : 'Submit'}
89
+ </Button>
90
+ </Form.Item>
91
+ </Form>
92
+ );
93
+ }
94
+ ```
95
+
96
+ ## With Edit Mode (Initial Values)
97
+
98
+ ```tsx
99
+ import { Form, Input, Button, Spin } from 'antd';
100
+ import { getUserRules, getUserPropertyDisplayName } from '@/types/model/rules/User.rules';
101
+ import { User } from '@/types/model';
102
+
103
+ interface UserEditFormProps {
104
+ user: User;
105
+ locale?: string;
106
+ onSubmit: (values: Partial<User>) => void;
107
+ loading?: boolean;
108
+ }
109
+
110
+ export function UserEditForm({ user, locale = 'ja', onSubmit, loading }: UserEditFormProps) {
111
+ const [form] = Form.useForm();
112
+ const rules = getUserRules(locale);
113
+
114
+ // Set initial values when user data changes
115
+ React.useEffect(() => {
116
+ form.setFieldsValue(user);
117
+ }, [user, form]);
118
+
119
+ return (
120
+ <Spin spinning={loading}>
121
+ <Form
122
+ form={form}
123
+ layout="vertical"
124
+ initialValues={user}
125
+ onFinish={onSubmit}
126
+ >
127
+ <Form.Item
128
+ name="name"
129
+ label={getUserPropertyDisplayName('name', locale)}
130
+ rules={rules.name}
131
+ >
132
+ <Input />
133
+ </Form.Item>
134
+
135
+ <Form.Item
136
+ name="email"
137
+ label={getUserPropertyDisplayName('email', locale)}
138
+ rules={rules.email}
139
+ >
140
+ <Input type="email" />
141
+ </Form.Item>
142
+
143
+ <Form.Item>
144
+ <Button type="primary" htmlType="submit" loading={loading}>
145
+ {locale === 'ja' ? '更新' : 'Update'}
146
+ </Button>
147
+ </Form.Item>
148
+ </Form>
149
+ </Spin>
150
+ );
151
+ }
152
+ ```
153
+
154
+ ## Dynamic Locale Switching
155
+
156
+ ```tsx
157
+ import { Form, Input, Select } from 'antd';
158
+ import { useState, useMemo } from 'react';
159
+ import { getUserRules, getUserPropertyDisplayName, getUserDisplayName } from '@/types/model/rules/User.rules';
160
+
161
+ export function UserFormWithLocale() {
162
+ const [locale, setLocale] = useState('ja');
163
+ const [form] = Form.useForm();
164
+
165
+ // Memoize rules to avoid recalculation
166
+ const rules = useMemo(() => getUserRules(locale), [locale]);
167
+
168
+ return (
169
+ <>
170
+ <Select value={locale} onChange={setLocale} style={{ marginBottom: 16 }}>
171
+ <Select.Option value="ja">日本語</Select.Option>
172
+ <Select.Option value="en">English</Select.Option>
173
+ <Select.Option value="vi">Tiếng Việt</Select.Option>
174
+ </Select>
175
+
176
+ <h2>{getUserDisplayName(locale)}</h2>
177
+
178
+ <Form form={form} layout="vertical">
179
+ <Form.Item
180
+ name="name"
181
+ label={getUserPropertyDisplayName('name', locale)}
182
+ rules={rules.name}
183
+ >
184
+ <Input />
185
+ </Form.Item>
186
+
187
+ <Form.Item
188
+ name="email"
189
+ label={getUserPropertyDisplayName('email', locale)}
190
+ rules={rules.email}
191
+ >
192
+ <Input type="email" />
193
+ </Form.Item>
194
+ </Form>
195
+ </>
196
+ );
197
+ }
198
+ ```
199
+
200
+ ## With Table Columns
201
+
202
+ ```tsx
203
+ import { Table, TableColumnsType } from 'antd';
204
+ import { getUserPropertyDisplayName } from '@/types/model/rules/User.rules';
205
+ import { User } from '@/types/model';
206
+
207
+ interface UserTableProps {
208
+ users: User[];
209
+ locale?: string;
210
+ loading?: boolean;
211
+ }
212
+
213
+ export function UserTable({ users, locale = 'ja', loading }: UserTableProps) {
214
+ const columns: TableColumnsType<User> = [
215
+ {
216
+ title: getUserPropertyDisplayName('name', locale),
217
+ dataIndex: 'name',
218
+ key: 'name',
219
+ },
220
+ {
221
+ title: getUserPropertyDisplayName('email', locale),
222
+ dataIndex: 'email',
223
+ key: 'email',
224
+ },
225
+ ];
226
+
227
+ return (
228
+ <Table
229
+ columns={columns}
230
+ dataSource={users}
231
+ rowKey="id"
232
+ loading={loading}
233
+ />
234
+ );
235
+ }
236
+ ```
237
+
238
+ ## Custom Form Hook
239
+
240
+ ```tsx
241
+ import { Form, FormInstance } from 'antd';
242
+ import { useMemo } from 'react';
243
+
244
+ // Generic hook for any model's rules
245
+ export function useModelForm<T>(
246
+ getRules: (locale: string) => Record<string, any[]>,
247
+ locale: string = 'ja'
248
+ ): {
249
+ form: FormInstance<T>;
250
+ rules: Record<string, any[]>;
251
+ } {
252
+ const [form] = Form.useForm<T>();
253
+ const rules = useMemo(() => getRules(locale), [getRules, locale]);
254
+
255
+ return { form, rules };
256
+ }
257
+
258
+ // Usage
259
+ import { getUserRules } from '@/types/model/rules/User.rules';
260
+
261
+ function MyComponent() {
262
+ const { form, rules } = useModelForm(getUserRules, 'ja');
263
+
264
+ return (
265
+ <Form form={form}>
266
+ <Form.Item name="name" rules={rules.name}>
267
+ <Input />
268
+ </Form.Item>
269
+ </Form>
270
+ );
271
+ }
272
+ ```
273
+
274
+ ## Modal Form
275
+
276
+ ```tsx
277
+ import { Modal, Form, Input, message } from 'antd';
278
+ import { getUserRules, getUserDisplayName, getUserPropertyDisplayName } from '@/types/model/rules/User.rules';
279
+ import { User } from '@/types/model';
280
+
281
+ interface UserModalFormProps {
282
+ open: boolean;
283
+ onClose: () => void;
284
+ onSubmit: (values: Partial<User>) => Promise<void>;
285
+ initialValues?: Partial<User>;
286
+ locale?: string;
287
+ }
288
+
289
+ export function UserModalForm({
290
+ open,
291
+ onClose,
292
+ onSubmit,
293
+ initialValues,
294
+ locale = 'ja'
295
+ }: UserModalFormProps) {
296
+ const [form] = Form.useForm();
297
+ const [loading, setLoading] = useState(false);
298
+ const rules = getUserRules(locale);
299
+
300
+ const handleSubmit = async () => {
301
+ try {
302
+ const values = await form.validateFields();
303
+ setLoading(true);
304
+ await onSubmit(values);
305
+ message.success(locale === 'ja' ? '保存しました' : 'Saved successfully');
306
+ form.resetFields();
307
+ onClose();
308
+ } catch (error) {
309
+ if (error instanceof Error) {
310
+ message.error(error.message);
311
+ }
312
+ } finally {
313
+ setLoading(false);
314
+ }
315
+ };
316
+
317
+ return (
318
+ <Modal
319
+ title={getUserDisplayName(locale)}
320
+ open={open}
321
+ onCancel={onClose}
322
+ onOk={handleSubmit}
323
+ confirmLoading={loading}
324
+ okText={locale === 'ja' ? '保存' : 'Save'}
325
+ cancelText={locale === 'ja' ? 'キャンセル' : 'Cancel'}
326
+ >
327
+ <Form
328
+ form={form}
329
+ layout="vertical"
330
+ initialValues={initialValues}
331
+ >
332
+ <Form.Item
333
+ name="name"
334
+ label={getUserPropertyDisplayName('name', locale)}
335
+ rules={rules.name}
336
+ >
337
+ <Input />
338
+ </Form.Item>
339
+
340
+ <Form.Item
341
+ name="email"
342
+ label={getUserPropertyDisplayName('email', locale)}
343
+ rules={rules.email}
344
+ >
345
+ <Input type="email" />
346
+ </Form.Item>
347
+ </Form>
348
+ </Modal>
349
+ );
350
+ }
351
+ ```
352
+
353
+ ## Validation Rule Types
354
+
355
+ Omnify generates these rule types based on schema:
356
+
357
+ | Schema | Generated Rule |
358
+ |--------|---------------|
359
+ | `required: true` | `{ required: true, message: {...} }` |
360
+ | `type: Email` | `{ type: 'email', message: {...} }` |
361
+ | `maxLength: N` | `{ max: N, message: {...} }` |
362
+ | `minLength: N` | `{ min: N, message: {...} }` |
363
+ | `max: N` (numeric) | `{ max: N, type: 'number', message: {...} }` |
364
+ | `min: N` (numeric) | `{ min: N, type: 'number', message: {...} }` |
365
+ | `pattern: regex` | `{ pattern: /regex/, message: {...} }` |
366
+
367
+ ## Built-in Validation Messages
368
+
369
+ Omnify includes templates for 5 languages:
370
+ - Japanese (ja)
371
+ - English (en)
372
+ - Vietnamese (vi)
373
+ - Korean (ko)
374
+ - Chinese (zh)
375
+
376
+ Custom templates can be configured in `omnify.config.ts`:
377
+
378
+ ```typescript
379
+ export default defineConfig({
380
+ output: {
381
+ typescript: {
382
+ validationTemplates: {
383
+ required: {
384
+ ja: '${displayName}を入力してください',
385
+ en: '${displayName} is required',
386
+ vi: '${displayName} là bắt buộc',
387
+ },
388
+ maxLength: {
389
+ ja: '${displayName}は${max}文字以内です',
390
+ en: '${displayName} must be at most ${max} characters',
391
+ },
392
+ },
393
+ },
394
+ },
395
+ });
396
+ ```
397
+
398
+ Available placeholders:
399
+ - `${displayName}` - Property display name
400
+ - `${min}` - Minimum value/length
401
+ - `${max}` - Maximum value/length
@@ -0,0 +1,254 @@
1
+ # Omnify TypeScript Generator Guide
2
+
3
+ This guide covers TypeScript-specific features and generated code patterns for Omnify.
4
+
5
+ ## Generated Files
6
+
7
+ When you run `npx omnify generate`, the following TypeScript files are generated:
8
+
9
+ - `base/*.ts` - Base model interfaces
10
+ - `enum/*.ts` - Enum types with multi-locale labels
11
+ - `rules/*.ts` - Ant Design compatible validation rules
12
+
13
+ ## Type Generation
14
+
15
+ ### Object Schema → Interface
16
+
17
+ ```yaml
18
+ # yaml-language-server: $schema=./node_modules/.omnify/combined-schema.json
19
+ name: User
20
+ properties:
21
+ id:
22
+ type: BigInt
23
+ required: true
24
+ name:
25
+ type: String
26
+ required: true
27
+ maxLength: 255
28
+ email:
29
+ type: String
30
+ required: true
31
+ unique: true
32
+ profile:
33
+ type: Json
34
+ createdAt:
35
+ type: DateTime
36
+ ```
37
+
38
+ Generated:
39
+ ```typescript
40
+ export interface User {
41
+ id: number;
42
+ name: string;
43
+ email: string;
44
+ profile: Record<string, unknown> | null;
45
+ createdAt: Date | null;
46
+ }
47
+ ```
48
+
49
+ ## Type Mapping
50
+
51
+ | Schema Type | TypeScript Type |
52
+ |-------------|-----------------|
53
+ | `String` | `string` |
54
+ | `LongText` | `string` |
55
+ | `Int` | `number` |
56
+ | `BigInt` | `number` |
57
+ | `Float` | `number` |
58
+ | `Boolean` | `boolean` |
59
+ | `Date` | `Date` |
60
+ | `DateTime` | `Date` |
61
+ | `Json` | `Record<string, unknown>` |
62
+ | `EnumRef` | Generated enum type |
63
+ | `Association` | Related model type / array |
64
+
65
+ ## Enum Generation (Multi-locale)
66
+
67
+ ```yaml
68
+ # schemas/PostStatus.yaml
69
+ name: PostStatus
70
+ kind: enum
71
+ displayName:
72
+ ja: 投稿ステータス
73
+ en: Post Status
74
+ values:
75
+ draft:
76
+ ja: 下書き
77
+ en: Draft
78
+ published:
79
+ ja: 公開済み
80
+ en: Published
81
+ archived:
82
+ ja: アーカイブ
83
+ en: Archived
84
+ ```
85
+
86
+ Generated:
87
+ ```typescript
88
+ export const PostStatus = {
89
+ draft: 'draft',
90
+ published: 'published',
91
+ archived: 'archived',
92
+ } as const;
93
+
94
+ export type PostStatus = typeof PostStatus[keyof typeof PostStatus];
95
+
96
+ // Multi-locale labels
97
+ export const PostStatusLabels: Record<PostStatus, Record<string, string>> = {
98
+ draft: { ja: '下書き', en: 'Draft' },
99
+ published: { ja: '公開済み', en: 'Published' },
100
+ archived: { ja: 'アーカイブ', en: 'Archived' },
101
+ };
102
+
103
+ // Get label for specific locale
104
+ export function getPostStatusLabel(value: PostStatus, locale: string = 'en'): string {
105
+ return PostStatusLabels[value]?.[locale] ?? PostStatusLabels[value]?.['en'] ?? value;
106
+ }
107
+
108
+ // Helper functions
109
+ export const PostStatusValues = Object.values(PostStatus);
110
+ export function isPostStatus(value: unknown): value is PostStatus {
111
+ return PostStatusValues.includes(value as PostStatus);
112
+ }
113
+ ```
114
+
115
+ ## Validation Rules (Ant Design)
116
+
117
+ Omnify generates Ant Design compatible validation rules in `rules/` directory.
118
+
119
+ **See detailed guide:** `.claude/omnify/antdesign-guide.md`
120
+
121
+ Quick example:
122
+ ```tsx
123
+ import { Form, Input } from 'antd';
124
+ import { getUserRules, getUserPropertyDisplayName } from './types/model/rules/User.rules';
125
+
126
+ function UserForm({ locale = 'ja' }) {
127
+ const rules = getUserRules(locale);
128
+ return (
129
+ <Form>
130
+ <Form.Item name="name" label={getUserPropertyDisplayName('name', locale)} rules={rules.name}>
131
+ <Input />
132
+ </Form.Item>
133
+ </Form>
134
+ );
135
+ }
136
+ ```
137
+
138
+ ## Association Types
139
+
140
+ ### ManyToOne
141
+ ```yaml
142
+ author:
143
+ type: Association
144
+ relation: ManyToOne
145
+ target: User
146
+ ```
147
+
148
+ Generated:
149
+ ```typescript
150
+ export interface Post {
151
+ authorId: number;
152
+ author?: User; // Optional: loaded relation
153
+ }
154
+ ```
155
+
156
+ ### OneToMany
157
+ ```yaml
158
+ posts:
159
+ type: Association
160
+ relation: OneToMany
161
+ target: Post
162
+ ```
163
+
164
+ Generated:
165
+ ```typescript
166
+ export interface User {
167
+ posts?: Post[]; // Optional: loaded relation array
168
+ }
169
+ ```
170
+
171
+ ### ManyToMany
172
+ ```yaml
173
+ tags:
174
+ type: Association
175
+ relation: ManyToMany
176
+ target: Tag
177
+ ```
178
+
179
+ Generated:
180
+ ```typescript
181
+ export interface Post {
182
+ tags?: Tag[]; // Optional: loaded relation array
183
+ }
184
+ ```
185
+
186
+ ## Nullable Fields
187
+
188
+ Fields without `required: true` are nullable:
189
+
190
+ ```yaml
191
+ description:
192
+ type: LongText # No required: true
193
+ ```
194
+
195
+ Generated:
196
+ ```typescript
197
+ description: string | null;
198
+ ```
199
+
200
+ ## Using Generated Types
201
+
202
+ ```typescript
203
+ import { User, Post, PostStatus, isPostStatus } from './types/omnify-types';
204
+
205
+ // Type-safe object creation
206
+ const user: User = {
207
+ id: 1,
208
+ name: 'John',
209
+ email: 'john@example.com',
210
+ profile: null,
211
+ createdAt: new Date(),
212
+ };
213
+
214
+ // Enum usage
215
+ const status: PostStatus = PostStatus.draft;
216
+
217
+ // Type guard
218
+ function handleStatus(value: unknown) {
219
+ if (isPostStatus(value)) {
220
+ console.log('Valid status:', value);
221
+ }
222
+ }
223
+ ```
224
+
225
+ ## Commands
226
+
227
+ ```bash
228
+ # Generate TypeScript types
229
+ npx omnify generate
230
+
231
+ # Force regenerate all files
232
+ npx omnify generate --force
233
+ ```
234
+
235
+ ## Configuration
236
+
237
+ ```typescript
238
+ // omnify.config.ts
239
+ import { defineConfig } from '@famgia/omnify';
240
+
241
+ export default defineConfig({
242
+ schemasDir: './schemas',
243
+ output: {
244
+ typescript: {
245
+ path: './src/types/model',
246
+ generateRules: true, // Generate Ant Design validation rules
247
+ },
248
+ },
249
+ locale: {
250
+ locales: ['ja', 'en'],
251
+ defaultLocale: 'ja',
252
+ },
253
+ });
254
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@famgia/omnify-typescript",
3
- "version": "0.0.28",
3
+ "version": "0.0.29",
4
4
  "description": "TypeScript type definitions generator for Omnify schemas",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -30,7 +30,8 @@
30
30
  },
31
31
  "files": [
32
32
  "dist",
33
- "scripts"
33
+ "scripts",
34
+ "ai-guides"
34
35
  ],
35
36
  "keywords": [
36
37
  "omnify",
@@ -47,7 +48,7 @@
47
48
  "directory": "packages/typescript-generator"
48
49
  },
49
50
  "dependencies": {
50
- "@famgia/omnify-types": "0.0.36"
51
+ "@famgia/omnify-types": "0.0.37"
51
52
  },
52
53
  "devDependencies": {
53
54
  "tsup": "^8.5.1",
@@ -2,348 +2,10 @@
2
2
 
3
3
  import fs from 'fs';
4
4
  import path from 'path';
5
+ import { fileURLToPath } from 'url';
5
6
 
6
- const TYPESCRIPT_GUIDE_CONTENT = `# Omnify TypeScript Generator Guide
7
-
8
- This guide covers TypeScript-specific features and generated code patterns for Omnify.
9
-
10
- ## Generated Files
11
-
12
- When you run \`npx omnify generate\`, the following TypeScript files are generated:
13
-
14
- - \`base/*.ts\` - Base model interfaces
15
- - \`enum/*.ts\` - Enum types with multi-locale labels
16
- - \`rules/*.ts\` - Ant Design compatible validation rules
17
-
18
- ## Type Generation
19
-
20
- ### Object Schema → Interface
21
-
22
- \`\`\`yaml
23
- # yaml-language-server: $schema=./node_modules/.omnify/combined-schema.json
24
- name: User
25
- properties:
26
- id:
27
- type: BigInt
28
- required: true
29
- name:
30
- type: String
31
- required: true
32
- maxLength: 255
33
- email:
34
- type: String
35
- required: true
36
- unique: true
37
- profile:
38
- type: Json
39
- createdAt:
40
- type: DateTime
41
- \`\`\`
42
-
43
- Generated:
44
- \`\`\`typescript
45
- export interface User {
46
- id: number;
47
- name: string;
48
- email: string;
49
- profile: Record<string, unknown> | null;
50
- createdAt: Date | null;
51
- }
52
- \`\`\`
53
-
54
- ## Type Mapping
55
-
56
- | Schema Type | TypeScript Type |
57
- |-------------|-----------------|
58
- | \`String\` | \`string\` |
59
- | \`LongText\` | \`string\` |
60
- | \`Int\` | \`number\` |
61
- | \`BigInt\` | \`number\` |
62
- | \`Float\` | \`number\` |
63
- | \`Boolean\` | \`boolean\` |
64
- | \`Date\` | \`Date\` |
65
- | \`DateTime\` | \`Date\` |
66
- | \`Json\` | \`Record<string, unknown>\` |
67
- | \`EnumRef\` | Generated enum type |
68
- | \`Association\` | Related model type / array |
69
-
70
- ## Enum Generation (Multi-locale)
71
-
72
- \`\`\`yaml
73
- # schemas/PostStatus.yaml
74
- name: PostStatus
75
- kind: enum
76
- displayName:
77
- ja: 投稿ステータス
78
- en: Post Status
79
- values:
80
- draft:
81
- ja: 下書き
82
- en: Draft
83
- published:
84
- ja: 公開済み
85
- en: Published
86
- archived:
87
- ja: アーカイブ
88
- en: Archived
89
- \`\`\`
90
-
91
- Generated:
92
- \`\`\`typescript
93
- export const PostStatus = {
94
- draft: 'draft',
95
- published: 'published',
96
- archived: 'archived',
97
- } as const;
98
-
99
- export type PostStatus = typeof PostStatus[keyof typeof PostStatus];
100
-
101
- // Multi-locale labels
102
- export const PostStatusLabels: Record<PostStatus, Record<string, string>> = {
103
- draft: { ja: '下書き', en: 'Draft' },
104
- published: { ja: '公開済み', en: 'Published' },
105
- archived: { ja: 'アーカイブ', en: 'Archived' },
106
- };
107
-
108
- // Get label for specific locale
109
- export function getPostStatusLabel(value: PostStatus, locale: string = 'en'): string {
110
- return PostStatusLabels[value]?.[locale] ?? PostStatusLabels[value]?.['en'] ?? value;
111
- }
112
-
113
- // Helper functions
114
- export const PostStatusValues = Object.values(PostStatus);
115
- export function isPostStatus(value: unknown): value is PostStatus {
116
- return PostStatusValues.includes(value as PostStatus);
117
- }
118
- \`\`\`
119
-
120
- ## Validation Rules (Ant Design)
121
-
122
- Omnify generates Ant Design compatible validation rules with multi-locale messages.
123
-
124
- \`\`\`yaml
125
- # schemas/User.yaml
126
- name: User
127
- displayName:
128
- ja: ユーザー
129
- en: User
130
- properties:
131
- name:
132
- type: String
133
- displayName:
134
- ja: 名前
135
- en: Name
136
- required: true
137
- maxLength: 100
138
- email:
139
- type: String
140
- displayName:
141
- ja: メールアドレス
142
- en: Email
143
- required: true
144
- \`\`\`
145
-
146
- Generated (\`rules/User.rules.ts\`):
147
- \`\`\`typescript
148
- export const UserDisplayName: LocaleMap = {
149
- ja: 'ユーザー',
150
- en: 'User',
151
- };
152
-
153
- export const UserPropertyDisplayNames: Record<string, LocaleMap> = {
154
- name: { ja: '名前', en: 'Name' },
155
- email: { ja: 'メールアドレス', en: 'Email' },
156
- };
157
-
158
- export const UserRules: Record<string, ValidationRule[]> = {
159
- name: [
160
- { required: true, message: { ja: '名前は必須です', en: 'Name is required' } },
161
- { max: 100, message: { ja: '名前は100文字以内で入力してください', en: 'Name must be at most 100 characters' } },
162
- ],
163
- email: [
164
- { required: true, message: { ja: 'メールアドレスは必須です', en: 'Email is required' } },
165
- ],
166
- };
167
-
168
- // Get rules for specific locale (for Ant Design Form)
169
- export function getUserRules(locale: string): Record<string, Array<{ required?: boolean; max?: number; message: string }>> { ... }
170
-
171
- // Get display names
172
- export function getUserDisplayName(locale: string): string { ... }
173
- export function getUserPropertyDisplayName(property: string, locale: string): string { ... }
174
- \`\`\`
175
-
176
- ### Using in Ant Design Form
177
-
178
- \`\`\`tsx
179
- import { Form, Input } from 'antd';
180
- import { getUserRules, getUserPropertyDisplayName } from './types/model/rules/User.rules';
181
-
182
- function UserForm({ locale = 'ja' }) {
183
- const rules = getUserRules(locale);
184
-
185
- return (
186
- <Form>
187
- <Form.Item
188
- name="name"
189
- label={getUserPropertyDisplayName('name', locale)}
190
- rules={rules.name}
191
- >
192
- <Input />
193
- </Form.Item>
194
- <Form.Item
195
- name="email"
196
- label={getUserPropertyDisplayName('email', locale)}
197
- rules={rules.email}
198
- >
199
- <Input />
200
- </Form.Item>
201
- </Form>
202
- );
203
- }
204
- \`\`\`
205
-
206
- ### Built-in Validation Templates
207
-
208
- Omnify includes built-in validation message templates for 5 languages:
209
- - Japanese (ja), English (en), Vietnamese (vi), Korean (ko), Chinese (zh)
210
-
211
- You can customize templates in \`omnify.config.ts\`:
212
- \`\`\`typescript
213
- export default defineConfig({
214
- output: {
215
- typescript: {
216
- validationTemplates: {
217
- required: {
218
- ja: '\${displayName}を入力してください',
219
- en: '\${displayName} is required',
220
- },
221
- maxLength: {
222
- ja: '\${displayName}は\${max}文字以内です',
223
- en: '\${displayName} must be \${max} characters or less',
224
- },
225
- },
226
- },
227
- },
228
- });
229
- \`\`\`
230
-
231
- ## Association Types
232
-
233
- ### ManyToOne
234
- \`\`\`yaml
235
- author:
236
- type: Association
237
- relation: ManyToOne
238
- target: User
239
- \`\`\`
240
-
241
- Generated:
242
- \`\`\`typescript
243
- export interface Post {
244
- authorId: number;
245
- author?: User; // Optional: loaded relation
246
- }
247
- \`\`\`
248
-
249
- ### OneToMany
250
- \`\`\`yaml
251
- posts:
252
- type: Association
253
- relation: OneToMany
254
- target: Post
255
- \`\`\`
256
-
257
- Generated:
258
- \`\`\`typescript
259
- export interface User {
260
- posts?: Post[]; // Optional: loaded relation array
261
- }
262
- \`\`\`
263
-
264
- ### ManyToMany
265
- \`\`\`yaml
266
- tags:
267
- type: Association
268
- relation: ManyToMany
269
- target: Tag
270
- \`\`\`
271
-
272
- Generated:
273
- \`\`\`typescript
274
- export interface Post {
275
- tags?: Tag[]; // Optional: loaded relation array
276
- }
277
- \`\`\`
278
-
279
- ## Nullable Fields
280
-
281
- Fields without \`required: true\` are nullable:
282
-
283
- \`\`\`yaml
284
- description:
285
- type: LongText # No required: true
286
- \`\`\`
287
-
288
- Generated:
289
- \`\`\`typescript
290
- description: string | null;
291
- \`\`\`
292
-
293
- ## Using Generated Types
294
-
295
- \`\`\`typescript
296
- import { User, Post, PostStatus, isPostStatus } from './types/omnify-types';
297
-
298
- // Type-safe object creation
299
- const user: User = {
300
- id: 1,
301
- name: 'John',
302
- email: 'john@example.com',
303
- profile: null,
304
- createdAt: new Date(),
305
- };
306
-
307
- // Enum usage
308
- const status: PostStatus = PostStatus.draft;
309
-
310
- // Type guard
311
- function handleStatus(value: unknown) {
312
- if (isPostStatus(value)) {
313
- console.log('Valid status:', value);
314
- }
315
- }
316
- \`\`\`
317
-
318
- ## Commands
319
-
320
- \`\`\`bash
321
- # Generate TypeScript types
322
- npx omnify generate --typescript
323
-
324
- # Generate to specific output
325
- npx omnify generate --typescript --output ./src/types
326
-
327
- # Watch for changes
328
- npx omnify watch --typescript
329
- \`\`\`
330
-
331
- ## Configuration
332
-
333
- \`\`\`javascript
334
- // omnify.config.js
335
- export default {
336
- schemasDir: './schemas',
337
- typescript: {
338
- outputDir: './types',
339
- outputFile: 'omnify-types.ts',
340
- enumsFile: 'enums.ts',
341
- generateHelpers: true, // Generate enum helpers
342
- strictNullChecks: true // Use | null for optional
343
- }
344
- };
345
- \`\`\`
346
- `;
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = path.dirname(__filename);
347
9
 
348
10
  function findProjectRoot() {
349
11
  // npm/pnpm set INIT_CWD to the directory where the install was run
@@ -359,17 +21,30 @@ function findProjectRoot() {
359
21
  return null;
360
22
  }
361
23
 
362
- function createTypescriptSkillFile(projectRoot) {
24
+ function copyAiGuidesToProject(projectRoot) {
363
25
  const omnifyDir = path.join(projectRoot, '.claude', 'omnify');
26
+ const aiGuidesDir = path.join(__dirname, '..', 'ai-guides');
364
27
 
365
28
  try {
29
+ // Create target directory
366
30
  if (!fs.existsSync(omnifyDir)) {
367
31
  fs.mkdirSync(omnifyDir, { recursive: true });
368
32
  }
369
33
 
370
- const guidePath = path.join(omnifyDir, 'typescript-guide.md');
371
- fs.writeFileSync(guidePath, TYPESCRIPT_GUIDE_CONTENT, 'utf-8');
372
- console.log(' Created .claude/omnify/typescript-guide.md');
34
+ // Copy all files from ai-guides directory
35
+ if (fs.existsSync(aiGuidesDir)) {
36
+ const files = fs.readdirSync(aiGuidesDir);
37
+ for (const file of files) {
38
+ const srcPath = path.join(aiGuidesDir, file);
39
+ const destPath = path.join(omnifyDir, file);
40
+
41
+ if (fs.statSync(srcPath).isFile()) {
42
+ fs.copyFileSync(srcPath, destPath);
43
+ console.log(` Created .claude/omnify/${file}`);
44
+ }
45
+ }
46
+ }
47
+
373
48
  return true;
374
49
  } catch {
375
50
  return false;
@@ -389,7 +64,7 @@ function main() {
389
64
 
390
65
  const projectRoot = findProjectRoot();
391
66
  if (projectRoot) {
392
- createTypescriptSkillFile(projectRoot);
67
+ copyAiGuidesToProject(projectRoot);
393
68
  }
394
69
  }
395
70