cmx-sdk 0.2.6 → 0.2.8
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/README.md +16 -0
- package/dist/add-studio-YUDYE2OH.js +72 -0
- package/dist/{chunk-XPP5MZKG.js → chunk-7TDMLYBI.js} +17 -45
- package/dist/chunk-EDXXR5BE.js +80 -0
- package/dist/chunk-EZMBZWH7.js +121 -0
- package/dist/chunk-FPQYL5GE.js +128 -0
- package/dist/chunk-NZQ6SBFS.js +35 -0
- package/dist/cli.js +1152 -619
- package/dist/index.d.ts +253 -385
- package/dist/index.js +154 -25
- package/dist/index.js.map +1 -1
- package/dist/{init-NDNG5Q5T.js → init-FLRQXJX4.js} +23 -51
- package/dist/interactive-menu-FYVOQSTL.js +80 -0
- package/dist/studio-HAS6DYLO.js +8 -0
- package/dist/update-sdk-KJZ6VB4M.js +10 -0
- package/dist/update-studio-TWCYSYIS.js +14 -0
- package/package.json +18 -10
- package/templates/AGENTS.md +173 -0
- package/templates/CLAUDE.md +28 -0
- package/templates/claude/commands/check.md +64 -0
- package/templates/claude/commands/next-action.md +66 -0
- package/templates/claude/skills/cmx-cache/SKILL.md +50 -0
- package/templates/claude/skills/cmx-cache/references/cache-patterns.md +153 -0
- package/templates/claude/skills/cmx-component/SKILL.md +108 -0
- package/templates/claude/skills/cmx-component/references/component-schema.md +123 -0
- package/templates/claude/skills/cmx-content/SKILL.md +158 -0
- package/templates/claude/skills/cmx-content/references/migration-patterns.md +120 -0
- package/templates/claude/skills/cmx-content/references/seed-patterns.md +146 -0
- package/templates/claude/skills/cmx-dev/SKILL.md +266 -0
- package/templates/claude/skills/cmx-dev/references/api-patterns.md +220 -0
- package/templates/claude/skills/cmx-dev/references/cli-reference.md +54 -0
- package/templates/claude/skills/cmx-form/SKILL.md +103 -0
- package/templates/claude/skills/cmx-form/references/form-template.md +152 -0
- package/templates/claude/skills/cmx-migrate/SKILL.md +501 -0
- package/templates/claude/skills/cmx-migrate/references/analysis-guide.md +127 -0
- package/templates/claude/skills/cmx-migrate/references/html-to-mdx.md +99 -0
- package/templates/claude/skills/cmx-migrate/references/intermediate-format.md +196 -0
- package/templates/claude/skills/cmx-migrate/references/tool-setup.md +150 -0
- package/templates/claude/skills/cmx-schema/SKILL.md +159 -0
- package/templates/claude/skills/cmx-schema/references/field-types.md +164 -0
- package/templates/claude/skills/cmx-schema/references/migration-scenarios.md +44 -0
- package/templates/claude/skills/cmx-seo/SKILL.md +54 -0
- package/templates/claude/skills/cmx-seo/references/seo-patterns.md +216 -0
- package/templates/claude/skills/cmx-style/SKILL.md +48 -0
- package/templates/claude/skills/cmx-style/references/style-patterns.md +114 -0
- package/dist/add-studio-J7M7KOWM.js +0 -125
- package/dist/interactive-menu-RPPCBCOU.js +0 -66
- package/dist/studio-3YGVKWS4.js +0 -7
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cmx-form
|
|
3
|
+
description: |
|
|
4
|
+
CMX Starter Kit のフォーム実装スキル。Admin 側フォーム定義からフロント UI、submissions API 送信、バリデーション、スパム対策までの一貫ワークフロー。
|
|
5
|
+
トリガー: 「フォームを作りたい」「お問い合わせ」「コンタクトフォーム」「送信機能」
|
|
6
|
+
「フォームを追加」「submissions」「フォームバリデーション」など。
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# CMX フォーム実装
|
|
10
|
+
|
|
11
|
+
## ワークフロー
|
|
12
|
+
|
|
13
|
+
### 1. フォーム設計
|
|
14
|
+
|
|
15
|
+
ユーザーに確認:
|
|
16
|
+
- 用途(お問い合わせ、資料請求、予約等)
|
|
17
|
+
- 必要なフィールドと種類
|
|
18
|
+
- バリデーション要件
|
|
19
|
+
- 送信後の挙動(サンクスメッセージ、リダイレクト)
|
|
20
|
+
|
|
21
|
+
### 2. Admin 側フォーム定義
|
|
22
|
+
|
|
23
|
+
**2-1. 既存フォームの確認**
|
|
24
|
+
|
|
25
|
+
まず既存のフォーム定義を確認して、重複がないことを確認:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npx cmx-sdk list-forms
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**2-2. JSON生成とユーザー確認**
|
|
32
|
+
|
|
33
|
+
フォーム定義 JSON を生成し、ユーザーに確認:
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
以下のフォームを Admin に登録します:
|
|
37
|
+
|
|
38
|
+
- 名前: {name}
|
|
39
|
+
- スラッグ: {slug}
|
|
40
|
+
- フィールド数: {n}
|
|
41
|
+
|
|
42
|
+
重複はありません。こちらで登録してもよろしいですか?
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**2-3. cmx-sdk で登録**
|
|
46
|
+
|
|
47
|
+
承認されたら、`cmx-sdk` コマンドで登録:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npx cmx-sdk create-form --json '{
|
|
51
|
+
"slug": "contact",
|
|
52
|
+
"name": "お問い合わせ",
|
|
53
|
+
"description": "お問い合わせフォーム",
|
|
54
|
+
"fields": [
|
|
55
|
+
{"key": "name", "label": "お名前", "type": "text", "required": true},
|
|
56
|
+
{"key": "email", "label": "メールアドレス", "type": "email", "required": true},
|
|
57
|
+
{"key": "message", "label": "メッセージ", "type": "textarea", "required": true}
|
|
58
|
+
]
|
|
59
|
+
}'
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### 3. フロント実装
|
|
63
|
+
|
|
64
|
+
クライアントコンポーネント(`"use client"`)として作成。
|
|
65
|
+
|
|
66
|
+
テンプレート: [references/form-template.md](references/form-template.md)
|
|
67
|
+
|
|
68
|
+
実装ポイント:
|
|
69
|
+
- `useState` でフォーム状態・送信状態・エラーを管理
|
|
70
|
+
- Zod スキーマでクライアントバリデーション
|
|
71
|
+
- ハニーポットフィールド(`_hp`)を隠しフィールドとして設置
|
|
72
|
+
- `POST /api/v1/sdk/submissions/{formSlug}` に送信
|
|
73
|
+
- 送信は `cmx-sdk` の関数 or 直接 fetch(APIキーは不要、公開エンドポイント)
|
|
74
|
+
|
|
75
|
+
### 4. 送信処理
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
const response = await fetch(`${process.env.NEXT_PUBLIC_CMX_API_URL}/api/v1/sdk/submissions/${formSlug}`, {
|
|
79
|
+
method: "POST",
|
|
80
|
+
headers: { "Content-Type": "application/json" },
|
|
81
|
+
body: JSON.stringify({ ...formData, _hp: honeypotValue }),
|
|
82
|
+
})
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
- `_hp` フィールドが空でない場合、サーバー側でスパムとして弾く
|
|
86
|
+
- 成功: 200 → サンクスメッセージ表示
|
|
87
|
+
- エラー: 4xx/5xx → エラーメッセージ表示
|
|
88
|
+
|
|
89
|
+
### 5. 動作確認
|
|
90
|
+
|
|
91
|
+
- フォーム送信テスト
|
|
92
|
+
- Admin 側で送信一覧に表示されることを確認
|
|
93
|
+
- バリデーションエラーの表示を確認
|
|
94
|
+
- ハニーポットが機能することを確認
|
|
95
|
+
|
|
96
|
+
## チェックリスト
|
|
97
|
+
|
|
98
|
+
- [ ] クライアントコンポーネント(`"use client"`)として作成
|
|
99
|
+
- [ ] Zod バリデーション
|
|
100
|
+
- [ ] ハニーポットフィールド(`_hp`)設置
|
|
101
|
+
- [ ] 送信中・成功・エラーの UI 状態
|
|
102
|
+
- [ ] site-config.md のトーン・デザインに沿ったスタイリング
|
|
103
|
+
- [ ] アクセシビリティ(label、aria、エラーメッセージの関連付け)
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# フォームテンプレート
|
|
2
|
+
|
|
3
|
+
## 基本構造
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
// src/app/{path}/contact-form.tsx
|
|
7
|
+
"use client"
|
|
8
|
+
|
|
9
|
+
import { useState } from "react"
|
|
10
|
+
import { z } from "zod"
|
|
11
|
+
import { Button } from "@/components/ui/button"
|
|
12
|
+
import { Input } from "@/components/ui/input"
|
|
13
|
+
import { Textarea } from "@/components/ui/textarea"
|
|
14
|
+
import { Label } from "@/components/ui/label"
|
|
15
|
+
|
|
16
|
+
const formSchema = z.object({
|
|
17
|
+
name: z.string().min(1, "お名前を入力してください"),
|
|
18
|
+
email: z.string().email("正しいメールアドレスを入力してください"),
|
|
19
|
+
message: z.string().min(1, "メッセージを入力してください"),
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
type FormData = z.infer<typeof formSchema>
|
|
23
|
+
|
|
24
|
+
export function ContactForm() {
|
|
25
|
+
const [formData, setFormData] = useState<FormData>({ name: "", email: "", message: "" })
|
|
26
|
+
const [honeypot, setHoneypot] = useState("")
|
|
27
|
+
const [errors, setErrors] = useState<Partial<Record<keyof FormData, string>>>({})
|
|
28
|
+
const [status, setStatus] = useState<"idle" | "submitting" | "success" | "error">("idle")
|
|
29
|
+
const [errorMessage, setErrorMessage] = useState("")
|
|
30
|
+
|
|
31
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
32
|
+
e.preventDefault()
|
|
33
|
+
setErrors({})
|
|
34
|
+
setErrorMessage("")
|
|
35
|
+
|
|
36
|
+
// バリデーション
|
|
37
|
+
const result = formSchema.safeParse(formData)
|
|
38
|
+
if (!result.success) {
|
|
39
|
+
const fieldErrors: Partial<Record<keyof FormData, string>> = {}
|
|
40
|
+
for (const issue of result.error.issues) {
|
|
41
|
+
const field = issue.path[0] as keyof FormData
|
|
42
|
+
fieldErrors[field] = issue.message
|
|
43
|
+
}
|
|
44
|
+
setErrors(fieldErrors)
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
setStatus("submitting")
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
const response = await fetch(
|
|
52
|
+
`${process.env.NEXT_PUBLIC_CMX_API_URL}/api/v1/sdk/submissions/contact`,
|
|
53
|
+
{
|
|
54
|
+
method: "POST",
|
|
55
|
+
headers: { "Content-Type": "application/json" },
|
|
56
|
+
body: JSON.stringify({ ...result.data, _hp: honeypot }),
|
|
57
|
+
}
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
if (!response.ok) {
|
|
61
|
+
throw new Error("送信に失敗しました")
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
setStatus("success")
|
|
65
|
+
setFormData({ name: "", email: "", message: "" })
|
|
66
|
+
} catch {
|
|
67
|
+
setStatus("error")
|
|
68
|
+
setErrorMessage("送信に失敗しました。しばらく経ってから再度お試しください。")
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (status === "success") {
|
|
73
|
+
return (
|
|
74
|
+
<div className="rounded-lg border bg-card p-8 text-center">
|
|
75
|
+
<h3 className="text-lg font-semibold mb-2">送信完了</h3>
|
|
76
|
+
<p className="text-muted-foreground">お問い合わせありがとうございます。</p>
|
|
77
|
+
</div>
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<form onSubmit={handleSubmit} className="space-y-6">
|
|
83
|
+
{/* ハニーポット(非表示) */}
|
|
84
|
+
<div className="hidden" aria-hidden="true">
|
|
85
|
+
<input
|
|
86
|
+
type="text"
|
|
87
|
+
name="_hp"
|
|
88
|
+
tabIndex={-1}
|
|
89
|
+
autoComplete="off"
|
|
90
|
+
value={honeypot}
|
|
91
|
+
onChange={(e) => setHoneypot(e.target.value)}
|
|
92
|
+
/>
|
|
93
|
+
</div>
|
|
94
|
+
|
|
95
|
+
<div className="space-y-2">
|
|
96
|
+
<Label htmlFor="name">お名前 *</Label>
|
|
97
|
+
<Input
|
|
98
|
+
id="name"
|
|
99
|
+
value={formData.name}
|
|
100
|
+
onChange={(e) => setFormData((prev) => ({ ...prev, name: e.target.value }))}
|
|
101
|
+
aria-invalid={!!errors.name}
|
|
102
|
+
aria-describedby={errors.name ? "name-error" : undefined}
|
|
103
|
+
/>
|
|
104
|
+
{errors.name && <p id="name-error" className="text-sm text-destructive">{errors.name}</p>}
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
<div className="space-y-2">
|
|
108
|
+
<Label htmlFor="email">メールアドレス *</Label>
|
|
109
|
+
<Input
|
|
110
|
+
id="email"
|
|
111
|
+
type="email"
|
|
112
|
+
value={formData.email}
|
|
113
|
+
onChange={(e) => setFormData((prev) => ({ ...prev, email: e.target.value }))}
|
|
114
|
+
aria-invalid={!!errors.email}
|
|
115
|
+
aria-describedby={errors.email ? "email-error" : undefined}
|
|
116
|
+
/>
|
|
117
|
+
{errors.email && <p id="email-error" className="text-sm text-destructive">{errors.email}</p>}
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
<div className="space-y-2">
|
|
121
|
+
<Label htmlFor="message">メッセージ *</Label>
|
|
122
|
+
<Textarea
|
|
123
|
+
id="message"
|
|
124
|
+
rows={5}
|
|
125
|
+
value={formData.message}
|
|
126
|
+
onChange={(e) => setFormData((prev) => ({ ...prev, message: e.target.value }))}
|
|
127
|
+
aria-invalid={!!errors.message}
|
|
128
|
+
aria-describedby={errors.message ? "message-error" : undefined}
|
|
129
|
+
/>
|
|
130
|
+
{errors.message && <p id="message-error" className="text-sm text-destructive">{errors.message}</p>}
|
|
131
|
+
</div>
|
|
132
|
+
|
|
133
|
+
{errorMessage && (
|
|
134
|
+
<div className="rounded-lg border border-destructive bg-destructive/10 p-4 text-sm text-destructive">
|
|
135
|
+
{errorMessage}
|
|
136
|
+
</div>
|
|
137
|
+
)}
|
|
138
|
+
|
|
139
|
+
<Button type="submit" disabled={status === "submitting"}>
|
|
140
|
+
{status === "submitting" ? "送信中..." : "送信する"}
|
|
141
|
+
</Button>
|
|
142
|
+
</form>
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## カスタマイズポイント
|
|
148
|
+
|
|
149
|
+
- `formSchema`: フィールドに合わせて Zod スキーマを変更
|
|
150
|
+
- `formSlug`: Admin で定義したフォームのスラッグに合わせる
|
|
151
|
+
- スタイリング: site-config.md のデザイン方針に沿って調整
|
|
152
|
+
- 送信後の挙動: サンクスメッセージ or リダイレクト
|