@einja/dev-cli 0.1.45 → 0.1.49
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 +7 -1
- package/dist/cli.js.map +1 -1
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +50 -11
- package/dist/commands/sync.js.map +1 -1
- package/dist/commands/sync.test.js +1 -1
- package/dist/commands/sync.test.js.map +1 -1
- package/dist/lib/sync/file-filter.d.ts.map +1 -1
- package/dist/lib/sync/file-filter.js +49 -0
- package/dist/lib/sync/file-filter.js.map +1 -1
- package/dist/lib/sync/file-filter.test.js +40 -0
- package/dist/lib/sync/file-filter.test.js.map +1 -1
- package/dist/lib/sync/json-processor.d.ts +49 -27
- package/dist/lib/sync/json-processor.d.ts.map +1 -1
- package/dist/lib/sync/json-processor.js +182 -82
- package/dist/lib/sync/json-processor.js.map +1 -1
- package/dist/lib/sync/json-processor.test.d.ts +2 -0
- package/dist/lib/sync/json-processor.test.d.ts.map +1 -0
- package/dist/lib/sync/json-processor.test.js +334 -0
- package/dist/lib/sync/json-processor.test.js.map +1 -0
- package/dist/lib/sync/metadata-manager.d.ts +6 -1
- package/dist/lib/sync/metadata-manager.d.ts.map +1 -1
- package/dist/lib/sync/metadata-manager.js +26 -6
- package/dist/lib/sync/metadata-manager.js.map +1 -1
- package/dist/types/sync.d.ts +2 -0
- package/dist/types/sync.d.ts.map +1 -1
- package/dist/types/sync.js +1 -0
- package/dist/types/sync.js.map +1 -1
- package/package.json +1 -1
- package/presets/default/.claude/agents/einja/backend-architect.md +27 -17
- package/presets/default/.claude/commands/einja/einja-sync.md +6 -6
- package/presets/default/.claude/skills/einja-coding-standards/references/testing-strategy.md +3 -3
- package/presets/default/docs/einja/instructions/setup-flow.md +40 -0
- package/presets/default/docs/einja/steering/architecture.md +7 -1
- package/presets/default/docs/einja/steering/development/api-development.md +199 -67
- package/presets/default/docs/einja/steering/development/backend-architecture.md +12 -16
- package/presets/default/docs/einja/steering/development/frontend-development.md +61 -50
- package/presets/default/docs/einja/steering/development/review-guidelines.md +4 -1
- package/presets/default/docs/einja/steering/development/testing-strategy.md +3 -3
- package/presets/default/package.json +73 -0
- package/presets/default/preset.yaml +2 -2
- package/presets/default/scripts/ensure-serena.sh +26 -7
|
@@ -60,10 +60,10 @@ apps/web/
|
|
|
60
60
|
│ │ │ │ └── page.tsx # 投稿詳細
|
|
61
61
|
│ │ │ └── profile/
|
|
62
62
|
│ │ │ └── page.tsx
|
|
63
|
-
│ │ ├── api/
|
|
64
|
-
│ │ │
|
|
65
|
-
│ │ │
|
|
66
|
-
│ │ │
|
|
63
|
+
│ │ ├── api/rpc/ # ドメインベースRPC
|
|
64
|
+
│ │ │ ├── users/[[...route]]/route.ts
|
|
65
|
+
│ │ │ ├── auth/[[...route]]/route.ts
|
|
66
|
+
│ │ │ └── posts/[[...route]]/route.ts
|
|
67
67
|
│ │ ├── layout.tsx # ルートレイアウト
|
|
68
68
|
│ │ └── page.tsx # トップページ
|
|
69
69
|
│ ├── components/ # UIコンポーネント
|
|
@@ -84,7 +84,7 @@ apps/web/
|
|
|
84
84
|
│ │ └── RegisterForm.tsx
|
|
85
85
|
│ ├── lib/ # ユーティリティ
|
|
86
86
|
│ │ ├── api/
|
|
87
|
-
│ │ │ ├──
|
|
87
|
+
│ │ │ ├── rpc.ts # Hono RPCクライアント設定
|
|
88
88
|
│ │ │ └── parse-response.ts # レスポンスパース&バリデーション
|
|
89
89
|
│ │ ├── query-client.ts # Tanstack Query設定
|
|
90
90
|
│ │ └── utils.ts # 共通ユーティリティ
|
|
@@ -116,10 +116,9 @@ apps/admin/
|
|
|
116
116
|
│ │ │ │ │ └── page.tsx
|
|
117
117
|
│ │ │ │ └── analytics/
|
|
118
118
|
│ │ │ │ └── page.tsx
|
|
119
|
-
│ │ ├── api/
|
|
120
|
-
│ │ │
|
|
121
|
-
│ │ │
|
|
122
|
-
│ │ │ └── route.ts
|
|
119
|
+
│ │ ├── api/rpc/ # ドメインベースRPC
|
|
120
|
+
│ │ │ ├── users/[[...route]]/route.ts
|
|
121
|
+
│ │ │ └── posts/[[...route]]/route.ts
|
|
123
122
|
│ │ ├── layout.tsx
|
|
124
123
|
│ │ └── page.tsx
|
|
125
124
|
│ ├── components/
|
|
@@ -198,27 +197,39 @@ export const paginatedUserListSchema = z.object({
|
|
|
198
197
|
|
|
199
198
|
### セットアップ
|
|
200
199
|
|
|
201
|
-
**
|
|
200
|
+
**RPCクライアントの初期化**:
|
|
202
201
|
|
|
203
|
-
|
|
204
|
-
// apps/web/src/lib/api/client.ts
|
|
205
|
-
import { hc } from 'hono/client'
|
|
206
|
-
import type { AppType } from '@/app/api/rpc/[[...route]]/route'
|
|
202
|
+
> **Note**: 以下は複数ドメインを追加した場合の完成形の例です。現在のテンプレートでは `users` ドメインのみ実装されています。
|
|
207
203
|
|
|
208
|
-
|
|
204
|
+
```typescript
|
|
205
|
+
// apps/web/src/lib/api/rpc.ts
|
|
206
|
+
import type { UsersAppType } from "@/app/api/rpc/users/[[...route]]/route";
|
|
207
|
+
import type { AuthAppType } from "@/app/api/rpc/auth/[[...route]]/route";
|
|
208
|
+
import type { PostsAppType } from "@/app/api/rpc/posts/[[...route]]/route";
|
|
209
|
+
import { hc } from "hono/client";
|
|
210
|
+
|
|
211
|
+
const usersClient = hc<UsersAppType>("/");
|
|
212
|
+
const authClient = hc<AuthAppType>("/");
|
|
213
|
+
const postsClient = hc<PostsAppType>("/");
|
|
214
|
+
|
|
215
|
+
export const rpc = {
|
|
216
|
+
users: usersClient.api.rpc.users,
|
|
217
|
+
auth: authClient.api.rpc.auth,
|
|
218
|
+
posts: postsClient.api.rpc.posts,
|
|
219
|
+
} as const;
|
|
209
220
|
```
|
|
210
221
|
|
|
211
|
-
|
|
222
|
+
**型定義のエクスポート**(ドメインごとのルートファイル):
|
|
212
223
|
|
|
213
224
|
```typescript
|
|
214
|
-
// apps/web/src/app/api/rpc/[[...route]]/route.ts
|
|
225
|
+
// apps/web/src/app/api/rpc/users/[[...route]]/route.ts
|
|
215
226
|
import { Hono } from 'hono'
|
|
216
227
|
import { handle } from 'hono/vercel'
|
|
217
228
|
import { userRoutes } from '@web/server/presentation/routes/userRoutes'
|
|
218
229
|
|
|
219
|
-
const app = new Hono().basePath('/api/rpc')
|
|
230
|
+
const app = new Hono().basePath('/api/rpc/users')
|
|
220
231
|
|
|
221
|
-
const routes = app.route('/
|
|
232
|
+
const routes = app.route('/', userRoutes)
|
|
222
233
|
|
|
223
234
|
export const GET = handle(app)
|
|
224
235
|
export const POST = handle(app)
|
|
@@ -226,7 +237,7 @@ export const PUT = handle(app)
|
|
|
226
237
|
export const DELETE = handle(app)
|
|
227
238
|
|
|
228
239
|
// 型のエクスポート(フロントエンドで使用)
|
|
229
|
-
export type
|
|
240
|
+
export type UsersAppType = typeof routes
|
|
230
241
|
```
|
|
231
242
|
|
|
232
243
|
### API呼び出しパターン
|
|
@@ -235,7 +246,7 @@ export type AppType = typeof routes
|
|
|
235
246
|
|
|
236
247
|
```typescript
|
|
237
248
|
// ユーザー一覧取得
|
|
238
|
-
const response = await
|
|
249
|
+
const response = await rpc.users.$get({
|
|
239
250
|
query: { page: '1', limit: '10' }
|
|
240
251
|
})
|
|
241
252
|
const data = await response.json() // 型推論: { users: User[], total: number }
|
|
@@ -245,7 +256,7 @@ const data = await response.json() // 型推論: { users: User[], total: number
|
|
|
245
256
|
|
|
246
257
|
```typescript
|
|
247
258
|
// ユーザー作成
|
|
248
|
-
const response = await
|
|
259
|
+
const response = await rpc.users.$post({
|
|
249
260
|
json: { email: 'user@example.com', name: 'User Name' }
|
|
250
261
|
})
|
|
251
262
|
const data = await response.json() // 型推論: { user: User }
|
|
@@ -255,7 +266,7 @@ const data = await response.json() // 型推論: { user: User }
|
|
|
255
266
|
|
|
256
267
|
```typescript
|
|
257
268
|
// ユーザー詳細取得
|
|
258
|
-
const response = await
|
|
269
|
+
const response = await rpc.users[':id'].$get({
|
|
259
270
|
param: { id: '123' }
|
|
260
271
|
})
|
|
261
272
|
const data = await response.json() // 型推論: { user: User }
|
|
@@ -265,7 +276,7 @@ const data = await response.json() // 型推論: { user: User }
|
|
|
265
276
|
|
|
266
277
|
```typescript
|
|
267
278
|
// ユーザー更新
|
|
268
|
-
const response = await
|
|
279
|
+
const response = await rpc.users[':id'].$put({
|
|
269
280
|
param: { id: '123' },
|
|
270
281
|
json: { name: 'Updated Name' }
|
|
271
282
|
})
|
|
@@ -276,7 +287,7 @@ const data = await response.json() // 型推論: { user: User }
|
|
|
276
287
|
|
|
277
288
|
```typescript
|
|
278
289
|
// ユーザー削除
|
|
279
|
-
const response = await
|
|
290
|
+
const response = await rpc.users[':id'].$delete({
|
|
280
291
|
param: { id: '123' }
|
|
281
292
|
})
|
|
282
293
|
const data = await response.json() // 型推論: { success: true }
|
|
@@ -344,13 +355,13 @@ export async function parseResponse<T>(
|
|
|
344
355
|
import { useQuery } from "@tanstack/react-query";
|
|
345
356
|
import { parseResponse } from "@/lib/api/parse-response";
|
|
346
357
|
import { paginatedUserListSchema } from "@/shared/schemas/user";
|
|
347
|
-
import {
|
|
358
|
+
import { rpc } from "@/lib/api/rpc";
|
|
348
359
|
|
|
349
360
|
export function useUsers(filters: UserFilters = {}) {
|
|
350
361
|
return useQuery({
|
|
351
362
|
queryKey: ["users", filters],
|
|
352
363
|
queryFn: async () => {
|
|
353
|
-
const response = await
|
|
364
|
+
const response = await rpc.users.$get({
|
|
354
365
|
query: { page: String(filters.page || 1), limit: String(filters.limit || 10) },
|
|
355
366
|
});
|
|
356
367
|
return parseResponse(response, paginatedUserListSchema);
|
|
@@ -417,11 +428,11 @@ try {
|
|
|
417
428
|
```typescript
|
|
418
429
|
// app/posts/page.tsx (Server Component - デフォルト)
|
|
419
430
|
import { PostList } from '@/components/features/posts/PostList'
|
|
420
|
-
import {
|
|
431
|
+
import { rpc } from '@/lib/api/rpc'
|
|
421
432
|
|
|
422
433
|
export default async function PostsPage() {
|
|
423
434
|
// サーバー側でデータフェッチ
|
|
424
|
-
const response = await
|
|
435
|
+
const response = await rpc.posts.$get({
|
|
425
436
|
query: { page: '1', limit: '10' }
|
|
426
437
|
})
|
|
427
438
|
const data = await response.json()
|
|
@@ -546,11 +557,11 @@ export default function PostsPage() {
|
|
|
546
557
|
import { Header } from '@/components/features/Header'
|
|
547
558
|
import { Sidebar } from '@/components/features/Sidebar'
|
|
548
559
|
import { PostListContainer } from '@/components/features/posts/PostListContainer'
|
|
549
|
-
import {
|
|
560
|
+
import { rpc } from '@/lib/api/rpc'
|
|
550
561
|
|
|
551
562
|
export default async function PostsPage() {
|
|
552
563
|
// サーバー側でデータフェッチ
|
|
553
|
-
const response = await
|
|
564
|
+
const response = await rpc.posts.$get()
|
|
554
565
|
const data = await response.json()
|
|
555
566
|
|
|
556
567
|
return (
|
|
@@ -596,11 +607,11 @@ export function PostListContainer({ initialData }) {
|
|
|
596
607
|
```typescript
|
|
597
608
|
// app/posts/[id]/page.tsx (Server Component)
|
|
598
609
|
import { PostDetail } from '@/components/features/posts/PostDetail'
|
|
599
|
-
import {
|
|
610
|
+
import { rpc } from '@/lib/api/rpc'
|
|
600
611
|
|
|
601
612
|
export default async function PostDetailPage({ params }: { params: { id: string } }) {
|
|
602
613
|
// サーバー側でデータフェッチ
|
|
603
|
-
const response = await
|
|
614
|
+
const response = await rpc.posts[':id'].$get({
|
|
604
615
|
param: { id: params.id }
|
|
605
616
|
})
|
|
606
617
|
const { post } = await response.json()
|
|
@@ -797,13 +808,13 @@ export default function RootLayout({ children }) {
|
|
|
797
808
|
|
|
798
809
|
```typescript
|
|
799
810
|
import { useQuery } from '@tanstack/react-query'
|
|
800
|
-
import {
|
|
811
|
+
import { rpc } from '@/lib/api/rpc'
|
|
801
812
|
|
|
802
813
|
export function usePostList(page: number, limit: number) {
|
|
803
814
|
return useQuery({
|
|
804
815
|
queryKey: ['posts', page, limit], // キャッシュキー
|
|
805
816
|
queryFn: async () => {
|
|
806
|
-
const response = await
|
|
817
|
+
const response = await rpc.posts.$get({
|
|
807
818
|
query: { page: String(page), limit: String(limit) }
|
|
808
819
|
})
|
|
809
820
|
if (!response.ok) {
|
|
@@ -841,7 +852,7 @@ export function PostList() {
|
|
|
841
852
|
|
|
842
853
|
```typescript
|
|
843
854
|
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
|
844
|
-
import {
|
|
855
|
+
import { rpc } from '@/lib/api/rpc'
|
|
845
856
|
import type { CreatePostInput } from '@repo/server-core/domain/validators/post'
|
|
846
857
|
|
|
847
858
|
export function useCreatePost() {
|
|
@@ -849,7 +860,7 @@ export function useCreatePost() {
|
|
|
849
860
|
|
|
850
861
|
return useMutation({
|
|
851
862
|
mutationFn: async (data: CreatePostInput) => {
|
|
852
|
-
const response = await
|
|
863
|
+
const response = await rpc.posts.$post({ json: data })
|
|
853
864
|
if (!response.ok) {
|
|
854
865
|
throw new Error('Failed to create post')
|
|
855
866
|
}
|
|
@@ -1111,7 +1122,7 @@ export function PostCard({ post }: PostCardProps) {
|
|
|
1111
1122
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
|
1112
1123
|
import { parseResponse } from '@/lib/api/parse-response'
|
|
1113
1124
|
import { paginatedPostListSchema } from '@/shared/schemas/post'
|
|
1114
|
-
import {
|
|
1125
|
+
import { rpc } from '@/lib/api/rpc'
|
|
1115
1126
|
import type { CreatePostInput, UpdatePostInput } from '@repo/server-core/domain/validators/post'
|
|
1116
1127
|
|
|
1117
1128
|
// 投稿一覧取得
|
|
@@ -1119,7 +1130,7 @@ export function usePostList(page: number, limit: number) {
|
|
|
1119
1130
|
return useQuery({
|
|
1120
1131
|
queryKey: ['posts', page, limit],
|
|
1121
1132
|
queryFn: async () => {
|
|
1122
|
-
const response = await
|
|
1133
|
+
const response = await rpc.posts.$get({
|
|
1123
1134
|
query: { page: String(page), limit: String(limit) }
|
|
1124
1135
|
})
|
|
1125
1136
|
return parseResponse(response, paginatedPostListSchema)
|
|
@@ -1132,7 +1143,7 @@ export function usePost(id: string) {
|
|
|
1132
1143
|
return useQuery({
|
|
1133
1144
|
queryKey: ['posts', id],
|
|
1134
1145
|
queryFn: async () => {
|
|
1135
|
-
const response = await
|
|
1146
|
+
const response = await rpc.posts[':id'].$get({ param: { id } })
|
|
1136
1147
|
return parseResponse(response, postSchema)
|
|
1137
1148
|
},
|
|
1138
1149
|
})
|
|
@@ -1144,7 +1155,7 @@ export function useCreatePost() {
|
|
|
1144
1155
|
|
|
1145
1156
|
return useMutation({
|
|
1146
1157
|
mutationFn: async (data: CreatePostInput) => {
|
|
1147
|
-
const response = await
|
|
1158
|
+
const response = await rpc.posts.$post({ json: data })
|
|
1148
1159
|
return parseResponse(response, postSchema)
|
|
1149
1160
|
},
|
|
1150
1161
|
onSuccess: () => {
|
|
@@ -1159,7 +1170,7 @@ export function useUpdatePost(id: string) {
|
|
|
1159
1170
|
|
|
1160
1171
|
return useMutation({
|
|
1161
1172
|
mutationFn: async (data: UpdatePostInput) => {
|
|
1162
|
-
const response = await
|
|
1173
|
+
const response = await rpc.posts[':id'].$put({
|
|
1163
1174
|
param: { id },
|
|
1164
1175
|
json: data
|
|
1165
1176
|
})
|
|
@@ -1178,7 +1189,7 @@ export function useDeletePost() {
|
|
|
1178
1189
|
|
|
1179
1190
|
return useMutation({
|
|
1180
1191
|
mutationFn: async (id: string) => {
|
|
1181
|
-
const response = await
|
|
1192
|
+
const response = await rpc.posts[':id'].$delete({ param: { id } })
|
|
1182
1193
|
return parseResponse(response, deleteResponseSchema)
|
|
1183
1194
|
},
|
|
1184
1195
|
onSuccess: () => {
|
|
@@ -1332,12 +1343,12 @@ export default function DashboardLayout({ children }: { children: React.ReactNod
|
|
|
1332
1343
|
// ✅ page.tsxはServer Component('use client'なし)
|
|
1333
1344
|
import { PostListContainer } from '@/components/features/posts/PostListContainer'
|
|
1334
1345
|
import { Button } from '@/components/ui/button'
|
|
1335
|
-
import {
|
|
1346
|
+
import { rpc } from '@/lib/api/rpc'
|
|
1336
1347
|
import Link from 'next/link'
|
|
1337
1348
|
|
|
1338
1349
|
export default async function PostsPage() {
|
|
1339
1350
|
// サーバー側でデータフェッチ
|
|
1340
|
-
const response = await
|
|
1351
|
+
const response = await rpc.posts.$get({
|
|
1341
1352
|
query: { page: '1', limit: '10' }
|
|
1342
1353
|
})
|
|
1343
1354
|
const data = await response.json()
|
|
@@ -1402,11 +1413,11 @@ export default function PostNewPage() {
|
|
|
1402
1413
|
```typescript
|
|
1403
1414
|
// ✅ page.tsxはServer Component('use client'なし)
|
|
1404
1415
|
import { PostDetail } from '@/components/features/posts/PostDetail'
|
|
1405
|
-
import {
|
|
1416
|
+
import { rpc } from '@/lib/api/rpc'
|
|
1406
1417
|
|
|
1407
1418
|
export default async function PostDetailPage({ params }: { params: { id: string } }) {
|
|
1408
1419
|
// サーバー側でデータフェッチ
|
|
1409
|
-
const response = await
|
|
1420
|
+
const response = await rpc.posts[':id'].$get({
|
|
1410
1421
|
param: { id: params.id }
|
|
1411
1422
|
})
|
|
1412
1423
|
const { post } = await response.json()
|
|
@@ -1453,7 +1464,7 @@ export function usePostList(page: number, limit: number) {
|
|
|
1453
1464
|
return useQuery({
|
|
1454
1465
|
queryKey: ['posts', page, limit],
|
|
1455
1466
|
queryFn: async () => {
|
|
1456
|
-
const response = await
|
|
1467
|
+
const response = await rpc.posts.$get({
|
|
1457
1468
|
query: { page: String(page), limit: String(limit) }
|
|
1458
1469
|
})
|
|
1459
1470
|
|
|
@@ -1531,7 +1542,7 @@ export class ErrorBoundary extends Component<Props, State> {
|
|
|
1531
1542
|
// ✅ page.tsxはServer Component('use client'なし)
|
|
1532
1543
|
import { PostListWithPagination } from '@/components/features/posts/PostListWithPagination'
|
|
1533
1544
|
import { Button } from '@/components/ui/button'
|
|
1534
|
-
import {
|
|
1545
|
+
import { rpc } from '@/lib/api/rpc'
|
|
1535
1546
|
import Link from 'next/link'
|
|
1536
1547
|
|
|
1537
1548
|
interface PostsPageProps {
|
|
@@ -1543,7 +1554,7 @@ export default async function PostsPage({ searchParams }: PostsPageProps) {
|
|
|
1543
1554
|
const limit = 10
|
|
1544
1555
|
|
|
1545
1556
|
// サーバー側でデータフェッチ
|
|
1546
|
-
const response = await
|
|
1557
|
+
const response = await rpc.posts.$get({
|
|
1547
1558
|
query: { page: String(page), limit: String(limit) }
|
|
1548
1559
|
})
|
|
1549
1560
|
const data = await response.json()
|
|
@@ -71,6 +71,8 @@ APIエンドポイントやアプリケーション機能の実装レビュー
|
|
|
71
71
|
- Zodスキーマによるバリデーションが全エンドポイントで実装されているか
|
|
72
72
|
- **🔗 Honoのメソッドチェーン形式が守られているか** - `new Hono().get().post().put().delete()`の形式で実装すること。個別呼び出し形式(`app.get(); app.post();`)では`typeof app`による型推論が損なわれ、Hono Clientでの型安全性が失われる(詳細: [API開発ガイド - メソッドチェーンパターン](./development/api-development.md#メソッドチェーンパターン))
|
|
73
73
|
- **🔗 サブルート内で`.use()`を使っていないか** - ミドルウェアはメインアプリ側で適用すること。サブルート内で`.use()`を使うと型が`ClientRequest<{}>`になり型推論が壊れる(詳細: [API開発ガイド - ミドルウェアと型推論](./development/api-development.md#ミドルウェアと型推論の注意点))
|
|
74
|
+
- **🔗 ドメインベースRPC分割が守られているか** - 各ドメインごとに `/api/rpc/{domain}/[[...route]]/route.ts` のエントリーポイントを作成すること
|
|
75
|
+
- **🔗 単一catch-allに全ルートを集約していないか** - 1つの `[[...route]]/route.ts` に複数ドメインのルートをまとめて登録してはならない。ドメインごとにエントリーポイントを分割すること
|
|
74
76
|
- エラーハンドリングがApplicationErrorで統一されているか
|
|
75
77
|
- loggerが使用され、console.logが使われていないか
|
|
76
78
|
- **相対パスの使用禁止** - import文、require文、ファイルパス指定で`../`や`./`などの相対パスが使用されていないか。必ずアプリ固有エイリアス(`@web/*`、`@admin/*`等)またはパッケージ名(`@repo/server-core`等)を使用すること
|
|
@@ -82,7 +84,8 @@ APIエンドポイントやアプリケーション機能の実装レビュー
|
|
|
82
84
|
- [ ] **APIクライアント実装ガイドの実装ルール遵守**
|
|
83
85
|
- Tanstack QueryとHonoクライアントを使用した型安全な実装になっているか
|
|
84
86
|
- Zodスキーマによるレスポンス検証が実装されているか
|
|
85
|
-
-
|
|
87
|
+
- `rpc`オブジェクト(ドメインごとのHono Client)を使用しているか
|
|
88
|
+
- 旧`apiClient`パターン(単一クライアント)を使用していないか(使用禁止)
|
|
86
89
|
- useMutationでのキャッシュ無効化が適切に実装されているか
|
|
87
90
|
- エラーハンドリングが実装されているか
|
|
88
91
|
- 型定義の整合性が保たれているか(バックエンドとフロントエンド間)
|
|
@@ -425,7 +425,7 @@ describe('UserRepository - データベースエラー', () => {
|
|
|
425
425
|
|
|
426
426
|
```typescript
|
|
427
427
|
// apps/web/__tests__/integration/post-api.test.ts
|
|
428
|
-
import {
|
|
428
|
+
import { rpc } from '@/lib/api/rpc'
|
|
429
429
|
import { prisma } from '@repo/server-core/infrastructure/database/client'
|
|
430
430
|
|
|
431
431
|
describe('Post API統合テスト', () => {
|
|
@@ -454,7 +454,7 @@ describe('Post API統合テスト', () => {
|
|
|
454
454
|
})
|
|
455
455
|
|
|
456
456
|
// When: API呼び出し
|
|
457
|
-
const response = await
|
|
457
|
+
const response = await rpc.posts.$get({
|
|
458
458
|
query: { page: '1', limit: '10' }
|
|
459
459
|
})
|
|
460
460
|
|
|
@@ -475,7 +475,7 @@ describe('Post API統合テスト', () => {
|
|
|
475
475
|
})
|
|
476
476
|
|
|
477
477
|
// When: 投稿作成APIを呼び出し
|
|
478
|
-
const response = await
|
|
478
|
+
const response = await rpc.posts.$post({
|
|
479
479
|
json: {
|
|
480
480
|
title: 'New Post',
|
|
481
481
|
content: 'Content',
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "einja-management-monorepo",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"packageManager": "pnpm@10.14.0",
|
|
6
|
+
"workspaces": [
|
|
7
|
+
"apps/*",
|
|
8
|
+
"packages/*"
|
|
9
|
+
],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"prepare": "husky",
|
|
12
|
+
"dev": "tsx scripts/worktree/dev.ts",
|
|
13
|
+
"dev:bg": "tsx scripts/worktree/dev.ts --background",
|
|
14
|
+
"dev:stop": "tsx scripts/worktree/dev.ts --stop",
|
|
15
|
+
"dev:status": "tsx scripts/worktree/dev.ts --status",
|
|
16
|
+
"dev:logs": "tail -f log/dev.log",
|
|
17
|
+
"dev:skip-setup": "turbo run dev",
|
|
18
|
+
"build": "dotenvx run -f .env.production -- turbo run build",
|
|
19
|
+
"build:dev": "dotenvx run -f .env.develop -- turbo run build",
|
|
20
|
+
"build:local": "turbo run build",
|
|
21
|
+
"start": "turbo run start",
|
|
22
|
+
"generate": "turbo run generate",
|
|
23
|
+
"lint": "turbo run lint",
|
|
24
|
+
"lint:fix": "turbo run lint:fix",
|
|
25
|
+
"format": "turbo run format",
|
|
26
|
+
"format:fix": "turbo run format:fix",
|
|
27
|
+
"typecheck": "turbo run typecheck",
|
|
28
|
+
"test": "turbo run test",
|
|
29
|
+
"test:watch": "turbo run test:watch",
|
|
30
|
+
"test:ui": "turbo run test:ui",
|
|
31
|
+
"test:coverage": "turbo run test:coverage",
|
|
32
|
+
"prepush": "pnpm prepush:lint && pnpm prepush:typecheck && pnpm prepush:test",
|
|
33
|
+
"prepush:lint": "lint-staged",
|
|
34
|
+
"prepush:typecheck": "turbo run typecheck",
|
|
35
|
+
"prepush:test": "turbo run test",
|
|
36
|
+
"db:generate": "turbo run db:generate",
|
|
37
|
+
"db:push": "turbo run db:push",
|
|
38
|
+
"db:migrate": "turbo run db:migrate",
|
|
39
|
+
"db:migrate:deploy": "turbo run db:migrate:deploy",
|
|
40
|
+
"db:studio": "turbo run db:studio",
|
|
41
|
+
"db:seed": "turbo run db:seed",
|
|
42
|
+
"dev:setup": "tsx scripts/setup-dev.ts",
|
|
43
|
+
"dotenvx": "dotenvx",
|
|
44
|
+
"env:update": "tsx scripts/env.ts",
|
|
45
|
+
"env:encrypt": "dotenvx encrypt",
|
|
46
|
+
"env:show": "tsx scripts/env-show.ts",
|
|
47
|
+
"env:rotate-secrets": "tsx scripts/env-rotate-secrets.ts",
|
|
48
|
+
"einja:sync": "npx --yes @einja/dev-cli@latest sync",
|
|
49
|
+
"init:github": "tsx scripts/init-github.ts",
|
|
50
|
+
"task:loop": "npx --yes @einja/dev-cli@latest task:loop",
|
|
51
|
+
"changeset": "changeset"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@changesets/changelog-github": "^0.5.1",
|
|
55
|
+
"@changesets/cli": "^2.29.0",
|
|
56
|
+
"@clack/prompts": "^0.11.0",
|
|
57
|
+
"@dotenvx/dotenvx": "^1.51.4",
|
|
58
|
+
"@types/node": "^25.0.3",
|
|
59
|
+
"husky": "^9.1.7",
|
|
60
|
+
"lint-staged": "^16.1.0",
|
|
61
|
+
"tsx": "^4.7.0",
|
|
62
|
+
"turbo": "^2.5.8"
|
|
63
|
+
},
|
|
64
|
+
"volta": {
|
|
65
|
+
"node": "22.16.0",
|
|
66
|
+
"pnpm": "10.14.0"
|
|
67
|
+
},
|
|
68
|
+
"dependencies": {
|
|
69
|
+
"@hono/zod-validator": "^0.7.6",
|
|
70
|
+
"hono": "^4.11.3",
|
|
71
|
+
"zod": "^4.3.5"
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -124,8 +124,8 @@ requirements:
|
|
|
124
124
|
"format:fix": "biome format --write ."
|
|
125
125
|
typecheck: "tsc --noEmit"
|
|
126
126
|
prepush: "{pm} run lint && {pm} run typecheck"
|
|
127
|
-
"task:loop": "npx @einja/dev-cli task:loop"
|
|
128
|
-
"einja:sync": "npx @einja/dev-cli sync"
|
|
127
|
+
"task:loop": "npx @einja/dev-cli@latest task:loop"
|
|
128
|
+
"einja:sync": "npx @einja/dev-cli@latest sync"
|
|
129
129
|
|
|
130
130
|
# システムコマンド(警告表示のみ、自動インストールしない)
|
|
131
131
|
systemCommands:
|
|
@@ -7,15 +7,34 @@ _SERENA_BASE="${1:-$(pwd)}"
|
|
|
7
7
|
_SERENA_PORT_FILE="$_SERENA_BASE/.serena-port"
|
|
8
8
|
_SERENA_DEFAULT_PORT="${SERENA_PORT:-9850}"
|
|
9
9
|
|
|
10
|
-
# ---
|
|
10
|
+
# --- ヘルパー関数 ---
|
|
11
|
+
_is_uint() {
|
|
12
|
+
case "$1" in
|
|
13
|
+
""|0|*[!0-9]*) return 1 ;;
|
|
14
|
+
*) return 0 ;;
|
|
15
|
+
esac
|
|
16
|
+
}
|
|
17
|
+
_escape_ere() {
|
|
18
|
+
printf '%s' "$1" | sed -e 's/[][(){}.^$*+?|\\]/\\&/g'
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
# --- 既存インスタンスチェック(PID×プロセス検証) ---
|
|
11
22
|
if [ -f "$_SERENA_PORT_FILE" ]; then
|
|
12
|
-
read -r _saved_port _saved_pid < "$_SERENA_PORT_FILE"
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
23
|
+
IFS=' ' read -r _saved_port _saved_pid _rest < "$_SERENA_PORT_FILE" || true
|
|
24
|
+
|
|
25
|
+
if _is_uint "$_saved_pid" && _is_uint "$_saved_port"; then
|
|
26
|
+
_cmd="$(ps -p "$_saved_pid" -ww -o command= 2>/dev/null || true)"
|
|
27
|
+
_base_ere="$(_escape_ere "$_SERENA_BASE")"
|
|
28
|
+
|
|
29
|
+
if [ -n "$_cmd" ] \
|
|
30
|
+
&& printf '%s\n' "$_cmd" | grep -Eq -- "(^|[[:space:]])--project(=|[[:space:]])${_base_ere}([[:space:]]|$)"; then
|
|
31
|
+
# 自プロジェクトのSerenaプロセス → 再利用
|
|
32
|
+
export SERENA_PORT="$_saved_port"
|
|
33
|
+
return 0 2>/dev/null || true
|
|
34
|
+
fi
|
|
17
35
|
fi
|
|
18
|
-
|
|
36
|
+
|
|
37
|
+
# PID死亡 or 数値不正 or 別プロセス/別プロジェクトのSerena → クリーンアップ
|
|
19
38
|
rm -f "$_SERENA_PORT_FILE"
|
|
20
39
|
fi
|
|
21
40
|
|