cmx-sdk 0.2.7 → 0.2.9
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/add-studio-YUDYE2OH.js +0 -0
- package/dist/chunk-7TDMLYBI.js +0 -0
- package/dist/chunk-EDXXR5BE.js +0 -0
- package/dist/{chunk-S3TFTN6H.js → chunk-EZMBZWH7.js} +57 -0
- package/dist/chunk-FPQYL5GE.js +0 -0
- package/dist/chunk-IIQLQIDP.js +0 -0
- package/dist/chunk-NZQ6SBFS.js +0 -0
- package/dist/cli.js +567 -121
- package/dist/index.d.ts +343 -2
- package/dist/index.js +79 -31
- package/dist/index.js.map +1 -1
- package/dist/init-FLRQXJX4.js +0 -0
- package/dist/{interactive-menu-QKE6FMPN.js → interactive-menu-FYVOQSTL.js} +1 -1
- package/dist/studio-HAS6DYLO.js +0 -0
- package/dist/{update-sdk-ZDFOSMN4.js → update-sdk-KJZ6VB4M.js} +1 -1
- package/dist/update-studio-TWCYSYIS.js +0 -0
- package/package.json +16 -14
- 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
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cmx-dev
|
|
3
|
+
description: |
|
|
4
|
+
CMX Starter Kitの開発ガイド。cmx-sdkを使ったページ作成、データ取得、MDXレンダリング、コード生成を支援。
|
|
5
|
+
トリガー: 「ページを追加」「ブログページを作りたい」「データを表示」「コレクションページ」
|
|
6
|
+
「データ型のページ」「MDXをレンダリング」「型を生成」「npx cmx-sdk codegen types」
|
|
7
|
+
「CMXからデータを取得」「新しいルートを作成」「プレビューページ」など。
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# CMX Starter Kit 開発ガイド
|
|
11
|
+
|
|
12
|
+
## プロジェクト構成
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
src/
|
|
16
|
+
├── app/ # Next.js App Router ページ
|
|
17
|
+
│ ├── blog/page.tsx # コレクション一覧ページ
|
|
18
|
+
│ ├── blog/[slug]/page.tsx # コレクション記事詳細ページ
|
|
19
|
+
│ └── preview/[token]/page.tsx # プレビューページ
|
|
20
|
+
├── components/custom/ # カスタムMDXコンポーネント
|
|
21
|
+
├── lib/
|
|
22
|
+
│ ├── api/
|
|
23
|
+
│ │ ├── admin-client.ts # cmx-sdk の re-export
|
|
24
|
+
│ │ └── public-client.ts # エラーハンドリング付きラッパー
|
|
25
|
+
│ ├── mdx/render.tsx # MDXレンダリング(カスタムコンポーネント注入)
|
|
26
|
+
│ ├── utils/
|
|
27
|
+
│ │ ├── data-fetching.ts # requireFetchContents, requireFetchContent
|
|
28
|
+
│ │ ├── metadata.ts # generateCollectionMetadata, generateContentMetadata
|
|
29
|
+
│ │ └── date.ts # formatContentDate
|
|
30
|
+
│ └── constants/collections.ts # COLLECTION_SLUGS 定義
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## タスク判定ツリー
|
|
34
|
+
|
|
35
|
+
- **コレクション(ブログ/ニュース等)のページを追加** → [コレクションページ作成](#コレクションページ作成)
|
|
36
|
+
- **データ型(チームメンバー/FAQ等)のページを追加** → [データ型ページ作成](#データ型ページ作成)
|
|
37
|
+
- **静的ページを追加** → [静的ページ作成](#静的ページ作成)
|
|
38
|
+
- **型安全なAPIコードを生成** → [コード生成](#コード生成)
|
|
39
|
+
- **API関数の詳細を知りたい** → [references/api-patterns.md](references/api-patterns.md) を参照
|
|
40
|
+
|
|
41
|
+
## コレクションページ作成
|
|
42
|
+
|
|
43
|
+
CMX Admin で管理されたコレクション(ブログ、ニュース等)のページを作成する。
|
|
44
|
+
|
|
45
|
+
### 一覧ページ
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
// src/app/{collection}/page.tsx
|
|
49
|
+
import type { Metadata } from "next"
|
|
50
|
+
import { COLLECTION_SLUGS } from "@/lib/constants/collections"
|
|
51
|
+
import { requireFetchContents } from "@/lib/utils/data-fetching"
|
|
52
|
+
import { generateCollectionMetadata } from "@/lib/utils/metadata"
|
|
53
|
+
import { formatContentDate } from "@/lib/utils/date"
|
|
54
|
+
|
|
55
|
+
export const dynamic = "force-dynamic"
|
|
56
|
+
|
|
57
|
+
export async function generateMetadata(): Promise<Metadata> {
|
|
58
|
+
return generateCollectionMetadata(COLLECTION_SLUGS.blog)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export default async function BlogListPage() {
|
|
62
|
+
const { collection, contents } = await requireFetchContents(COLLECTION_SLUGS.blog)
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<div className="container mx-auto px-4 py-12">
|
|
66
|
+
<h1 className="text-4xl font-bold mb-8">{collection.name}</h1>
|
|
67
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
68
|
+
{contents.map((item) => (
|
|
69
|
+
<a key={item.id} href={`/blog/${item.slug}`}>
|
|
70
|
+
<h2>{item.title}</h2>
|
|
71
|
+
<p>{item.description}</p>
|
|
72
|
+
{item.publishedAt && <time>{formatContentDate(item.publishedAt)}</time>}
|
|
73
|
+
</a>
|
|
74
|
+
))}
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### 詳細ページ(MDXレンダリング付き)
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
// src/app/{collection}/[slug]/page.tsx
|
|
85
|
+
import type { Metadata } from "next"
|
|
86
|
+
import { COLLECTION_SLUGS } from "@/lib/constants/collections"
|
|
87
|
+
import { requireFetchContent } from "@/lib/utils/data-fetching"
|
|
88
|
+
import { generateContentMetadata } from "@/lib/utils/metadata"
|
|
89
|
+
import { renderMdx } from "@/lib/mdx/render"
|
|
90
|
+
|
|
91
|
+
export const dynamic = "force-dynamic"
|
|
92
|
+
|
|
93
|
+
interface PageProps {
|
|
94
|
+
params: Promise<{ slug: string }>
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
|
|
98
|
+
const { slug } = await params
|
|
99
|
+
return generateContentMetadata(COLLECTION_SLUGS.blog, slug)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export default async function BlogPostPage({ params }: PageProps) {
|
|
103
|
+
const { slug } = await params
|
|
104
|
+
const { content, references } = await requireFetchContent(COLLECTION_SLUGS.blog, slug)
|
|
105
|
+
const { content: renderedContent } = await renderMdx(content.mdx, references)
|
|
106
|
+
|
|
107
|
+
return (
|
|
108
|
+
<article className="container mx-auto px-4 py-12">
|
|
109
|
+
<div className="max-w-3xl mx-auto">
|
|
110
|
+
<h1 className="text-4xl font-bold mb-4">{content.title}</h1>
|
|
111
|
+
<div className="prose prose-lg max-w-none">{renderedContent}</div>
|
|
112
|
+
</div>
|
|
113
|
+
</article>
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### 新しいコレクションを追加する際の手順
|
|
119
|
+
|
|
120
|
+
1. `src/lib/constants/collections.ts` に slug を追加
|
|
121
|
+
2. `src/app/{slug}/page.tsx` — 一覧ページ作成
|
|
122
|
+
3. `src/app/{slug}/[slug]/page.tsx` — 詳細ページ作成
|
|
123
|
+
|
|
124
|
+
## データ型ページ作成
|
|
125
|
+
|
|
126
|
+
CMX Admin で管理されたデータ型(チームメンバー、FAQ等)を表示するページ。
|
|
127
|
+
|
|
128
|
+
```tsx
|
|
129
|
+
// src/app/team/page.tsx
|
|
130
|
+
import { getDataEntries } from "@/lib/api/admin-client"
|
|
131
|
+
|
|
132
|
+
export const dynamic = "force-dynamic"
|
|
133
|
+
|
|
134
|
+
interface TeamMember {
|
|
135
|
+
id: string
|
|
136
|
+
name?: string
|
|
137
|
+
role?: string
|
|
138
|
+
bio?: string
|
|
139
|
+
image?: string
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export default async function TeamPage() {
|
|
143
|
+
const data = await getDataEntries("team-members", {
|
|
144
|
+
sortBy: "createdAt",
|
|
145
|
+
sortOrder: "asc",
|
|
146
|
+
// status: "published", // デフォルト。全ステータス取得は "all"
|
|
147
|
+
})
|
|
148
|
+
if (!data) throw new Error("Failed to fetch team members")
|
|
149
|
+
const members = data.items as unknown as TeamMember[]
|
|
150
|
+
|
|
151
|
+
return (
|
|
152
|
+
<div className="container mx-auto px-4 py-12">
|
|
153
|
+
<h1 className="text-4xl font-bold mb-8">チーム</h1>
|
|
154
|
+
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
|
155
|
+
{members.map((m) => (
|
|
156
|
+
<div key={m.id} className="text-center">
|
|
157
|
+
<h3 className="font-semibold">{m.name}</h3>
|
|
158
|
+
<p className="text-muted-foreground">{m.role}</p>
|
|
159
|
+
</div>
|
|
160
|
+
))}
|
|
161
|
+
</div>
|
|
162
|
+
</div>
|
|
163
|
+
)
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**型安全にしたい場合:** [コード生成](#コード生成)で型付き関数を生成。
|
|
168
|
+
|
|
169
|
+
## 静的ページ作成
|
|
170
|
+
|
|
171
|
+
CMX APIを使わない単純なページ。
|
|
172
|
+
|
|
173
|
+
```tsx
|
|
174
|
+
// src/app/about/page.tsx
|
|
175
|
+
export const metadata = {
|
|
176
|
+
title: "About | CMX",
|
|
177
|
+
description: "About us page",
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export default function AboutPage() {
|
|
181
|
+
return (
|
|
182
|
+
<div className="container mx-auto px-4 py-12">
|
|
183
|
+
<h1 className="text-4xl font-bold mb-4">About</h1>
|
|
184
|
+
<p>Your content here</p>
|
|
185
|
+
</div>
|
|
186
|
+
)
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## コード生成
|
|
191
|
+
|
|
192
|
+
`cmx-sdk` CLI でワークスペースのスキーマから型付きTypeScript関数を自動生成。
|
|
193
|
+
|
|
194
|
+
### 実行
|
|
195
|
+
|
|
196
|
+
<!-- cmx-sync:start id="skill-cmx-dev-codegen-command-block" -->
|
|
197
|
+
```bash
|
|
198
|
+
npx cmx-sdk codegen types # デフォルト: cmx/generated/
|
|
199
|
+
npx cmx-sdk codegen types --output cmx/generated # 出力先を明示(通常は省略可)
|
|
200
|
+
```
|
|
201
|
+
<!-- cmx-sync:end -->
|
|
202
|
+
|
|
203
|
+
**前提:** `.env.local`(または `.env`)に `CMX_API_URL` と `CMX_API_KEY` が設定されていること。
|
|
204
|
+
|
|
205
|
+
### 生成されるもの
|
|
206
|
+
|
|
207
|
+
```
|
|
208
|
+
cmx/generated/
|
|
209
|
+
├── index.ts # 全エクスポート
|
|
210
|
+
├── collections/
|
|
211
|
+
│ ├── blog.ts # getBlogContents(), getBlogContentDetail()
|
|
212
|
+
│ └── pages.ts # getPagesContents(), getPagesContentDetail()
|
|
213
|
+
└── data-types/
|
|
214
|
+
├── team-members.ts # TeamMember型, getTeamMembers(), getTeamMemberById()
|
|
215
|
+
└── faq.ts # Faq型, getFaqs(), getFaqById()
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### 使い方
|
|
219
|
+
|
|
220
|
+
```tsx
|
|
221
|
+
// Before(汎用、型なし)
|
|
222
|
+
import { getDataEntries } from "cmx-sdk"
|
|
223
|
+
const { items } = await getDataEntries("team-members")
|
|
224
|
+
// items[0].name は unknown
|
|
225
|
+
|
|
226
|
+
// After(型付き、自動生成)
|
|
227
|
+
import { getTeamMembers, getTeamMemberById } from "@cmx/generated"
|
|
228
|
+
const { items } = await getTeamMembers()
|
|
229
|
+
// items[0].name は string ✅
|
|
230
|
+
const member = await getTeamMemberById("uuid")
|
|
231
|
+
// member.name は string ✅
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### スキーマ変更時
|
|
235
|
+
|
|
236
|
+
CMX Admin でコレクションやデータ型を変更した場合は再実行:
|
|
237
|
+
|
|
238
|
+
<!-- cmx-sync:start id="skill-cmx-dev-regenerate-command-block" -->
|
|
239
|
+
```bash
|
|
240
|
+
npx cmx-sdk codegen types
|
|
241
|
+
```
|
|
242
|
+
<!-- cmx-sync:end -->
|
|
243
|
+
|
|
244
|
+
## 共通パターン
|
|
245
|
+
|
|
246
|
+
### `export const dynamic = "force-dynamic"`
|
|
247
|
+
|
|
248
|
+
全てのCMXデータ取得ページに必須。ビルド時にAPIコールが失敗しないようにする。
|
|
249
|
+
|
|
250
|
+
### MDXレンダリング
|
|
251
|
+
|
|
252
|
+
常に `src/lib/mdx/render.tsx` の `renderMdx` を使用。カスタムコンポーネントが自動注入される。
|
|
253
|
+
|
|
254
|
+
```tsx
|
|
255
|
+
import { renderMdx } from "@/lib/mdx/render"
|
|
256
|
+
const { content } = await renderMdx(content.mdx, references)
|
|
257
|
+
// content を JSX として直接レンダリング
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### メタデータ生成
|
|
261
|
+
|
|
262
|
+
コレクションページには既存ユーティリティを使用:
|
|
263
|
+
|
|
264
|
+
```tsx
|
|
265
|
+
import { generateCollectionMetadata, generateContentMetadata } from "@/lib/utils/metadata"
|
|
266
|
+
```
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# CMX SDK API リファレンス
|
|
2
|
+
|
|
3
|
+
## コンテンツ取得関数
|
|
4
|
+
|
|
5
|
+
### getCollectionContents(slug, options?)
|
|
6
|
+
|
|
7
|
+
コレクションの記事一覧を取得。
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
import { getCollectionContents } from "@/lib/api/admin-client"
|
|
11
|
+
// または直接: import { getCollectionContents } from "cmx-sdk"
|
|
12
|
+
|
|
13
|
+
const data = await getCollectionContents("blog", {
|
|
14
|
+
sortBy: "publishedAt",
|
|
15
|
+
sortOrder: "desc",
|
|
16
|
+
limit: 10,
|
|
17
|
+
status: "published", // デフォルト。"all" で全ステータス、"draft,review" でカンマ区切り複数指定
|
|
18
|
+
})
|
|
19
|
+
// data.collection: { name, slug, description }
|
|
20
|
+
// data.contents: Array<{ id, title, slug, description, publishedAt, status }>
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**ユースケース:**
|
|
24
|
+
- `status: "published"`(デフォルト): 公開済みのみ取得(フロント表示用)
|
|
25
|
+
- `status: "all"`: 全ステータス取得(AI執筆時の参照用)
|
|
26
|
+
- `status: "draft,review"`: 複数ステータス指定
|
|
27
|
+
|
|
28
|
+
### getCollectionContentDetail(collectionSlug, contentSlug)
|
|
29
|
+
|
|
30
|
+
記事の詳細とMDX本文・リファレンスを取得。
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
import { getCollectionContentDetail } from "@/lib/api/admin-client"
|
|
34
|
+
|
|
35
|
+
const data = await getCollectionContentDetail("blog", "my-post")
|
|
36
|
+
// data.collection: { name, slug }
|
|
37
|
+
// data.content: { id, title, slug, description, mdx, publishedAt, ... }
|
|
38
|
+
// data.references: { contents: {...}, assets: {...} }
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### getDataEntries(typeSlug, options?)
|
|
42
|
+
|
|
43
|
+
データ型のエントリ一覧を取得。
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
import { getDataEntries } from "@/lib/api/admin-client"
|
|
47
|
+
|
|
48
|
+
const data = await getDataEntries("team-members", {
|
|
49
|
+
sortBy: "createdAt", // ソートフィールド
|
|
50
|
+
sortOrder: "desc", // "asc" | "desc"
|
|
51
|
+
limit: 10, // 取得件数
|
|
52
|
+
status: "published", // デフォルト。"all" で全ステータス
|
|
53
|
+
})
|
|
54
|
+
// data.items: Array<{ id, ...フィールド }>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**ステータスフィルタ:**
|
|
58
|
+
- `status: "published"`(デフォルト): 公開済みのみ
|
|
59
|
+
- `status: "all"`: 全ステータス(draft, published)
|
|
60
|
+
|
|
61
|
+
### getDataEntry(typeSlug, id)
|
|
62
|
+
|
|
63
|
+
データ型の単一エントリを取得。
|
|
64
|
+
|
|
65
|
+
```tsx
|
|
66
|
+
import { getDataEntry } from "@/lib/api/admin-client"
|
|
67
|
+
|
|
68
|
+
const entry = await getDataEntry("team-members", "entry-uuid")
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### getPreviewByToken(token)
|
|
72
|
+
|
|
73
|
+
プレビュートークンでコンテンツを取得(認証不要)。
|
|
74
|
+
|
|
75
|
+
```tsx
|
|
76
|
+
import { getPreviewByToken } from "@/lib/api/admin-client"
|
|
77
|
+
|
|
78
|
+
const data = await getPreviewByToken(token)
|
|
79
|
+
if (data) {
|
|
80
|
+
// data.content: { title, mdx, status, environment, ... }
|
|
81
|
+
// data.collection: { name, slug }
|
|
82
|
+
// data.references: { contents, assets }
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## ラッパー関数
|
|
87
|
+
|
|
88
|
+
`src/lib/utils/data-fetching.ts` のヘルパー。失敗時にエラーをthrowする。
|
|
89
|
+
|
|
90
|
+
```tsx
|
|
91
|
+
import {
|
|
92
|
+
requireFetchContents, // getCollectionContents のラッパー
|
|
93
|
+
requireFetchContent, // getCollectionContentDetail のラッパー
|
|
94
|
+
requireDataEntries, // getDataEntries のラッパー
|
|
95
|
+
} from "@/lib/utils/data-fetching"
|
|
96
|
+
|
|
97
|
+
// 失敗時は Error をthrow(ページレベルでcatch)
|
|
98
|
+
const { collection, contents } = await requireFetchContents("blog")
|
|
99
|
+
const { content, references } = await requireFetchContent("blog", "my-post")
|
|
100
|
+
const { items } = await requireDataEntries("faq")
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## MDXレンダリング
|
|
104
|
+
|
|
105
|
+
```tsx
|
|
106
|
+
import { renderMdx } from "@/lib/mdx/render"
|
|
107
|
+
|
|
108
|
+
// references はAPI応答から取得したもの
|
|
109
|
+
const { content } = await renderMdx(item.mdx, references)
|
|
110
|
+
|
|
111
|
+
// content はReactElement → JSXに直接埋め込み可能
|
|
112
|
+
<div className="prose prose-lg max-w-none">{content}</div>
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
`renderMdx` は内部で `src/components/custom/index.ts` の全エクスポートを自動注入する。
|
|
116
|
+
|
|
117
|
+
## コレクション付属データタイプ API
|
|
118
|
+
|
|
119
|
+
### コレクションのデータタイプ一覧(SDK API)
|
|
120
|
+
|
|
121
|
+
```
|
|
122
|
+
GET /api/v1/sdk/manage/collections/{slug}/data-types
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### コレクションにデータタイプを追加(SDK API)
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
POST /api/v1/sdk/manage/collections/{slug}/data-types
|
|
129
|
+
Body: { "presetSlug": "categories" } // プリセットから追加
|
|
130
|
+
Body: { "slug": "custom", "name": "カスタム", "referenceType": "single", "fields": [...] } // カスタム
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### コレクションからデータタイプを削除(SDK API)
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
DELETE /api/v1/sdk/manage/collections/{slug}/data-types/{dtSlug}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### プリセット一覧(SDK API)
|
|
140
|
+
|
|
141
|
+
```
|
|
142
|
+
GET /api/v1/sdk/manage/collection-presets?type=post
|
|
143
|
+
→ { "recommended": [...], "others": [...] }
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### コンテンツの参照取得/設定(SDK API)
|
|
147
|
+
|
|
148
|
+
```
|
|
149
|
+
GET /api/v1/sdk/manage/contents/{id}/references
|
|
150
|
+
PUT /api/v1/sdk/manage/contents/{id}/references
|
|
151
|
+
Body: { "references": [{ "fieldSlug": "categories", "dataEntryIds": ["uuid"] }] }
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### MCP API(AI向け)
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
GET /api/mcp/collections/{slug}/data-types # データタイプ一覧
|
|
158
|
+
GET /api/mcp/collections/{slug}/data-types/{dtSlug}/entries # エントリ一覧
|
|
159
|
+
GET /api/mcp/collection-presets?type=post # プリセット一覧
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## キャッシュタグ
|
|
163
|
+
|
|
164
|
+
Next.js のオンデマンド再検証用:
|
|
165
|
+
|
|
166
|
+
```tsx
|
|
167
|
+
import { CACHE_TAGS } from "@/lib/api/admin-client"
|
|
168
|
+
import { revalidateTag } from "next/cache"
|
|
169
|
+
|
|
170
|
+
CACHE_TAGS.collections // 全コレクション
|
|
171
|
+
CACHE_TAGS.collection("blog") // 特定コレクション
|
|
172
|
+
CACHE_TAGS.content("blog", "slug") // 特定記事
|
|
173
|
+
CACHE_TAGS.data // 全データ型
|
|
174
|
+
CACHE_TAGS.dataType("faq") // 特定データ型
|
|
175
|
+
|
|
176
|
+
// 再検証
|
|
177
|
+
revalidateTag(CACHE_TAGS.collection("blog"))
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## メタデータ生成
|
|
181
|
+
|
|
182
|
+
```tsx
|
|
183
|
+
import {
|
|
184
|
+
generateCollectionMetadata, // 一覧ページ用
|
|
185
|
+
generateContentMetadata, // 詳細ページ用
|
|
186
|
+
} from "@/lib/utils/metadata"
|
|
187
|
+
|
|
188
|
+
// 一覧ページ
|
|
189
|
+
export async function generateMetadata() {
|
|
190
|
+
return generateCollectionMetadata("blog")
|
|
191
|
+
// → { title: "ブログ | CMX", description: "..." }
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// 詳細ページ
|
|
195
|
+
export async function generateMetadata({ params }) {
|
|
196
|
+
const { slug } = await params
|
|
197
|
+
return generateContentMetadata("blog", slug)
|
|
198
|
+
// → { title: "記事タイトル | ブログ | CMX", description: "..." }
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## 型インポート
|
|
203
|
+
|
|
204
|
+
```tsx
|
|
205
|
+
import type {
|
|
206
|
+
CollectionContentsResponse,
|
|
207
|
+
CollectionContentDetailResponse,
|
|
208
|
+
DataListResponse,
|
|
209
|
+
DataEntryItem,
|
|
210
|
+
PreviewResponse,
|
|
211
|
+
References,
|
|
212
|
+
} from "cmx-sdk"
|
|
213
|
+
|
|
214
|
+
// エイリアス(admin-client.ts で定義済み)
|
|
215
|
+
import type {
|
|
216
|
+
CollectionInfo, // PublicCollectionInfo のエイリアス
|
|
217
|
+
ContentListItem, // PublicContentListItem のエイリアス
|
|
218
|
+
ContentDetail, // PublicContentDetail のエイリアス
|
|
219
|
+
} from "@/lib/api/admin-client"
|
|
220
|
+
```
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# cmx-sdk CLI リファレンス
|
|
2
|
+
|
|
3
|
+
## コード生成
|
|
4
|
+
|
|
5
|
+
<!-- cmx-sync:start id="skill-cmx-dev-cli-codegen-table" -->
|
|
6
|
+
| コマンド | 説明 | 冪等性 |
|
|
7
|
+
|---------|------|--------|
|
|
8
|
+
| `npx cmx-sdk codegen types` | スキーマから TypeScript 型・取得関数を生成(`cmx/generated/`) | はい(何度でも再実行可能) |
|
|
9
|
+
| `npx cmx-sdk codegen types --output <dir>` | 出力先を明示指定 | はい |
|
|
10
|
+
| `npx cmx-sdk codegen pages --template layered` | ページ雛形を生成(`src/app/`, `src/features/`) | 既存ファイルは上書きしない |
|
|
11
|
+
| `npx cmx-sdk codegen pages --template layered --only {category}:{slug}` | 特定のコレクション/データタイプ/フォームのみ生成 | 既存ファイルは上書きしない |
|
|
12
|
+
<!-- cmx-sync:end -->
|
|
13
|
+
|
|
14
|
+
**category** は `collections`, `data-types`, `forms` のいずれか。
|
|
15
|
+
|
|
16
|
+
## スキーマ管理
|
|
17
|
+
|
|
18
|
+
| コマンド | 説明 |
|
|
19
|
+
|---------|------|
|
|
20
|
+
| `npx cmx-sdk list-collections` | コレクション一覧 |
|
|
21
|
+
| `npx cmx-sdk list-data-types` | グローバルデータタイプ一覧 |
|
|
22
|
+
| `npx cmx-sdk list-forms` | フォーム一覧 |
|
|
23
|
+
| `npx cmx-sdk list-collection-data-types --collection {slug}` | コレクション付属データタイプ一覧 |
|
|
24
|
+
| `npx cmx-sdk list-collection-presets --type {type}` | プリセット一覧 |
|
|
25
|
+
| `npx cmx-sdk create-collection --json '{...}'` | コレクション作成 |
|
|
26
|
+
| `npx cmx-sdk create-data-type --json '{...}'` | グローバルデータタイプ作成 |
|
|
27
|
+
| `npx cmx-sdk create-form --json '{...}'` | フォーム作成 |
|
|
28
|
+
| `npx cmx-sdk add-collection-data-type --collection {slug} --preset {preset}` | 付属データタイプ追加(プリセット) |
|
|
29
|
+
| `npx cmx-sdk add-collection-data-type --collection {slug} --json '{...}'` | 付属データタイプ追加(カスタム) |
|
|
30
|
+
|
|
31
|
+
## コンテンツ管理
|
|
32
|
+
|
|
33
|
+
| コマンド | 説明 |
|
|
34
|
+
|---------|------|
|
|
35
|
+
| `npx cmx-sdk create-content --collection {slug} --json '{...}'` | コンテンツ作成(draft ステータス) |
|
|
36
|
+
| `npx cmx-sdk create-content --collection {slug} --file {path}` | ファイルから作成 |
|
|
37
|
+
| `npx cmx-sdk request-review-content --id {contentId}` | レビュー依頼(draft → review) |
|
|
38
|
+
| `npx cmx-sdk publish-content --id {contentId}` | 公開(review → published) |
|
|
39
|
+
| `npx cmx-sdk set-content-references --id {contentId} --json '{...}'` | 参照設定(カテゴリ・タグ) |
|
|
40
|
+
| `npx cmx-sdk create-data-entry --type-slug {slug} --json '{...}'` | データタイプエントリ作成 |
|
|
41
|
+
|
|
42
|
+
## コンポーネント
|
|
43
|
+
|
|
44
|
+
<!-- cmx-sync:start id="skill-cmx-dev-cli-components-table" -->
|
|
45
|
+
| コマンド | 説明 |
|
|
46
|
+
|---------|------|
|
|
47
|
+
| `npx cmx-sdk components scaffold --name {Name}` | コンポーネント雛形生成 |
|
|
48
|
+
| `npx cmx-sdk sync-components` | `cmx/components/` の JSON 定義を Admin に同期 |
|
|
49
|
+
<!-- cmx-sync:end -->
|
|
50
|
+
|
|
51
|
+
## 前提
|
|
52
|
+
|
|
53
|
+
- `.env.local`(または `.env`)に `CMX_API_KEY` と `CMX_API_URL` が設定されていること
|
|
54
|
+
- `npx cmx-sdk` はプロジェクトルートから実行すること
|
|
@@ -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、エラーメッセージの関連付け)
|