@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.
Files changed (42) hide show
  1. package/dist/cli.js +7 -1
  2. package/dist/cli.js.map +1 -1
  3. package/dist/commands/sync.d.ts.map +1 -1
  4. package/dist/commands/sync.js +50 -11
  5. package/dist/commands/sync.js.map +1 -1
  6. package/dist/commands/sync.test.js +1 -1
  7. package/dist/commands/sync.test.js.map +1 -1
  8. package/dist/lib/sync/file-filter.d.ts.map +1 -1
  9. package/dist/lib/sync/file-filter.js +49 -0
  10. package/dist/lib/sync/file-filter.js.map +1 -1
  11. package/dist/lib/sync/file-filter.test.js +40 -0
  12. package/dist/lib/sync/file-filter.test.js.map +1 -1
  13. package/dist/lib/sync/json-processor.d.ts +49 -27
  14. package/dist/lib/sync/json-processor.d.ts.map +1 -1
  15. package/dist/lib/sync/json-processor.js +182 -82
  16. package/dist/lib/sync/json-processor.js.map +1 -1
  17. package/dist/lib/sync/json-processor.test.d.ts +2 -0
  18. package/dist/lib/sync/json-processor.test.d.ts.map +1 -0
  19. package/dist/lib/sync/json-processor.test.js +334 -0
  20. package/dist/lib/sync/json-processor.test.js.map +1 -0
  21. package/dist/lib/sync/metadata-manager.d.ts +6 -1
  22. package/dist/lib/sync/metadata-manager.d.ts.map +1 -1
  23. package/dist/lib/sync/metadata-manager.js +26 -6
  24. package/dist/lib/sync/metadata-manager.js.map +1 -1
  25. package/dist/types/sync.d.ts +2 -0
  26. package/dist/types/sync.d.ts.map +1 -1
  27. package/dist/types/sync.js +1 -0
  28. package/dist/types/sync.js.map +1 -1
  29. package/package.json +1 -1
  30. package/presets/default/.claude/agents/einja/backend-architect.md +27 -17
  31. package/presets/default/.claude/commands/einja/einja-sync.md +6 -6
  32. package/presets/default/.claude/skills/einja-coding-standards/references/testing-strategy.md +3 -3
  33. package/presets/default/docs/einja/instructions/setup-flow.md +40 -0
  34. package/presets/default/docs/einja/steering/architecture.md +7 -1
  35. package/presets/default/docs/einja/steering/development/api-development.md +199 -67
  36. package/presets/default/docs/einja/steering/development/backend-architecture.md +12 -16
  37. package/presets/default/docs/einja/steering/development/frontend-development.md +61 -50
  38. package/presets/default/docs/einja/steering/development/review-guidelines.md +4 -1
  39. package/presets/default/docs/einja/steering/development/testing-strategy.md +3 -3
  40. package/presets/default/package.json +73 -0
  41. package/presets/default/preset.yaml +2 -2
  42. 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/ # API Routes
64
- │ │ │ └── rpc/
65
- │ │ │ └── [[...route]]/
66
- │ │ │ └── route.ts # Honoエントリーポイント
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
- │ │ │ ├── client.ts # Hono Client設定
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
- │ │ │ └── rpc/
121
- │ │ │ └── [[...route]]/
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
- **Hono Clientの初期化**:
200
+ **RPCクライアントの初期化**:
202
201
 
203
- ```typescript
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
- export const apiClient = hc<AppType>('/')
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('/users', userRoutes)
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 AppType = typeof routes
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 apiClient.api.rpc.users.$get({
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 apiClient.api.rpc.users.$post({
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 apiClient.api.rpc.users[':id'].$get({
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 apiClient.api.rpc.users[':id'].$put({
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 apiClient.api.rpc.users[':id'].$delete({
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 { apiClient } from "@/lib/api/client";
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 apiClient.api.rpc.users.$get({
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 { apiClient } from '@/lib/api-client'
431
+ import { rpc } from '@/lib/api/rpc'
421
432
 
422
433
  export default async function PostsPage() {
423
434
  // サーバー側でデータフェッチ
424
- const response = await apiClient.posts.$get({
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 { apiClient } from '@/lib/api-client'
560
+ import { rpc } from '@/lib/api/rpc'
550
561
 
551
562
  export default async function PostsPage() {
552
563
  // サーバー側でデータフェッチ
553
- const response = await apiClient.posts.$get()
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 { apiClient } from '@/lib/api-client'
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 apiClient.posts[':id'].$get({
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 { apiClient } from '@/lib/api-client'
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 apiClient.posts.$get({
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 { apiClient } from '@/lib/api-client'
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 apiClient.posts.$post({ json: data })
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 { apiClient } from '@/lib/api/client'
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 apiClient.api.posts.$get({
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 apiClient.api.posts[':id'].$get({ param: { id } })
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 apiClient.api.posts.$post({ json: data })
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 apiClient.api.posts[':id'].$put({
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 apiClient.api.posts[':id'].$delete({ param: { id } })
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 { apiClient } from '@/lib/api-client'
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 apiClient.posts.$get({
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 { apiClient } from '@/lib/api-client'
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 apiClient.posts[':id'].$get({
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 apiClient.posts.$get({
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 { apiClient } from '@/lib/api-client'
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 apiClient.posts.$get({
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
- - apiClientsディレクトリ構成が守られているか(フェッチ関数とhooksの分離)
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 { apiClient } from '@/lib/api-client'
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 apiClient.posts.$get({
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 apiClient.posts.$post({
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
- # --- 既存インスタンスチェック(PIDベース) ---
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
- if [ -n "$_saved_pid" ] && kill -0 "$_saved_pid" 2>/dev/null; then
14
- # PIDが生存 自プロジェクトのSerena
15
- export SERENA_PORT="$_saved_port"
16
- return 0 2>/dev/null || true
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
- # PID死亡 → クリーンアップ
36
+
37
+ # PID死亡 or 数値不正 or 別プロセス/別プロジェクトのSerena → クリーンアップ
19
38
  rm -f "$_SERENA_PORT_FILE"
20
39
  fi
21
40