├── .clinerules ├── .cursor └── rules │ ├── 000_general.mdc │ ├── 001_bestPractices_common.mdc │ ├── 002_bestPractices_frontend.mdc │ └── 003_bestPractices_backend.mdc ├── .gitignore ├── .windsurfrules ├── README.md ├── package-lock.json ├── package.json ├── rules ├── backend │ ├── 000_init.md │ └── 001_nestjs.md ├── build-mdc.ts ├── common │ ├── 000_init.md │ ├── 001_basic.md │ ├── 002_structure.md │ ├── 003_typescript.md │ ├── 004_techStack.md │ ├── 005_db.md │ ├── 007_deployment.md │ ├── 008_git.md │ └── 009_tdd.md ├── frontend │ ├── 000_init.md │ ├── 002_nextjs.md │ └── 003_tailwind.md └── general │ ├── 000_init.md │ ├── 001_intro.md │ ├── 002_checkInstruction.md │ ├── 003_step1.md │ ├── 004_step2.md │ ├── 005_step3.md │ └── 006_last.md └── tsconfig.json /.clinerules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ks0318-p/Cursor-Project-Rules/8c7c63b33040e4abd814ed6a9cd4182bbe615234/.clinerules -------------------------------------------------------------------------------- /.cursor/rules/000_general.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: this file explains code practices, please always refer to this file first 3 | globs: * 4 | alwaysApply: true 5 | --- 6 | 7 | # 000_general.mdc 8 | - このファイルが読み込まれたら「000_general.mdcを読み込みました!」と作業着手前にユーザーに必ず伝えてください。 9 | 10 | -- 11 | 12 | あなたは高度にスケーラブルで保守性の高いシステムの構築に特化したAIコーディングエージェントです。 13 | ユーザーとのコミュニケーションは常に日本語でお願いします。 14 | 15 | 以下の指示に従って、効率的かつ正確にタスクを遂行してください。 16 | 17 | -- 18 | 19 | まず、ユーザーから受け取った指示を確認します: 20 | <指示> 21 | {{instructions}} 22 | 23 | 24 | 25 | この指示を元に、以下のプロセスに従って作業を進めてください: 26 | 27 | ## 作業フロー 28 | 29 | 1. プロンプトの確認とモードの決定 30 | まず、ユーザーから受け取った指示を確認します: 31 | <指示> 32 | {{instructions}} 33 | 34 | 35 | 36 | ユーザーの指示から今あなたに求められているタスクを把握してください。 37 | 38 | タスクの性質によってあなたの行う作業モードが変わります: 39 | 1. 実装計画の立案→「実装計画立案モード」 40 | 2. 実際の実装や修正作業→「実装モード」 41 | 3. デバッグの実行→「デバッグモード」 42 | 43 | - ユーザーからの指示をもとに作業モードが決まったら、**「~~~~~モードで作業を始めます!」と宣言して下さい。** 44 | - 決まらなかったら「実装モード」にセットして下さい。 45 | 46 | --- 47 | 48 | 2. 指示作業の実行 49 | 【重要】「1. プロンプトの確認とモードの決定」で判断した自身のモードにより 以下の指示は読み分けてください。 50 | 51 | ### 「実装計画立案モード」の場合 52 | 作業前に必ず「実装計画立案モードで作業を開始します!」とユーザーに伝えて下さい。 53 | 54 | #### 重要ルール(実装計画立案モード) 55 | - git status で現在のgit のコンテキストを確認して下さい。 56 | - 要求されている変更について深く考察し、既存のコードを分析して必要な変更の全範囲をマッピングしてください。 57 | - 計画を提案する前に、1~2個の明確化質問をしてください。それでも不明確であればさらに質問をして下さい。 58 | - 回答を得たら、包括的な行動計画を作成し、その計画の承認を求めてください。 59 | - タスク実行のための具体的なステップを詳細に列挙してください。 60 | - それらのステップの最適な実行順序を決定してください。 61 | - 影響対象であるファイルをすべて列挙してください。 62 | - ファイルの新規作成の場合はどのようなフォルダやファイルを作成するかを明確に伝えてください。 63 | - また、ファイルの中の影響を受けるコードを全て理由付きで説明してください。 64 | - 実装計画立案はタスク実行の最終的な結果を最大化する最重要ステップです。時間をかけてでも、十分に詳細かつ包括的な分析を行ってください。 65 | - すべてのタスクが完了したら、実装計画を再評価してください。 66 | - 当初の指示内容との整合性を確認し、必要に応じて調整を行ってください。 67 | 68 | -- 69 | 70 | ### 「実装モード」の場合 71 | 作業前に必ず「実装モードで作業を開始します!」とユーザーに伝えて下さい。 72 | 73 | #### 重要ルール(実装モード) 74 | - git status で現在のgit のコンテキストを確認して下さい。 75 | - タスクの実行に必要なステップを一つずつ実行してください。 76 | - 各ステップの完了後、簡潔に進捗を報告してください。 77 | - **明示的に指示されていない変更は行わないでください。** 必要と思われる変更がある場合は、まず提案として報告し、承認を得てから実施してください。 78 | - エラーや不整合が発生した場合は、以下のプロセスで対応してください: 79 | - 問題の切り分けと原因特定(ログ分析、デバッグ情報の確認) 80 | - 対策案の作成と実施 81 | - 修正後の動作検証 82 | - デバッグログの確認と分析 83 | - コードを書いた後、コードのスケーラビリティと保守性について深く考察してください。 84 | 85 | -- 86 | 87 | ### 「デバッグモード」の場合 88 | 作業前に必ず「デバッグモードで作業を開始するでやんす!」とユーザーに伝えて下さい。 89 | 90 | #### 重要ルール(デバッグモード) 91 | - git status で現在のgit のコンテキストを確認して下さい。 92 | - 以下のステップに従ってdebugを行って下さい。 93 | 94 | 1. **問題の明確化**: エラーの正確な症状を特定し、再現方法を確立する 95 | 2. **仮説の列挙**: エラーの原因となり得る可能性を5〜7個挙げる(重要) 96 | 3. **優先順位付け**: 最も可能性の高いものから順に調査する 97 | 4. **系統的検証**: 各仮説を検証するために適切なログを戦略的に配置する 98 | 5. **検証と修正**: 根本原因を特定したら、修正し、テストで確認する 99 | 6. **知見の文書化**: 解決した問題と学びを共有する 100 | 101 | 102 | 103 | 104 | 3. 結果報告 105 | 各々のモードによって定義された「結果報告フォーマット」に沿って返して下さい。 106 | 107 | ### 「実装計画立案モード」 108 | - 実装計画の詳細度は、タスクの複雑さと重要性に応じて調整してください。不明点がある場合は、計画を確定する前に必ず質問してください。 109 | 110 | #### 「実装計画立案モード」の結果報告フォーマット 111 | 112 | ```markdown 113 | # 実装計画 114 | 115 | ## 概要 116 | 117 | [実装内容の簡潔な説明] 118 | 119 | ## ファイル変更計画 120 | 121 | - 新規: `[ファイルパス]` - [目的] 122 | - 更新: `[ファイルパス]` - [変更内容] 123 | - 削除: `[ファイルパス]` - [理由] 124 | 125 | ## 主要実装ステップ 126 | 127 | 1. - [x] Task1 // 既に終わっているTaskの場合 128 | 2. - [ ] Task2 129 | 3. - [ ] Task3 130 | ... 131 | 132 | ## 技術的考慮事項 133 | 134 | - [重要な技術的ポイント] 135 | - [潜在的な課題] 136 | 137 | ## 見積時間 138 | 139 | [総見積時間と簡単な内訳] 140 | ``` 141 | 142 | ### 「実装モード」 143 | 144 | #### 「実装モード」の結果報告フォーマット 145 | 146 | 以下のフォーマットで最終的な結果を報告してください: 147 | 148 | ```markdown 149 | # 実行結果報告 150 | 151 | ## 概要 152 | 153 | [全体の要約を簡潔に記述] 154 | 155 | ## 実行ステップ 156 | 157 | 1. [ステップ1の説明と結果] 158 | 2. [ステップ2の説明と結果] 159 | ... 160 | 161 | ## 最終成果物 162 | 163 | [成果物の詳細] 164 | 165 | ## 課題対応(該当する場合) 166 | 167 | - 発生した問題と対応内容 168 | - 今後の注意点 169 | 170 | ## 注意点・改善提案 171 | 172 | - [気づいた点や改善提案があれば記述] 173 | ``` 174 | 175 | ### 「デバッグモード」 176 | 177 | #### 「デバッグモード」の結果報告フォーマット 178 | 179 | 以下のフォーマットで最終的な結果を報告してください: 180 | 181 | ```markdown 182 | ## 問題 183 | 184 | - **起きている現象**: [簡潔な説明] 185 | 186 | ## 仮説一覧 187 | 188 | - [仮説1] 189 | - [仮説2] 190 | 191 | ## 検証結果(検証済みでない場合はユーザーに検証を促して終了する。) 192 | 193 | ### 原因 194 | 195 | [特定された根本原因の簡潔な説明] 196 | 197 | ## 解決策 198 | 199 | [実施した修正の概要] 200 | 201 | ## 次のステップ 202 | [フォローアップとして必要なアクション] 203 | ``` 204 | 205 | 206 | 207 | 208 | 209 | ## 最後に 210 | - ユーザーへのメッセージの末尾に「読み込んだMDCファイルの一覧」を出して下さい。 211 | 212 | --- 213 | 214 | 215 | -------------------------------------------------------------------------------- /.cursor/rules/001_bestPractices_common.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: this file explains best practices. please always refer to this file. 3 | globs: * 4 | alwaysApply: true 5 | --- 6 | 7 | # 001_bestPractices_common.mdc 8 | - このファイルが読み込まれたら必ず「001_bestPractices_common.mdcを読み込みました!」と作業着手前にユーザーに必ず伝えてください。 9 | 10 | 11 | ## 基本原則 12 | 以下のルールを遵守して下さい。 13 | 14 | ### 1. コミュニケーション 15 | - ユーザーとのコミュニケーションは常に日本語でお願いします。 16 | 17 | ### 2. 重複実装の防止 18 | - 実装前に以下の確認を行ってください: 19 | - 既存の類似機能の有無 20 | - 同名または類似名の関数やコンポーネント 21 | - 重複するAPIエンドポイント 22 | - 共通化可能な処理の特定 23 | 24 | ### 3. 単一責任の原則 25 | - 関数が長くなりすぎた場合は、小さな関数に分割して下さい。 26 | - ファイルが大きくなりすぎた場合は、小さなファイルに分割して下さい。 27 | 28 | ### 4. 参照禁止ファイル 29 | - .envファイルの作成・読込・編集・削除は厳禁です。ユーザーに作業を促して下さい。 30 | - .envファイルはプロジェクトルートに配置しています。 31 | 32 | -- 33 | 34 | ## プロジェクト構成 35 | 本プロジェクトは、 36 | Nxモノレポを使用したマイクロサービスアーキテクチャを採用しています。 37 | フロントエンドにはNext.js App Router、バックエンドにはNestJS、データベースアクセスにはPrismaを使用しています。 38 | 39 | ### ルートディレクトリ構造 40 | 41 | ``` 42 | / 43 | ├── apps/ # アプリケーションコード 44 | │ ├── api/ # NestJSバックエンドアプリケーション 45 | │ ├── api-e2e/ # バックエンドのE2Eテスト 46 | │ ├── frontend/ # Next.jsフロントエンドアプリケーション 47 | │ └── frontend-e2e/ # フロントエンドのE2Eテスト 48 | ├── prisma/ # Prisma Schema及びマイグレーションファイル 49 | ├── terraform/ # Terraformによるインフラ定義 50 | │ ├── environments/ # 環境別設定 51 | │ └── modules/ # 再利用可能なモジュール 52 | ├── github/ # GitHub関連ファイル 53 | ├── customRules/ # カスタムルール定義 54 | └── ... (設定ファイル類) 55 | ``` 56 | 57 | 58 | ## プログラミング言語 59 | 本プロジェクトは、バックエンドの実装もフロントエンドの実装も、TypeScriptを使用しています。基本、いかなる場合でもTypeScriptを使用して実装してください。 60 | 61 | ### 1. 型の使用 62 | 63 | - 明示的な型アノテーションを使用 64 | - `any`型は避け、代わりに`unknown`使用 65 | - 複雑な型は`interface`/`type`で定義 66 | - 配列型は`T[]`形式を優先 67 | - 再利用可能な型は個別ファイルにエクスポート 68 | 69 | ### 2. インターフェース/型エイリアス 70 | 71 | - 拡張必要時は`interface` 72 | - 高度な型操作には`type` 73 | - `I`プレフィックス不使用 74 | - 関連する型は同ファイルにまとめる 75 | 76 | ### 3. Null/Undefinedの扱い 77 | 78 | - オプショナルチェーン`?.`活用 79 | - Nullish合体演算子`??`使用 80 | - 非nullアサーション`!`は避ける 81 | - 早期リターンでネスト削減 82 | 83 | ### 4. モジュール構成 84 | 85 | - 絶対パスは`@/*`エイリアス使用 86 | - 型のみの場合は`import type` 87 | - 名前付きエクスポート優先 88 | - 循環参照を避ける 89 | 90 | ### 5. エラー処理 91 | 92 | - 具体的なエラー型を使用 93 | - キャッチしたエラーに型付け 94 | - 非同期は`try/catch`または`Promise.catch()` 95 | 96 | ### 6. コード品質 97 | 98 | - strictモード維持 99 | - 未使用の変数/型は削除 100 | - 型の計算コスト考慮 101 | - 型定義の循環参照回避 102 | 103 | 104 | 105 | 106 | ## このプロジェクトで使用している技術スタック 107 | 108 | ### コア技術 109 | 110 | 1. **Node.js** 111 | 112 | - バージョン: 20.18.0(volta管理) 113 | - JavaScript ランタイム環境 114 | - サーバーサイドとフロントエンド開発の統一プラットフォーム 115 | 116 | 2. **TypeScript** 117 | 118 | - バージョン: 5.5.4 119 | - 静的型付け 120 | - 型推論と型チェック 121 | - インターフェースと型定義 122 | - 全てのコードベースで使用 123 | 124 | 3. **Next.js** 125 | 126 | - バージョン: 14.2.11 127 | - React フレームワーク 128 | - SSR (Server-Side Rendering) と SSG (Static Site Generation) 129 | - App Router の活用 130 | - ページとルーティングの統合管理 131 | 132 | 4. **React** 133 | 134 | - バージョン: 18.3.1 135 | - コンポーネントベースのUIライブラリ 136 | - フックの活用(useState, useEffect など) 137 | - サーバーコンポーネントとクライアントコンポーネント 138 | 139 | 5. **NestJS** 140 | 141 | - バージョン: 10.4.1 142 | - サーバーサイドフレームワーク 143 | - モジュール化されたアーキテクチャ 144 | - 依存性の注入 145 | - デコレータベースのAPI設計 146 | 147 | 6. **Prisma** 148 | 149 | - バージョン: 5.19.0 150 | - ORM(Object-Relational Mapping) 151 | - データベースマイグレーション 152 | - 型安全なデータベースアクセス 153 | - PostgreSQLとの統合 154 | 155 | 7. **Zod** 156 | 157 | - バージョン: 3.23.8 158 | - スキーマ検証 159 | - ランタイム型チェック 160 | - 型推論との連携 161 | 162 | 8. **Redux Toolkit** 163 | - バージョン: 2.2.7 164 | - 状態管理ライブラリ 165 | - RTK Query によるデータフェッチング 166 | - スライスによる状態管理の簡素化 167 | 168 | ### フロントエンド技術 169 | 170 | 1. **TailwindCSS** 171 | 172 | - バージョン: 3.4.4 173 | - ユーティリティファーストCSSフレームワーク 174 | - カスタマイズ可能なデザインシステム 175 | 176 | 2. **Radix UI** 177 | 178 | - アクセシブルなUIコンポーネント 179 | - ヘッドレスコンポーネントライブラリ 180 | - スタイリングの自由度 181 | 182 | 3. **React Hook Form** 183 | 184 | - バージョン: 7.53.0 185 | - パフォーマンスに優れたフォーム管理 186 | - バリデーション連携 187 | 188 | 4. **TanStack Query** 189 | 190 | - バージョン: 5.56.2 191 | - データフェッチングとキャッシュ管理 192 | - 非同期状態管理 193 | 194 | 5. **TanStack Table** 195 | - バージョン: 8.19.3 196 | - 高度なテーブル管理 197 | - ソート、フィルタリング、ページネーション 198 | 199 | ### バックエンド技術 200 | 201 | 1. **PostgreSQL** 202 | 203 | - リレーショナルデータベース 204 | - 強力なクエリ機能 205 | - トランザクション処理 206 | 207 | 2. **BullMQ** 208 | 209 | - バージョン: 5.12.2 210 | - Redis ベースのジョブキューシステム 211 | - 非同期タスク処理 212 | - バックグラウンドジョブ管理 213 | 214 | 3. **AWS SDK** 215 | 216 | - クラウドサービス統合 217 | - S3、Cognito などの AWS サービスとの連携 218 | 219 | 4. **OpenAPI/Swagger** 220 | - API ドキュメント生成 221 | - API スキーマ定義 222 | - クライアント生成の基盤 223 | 224 | ### 開発ツール 225 | 226 | 1. **Nx** 227 | 228 | - バージョン: 20.1.2 229 | - モノレポ管理 230 | - ビルドシステム 231 | - タスク実行とキャッシング 232 | 233 | 2. **ESLint** 234 | 235 | - バージョン: 9.6.0 236 | - コード品質チェック 237 | - 自動修正機能 238 | 239 | 3. **Prettier** 240 | 241 | - バージョン: 3.3.2 242 | - コードフォーマッター 243 | - 一貫したコードスタイル 244 | 245 | 4. **Orval** 246 | 247 | - バージョン: 6.31.0 248 | - API クライアント自動生成 249 | - OpenAPI スキーマからの型生成 250 | 251 | 5. **Jest** 252 | 253 | - バージョン: 29.7.0 254 | - テストフレームワーク 255 | - ユニットテストとインテグレーションテスト 256 | 257 | 6. **Cypress** 258 | - バージョン: 13.15.2 259 | - E2E テスト 260 | - ブラウザ自動化テスト 261 | 262 | ## 開発環境のセットアップ 263 | 264 | ### 必要なツール 265 | 266 | 1. **Node.js のインストール** 267 | 268 | - volta または nvm を使用した Node.js 20.18.0 のインストール 269 | - yarn 1.22.19 のインストール 270 | 271 | 2. **環境変数の設定** 272 | 273 | - `.env.example` をコピーして `.env` ファイルを作成 274 | - 必要な環境変数を設定 275 | 276 | 3. **プロジェクトのセットアップ** 277 | 278 | ```bash 279 | # リポジトリのクローン 280 | git clone https://github.com/xxxxx/xxxxx.git 281 | 282 | # 依存関係のインストール 283 | yarn install 284 | 285 | # データベースのセットアップ 286 | npx prisma migrate dev 287 | 288 | # APIクライアントの生成 289 | yarn codegen 290 | 291 | # 開発サーバーの起動 292 | yarn dev 293 | ``` 294 | 295 | ### 開発ワークフロー 296 | 297 | 1. **フロントエンド開発** 298 | 299 | ```bash 300 | # フロントエンドのみ開発 301 | yarn dev:ui 302 | ``` 303 | 304 | 2. **バックエンド開発** 305 | 306 | ```bash 307 | # バックエンドのみ開発 308 | yarn dev:api 309 | ``` 310 | 311 | 3. **データベース操作** 312 | 313 | ```bash 314 | # マイグレーションの作成 315 | npx prisma migrate dev --name [マイグレーション名] 316 | 317 | # スキーマの生成 318 | npx prisma generate 319 | 320 | # データベースのシード 321 | yarn seed 322 | ``` 323 | 324 | 4. **APIクライアント生成** 325 | 326 | ```bash 327 | # OpenAPIスキーマからクライアント生成 328 | yarn codegen 329 | ``` 330 | 331 | 5. **リントとフォーマット** 332 | 333 | ```bash 334 | # リント 335 | yarn lint 336 | 337 | # フォーマット 338 | yarn prettier 339 | ``` 340 | 341 | ## 技術的制約 342 | 343 | 1. **フロントエンドの制約** 344 | 345 | - Next.js App Router による制約 346 | - サーバーコンポーネントとクライアントコンポーネントの分離 347 | - データフェッチング戦略の選択 348 | 349 | 2. **バックエンドの制約** 350 | 351 | - NestJS のモジュール構造に従う必要性 352 | - Prisma スキーマの制約 353 | - API設計の一貫性 354 | 355 | 3. **パフォーマンスの制約** 356 | - 大量のデータ処理時のメモリ使用量 357 | - APIレスポンスタイムの最適化 358 | - フロントエンドのレンダリングパフォーマンス 359 | 360 | ## 依存関係 361 | 362 | ### 主要な依存関係 363 | 364 | 1. **フロントエンド** 365 | 366 | - next: Reactフレームワーク 367 | - react: UIライブラリ 368 | - @tanstack/react-query: データフェッチング 369 | - @reduxjs/toolkit: 状態管理 370 | - tailwindcss: スタイリング 371 | - zod: バリデーション 372 | 373 | 2. **バックエンド** 374 | 375 | - @nestjs/core: サーバーフレームワーク 376 | - @prisma/client: データベースORM 377 | - @nestjs/swagger: APIドキュメント 378 | - bullmq: ジョブキュー 379 | - class-validator: バリデーション 380 | 381 | 3. **共通** 382 | - typescript: 型システム 383 | - axios: HTTPクライアント 384 | - jest: テスト 385 | 386 | ### 依存関係管理 387 | 388 | 1. **Nx モノレポ** 389 | 390 | - apps/frontend: フロントエンドアプリケーション 391 | - apps/api: バックエンドアプリケーション 392 | - prisma: データベーススキーマ 393 | 394 | 2. **パッケージ管理** 395 | - yarn による依存関係管理 396 | - package.json でのバージョン指定 397 | - yarn.lock によるロック 398 | 399 | 400 | ### DBに関する重要なルール 401 | - あなたはDB設計のプロです。 402 | - 常に現状のDB設計に沿って実装をします。 403 | - ORMはPrismaを採用しています。 404 | - schema.prismaは、/prisma内に定義されています。 405 | - schema.prismaを編集、マイグレーションを行った場合は必ず下記に記したER図も更新するようにしてください。 406 | - migrationは必ず 407 | 408 | ### ER図 409 | 410 | ```mermaid 411 | erDiagram 412 | User ||--o{ User : "createdBy/updatedBy関連" 413 | User }|--o{ Tenant : "belongs to" 414 | User }|--o{ UserAccount : "has many" 415 | User }|--o{ UserProject : "has many" 416 | User ||--o{ Account : "createdBy/updatedBy関連" 417 | User ||--o{ Project : "createdBy/updatedBy関連" 418 | User ||--o{ Invitation : "createdBy/updatedBy/invitedUser関連" 419 | 420 | Tenant ||--o{ User : "has many" 421 | Tenant ||--o{ Account : "has many" 422 | Tenant ||--o{ Project : "has many" 423 | Tenant ||--o{ Invitation : "has many" 424 | 425 | Account ||--o{ Project : "has many" 426 | Account ||--o{ UserAccount : "has many" 427 | Account ||--o{ Invitation : "has many" 428 | 429 | Project ||--o{ UserProject : "has many" 430 | 431 | UserAccount }|--|| User : "belongs to" 432 | UserAccount }|--|| Account : "belongs to" 433 | 434 | UserProject }|--|| User : "belongs to" 435 | UserProject }|--|| Project : "belongs to" 436 | 437 | Invitation }o--|| Account : "may belong to" 438 | Invitation }o--|| Project : "may belong to" 439 | Invitation }o--|| Tenant : "may belong to" 440 | Invitation }|--|| User : "invited user" 441 | ``` 442 | 443 | 以下は各エンティティの主要属性です: 444 | 445 | ### User 446 | - id, tenantId, name, email, phoneNumber, tenantRole, createdAt, updatedAt 447 | - テナントに所属し、アカウントやプロジェクトにアクセス権を持つ 448 | 449 | ### Tenant 450 | - id, name, imageUrl, isAgency, createdAt, updatedAt 451 | - 組織を表し、複数のアカウントとユーザーを保持できる 452 | 453 | ### Account 454 | - id, tenantId, name, imageUrl, closingMonth, createdAt, updatedAt 455 | - テナント内の会計単位、複数のプロジェクトを持つ 456 | 457 | ### Project 458 | - id, accountId, tenantId, name, imageUrl, dispOrder, createdAt, updatedAt 459 | - 広告管理の中心的な単位、広告やメトリクスなど多くのリソースを持つ 460 | 461 | ### UserAccount 462 | - userId, accountId, role 463 | - ユーザーとアカウントの関連付け、権限管理 464 | 465 | ### UserProject 466 | - userId, projectId, role 467 | - ユーザーとプロジェクトの関連付け、権限管理 468 | 469 | ### Invitation 470 | - id, tenantId, accountId, projectId, email, inviteTo, role, status, expiresAt, token, invitedUserId 471 | - ユーザー招待の管理 472 | 473 | 474 | 475 | ## デプロイ概要 476 | 477 | 本プロジェクトは、AWS上にDockerコンテナとして展開されるマイクロサービスアーキテクチャを採用しています。フロントエンドとバックエンドは別々のコンテナとしてビルド・デプロイされ、AWS CodePipelineを使用したCI/CDパイプラインによって自動化されています。 478 | 479 | ### デプロイフロー 480 | 481 | このプロジェクトのデプロイフローは以下のように構成されています: 482 | 483 | ```mermaid 484 | flowchart TB 485 | GitPush[Gitリポジトリへのプッシュ] --> CodePipeline[AWS CodePipeline] 486 | CodePipeline --> ParallelBuilds[並行ビルドプロセス] 487 | 488 | subgraph ParallelBuilds[並行ビルドプロセス] 489 | BackendBuild[バックエンドビルド] 490 | FrontendBuild[フロントエンドビルド] 491 | end 492 | 493 | BackendBuild --> BackendECR[バックエンドイメージをECRにプッシュ] 494 | FrontendBuild --> FrontendECR[フロントエンドイメージをECRにプッシュ] 495 | 496 | BackendECR --> DBMigration[DBマイグレーション実行] 497 | DBMigration --> SeedData[シードデータ投入] 498 | 499 | BackendECR --> BackendDeploy[バックエンドをECSにデプロイ] 500 | FrontendECR --> FrontendDeploy[フロントエンドをECSにデプロイ] 501 | ``` 502 | 503 | ### 重要なファイル 504 | 505 | デプロイプロセスを理解・修正する際には、以下のファイルを特に注視する必要があります: 506 | 507 | #### Dockerfiles 508 | 509 | 1. **Dockerfile.frontend** 510 | - フロントエンド(Next.js)のビルドとデプロイに使用 511 | - マルチステージビルド:ビルド環境と本番環境を分離 512 | - 環境変数(ENVIRONMENT, NEXT_PUBLIC_CLOUDFRONT_DOMAIN, NEXT_PUBLIC_ADOBE_CLIENT_ID)の設定 513 | - ポート3000でNext.jsアプリケーションを起動 514 | 515 | 2. **Dockerfile.backend** 516 | - バックエンド(NestJS)のビルドとデプロイに使用 517 | - マルチステージビルド構成 518 | - 環境変数(ENVIRONMENT)の設定 519 | - メモリ制限設定(--max-old-space-size=6144)付きでNode.jsアプリケーションを起動 520 | 521 | #### BuildSpec設定 522 | 523 | 1. **buildspec_frontend.yml** 524 | - フロントエンドのCI/CDパイプライン設定 525 | - インストール、ビルド、デプロイフェーズを定義 526 | - AWS ECRへのログインとイメージのプッシュ 527 | - 環境変数を使用したDockerビルド引数の設定 528 | - ECSデプロイ用のimagedefinitions.jsonの生成 529 | 530 | 2. **buildspec_backend.yml** 531 | - バックエンドのCI/CDパイプライン設定 532 | - インストール、ビルド、デプロイフェーズを定義 533 | - データベースマイグレーションとシードデータ投入を実行 534 | - AWS ECRへのログインとイメージのプッシュ 535 | - ECSデプロイ用のimagedefinitions.jsonの生成 536 | 537 | ### 環境変数 538 | 539 | デプロイプロセスでは、以下の重要な環境変数が使用されています: 540 | 541 | #### 共通 542 | - `ENVIRONMENT`: 環境識別子(dev, stg, prod等) 543 | - `IMAGE_TAG`: コンテナイメージのタグ 544 | - `IMAGE_REPO_URL`: ECRレポジトリURL 545 | - `AWS_REGION`: AWSリージョン 546 | - `AWS_ACCOUNT_ID`: AWSアカウントID 547 | 548 | #### フロントエンド特有 549 | - `NEXT_PUBLIC_CLOUDFRONT_DOMAIN`: CloudFrontのドメイン 550 | - `NEXT_PUBLIC_ADOBE_CLIENT_ID`: Adobe APIのクライアントID 551 | 552 | ### デプロイ後の処理 553 | 554 | バックエンドのデプロイプロセスでは、イメージのビルドとプッシュ後に以下の追加タスクが実行されます: 555 | 556 | 1. **データベースマイグレーション** (`yarn prisma migrate deploy`) 557 | - Prismaを使用したスキーマ変更の適用 558 | 559 | 2. **シードデータ投入** (`yarn seed:prod`) 560 | - 本番環境用の初期データ設定 561 | 562 | ### インフラストラクチャ 563 | 564 | デプロイ先のインフラストラクチャはTerraformで管理されています。関連するTerraform設定は以下のディレクトリにあります: 565 | 566 | - `terraform/modules/ap-northeast-1/codeBuild/`: CodeBuildの設定 567 | - `terraform/modules/ap-northeast-1/ecs/`: ECSの設定 568 | - `terraform/modules/ap-northeast-1/ecr/`: ECRの設定 569 | 570 | ### デプロイに関する注意点 571 | 572 | 1. **マイグレーション順序** 573 | - バックエンドデプロイ時、コンテナデプロイ前にマイグレーションが実行されます 574 | - マイグレーションに失敗した場合、デプロイプロセス全体が失敗します 575 | 576 | 2. **環境変数管理** 577 | - 環境変数はCodePipelineで設定され、BuildSpecとDockerfileに渡されます 578 | - 新しい環境変数を追加する際はパイプライン設定とDockerfileの両方を更新する必要があります 579 | 580 | 3. **マルチステージビルド** 581 | - 本番イメージサイズを最小化するためマルチステージビルドを使用しています 582 | - 依存関係インストールとビルドは最初のステージで行われ、成果物のみが本番イメージにコピーされます 583 | 584 | ### AI支援時のデプロイ作業 585 | 586 | AIにデプロイ関連のタスクを依頼する際は、以下の点に注意してください: 587 | 588 | 1. **変更対象の明確化** 589 | - `Dockerfile.frontend`, `Dockerfile.backend`, `buildspec_frontend.yml`, `buildspec_backend.yml` の変更内容を明確に指示する 590 | - 環境変数の追加や変更がある場合は、全ての関連ファイルでの変更箇所を確認する 591 | 592 | 2. **デプロイフローへの影響** 593 | - コードの変更がデプロイプロセスに与える影響を考慮する 594 | - 特にマイグレーションやシードデータに影響する変更には注意が必要 595 | 596 | 3. **環境固有の設定** 597 | - 環境固有(dev/stg/prod)の設定や条件分岐がある場合は明示的に指示する 598 | 599 | 4. **ビルド順序と依存関係** 600 | - フロントエンドとバックエンドの依存関係を考慮する 601 | - ビルドプロセスの順序に影響する変更には特に注意する 602 | 603 | AIは必ず上記の4つのファイルを精査してから、デプロイ関連のタスクを実行するようにしてください。 604 | 605 | 606 | 607 | ## 概要 608 | このドキュメントでは、コミットとプルリクエストの作成に関するベストプラクティスを説明します。 609 | 610 | ### コミットの作成 611 | 612 | コミットを作成する際は、以下の手順に従います: 613 | 614 | 1. 変更の確認 615 | 616 | ```bash 617 | # 未追跡ファイルと変更の確認 618 | git status 619 | 620 | # 変更内容の詳細確認 621 | git diff 622 | 623 | # コミットメッセージのスタイル確認 624 | git log 625 | ``` 626 | 627 | 2. 変更の分析 628 | 629 | - 変更または追加されたファイルの特定 630 | - 変更の性質(新機能、バグ修正、リファクタリングなど)の把握 631 | - プロジェクトへの影響評価 632 | - 機密情報の有無確認 633 | 634 | 3. コミットメッセージの作成 635 | 636 | - 「なぜ」に焦点を当てる 637 | - 明確で簡潔な言葉を使用 638 | - 変更の目的を正確に反映 639 | - 一般的な表現を避ける 640 | 641 | 4. コミットの実行 642 | 643 | ```bash 644 | # 関連ファイルのみをステージング 645 | git add 646 | 647 | # コミットメッセージの作成(HEREDOCを使用) 648 | git commit -m "$(cat <<'EOF' 649 | feat: ユーザー認証にResult型を導入 650 | 651 | - エラー処理をより型安全に 652 | - エラーケースの明示的な処理を強制 653 | - テストの改善 654 | 655 | EOF 656 | )" 657 | ``` 658 | 659 | ### プルリクエストの作成 660 | 661 | プルリクエストを作成する際は、以下の手順に従います: 662 | 663 | 1. ブランチの状態確認 664 | 665 | ```bash 666 | # 未コミットの変更確認 667 | git status 668 | 669 | # 変更内容の確認 670 | git diff 671 | 672 | # mainからの差分確認 673 | git diff main...HEAD 674 | 675 | # コミット履歴の確認 676 | git log 677 | ``` 678 | 679 | 2. 変更の分析 680 | 681 | - mainから分岐後のすべてのコミットの確認 682 | - 変更の性質と目的の把握 683 | - プロジェクトへの影響評価 684 | - 機密情報の有無確認 685 | 686 | 3. プルリクエストの作成 687 | 688 | ```bash 689 | # プルリクエストの作成(HEREDOCを使用) 690 | gh pr create --title "feat: Result型によるエラー処理の改善" --body "$(cat <<'EOF' 691 | ## 概要 692 | 693 | エラー処理をより型安全にするため、Result型を導入しました。 694 | 695 | ## 変更内容 696 | 697 | - neverthrowを使用したResult型の導入 698 | - エラーケースの明示的な型定義 699 | - テストケースの追加 700 | 701 | ## レビューのポイント 702 | 703 | - Result型の使用方法が適切か 704 | - エラーケースの網羅性 705 | - テストの十分性 706 | EOF 707 | )" 708 | ``` 709 | 710 | ### 重要な注意事項 711 | 712 | 1. コミット関連 713 | 714 | - 可能な場合は `git commit -am` を使用 715 | - 関係ないファイルは含めない 716 | - 空のコミットは作成しない 717 | - git設定は変更しない 718 | 719 | 2. プルリクエスト関連 720 | 721 | - 必要に応じて新しいブランチを作成 722 | - 変更を適切にコミット 723 | - リモートへのプッシュは `-u` フラグを使用 724 | - すべての変更を分析 725 | 726 | 3. 避けるべき操作 727 | - 対話的なgitコマンド(-iフラグ)の使用 728 | - リモートリポジトリへの直接プッシュ 729 | - git設定の変更 730 | 731 | ### コミットメッセージの例 732 | 733 | ```bash 734 | # 新機能の追加 735 | feat: Result型によるエラー処理の導入 736 | 737 | # 既存機能の改善 738 | update: キャッシュ機能のパフォーマンス改善 739 | 740 | # バグ修正 741 | fix: 認証トークンの期限切れ処理を修正 742 | 743 | # リファクタリング 744 | refactor: Adapterパターンを使用して外部依存を抽象化 745 | 746 | # テスト追加 747 | test: Result型のエラーケースのテストを追加 748 | 749 | # ドキュメント更新 750 | docs: エラー処理のベストプラクティスを追加 751 | ``` 752 | 753 | ### プルリクエストの例 754 | 755 | ```markdown 756 | ## 概要 757 | 758 | TypeScriptのエラー処理をより型安全にするため、Result型を導入しました。 759 | 760 | ## 変更内容 761 | 762 | - neverthrowライブラリの導入 763 | - APIクライアントでのResult型の使用 764 | - エラーケースの型定義 765 | - テストケースの追加 766 | 767 | ## 技術的な詳細 768 | 769 | - 既存の例外処理をResult型に置き換え 770 | - エラー型の共通化 771 | - モック実装の改善 772 | 773 | ## レビューのポイント 774 | 775 | - Result型の使用方法が適切か 776 | - エラーケースの網羅性 777 | - テストの十分性 778 | ``` 779 | 780 | 781 | ## テスト駆動開発 (TDD) ルール 782 | 783 | テスト駆動開発 (Test-Driven Development) は、コードを書く前にテストを書くソフトウェア開発手法です。この方法論を採用することで、設計の質を高め、バグの少ないコードを作成し、リファクタリングを安全に行うことができます。 784 | 785 | ## TDDの基本サイクル 786 | 787 | ```mermaid 788 | flowchart LR 789 | Red[Red: 失敗するテストを書く] --> Green[Green: テストが通る最小限の実装をする] 790 | Green --> Refactor[Refactor: コードを改善する] 791 | Refactor --> Red 792 | ``` 793 | 794 | 1. **Red**: まず失敗するテストを書く 795 | 796 | - 必要な機能を明確に定義 797 | - 期待する振る舞いをテストコードで表現 798 | - この時点ではテストは失敗する(赤) 799 | 800 | 2. **Green**: テストが通るように最小限の実装をする 801 | 802 | - テストをパスさせるための最も単純な実装を行う 803 | - パフォーマンスやコードの美しさより機能性を優先 804 | - この時点でテストは成功する(緑) 805 | 806 | 3. **Refactor**: コードをリファクタリングして改善する 807 | - 重複を排除し、コードを整理 808 | - 可読性とメンテナンス性を向上 809 | - テストが依然として通ることを確認 810 | 811 | ## TDDの重要な考え方 812 | 813 | - **テストは仕様である**: テストコードは実装の仕様を表現したもの 814 | - **最初に「何を」考え、次に「どのように」考える**: テストで「何を」達成すべきかを明確にしてから、「どのように」実装するかを考える 815 | - **小さなステップで進める**: 一度に大きな変更を行わず、小さな一歩ずつ進める 816 | - **テストカバレッジより意図のカバレッジを重視**: 単にコードラインをカバーするだけでなく、ビジネスロジックの意図を正確にテストする 817 | 818 | ## テスト構造の原則 819 | 820 | ### AAA (Arrange-Act-Assert) パターン 821 | 822 | テストコードは以下の3つのセクションで構成することをお勧めします: 823 | 824 | 1. **Arrange (準備)**: テストの前提条件を設定 825 | 2. **Act (実行)**: テスト対象の機能を実行 826 | 3. **Assert (検証)**: 期待する結果を検証 827 | 828 | ```typescript 829 | // Jest を使用した例 830 | describe('UserService', () => { 831 | it('should return user by id when user exists', async () => { 832 | // Arrange 833 | const mockUser = { id: 1, name: 'John Doe' }; 834 | const userRepositoryMock = { 835 | findById: jest.fn().mockResolvedValue(mockUser), 836 | }; 837 | const userService = new UserService(userRepositoryMock); 838 | 839 | // Act 840 | const result = await userService.getUserById(1); 841 | 842 | // Assert 843 | expect(result).toEqual(mockUser); 844 | expect(userRepositoryMock.findById).toHaveBeenCalledWith(1); 845 | }); 846 | }); 847 | ``` 848 | 849 | ### テスト名の命名規則 850 | 851 | 良いテスト名は「状況→操作→結果」の形式で記述します: 852 | 853 | ```typescript 854 | it('有効なユーザーIDが提供された場合_getUserByIdを呼び出すと_ユーザー情報を返すこと', async () => { 855 | // テスト本体 856 | }); 857 | 858 | // または英語で 859 | it('should return user information when getUserById is called with valid user ID', async () => { 860 | // テスト本体 861 | }); 862 | ``` 863 | 864 | ## フロントエンドとバックエンドのテスト戦略 865 | 866 | ### Next.jsフロントエンドのテスト階層 867 | 868 | ```mermaid 869 | flowchart TD 870 | Unit[単体テスト: コンポーネント、ユーティリティ] --> Integration[統合テスト: 複数コンポーネント、データフロー] 871 | Integration --> E2E[E2Eテスト: ユーザーフロー全体] 872 | ``` 873 | 874 | #### 単体テスト (Jest + React Testing Library) 875 | 876 | ```typescript 877 | // components/Button.test.tsx 878 | import { render, screen, fireEvent } from '@testing-library/react'; 879 | import Button from './Button'; 880 | 881 | describe('Button', () => { 882 | it('renders correctly with text', () => { 883 | render(); 884 | expect(screen.getByText('Click me')).toBeInTheDocument(); 885 | }); 886 | 887 | it('calls onClick handler when clicked', () => { 888 | const handleClick = jest.fn(); 889 | render(); 890 | fireEvent.click(screen.getByText('Click me')); 891 | expect(handleClick).toHaveBeenCalledTimes(1); 892 | }); 893 | }); 894 | ``` 895 | 896 | #### 統合テスト 897 | 898 | ```typescript 899 | // features/UserProfile.test.tsx 900 | import { render, screen, waitFor } from '@testing-library/react'; 901 | import UserProfile from './UserProfile'; 902 | import { UserProvider } from '../contexts/UserContext'; 903 | 904 | // モックの設定 905 | jest.mock('../api/user', () => ({ 906 | fetchUserData: jest.fn().mockResolvedValue({ name: 'John Doe', email: 'john@example.com' }) 907 | })); 908 | 909 | describe('UserProfile', () => { 910 | it('fetches and displays user data', async () => { 911 | render( 912 | 913 | 914 | 915 | ); 916 | 917 | // ローディング状態の確認 918 | expect(screen.getByText('Loading...')).toBeInTheDocument(); 919 | 920 | // データ取得後の表示確認 921 | await waitFor(() => { 922 | expect(screen.getByText('John Doe')).toBeInTheDocument(); 923 | expect(screen.getByText('john@example.com')).toBeInTheDocument(); 924 | }); 925 | }); 926 | }); 927 | ``` 928 | 929 | #### E2Eテスト (Cypress) 930 | 931 | ```typescript 932 | // cypress/e2e/login.cy.ts 933 | describe('Login Flow', () => { 934 | it('allows user to login and redirects to dashboard', () => { 935 | cy.visit('/login'); 936 | 937 | cy.get('input[name="email"]').type('user@example.com'); 938 | cy.get('input[name="password"]').type('password123'); 939 | cy.get('button[type="submit"]').click(); 940 | 941 | // ダッシュボードへのリダイレクトと表示確認 942 | cy.url().should('include', '/dashboard'); 943 | cy.get('h1').should('contain', 'Welcome to your Dashboard'); 944 | }); 945 | }); 946 | ``` 947 | 948 | ### NestJSバックエンドのテスト階層 949 | 950 | ```mermaid 951 | flowchart TD 952 | Unit[単体テスト: サービス、パイプ、ガード] --> Integration[統合テスト: コントローラー、モジュール] 953 | Integration --> E2E[E2Eテスト: APIエンドポイント全体] 954 | ``` 955 | 956 | #### 単体テスト (Jest) 957 | 958 | ```typescript 959 | // users/users.service.spec.ts 960 | import { Test } from '@nestjs/testing'; 961 | import { UsersService } from './users.service'; 962 | import { UsersRepository } from './users.repository'; 963 | 964 | describe('UsersService', () => { 965 | let usersService: UsersService; 966 | let usersRepository: UsersRepository; 967 | 968 | beforeEach(async () => { 969 | const moduleRef = await Test.createTestingModule({ 970 | providers: [ 971 | UsersService, 972 | { 973 | provide: UsersRepository, 974 | useValue: { 975 | findById: jest.fn(), 976 | save: jest.fn(), 977 | }, 978 | }, 979 | ], 980 | }).compile(); 981 | 982 | usersService = moduleRef.get(UsersService); 983 | usersRepository = moduleRef.get(UsersRepository); 984 | }); 985 | 986 | describe('findById', () => { 987 | it('should return a user when user exists', async () => { 988 | const mockUser = { id: 1, name: 'John Doe' }; 989 | jest.spyOn(usersRepository, 'findById').mockResolvedValue(mockUser); 990 | 991 | const result = await usersService.findById(1); 992 | 993 | expect(result).toEqual(mockUser); 994 | expect(usersRepository.findById).toHaveBeenCalledWith(1); 995 | }); 996 | }); 997 | }); 998 | ``` 999 | 1000 | #### 統合テスト (NestJS Testing) 1001 | 1002 | ```typescript 1003 | // users/users.controller.spec.ts 1004 | import { Test } from '@nestjs/testing'; 1005 | import { UsersController } from './users.controller'; 1006 | import { UsersService } from './users.service'; 1007 | 1008 | describe('UsersController', () => { 1009 | let usersController: UsersController; 1010 | let usersService: UsersService; 1011 | 1012 | beforeEach(async () => { 1013 | const moduleRef = await Test.createTestingModule({ 1014 | controllers: [UsersController], 1015 | providers: [ 1016 | { 1017 | provide: UsersService, 1018 | useValue: { 1019 | findById: jest.fn(), 1020 | create: jest.fn(), 1021 | }, 1022 | }, 1023 | ], 1024 | }).compile(); 1025 | 1026 | usersController = moduleRef.get(UsersController); 1027 | usersService = moduleRef.get(UsersService); 1028 | }); 1029 | 1030 | describe('findById', () => { 1031 | it('should return a user', async () => { 1032 | const mockUser = { id: 1, name: 'John Doe' }; 1033 | jest.spyOn(usersService, 'findById').mockResolvedValue(mockUser); 1034 | 1035 | const result = await usersController.findById('1'); 1036 | 1037 | expect(result).toEqual(mockUser); 1038 | }); 1039 | }); 1040 | }); 1041 | ``` 1042 | 1043 | #### E2Eテスト (SuperTest) 1044 | 1045 | ```typescript 1046 | // test/users.e2e-spec.ts 1047 | import { Test } from '@nestjs/testing'; 1048 | import { INestApplication } from '@nestjs/common'; 1049 | import * as request from 'supertest'; 1050 | import { AppModule } from '../src/app.module'; 1051 | 1052 | describe('UsersController (e2e)', () => { 1053 | let app: INestApplication; 1054 | 1055 | beforeAll(async () => { 1056 | const moduleFixture = await Test.createTestingModule({ 1057 | imports: [AppModule], 1058 | }).compile(); 1059 | 1060 | app = moduleFixture.createNestApplication(); 1061 | await app.init(); 1062 | }); 1063 | 1064 | afterAll(async () => { 1065 | await app.close(); 1066 | }); 1067 | 1068 | it('/users/:id (GET)', () => { 1069 | return request(app.getHttpServer()) 1070 | .get('/users/1') 1071 | .expect(200) 1072 | .expect((res) => { 1073 | expect(res.body).toHaveProperty('id'); 1074 | expect(res.body).toHaveProperty('name'); 1075 | }); 1076 | }); 1077 | }); 1078 | ``` 1079 | 1080 | ## 並行テスト実行 1081 | 1082 | 大規模なテストスイートでは、テスト実行時間を短縮するために並行実行が有効です。 1083 | 1084 | ### Jestでの並行テスト実行 1085 | 1086 | ```json 1087 | // jest.config.js 1088 | module.exports = { 1089 | // ...他の設定 1090 | maxWorkers: '50%', // CPUコアの50%を使用 1091 | // または 1092 | maxWorkers: 4, // 固定ワーカー数 1093 | }; 1094 | ``` 1095 | 1096 | ### 並行テスト実行の注意点 1097 | 1098 | 1. **テスト間の独立性確保** 1099 | 1100 | - テストは他のテストに依存しないこと 1101 | - 共有リソース(DBなど)へのアクセスを適切に分離 1102 | 1103 | 2. **データ分離** 1104 | 1105 | - テスト用DBの分離またはトランザクションのロールバック 1106 | - テスト前後のデータクリーンアップ 1107 | 1108 | 3. **リソース競合の回避** 1109 | - ファイル操作やポート使用の競合に注意 1110 | - 環境変数の競合回避 1111 | 1112 | ```typescript 1113 | // マルチテナントDB環境でのテスト分離の例 1114 | beforeEach(async () => { 1115 | // テスト用のスキーマを動的に生成 1116 | const schemaName = `test_${Math.random().toString(36).substring(2, 7)}`; 1117 | await db.query(`CREATE SCHEMA IF NOT EXISTS ${schemaName}`); 1118 | await db.query(`SET search_path TO ${schemaName},public`); 1119 | 1120 | // マイグレーション実行 1121 | await runMigrations(schemaName); 1122 | 1123 | // このテストのコンテキストにスキーマ名を保存 1124 | testContext.schemaName = schemaName; 1125 | }); 1126 | 1127 | afterEach(async () => { 1128 | // テスト用スキーマを削除 1129 | await db.query(`DROP SCHEMA IF EXISTS ${testContext.schemaName} CASCADE`); 1130 | }); 1131 | ``` 1132 | 1133 | ## モックとスタブ 1134 | 1135 | ### 外部依存のモック化 1136 | 1137 | ```typescript 1138 | // ユーザーサービスのテスト 1139 | describe('UsersService', () => { 1140 | it('should send welcome email when user is created', async () => { 1141 | // EmailServiceのモック 1142 | const emailServiceMock = { 1143 | sendWelcomeEmail: jest.fn().mockResolvedValue(true), 1144 | }; 1145 | 1146 | const usersService = new UsersService(userRepositoryMock, emailServiceMock); 1147 | 1148 | await usersService.createUser({ name: 'John', email: 'john@example.com' }); 1149 | 1150 | // EmailServiceが正しく呼び出されたか検証 1151 | expect(emailServiceMock.sendWelcomeEmail).toHaveBeenCalledWith('john@example.com', 'John'); 1152 | }); 1153 | }); 1154 | ``` 1155 | 1156 | ### データベースのモック化 1157 | 1158 | Prismaを使用する場合は、`jest-mock-extended`や`@prisma/client/testing`を使用して効果的にモックできます: 1159 | 1160 | ```typescript 1161 | // Prismaクライアントのモック 1162 | import { PrismaClient } from '@prisma/client'; 1163 | import { mockDeep, mockReset, DeepMockProxy } from 'jest-mock-extended'; 1164 | 1165 | jest.mock('@prisma/client', () => ({ 1166 | PrismaClient: jest.fn(), 1167 | })); 1168 | 1169 | let prisma: DeepMockProxy; 1170 | 1171 | beforeEach(() => { 1172 | prisma = mockDeep(); 1173 | (PrismaClient as jest.Mock).mockImplementation(() => prisma); 1174 | }); 1175 | 1176 | test('should create a new user', async () => { 1177 | const mockUser = { id: 1, name: 'John', email: 'john@example.com' }; 1178 | prisma.user.create.mockResolvedValue(mockUser); 1179 | 1180 | const userService = new UserService(prisma); 1181 | const result = await userService.createUser({ name: 'John', email: 'john@example.com' }); 1182 | 1183 | expect(result).toEqual(mockUser); 1184 | expect(prisma.user.create).toHaveBeenCalledWith({ 1185 | data: { name: 'John', email: 'john@example.com' }, 1186 | }); 1187 | }); 1188 | ``` 1189 | 1190 | ## テストリファクタリング 1191 | 1192 | テストコード自体も定期的にリファクタリングすることが大切です: 1193 | 1194 | ### DRYなテストコード 1195 | 1196 | ```typescript 1197 | // テスト前の共通セットアップ 1198 | function createUserService(overrides = {}) { 1199 | return new UserService({ 1200 | userRepository: { findById: jest.fn(), save: jest.fn() }, 1201 | emailService: { sendWelcomeEmail: jest.fn() }, 1202 | ...overrides, 1203 | }); 1204 | } 1205 | 1206 | describe('UserService', () => { 1207 | it('should find user by id', async () => { 1208 | const mockUser = { id: 1, name: 'John' }; 1209 | const mockUserRepo = { findById: jest.fn().mockResolvedValue(mockUser) }; 1210 | const userService = createUserService({ userRepository: mockUserRepo }); 1211 | 1212 | const result = await userService.findById(1); 1213 | 1214 | expect(result).toEqual(mockUser); 1215 | }); 1216 | 1217 | // 他のテスト... 1218 | }); 1219 | ``` 1220 | 1221 | ### テストヘルパー関数 1222 | 1223 | ```typescript 1224 | // テストユーティリティ 1225 | function createTestUser(overrides = {}) { 1226 | return { 1227 | id: 1, 1228 | name: 'John Doe', 1229 | email: 'john@example.com', 1230 | isActive: true, 1231 | ...overrides, 1232 | }; 1233 | } 1234 | 1235 | it('should activate inactive user', async () => { 1236 | const inactiveUser = createTestUser({ isActive: false }); 1237 | // ...テストの続き 1238 | }); 1239 | ``` 1240 | 1241 | ## コードカバレッジとレポーティング 1242 | 1243 | ### Jestでのカバレッジ測定 1244 | 1245 | ```bash 1246 | # カバレッジ測定付きでテスト実行 1247 | yarn test --coverage 1248 | ``` 1249 | 1250 | ```json 1251 | // jest.config.js 1252 | module.exports = { 1253 | // ... 1254 | coverageThreshold: { 1255 | global: { 1256 | branches: 80, 1257 | functions: 80, 1258 | lines: 80, 1259 | statements: 80 1260 | } 1261 | }, 1262 | collectCoverageFrom: [ 1263 | "src/**/*.{js,jsx,ts,tsx}", 1264 | "!src/**/*.d.ts", 1265 | "!src/index.{js,ts}", 1266 | "!src/**/*.stories.{js,jsx,ts,tsx}" 1267 | ] 1268 | }; 1269 | ``` 1270 | 1271 | ## TDDの導入と習慣化 1272 | 1273 | ### TDDへの段階的移行 1274 | 1275 | 1. **既存コードへのテスト追加から始める** 1276 | 1277 | - 重要な機能やバグ修正時にまずテストを追加 1278 | 1279 | 2. **新機能開発にTDDを適用** 1280 | 1281 | - 新しい機能開発時にはテストファーストで進める 1282 | 1283 | 3. **チーム内でTDDセッションを実施** 1284 | - ペアプログラミングやモブプログラミングでTDDを実践 1285 | 1286 | ### GitによるTDDの強化 1287 | 1288 | TDDのサイクルに合わせたコミット戦略: 1289 | 1290 | ```bash 1291 | # Red: 失敗するテストを書く 1292 | git add src/user/user.service.spec.ts 1293 | git commit -m "test: Add test for user activation" 1294 | 1295 | # Green: 実装を行い、テストをパスさせる 1296 | git add src/user/user.service.ts 1297 | git commit -m "feat: Implement user activation" 1298 | 1299 | # Refactor: コードを改善する 1300 | git add src/user/user.service.ts 1301 | git commit -m "refactor: Improve user activation logic" 1302 | ``` 1303 | 1304 | ## NestJSとNext.jsのテスト設定 1305 | 1306 | ### NestJSのテスト設定 1307 | 1308 | ```typescript 1309 | // apps/api/jest.config.ts 1310 | module.exports = { 1311 | displayName: 'api', 1312 | preset: '../../jest.preset.js', 1313 | globals: { 1314 | 'ts-jest': { 1315 | tsconfig: '/tsconfig.spec.json', 1316 | }, 1317 | }, 1318 | testEnvironment: 'node', 1319 | transform: { 1320 | '^.+\\.[tj]s$': 'ts-jest', 1321 | }, 1322 | moduleFileExtensions: ['ts', 'js', 'html'], 1323 | coverageDirectory: '../../coverage/apps/api', 1324 | }; 1325 | ``` 1326 | 1327 | ### Next.jsのテスト設定 1328 | 1329 | ```typescript 1330 | // apps/frontend/jest.config.ts 1331 | module.exports = { 1332 | displayName: 'frontend', 1333 | preset: '../../jest.preset.js', 1334 | transform: { 1335 | '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nrwl/react/plugins/jest', 1336 | '^.+\\.[tj]sx?$': ['babel-jest', { presets: ['@nrwl/next/babel'] }], 1337 | }, 1338 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], 1339 | coverageDirectory: '../../coverage/apps/frontend', 1340 | setupFilesAfterEnv: ['/jest.setup.js'], 1341 | testEnvironment: 'jsdom', 1342 | }; 1343 | ``` 1344 | 1345 | ## まとめ 1346 | 1347 | テスト駆動開発は単なる手法ではなく、品質を重視する姿勢とフィードバックを素早く得るプロセスです。TDDを習慣化することで: 1348 | 1349 | 1. **設計の質向上**: 必要な機能を明確に定義し、クリーンなAPIを設計 1350 | 2. **バグの削減**: エッジケースやエラー処理を事前に考慮 1351 | 3. **リファクタリングの安全性確保**: 既存機能を壊さずにコードを改善 1352 | 4. **開発速度の向上**: 初期は遅く感じても、長期的にはバグ修正時間の削減で効率化 1353 | 5. **ドキュメントとしての価値**: テストが仕様を示す生きたドキュメントとなる 1354 | 1355 | 新たな機能開発や既存コードの修正時には、まずテストから始めることを心がけましょう。 1356 | 1357 | 1358 | -------------------------------------------------------------------------------- /.cursor/rules/002_bestPractices_frontend.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: this is the rule we have to follow when working on frontend 3 | globs: apps/frontend/*/** 4 | alwaysApply: false 5 | --- 6 | 7 | # 002_bestPractices_frontend.mdc 8 | - このファイルが読み込まれたら「002_bestPractices_frontend.mdcを読み込みました!」と作業着手前にユーザーに必ず伝えてください。 9 | 10 | -- 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /.cursor/rules/003_bestPractices_backend.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: this is the rule we have to follow when working on Backend 3 | globs: apps/backend/*/** 4 | alwaysApply: false 5 | --- 6 | 7 | # 003_bestPractices_backend.mdc 8 | - このファイルが読み込まれたら「003_bestPractices_backend.mdcを読み込みました!」と作業着手前にユーザーに必ず伝えてください。 9 | 10 | -- 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # .gitignore file 2 | 3 | # Ignore .specstory files 4 | .specstory 5 | 6 | # Node.js 7 | node_modules/ 8 | npm-debug.log 9 | yarn-error.log 10 | yarn-debug.log 11 | 12 | # Build directories 13 | dist/ 14 | build/ 15 | 16 | # Environment variables 17 | .env 18 | .env.local 19 | .env.development.local 20 | .env.test.local 21 | .env.production.local 22 | 23 | # IDE and editor files 24 | .idea/ 25 | .vscode/ 26 | *.swp 27 | *.swo 28 | *~ 29 | 30 | # OS generated files 31 | .DS_Store 32 | .DS_Store? 33 | ._* 34 | .Spotlight-V100 35 | .Trashes 36 | ehthumbs.db 37 | Thumbs.db -------------------------------------------------------------------------------- /.windsurfrules: -------------------------------------------------------------------------------- 1 | 必ず日本語で話して -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 注意 2 | 2025年5月現在、著者はこのrulesを使っていないです。 3 | 4 | ## このレポジトリの目的 5 | 6 | このレポジトリの主目的は、zennの記事(https://zenn.dev/ks0318/articles/b8eb2c9396f9cb)で語られているCursorのProjectRulesの運用イメージを伝えることです。 7 | 8 | 記事用に急いで作ったのでそのままは使えずプロジェクトによって結構チューニングが必要です。実際に僕が使っているものも基本構成などはこれをベースに拡張していますが、細かい中身はAIに作らせたサンプルです。 9 | 10 | ## 参考にしたレポジトリやWebサイト 11 | 12 | - https://github.com/mizchi/ailab 13 | - https://github.com/kinopeee/cursorrules 14 | - https://dotcursorrules.com/rules 15 | - https://github.com/CodeGuide-dev/codeguide-starter-lite 16 | - https://x.com/rileybrown_ai/status/1891632470207979646 17 | - https://x.com/jelanifuel/status/1897036963162808617 18 | 19 | ## ライセンス 20 | MIT 21 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cursor-project-rules", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "cursor-project-rules", 9 | "version": "1.0.0", 10 | "license": "MIT", 11 | "devDependencies": { 12 | "@types/fs-extra": "^11.0.4", 13 | "@types/glob": "^8.1.0", 14 | "fs-extra": "^11.3.0", 15 | "glob": "^11.0.1", 16 | "path": "^0.12.7", 17 | "ts-node": "^10.9.2", 18 | "tsx": "^4.19.3", 19 | "typescript": "^5.8.2" 20 | } 21 | }, 22 | "node_modules/@cspotcode/source-map-support": { 23 | "version": "0.8.1", 24 | "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", 25 | "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", 26 | "dev": true, 27 | "license": "MIT", 28 | "dependencies": { 29 | "@jridgewell/trace-mapping": "0.3.9" 30 | }, 31 | "engines": { 32 | "node": ">=12" 33 | } 34 | }, 35 | "node_modules/@esbuild/aix-ppc64": { 36 | "version": "0.25.0", 37 | "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz", 38 | "integrity": "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==", 39 | "cpu": [ 40 | "ppc64" 41 | ], 42 | "dev": true, 43 | "license": "MIT", 44 | "optional": true, 45 | "os": [ 46 | "aix" 47 | ], 48 | "engines": { 49 | "node": ">=18" 50 | } 51 | }, 52 | "node_modules/@esbuild/android-arm": { 53 | "version": "0.25.0", 54 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.0.tgz", 55 | "integrity": "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==", 56 | "cpu": [ 57 | "arm" 58 | ], 59 | "dev": true, 60 | "license": "MIT", 61 | "optional": true, 62 | "os": [ 63 | "android" 64 | ], 65 | "engines": { 66 | "node": ">=18" 67 | } 68 | }, 69 | "node_modules/@esbuild/android-arm64": { 70 | "version": "0.25.0", 71 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz", 72 | "integrity": "sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==", 73 | "cpu": [ 74 | "arm64" 75 | ], 76 | "dev": true, 77 | "license": "MIT", 78 | "optional": true, 79 | "os": [ 80 | "android" 81 | ], 82 | "engines": { 83 | "node": ">=18" 84 | } 85 | }, 86 | "node_modules/@esbuild/android-x64": { 87 | "version": "0.25.0", 88 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.0.tgz", 89 | "integrity": "sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==", 90 | "cpu": [ 91 | "x64" 92 | ], 93 | "dev": true, 94 | "license": "MIT", 95 | "optional": true, 96 | "os": [ 97 | "android" 98 | ], 99 | "engines": { 100 | "node": ">=18" 101 | } 102 | }, 103 | "node_modules/@esbuild/darwin-arm64": { 104 | "version": "0.25.0", 105 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz", 106 | "integrity": "sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==", 107 | "cpu": [ 108 | "arm64" 109 | ], 110 | "dev": true, 111 | "license": "MIT", 112 | "optional": true, 113 | "os": [ 114 | "darwin" 115 | ], 116 | "engines": { 117 | "node": ">=18" 118 | } 119 | }, 120 | "node_modules/@esbuild/darwin-x64": { 121 | "version": "0.25.0", 122 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz", 123 | "integrity": "sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==", 124 | "cpu": [ 125 | "x64" 126 | ], 127 | "dev": true, 128 | "license": "MIT", 129 | "optional": true, 130 | "os": [ 131 | "darwin" 132 | ], 133 | "engines": { 134 | "node": ">=18" 135 | } 136 | }, 137 | "node_modules/@esbuild/freebsd-arm64": { 138 | "version": "0.25.0", 139 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz", 140 | "integrity": "sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==", 141 | "cpu": [ 142 | "arm64" 143 | ], 144 | "dev": true, 145 | "license": "MIT", 146 | "optional": true, 147 | "os": [ 148 | "freebsd" 149 | ], 150 | "engines": { 151 | "node": ">=18" 152 | } 153 | }, 154 | "node_modules/@esbuild/freebsd-x64": { 155 | "version": "0.25.0", 156 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz", 157 | "integrity": "sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==", 158 | "cpu": [ 159 | "x64" 160 | ], 161 | "dev": true, 162 | "license": "MIT", 163 | "optional": true, 164 | "os": [ 165 | "freebsd" 166 | ], 167 | "engines": { 168 | "node": ">=18" 169 | } 170 | }, 171 | "node_modules/@esbuild/linux-arm": { 172 | "version": "0.25.0", 173 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz", 174 | "integrity": "sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==", 175 | "cpu": [ 176 | "arm" 177 | ], 178 | "dev": true, 179 | "license": "MIT", 180 | "optional": true, 181 | "os": [ 182 | "linux" 183 | ], 184 | "engines": { 185 | "node": ">=18" 186 | } 187 | }, 188 | "node_modules/@esbuild/linux-arm64": { 189 | "version": "0.25.0", 190 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz", 191 | "integrity": "sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==", 192 | "cpu": [ 193 | "arm64" 194 | ], 195 | "dev": true, 196 | "license": "MIT", 197 | "optional": true, 198 | "os": [ 199 | "linux" 200 | ], 201 | "engines": { 202 | "node": ">=18" 203 | } 204 | }, 205 | "node_modules/@esbuild/linux-ia32": { 206 | "version": "0.25.0", 207 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz", 208 | "integrity": "sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==", 209 | "cpu": [ 210 | "ia32" 211 | ], 212 | "dev": true, 213 | "license": "MIT", 214 | "optional": true, 215 | "os": [ 216 | "linux" 217 | ], 218 | "engines": { 219 | "node": ">=18" 220 | } 221 | }, 222 | "node_modules/@esbuild/linux-loong64": { 223 | "version": "0.25.0", 224 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz", 225 | "integrity": "sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==", 226 | "cpu": [ 227 | "loong64" 228 | ], 229 | "dev": true, 230 | "license": "MIT", 231 | "optional": true, 232 | "os": [ 233 | "linux" 234 | ], 235 | "engines": { 236 | "node": ">=18" 237 | } 238 | }, 239 | "node_modules/@esbuild/linux-mips64el": { 240 | "version": "0.25.0", 241 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz", 242 | "integrity": "sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==", 243 | "cpu": [ 244 | "mips64el" 245 | ], 246 | "dev": true, 247 | "license": "MIT", 248 | "optional": true, 249 | "os": [ 250 | "linux" 251 | ], 252 | "engines": { 253 | "node": ">=18" 254 | } 255 | }, 256 | "node_modules/@esbuild/linux-ppc64": { 257 | "version": "0.25.0", 258 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz", 259 | "integrity": "sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==", 260 | "cpu": [ 261 | "ppc64" 262 | ], 263 | "dev": true, 264 | "license": "MIT", 265 | "optional": true, 266 | "os": [ 267 | "linux" 268 | ], 269 | "engines": { 270 | "node": ">=18" 271 | } 272 | }, 273 | "node_modules/@esbuild/linux-riscv64": { 274 | "version": "0.25.0", 275 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz", 276 | "integrity": "sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==", 277 | "cpu": [ 278 | "riscv64" 279 | ], 280 | "dev": true, 281 | "license": "MIT", 282 | "optional": true, 283 | "os": [ 284 | "linux" 285 | ], 286 | "engines": { 287 | "node": ">=18" 288 | } 289 | }, 290 | "node_modules/@esbuild/linux-s390x": { 291 | "version": "0.25.0", 292 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz", 293 | "integrity": "sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==", 294 | "cpu": [ 295 | "s390x" 296 | ], 297 | "dev": true, 298 | "license": "MIT", 299 | "optional": true, 300 | "os": [ 301 | "linux" 302 | ], 303 | "engines": { 304 | "node": ">=18" 305 | } 306 | }, 307 | "node_modules/@esbuild/linux-x64": { 308 | "version": "0.25.0", 309 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz", 310 | "integrity": "sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==", 311 | "cpu": [ 312 | "x64" 313 | ], 314 | "dev": true, 315 | "license": "MIT", 316 | "optional": true, 317 | "os": [ 318 | "linux" 319 | ], 320 | "engines": { 321 | "node": ">=18" 322 | } 323 | }, 324 | "node_modules/@esbuild/netbsd-arm64": { 325 | "version": "0.25.0", 326 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz", 327 | "integrity": "sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==", 328 | "cpu": [ 329 | "arm64" 330 | ], 331 | "dev": true, 332 | "license": "MIT", 333 | "optional": true, 334 | "os": [ 335 | "netbsd" 336 | ], 337 | "engines": { 338 | "node": ">=18" 339 | } 340 | }, 341 | "node_modules/@esbuild/netbsd-x64": { 342 | "version": "0.25.0", 343 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz", 344 | "integrity": "sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==", 345 | "cpu": [ 346 | "x64" 347 | ], 348 | "dev": true, 349 | "license": "MIT", 350 | "optional": true, 351 | "os": [ 352 | "netbsd" 353 | ], 354 | "engines": { 355 | "node": ">=18" 356 | } 357 | }, 358 | "node_modules/@esbuild/openbsd-arm64": { 359 | "version": "0.25.0", 360 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz", 361 | "integrity": "sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==", 362 | "cpu": [ 363 | "arm64" 364 | ], 365 | "dev": true, 366 | "license": "MIT", 367 | "optional": true, 368 | "os": [ 369 | "openbsd" 370 | ], 371 | "engines": { 372 | "node": ">=18" 373 | } 374 | }, 375 | "node_modules/@esbuild/openbsd-x64": { 376 | "version": "0.25.0", 377 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz", 378 | "integrity": "sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==", 379 | "cpu": [ 380 | "x64" 381 | ], 382 | "dev": true, 383 | "license": "MIT", 384 | "optional": true, 385 | "os": [ 386 | "openbsd" 387 | ], 388 | "engines": { 389 | "node": ">=18" 390 | } 391 | }, 392 | "node_modules/@esbuild/sunos-x64": { 393 | "version": "0.25.0", 394 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz", 395 | "integrity": "sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==", 396 | "cpu": [ 397 | "x64" 398 | ], 399 | "dev": true, 400 | "license": "MIT", 401 | "optional": true, 402 | "os": [ 403 | "sunos" 404 | ], 405 | "engines": { 406 | "node": ">=18" 407 | } 408 | }, 409 | "node_modules/@esbuild/win32-arm64": { 410 | "version": "0.25.0", 411 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz", 412 | "integrity": "sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==", 413 | "cpu": [ 414 | "arm64" 415 | ], 416 | "dev": true, 417 | "license": "MIT", 418 | "optional": true, 419 | "os": [ 420 | "win32" 421 | ], 422 | "engines": { 423 | "node": ">=18" 424 | } 425 | }, 426 | "node_modules/@esbuild/win32-ia32": { 427 | "version": "0.25.0", 428 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz", 429 | "integrity": "sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==", 430 | "cpu": [ 431 | "ia32" 432 | ], 433 | "dev": true, 434 | "license": "MIT", 435 | "optional": true, 436 | "os": [ 437 | "win32" 438 | ], 439 | "engines": { 440 | "node": ">=18" 441 | } 442 | }, 443 | "node_modules/@esbuild/win32-x64": { 444 | "version": "0.25.0", 445 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz", 446 | "integrity": "sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==", 447 | "cpu": [ 448 | "x64" 449 | ], 450 | "dev": true, 451 | "license": "MIT", 452 | "optional": true, 453 | "os": [ 454 | "win32" 455 | ], 456 | "engines": { 457 | "node": ">=18" 458 | } 459 | }, 460 | "node_modules/@isaacs/cliui": { 461 | "version": "8.0.2", 462 | "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", 463 | "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", 464 | "dev": true, 465 | "license": "ISC", 466 | "dependencies": { 467 | "string-width": "^5.1.2", 468 | "string-width-cjs": "npm:string-width@^4.2.0", 469 | "strip-ansi": "^7.0.1", 470 | "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", 471 | "wrap-ansi": "^8.1.0", 472 | "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" 473 | }, 474 | "engines": { 475 | "node": ">=12" 476 | } 477 | }, 478 | "node_modules/@jridgewell/resolve-uri": { 479 | "version": "3.1.2", 480 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", 481 | "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", 482 | "dev": true, 483 | "license": "MIT", 484 | "engines": { 485 | "node": ">=6.0.0" 486 | } 487 | }, 488 | "node_modules/@jridgewell/sourcemap-codec": { 489 | "version": "1.5.0", 490 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", 491 | "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", 492 | "dev": true, 493 | "license": "MIT" 494 | }, 495 | "node_modules/@jridgewell/trace-mapping": { 496 | "version": "0.3.9", 497 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", 498 | "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", 499 | "dev": true, 500 | "license": "MIT", 501 | "dependencies": { 502 | "@jridgewell/resolve-uri": "^3.0.3", 503 | "@jridgewell/sourcemap-codec": "^1.4.10" 504 | } 505 | }, 506 | "node_modules/@tsconfig/node10": { 507 | "version": "1.0.11", 508 | "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", 509 | "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", 510 | "dev": true, 511 | "license": "MIT" 512 | }, 513 | "node_modules/@tsconfig/node12": { 514 | "version": "1.0.11", 515 | "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", 516 | "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", 517 | "dev": true, 518 | "license": "MIT" 519 | }, 520 | "node_modules/@tsconfig/node14": { 521 | "version": "1.0.3", 522 | "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", 523 | "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", 524 | "dev": true, 525 | "license": "MIT" 526 | }, 527 | "node_modules/@tsconfig/node16": { 528 | "version": "1.0.4", 529 | "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", 530 | "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", 531 | "dev": true, 532 | "license": "MIT" 533 | }, 534 | "node_modules/@types/fs-extra": { 535 | "version": "11.0.4", 536 | "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-11.0.4.tgz", 537 | "integrity": "sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==", 538 | "dev": true, 539 | "license": "MIT", 540 | "dependencies": { 541 | "@types/jsonfile": "*", 542 | "@types/node": "*" 543 | } 544 | }, 545 | "node_modules/@types/glob": { 546 | "version": "8.1.0", 547 | "resolved": "https://registry.npmjs.org/@types/glob/-/glob-8.1.0.tgz", 548 | "integrity": "sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w==", 549 | "dev": true, 550 | "license": "MIT", 551 | "dependencies": { 552 | "@types/minimatch": "^5.1.2", 553 | "@types/node": "*" 554 | } 555 | }, 556 | "node_modules/@types/jsonfile": { 557 | "version": "6.1.4", 558 | "resolved": "https://registry.npmjs.org/@types/jsonfile/-/jsonfile-6.1.4.tgz", 559 | "integrity": "sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==", 560 | "dev": true, 561 | "license": "MIT", 562 | "dependencies": { 563 | "@types/node": "*" 564 | } 565 | }, 566 | "node_modules/@types/minimatch": { 567 | "version": "5.1.2", 568 | "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", 569 | "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", 570 | "dev": true, 571 | "license": "MIT" 572 | }, 573 | "node_modules/@types/node": { 574 | "version": "22.13.9", 575 | "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.9.tgz", 576 | "integrity": "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw==", 577 | "dev": true, 578 | "license": "MIT", 579 | "dependencies": { 580 | "undici-types": "~6.20.0" 581 | } 582 | }, 583 | "node_modules/acorn": { 584 | "version": "8.14.1", 585 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", 586 | "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", 587 | "dev": true, 588 | "license": "MIT", 589 | "bin": { 590 | "acorn": "bin/acorn" 591 | }, 592 | "engines": { 593 | "node": ">=0.4.0" 594 | } 595 | }, 596 | "node_modules/acorn-walk": { 597 | "version": "8.3.4", 598 | "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", 599 | "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", 600 | "dev": true, 601 | "license": "MIT", 602 | "dependencies": { 603 | "acorn": "^8.11.0" 604 | }, 605 | "engines": { 606 | "node": ">=0.4.0" 607 | } 608 | }, 609 | "node_modules/ansi-regex": { 610 | "version": "6.1.0", 611 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", 612 | "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", 613 | "dev": true, 614 | "license": "MIT", 615 | "engines": { 616 | "node": ">=12" 617 | }, 618 | "funding": { 619 | "url": "https://github.com/chalk/ansi-regex?sponsor=1" 620 | } 621 | }, 622 | "node_modules/ansi-styles": { 623 | "version": "6.2.1", 624 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", 625 | "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", 626 | "dev": true, 627 | "license": "MIT", 628 | "engines": { 629 | "node": ">=12" 630 | }, 631 | "funding": { 632 | "url": "https://github.com/chalk/ansi-styles?sponsor=1" 633 | } 634 | }, 635 | "node_modules/arg": { 636 | "version": "4.1.3", 637 | "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", 638 | "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", 639 | "dev": true, 640 | "license": "MIT" 641 | }, 642 | "node_modules/balanced-match": { 643 | "version": "1.0.2", 644 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 645 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 646 | "dev": true, 647 | "license": "MIT" 648 | }, 649 | "node_modules/brace-expansion": { 650 | "version": "2.0.1", 651 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", 652 | "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", 653 | "dev": true, 654 | "license": "MIT", 655 | "dependencies": { 656 | "balanced-match": "^1.0.0" 657 | } 658 | }, 659 | "node_modules/color-convert": { 660 | "version": "2.0.1", 661 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 662 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 663 | "dev": true, 664 | "license": "MIT", 665 | "dependencies": { 666 | "color-name": "~1.1.4" 667 | }, 668 | "engines": { 669 | "node": ">=7.0.0" 670 | } 671 | }, 672 | "node_modules/color-name": { 673 | "version": "1.1.4", 674 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 675 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 676 | "dev": true, 677 | "license": "MIT" 678 | }, 679 | "node_modules/create-require": { 680 | "version": "1.1.1", 681 | "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", 682 | "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", 683 | "dev": true, 684 | "license": "MIT" 685 | }, 686 | "node_modules/cross-spawn": { 687 | "version": "7.0.6", 688 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", 689 | "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", 690 | "dev": true, 691 | "license": "MIT", 692 | "dependencies": { 693 | "path-key": "^3.1.0", 694 | "shebang-command": "^2.0.0", 695 | "which": "^2.0.1" 696 | }, 697 | "engines": { 698 | "node": ">= 8" 699 | } 700 | }, 701 | "node_modules/diff": { 702 | "version": "4.0.2", 703 | "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", 704 | "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", 705 | "dev": true, 706 | "license": "BSD-3-Clause", 707 | "engines": { 708 | "node": ">=0.3.1" 709 | } 710 | }, 711 | "node_modules/eastasianwidth": { 712 | "version": "0.2.0", 713 | "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", 714 | "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", 715 | "dev": true, 716 | "license": "MIT" 717 | }, 718 | "node_modules/emoji-regex": { 719 | "version": "9.2.2", 720 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", 721 | "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", 722 | "dev": true, 723 | "license": "MIT" 724 | }, 725 | "node_modules/esbuild": { 726 | "version": "0.25.0", 727 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz", 728 | "integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==", 729 | "dev": true, 730 | "hasInstallScript": true, 731 | "license": "MIT", 732 | "bin": { 733 | "esbuild": "bin/esbuild" 734 | }, 735 | "engines": { 736 | "node": ">=18" 737 | }, 738 | "optionalDependencies": { 739 | "@esbuild/aix-ppc64": "0.25.0", 740 | "@esbuild/android-arm": "0.25.0", 741 | "@esbuild/android-arm64": "0.25.0", 742 | "@esbuild/android-x64": "0.25.0", 743 | "@esbuild/darwin-arm64": "0.25.0", 744 | "@esbuild/darwin-x64": "0.25.0", 745 | "@esbuild/freebsd-arm64": "0.25.0", 746 | "@esbuild/freebsd-x64": "0.25.0", 747 | "@esbuild/linux-arm": "0.25.0", 748 | "@esbuild/linux-arm64": "0.25.0", 749 | "@esbuild/linux-ia32": "0.25.0", 750 | "@esbuild/linux-loong64": "0.25.0", 751 | "@esbuild/linux-mips64el": "0.25.0", 752 | "@esbuild/linux-ppc64": "0.25.0", 753 | "@esbuild/linux-riscv64": "0.25.0", 754 | "@esbuild/linux-s390x": "0.25.0", 755 | "@esbuild/linux-x64": "0.25.0", 756 | "@esbuild/netbsd-arm64": "0.25.0", 757 | "@esbuild/netbsd-x64": "0.25.0", 758 | "@esbuild/openbsd-arm64": "0.25.0", 759 | "@esbuild/openbsd-x64": "0.25.0", 760 | "@esbuild/sunos-x64": "0.25.0", 761 | "@esbuild/win32-arm64": "0.25.0", 762 | "@esbuild/win32-ia32": "0.25.0", 763 | "@esbuild/win32-x64": "0.25.0" 764 | } 765 | }, 766 | "node_modules/foreground-child": { 767 | "version": "3.3.1", 768 | "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", 769 | "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", 770 | "dev": true, 771 | "license": "ISC", 772 | "dependencies": { 773 | "cross-spawn": "^7.0.6", 774 | "signal-exit": "^4.0.1" 775 | }, 776 | "engines": { 777 | "node": ">=14" 778 | }, 779 | "funding": { 780 | "url": "https://github.com/sponsors/isaacs" 781 | } 782 | }, 783 | "node_modules/fs-extra": { 784 | "version": "11.3.0", 785 | "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz", 786 | "integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==", 787 | "dev": true, 788 | "license": "MIT", 789 | "dependencies": { 790 | "graceful-fs": "^4.2.0", 791 | "jsonfile": "^6.0.1", 792 | "universalify": "^2.0.0" 793 | }, 794 | "engines": { 795 | "node": ">=14.14" 796 | } 797 | }, 798 | "node_modules/fsevents": { 799 | "version": "2.3.3", 800 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 801 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 802 | "dev": true, 803 | "hasInstallScript": true, 804 | "license": "MIT", 805 | "optional": true, 806 | "os": [ 807 | "darwin" 808 | ], 809 | "engines": { 810 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 811 | } 812 | }, 813 | "node_modules/get-tsconfig": { 814 | "version": "4.10.0", 815 | "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.0.tgz", 816 | "integrity": "sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==", 817 | "dev": true, 818 | "license": "MIT", 819 | "dependencies": { 820 | "resolve-pkg-maps": "^1.0.0" 821 | }, 822 | "funding": { 823 | "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" 824 | } 825 | }, 826 | "node_modules/glob": { 827 | "version": "11.0.1", 828 | "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.1.tgz", 829 | "integrity": "sha512-zrQDm8XPnYEKawJScsnM0QzobJxlT/kHOOlRTio8IH/GrmxRE5fjllkzdaHclIuNjUQTJYH2xHNIGfdpJkDJUw==", 830 | "dev": true, 831 | "license": "ISC", 832 | "dependencies": { 833 | "foreground-child": "^3.1.0", 834 | "jackspeak": "^4.0.1", 835 | "minimatch": "^10.0.0", 836 | "minipass": "^7.1.2", 837 | "package-json-from-dist": "^1.0.0", 838 | "path-scurry": "^2.0.0" 839 | }, 840 | "bin": { 841 | "glob": "dist/esm/bin.mjs" 842 | }, 843 | "engines": { 844 | "node": "20 || >=22" 845 | }, 846 | "funding": { 847 | "url": "https://github.com/sponsors/isaacs" 848 | } 849 | }, 850 | "node_modules/graceful-fs": { 851 | "version": "4.2.11", 852 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", 853 | "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", 854 | "dev": true, 855 | "license": "ISC" 856 | }, 857 | "node_modules/inherits": { 858 | "version": "2.0.3", 859 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 860 | "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", 861 | "dev": true, 862 | "license": "ISC" 863 | }, 864 | "node_modules/is-fullwidth-code-point": { 865 | "version": "3.0.0", 866 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 867 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", 868 | "dev": true, 869 | "license": "MIT", 870 | "engines": { 871 | "node": ">=8" 872 | } 873 | }, 874 | "node_modules/isexe": { 875 | "version": "2.0.0", 876 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 877 | "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", 878 | "dev": true, 879 | "license": "ISC" 880 | }, 881 | "node_modules/jackspeak": { 882 | "version": "4.1.0", 883 | "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.0.tgz", 884 | "integrity": "sha512-9DDdhb5j6cpeitCbvLO7n7J4IxnbM6hoF6O1g4HQ5TfhvvKN8ywDM7668ZhMHRqVmxqhps/F6syWK2KcPxYlkw==", 885 | "dev": true, 886 | "license": "BlueOak-1.0.0", 887 | "dependencies": { 888 | "@isaacs/cliui": "^8.0.2" 889 | }, 890 | "engines": { 891 | "node": "20 || >=22" 892 | }, 893 | "funding": { 894 | "url": "https://github.com/sponsors/isaacs" 895 | } 896 | }, 897 | "node_modules/jsonfile": { 898 | "version": "6.1.0", 899 | "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", 900 | "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", 901 | "dev": true, 902 | "license": "MIT", 903 | "dependencies": { 904 | "universalify": "^2.0.0" 905 | }, 906 | "optionalDependencies": { 907 | "graceful-fs": "^4.1.6" 908 | } 909 | }, 910 | "node_modules/lru-cache": { 911 | "version": "11.0.2", 912 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.2.tgz", 913 | "integrity": "sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA==", 914 | "dev": true, 915 | "license": "ISC", 916 | "engines": { 917 | "node": "20 || >=22" 918 | } 919 | }, 920 | "node_modules/make-error": { 921 | "version": "1.3.6", 922 | "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", 923 | "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", 924 | "dev": true, 925 | "license": "ISC" 926 | }, 927 | "node_modules/minimatch": { 928 | "version": "10.0.1", 929 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", 930 | "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", 931 | "dev": true, 932 | "license": "ISC", 933 | "dependencies": { 934 | "brace-expansion": "^2.0.1" 935 | }, 936 | "engines": { 937 | "node": "20 || >=22" 938 | }, 939 | "funding": { 940 | "url": "https://github.com/sponsors/isaacs" 941 | } 942 | }, 943 | "node_modules/minipass": { 944 | "version": "7.1.2", 945 | "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", 946 | "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", 947 | "dev": true, 948 | "license": "ISC", 949 | "engines": { 950 | "node": ">=16 || 14 >=14.17" 951 | } 952 | }, 953 | "node_modules/package-json-from-dist": { 954 | "version": "1.0.1", 955 | "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", 956 | "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", 957 | "dev": true, 958 | "license": "BlueOak-1.0.0" 959 | }, 960 | "node_modules/path": { 961 | "version": "0.12.7", 962 | "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz", 963 | "integrity": "sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==", 964 | "dev": true, 965 | "license": "MIT", 966 | "dependencies": { 967 | "process": "^0.11.1", 968 | "util": "^0.10.3" 969 | } 970 | }, 971 | "node_modules/path-key": { 972 | "version": "3.1.1", 973 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", 974 | "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", 975 | "dev": true, 976 | "license": "MIT", 977 | "engines": { 978 | "node": ">=8" 979 | } 980 | }, 981 | "node_modules/path-scurry": { 982 | "version": "2.0.0", 983 | "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", 984 | "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", 985 | "dev": true, 986 | "license": "BlueOak-1.0.0", 987 | "dependencies": { 988 | "lru-cache": "^11.0.0", 989 | "minipass": "^7.1.2" 990 | }, 991 | "engines": { 992 | "node": "20 || >=22" 993 | }, 994 | "funding": { 995 | "url": "https://github.com/sponsors/isaacs" 996 | } 997 | }, 998 | "node_modules/process": { 999 | "version": "0.11.10", 1000 | "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", 1001 | "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", 1002 | "dev": true, 1003 | "license": "MIT", 1004 | "engines": { 1005 | "node": ">= 0.6.0" 1006 | } 1007 | }, 1008 | "node_modules/resolve-pkg-maps": { 1009 | "version": "1.0.0", 1010 | "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", 1011 | "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", 1012 | "dev": true, 1013 | "license": "MIT", 1014 | "funding": { 1015 | "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" 1016 | } 1017 | }, 1018 | "node_modules/shebang-command": { 1019 | "version": "2.0.0", 1020 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", 1021 | "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", 1022 | "dev": true, 1023 | "license": "MIT", 1024 | "dependencies": { 1025 | "shebang-regex": "^3.0.0" 1026 | }, 1027 | "engines": { 1028 | "node": ">=8" 1029 | } 1030 | }, 1031 | "node_modules/shebang-regex": { 1032 | "version": "3.0.0", 1033 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", 1034 | "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", 1035 | "dev": true, 1036 | "license": "MIT", 1037 | "engines": { 1038 | "node": ">=8" 1039 | } 1040 | }, 1041 | "node_modules/signal-exit": { 1042 | "version": "4.1.0", 1043 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", 1044 | "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", 1045 | "dev": true, 1046 | "license": "ISC", 1047 | "engines": { 1048 | "node": ">=14" 1049 | }, 1050 | "funding": { 1051 | "url": "https://github.com/sponsors/isaacs" 1052 | } 1053 | }, 1054 | "node_modules/string-width": { 1055 | "version": "5.1.2", 1056 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", 1057 | "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", 1058 | "dev": true, 1059 | "license": "MIT", 1060 | "dependencies": { 1061 | "eastasianwidth": "^0.2.0", 1062 | "emoji-regex": "^9.2.2", 1063 | "strip-ansi": "^7.0.1" 1064 | }, 1065 | "engines": { 1066 | "node": ">=12" 1067 | }, 1068 | "funding": { 1069 | "url": "https://github.com/sponsors/sindresorhus" 1070 | } 1071 | }, 1072 | "node_modules/string-width-cjs": { 1073 | "name": "string-width", 1074 | "version": "4.2.3", 1075 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 1076 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 1077 | "dev": true, 1078 | "license": "MIT", 1079 | "dependencies": { 1080 | "emoji-regex": "^8.0.0", 1081 | "is-fullwidth-code-point": "^3.0.0", 1082 | "strip-ansi": "^6.0.1" 1083 | }, 1084 | "engines": { 1085 | "node": ">=8" 1086 | } 1087 | }, 1088 | "node_modules/string-width-cjs/node_modules/ansi-regex": { 1089 | "version": "5.0.1", 1090 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 1091 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 1092 | "dev": true, 1093 | "license": "MIT", 1094 | "engines": { 1095 | "node": ">=8" 1096 | } 1097 | }, 1098 | "node_modules/string-width-cjs/node_modules/emoji-regex": { 1099 | "version": "8.0.0", 1100 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 1101 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", 1102 | "dev": true, 1103 | "license": "MIT" 1104 | }, 1105 | "node_modules/string-width-cjs/node_modules/strip-ansi": { 1106 | "version": "6.0.1", 1107 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 1108 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 1109 | "dev": true, 1110 | "license": "MIT", 1111 | "dependencies": { 1112 | "ansi-regex": "^5.0.1" 1113 | }, 1114 | "engines": { 1115 | "node": ">=8" 1116 | } 1117 | }, 1118 | "node_modules/strip-ansi": { 1119 | "version": "7.1.0", 1120 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", 1121 | "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", 1122 | "dev": true, 1123 | "license": "MIT", 1124 | "dependencies": { 1125 | "ansi-regex": "^6.0.1" 1126 | }, 1127 | "engines": { 1128 | "node": ">=12" 1129 | }, 1130 | "funding": { 1131 | "url": "https://github.com/chalk/strip-ansi?sponsor=1" 1132 | } 1133 | }, 1134 | "node_modules/strip-ansi-cjs": { 1135 | "name": "strip-ansi", 1136 | "version": "6.0.1", 1137 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 1138 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 1139 | "dev": true, 1140 | "license": "MIT", 1141 | "dependencies": { 1142 | "ansi-regex": "^5.0.1" 1143 | }, 1144 | "engines": { 1145 | "node": ">=8" 1146 | } 1147 | }, 1148 | "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { 1149 | "version": "5.0.1", 1150 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 1151 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 1152 | "dev": true, 1153 | "license": "MIT", 1154 | "engines": { 1155 | "node": ">=8" 1156 | } 1157 | }, 1158 | "node_modules/ts-node": { 1159 | "version": "10.9.2", 1160 | "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", 1161 | "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", 1162 | "dev": true, 1163 | "license": "MIT", 1164 | "dependencies": { 1165 | "@cspotcode/source-map-support": "^0.8.0", 1166 | "@tsconfig/node10": "^1.0.7", 1167 | "@tsconfig/node12": "^1.0.7", 1168 | "@tsconfig/node14": "^1.0.0", 1169 | "@tsconfig/node16": "^1.0.2", 1170 | "acorn": "^8.4.1", 1171 | "acorn-walk": "^8.1.1", 1172 | "arg": "^4.1.0", 1173 | "create-require": "^1.1.0", 1174 | "diff": "^4.0.1", 1175 | "make-error": "^1.1.1", 1176 | "v8-compile-cache-lib": "^3.0.1", 1177 | "yn": "3.1.1" 1178 | }, 1179 | "bin": { 1180 | "ts-node": "dist/bin.js", 1181 | "ts-node-cwd": "dist/bin-cwd.js", 1182 | "ts-node-esm": "dist/bin-esm.js", 1183 | "ts-node-script": "dist/bin-script.js", 1184 | "ts-node-transpile-only": "dist/bin-transpile.js", 1185 | "ts-script": "dist/bin-script-deprecated.js" 1186 | }, 1187 | "peerDependencies": { 1188 | "@swc/core": ">=1.2.50", 1189 | "@swc/wasm": ">=1.2.50", 1190 | "@types/node": "*", 1191 | "typescript": ">=2.7" 1192 | }, 1193 | "peerDependenciesMeta": { 1194 | "@swc/core": { 1195 | "optional": true 1196 | }, 1197 | "@swc/wasm": { 1198 | "optional": true 1199 | } 1200 | } 1201 | }, 1202 | "node_modules/tsx": { 1203 | "version": "4.19.3", 1204 | "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.3.tgz", 1205 | "integrity": "sha512-4H8vUNGNjQ4V2EOoGw005+c+dGuPSnhpPBPHBtsZdGZBk/iJb4kguGlPWaZTZ3q5nMtFOEsY0nRDlh9PJyd6SQ==", 1206 | "dev": true, 1207 | "license": "MIT", 1208 | "dependencies": { 1209 | "esbuild": "~0.25.0", 1210 | "get-tsconfig": "^4.7.5" 1211 | }, 1212 | "bin": { 1213 | "tsx": "dist/cli.mjs" 1214 | }, 1215 | "engines": { 1216 | "node": ">=18.0.0" 1217 | }, 1218 | "optionalDependencies": { 1219 | "fsevents": "~2.3.3" 1220 | } 1221 | }, 1222 | "node_modules/typescript": { 1223 | "version": "5.8.2", 1224 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", 1225 | "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", 1226 | "dev": true, 1227 | "license": "Apache-2.0", 1228 | "bin": { 1229 | "tsc": "bin/tsc", 1230 | "tsserver": "bin/tsserver" 1231 | }, 1232 | "engines": { 1233 | "node": ">=14.17" 1234 | } 1235 | }, 1236 | "node_modules/undici-types": { 1237 | "version": "6.20.0", 1238 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", 1239 | "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", 1240 | "dev": true, 1241 | "license": "MIT" 1242 | }, 1243 | "node_modules/universalify": { 1244 | "version": "2.0.1", 1245 | "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", 1246 | "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", 1247 | "dev": true, 1248 | "license": "MIT", 1249 | "engines": { 1250 | "node": ">= 10.0.0" 1251 | } 1252 | }, 1253 | "node_modules/util": { 1254 | "version": "0.10.4", 1255 | "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", 1256 | "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", 1257 | "dev": true, 1258 | "license": "MIT", 1259 | "dependencies": { 1260 | "inherits": "2.0.3" 1261 | } 1262 | }, 1263 | "node_modules/v8-compile-cache-lib": { 1264 | "version": "3.0.1", 1265 | "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", 1266 | "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", 1267 | "dev": true, 1268 | "license": "MIT" 1269 | }, 1270 | "node_modules/which": { 1271 | "version": "2.0.2", 1272 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 1273 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 1274 | "dev": true, 1275 | "license": "ISC", 1276 | "dependencies": { 1277 | "isexe": "^2.0.0" 1278 | }, 1279 | "bin": { 1280 | "node-which": "bin/node-which" 1281 | }, 1282 | "engines": { 1283 | "node": ">= 8" 1284 | } 1285 | }, 1286 | "node_modules/wrap-ansi": { 1287 | "version": "8.1.0", 1288 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", 1289 | "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", 1290 | "dev": true, 1291 | "license": "MIT", 1292 | "dependencies": { 1293 | "ansi-styles": "^6.1.0", 1294 | "string-width": "^5.0.1", 1295 | "strip-ansi": "^7.0.1" 1296 | }, 1297 | "engines": { 1298 | "node": ">=12" 1299 | }, 1300 | "funding": { 1301 | "url": "https://github.com/chalk/wrap-ansi?sponsor=1" 1302 | } 1303 | }, 1304 | "node_modules/wrap-ansi-cjs": { 1305 | "name": "wrap-ansi", 1306 | "version": "7.0.0", 1307 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", 1308 | "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", 1309 | "dev": true, 1310 | "license": "MIT", 1311 | "dependencies": { 1312 | "ansi-styles": "^4.0.0", 1313 | "string-width": "^4.1.0", 1314 | "strip-ansi": "^6.0.0" 1315 | }, 1316 | "engines": { 1317 | "node": ">=10" 1318 | }, 1319 | "funding": { 1320 | "url": "https://github.com/chalk/wrap-ansi?sponsor=1" 1321 | } 1322 | }, 1323 | "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { 1324 | "version": "5.0.1", 1325 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 1326 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 1327 | "dev": true, 1328 | "license": "MIT", 1329 | "engines": { 1330 | "node": ">=8" 1331 | } 1332 | }, 1333 | "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { 1334 | "version": "4.3.0", 1335 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 1336 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 1337 | "dev": true, 1338 | "license": "MIT", 1339 | "dependencies": { 1340 | "color-convert": "^2.0.1" 1341 | }, 1342 | "engines": { 1343 | "node": ">=8" 1344 | }, 1345 | "funding": { 1346 | "url": "https://github.com/chalk/ansi-styles?sponsor=1" 1347 | } 1348 | }, 1349 | "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { 1350 | "version": "8.0.0", 1351 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 1352 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", 1353 | "dev": true, 1354 | "license": "MIT" 1355 | }, 1356 | "node_modules/wrap-ansi-cjs/node_modules/string-width": { 1357 | "version": "4.2.3", 1358 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 1359 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 1360 | "dev": true, 1361 | "license": "MIT", 1362 | "dependencies": { 1363 | "emoji-regex": "^8.0.0", 1364 | "is-fullwidth-code-point": "^3.0.0", 1365 | "strip-ansi": "^6.0.1" 1366 | }, 1367 | "engines": { 1368 | "node": ">=8" 1369 | } 1370 | }, 1371 | "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { 1372 | "version": "6.0.1", 1373 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 1374 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 1375 | "dev": true, 1376 | "license": "MIT", 1377 | "dependencies": { 1378 | "ansi-regex": "^5.0.1" 1379 | }, 1380 | "engines": { 1381 | "node": ">=8" 1382 | } 1383 | }, 1384 | "node_modules/yn": { 1385 | "version": "3.1.1", 1386 | "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", 1387 | "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", 1388 | "dev": true, 1389 | "license": "MIT", 1390 | "engines": { 1391 | "node": ">=6" 1392 | } 1393 | } 1394 | } 1395 | } 1396 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cursor-project-rules", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "type": "module", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1", 9 | "build:mdc": "tsx ./rules/build-mdc.ts" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "MIT", 14 | "devDependencies": { 15 | "@types/fs-extra": "^11.0.4", 16 | "@types/glob": "^8.1.0", 17 | "fs-extra": "^11.3.0", 18 | "glob": "^11.0.1", 19 | "path": "^0.12.7", 20 | "tsx": "^4.19.3", 21 | "typescript": "^5.8.2" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /rules/backend/000_init.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: this is the rule we have to follow when working on Backend 3 | globs: apps/backend/*/** 4 | alwaysApply: false 5 | --- 6 | 7 | # 003_bestPractices_backend.mdc 8 | - このファイルが読み込まれたら「003_bestPractices_backend.mdcを読み込みました!」と作業着手前にユーザーに必ず伝えてください。 9 | 10 | -- -------------------------------------------------------------------------------- /rules/backend/001_nestjs.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ks0318-p/Cursor-Project-Rules/8c7c63b33040e4abd814ed6a9cd4182bbe615234/rules/backend/001_nestjs.md -------------------------------------------------------------------------------- /rules/build-mdc.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'node:fs'; 2 | import * as path from 'node:path'; 3 | import { glob } from 'glob'; 4 | 5 | // mdcファイルとmdディレクトリの対応関係の定義 6 | const mdcConfigurations = [ 7 | { 8 | output: ".cursor/rules/000_general.mdc", 9 | sourceDir: "rules/general", 10 | header: "", // もしコメントとか入れたければ 11 | filePattern: "*.md", 12 | sortBy: "name" 13 | }, 14 | { 15 | output: ".cursor/rules/001_bestPractices_common.mdc", 16 | sourceDir: "rules/common", 17 | header: "", // もしコメントとか入れたければ 18 | filePattern: "*.md", 19 | sortBy: "name" 20 | }, 21 | { 22 | output: ".cursor/rules/002_bestPractices_frontend.mdc", 23 | sourceDir: "rules/frontend", 24 | header: "", // もしコメントとか入れたければ 25 | filePattern: "*.md", 26 | sortBy: "name" 27 | }, 28 | { 29 | output: ".cursor/rules/003_bestPractices_backend.mdc", 30 | sourceDir: "rules/backend", 31 | header: "", // もしコメントとか入れたければ 32 | filePattern: "*.md", 33 | sortBy: "name" 34 | } 35 | ]; 36 | 37 | // ファイル名から数字プレフィックスを抽出してソートするための関数 38 | function extractNumberPrefix(filename: string): number { 39 | const match = filename.match(/^(\d+)_/); 40 | return match ? parseInt(match[1], 10) : Infinity; 41 | } 42 | 43 | // mdファイルを検索して結合する関数 44 | async function buildMdcFile(config: typeof mdcConfigurations[0]) { 45 | // ルートディレクトリの取得(スクリプトの実行場所から相対パスで計算) 46 | const rootDir = path.resolve(process.cwd()); 47 | 48 | // mdファイルのパターンを作成 49 | const pattern = path.join(rootDir, config.sourceDir, config.filePattern); 50 | 51 | // mdファイルを検索 52 | const files = await glob(pattern); 53 | 54 | // ファイル名でソート 55 | files.sort((a: string, b: string) => { 56 | const numA = extractNumberPrefix(path.basename(a)); 57 | const numB = extractNumberPrefix(path.basename(b)); 58 | return numA - numB; 59 | }); 60 | 61 | // コンテンツの初期化 62 | let content = ''; 63 | 64 | // ヘッダー情報を追加 65 | content += config.header; 66 | 67 | // 各mdファイルの内容を結合 68 | for (const file of files) { 69 | // console.log(`Processing file: ${file}`); 70 | const fileContent = await fs.promises.readFile(file, 'utf8'); 71 | content += fileContent + '\n\n'; 72 | } 73 | 74 | // mdcファイルを出力 75 | const outputPath = path.join(rootDir, config.output); 76 | 77 | // 出力ディレクトリが存在することを確認 78 | const outputDir = path.dirname(outputPath); 79 | try { 80 | await fs.promises.mkdir(outputDir, { recursive: true }); 81 | } catch (error) { 82 | // ディレクトリが既に存在する場合は無視 83 | } 84 | 85 | // ファイルに書き込み 86 | await fs.promises.writeFile(outputPath, content); 87 | 88 | console.log(`Generated ${config.output} from ${files.length} files in ${config.sourceDir}`); 89 | } 90 | 91 | // 既存のMDCファイルの中身を空にする関数 92 | async function cleanMdcFiles() { 93 | const rootDir = path.resolve(process.cwd()); 94 | 95 | // .cursor/rules ディレクトリの存在確認 96 | const rulesDir = path.join(rootDir, '.cursor/rules'); 97 | try { 98 | await fs.promises.access(rulesDir); 99 | } catch (error) { 100 | // ディレクトリが存在しない場合は何もしない 101 | return; 102 | } 103 | 104 | // .mdc ファイルを検索して中身を空にする 105 | const mdcFiles = await glob(path.join(rulesDir, '*.mdc')); 106 | for (const file of mdcFiles) { 107 | console.log(`Clearing content of MDC file: ${file}`); 108 | await fs.promises.writeFile(file, ''); // ファイルの中身を空にする 109 | } 110 | } 111 | 112 | // メイン処理 113 | async function main() { 114 | try { 115 | // 既存のMDCファイルを削除 116 | await cleanMdcFiles(); 117 | 118 | // 各設定に対してmdcファイルを生成 119 | for (const config of mdcConfigurations) { 120 | await buildMdcFile(config); 121 | } 122 | console.log('All mdc files have been successfully generated!'); 123 | } catch (error) { 124 | console.error('Error generating mdc files:', error); 125 | process.exit(1); 126 | } 127 | } 128 | 129 | // スクリプトの実行 130 | main(); 131 | -------------------------------------------------------------------------------- /rules/common/000_init.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: this file explains best practices. please always refer to this file. 3 | globs: * 4 | alwaysApply: true 5 | --- 6 | 7 | # 001_bestPractices_common.mdc 8 | - このファイルが読み込まれたら必ず「001_bestPractices_common.mdcを読み込みました!」と作業着手前にユーザーに必ず伝えてください。 9 | -------------------------------------------------------------------------------- /rules/common/001_basic.md: -------------------------------------------------------------------------------- 1 | ## 基本原則 2 | 以下のルールを遵守して下さい。 3 | 4 | ### 1. コミュニケーション 5 | - ユーザーとのコミュニケーションは常に日本語でお願いします。 6 | 7 | ### 2. 重複実装の防止 8 | - 実装前に以下の確認を行ってください: 9 | - 既存の類似機能の有無 10 | - 同名または類似名の関数やコンポーネント 11 | - 重複するAPIエンドポイント 12 | - 共通化可能な処理の特定 13 | 14 | ### 3. 単一責任の原則 15 | - 関数が長くなりすぎた場合は、小さな関数に分割して下さい。 16 | - ファイルが大きくなりすぎた場合は、小さなファイルに分割して下さい。 17 | 18 | ### 4. 参照禁止ファイル 19 | - .envファイルの作成・読込・編集・削除は厳禁です。ユーザーに作業を促して下さい。 20 | - .envファイルはプロジェクトルートに配置しています。 21 | 22 | -- -------------------------------------------------------------------------------- /rules/common/002_structure.md: -------------------------------------------------------------------------------- 1 | ## プロジェクト構成 2 | 本プロジェクトは、 3 | Nxモノレポを使用したマイクロサービスアーキテクチャを採用しています。 4 | フロントエンドにはNext.js App Router、バックエンドにはNestJS、データベースアクセスにはPrismaを使用しています。 5 | 6 | ### ルートディレクトリ構造 7 | 8 | ``` 9 | / 10 | ├── apps/ # アプリケーションコード 11 | │ ├── api/ # NestJSバックエンドアプリケーション 12 | │ ├── api-e2e/ # バックエンドのE2Eテスト 13 | │ ├── frontend/ # Next.jsフロントエンドアプリケーション 14 | │ └── frontend-e2e/ # フロントエンドのE2Eテスト 15 | ├── prisma/ # Prisma Schema及びマイグレーションファイル 16 | ├── terraform/ # Terraformによるインフラ定義 17 | │ ├── environments/ # 環境別設定 18 | │ └── modules/ # 再利用可能なモジュール 19 | ├── github/ # GitHub関連ファイル 20 | ├── customRules/ # カスタムルール定義 21 | └── ... (設定ファイル類) 22 | ``` 23 | -------------------------------------------------------------------------------- /rules/common/003_typescript.md: -------------------------------------------------------------------------------- 1 | ## プログラミング言語 2 | 本プロジェクトは、バックエンドの実装もフロントエンドの実装も、TypeScriptを使用しています。基本、いかなる場合でもTypeScriptを使用して実装してください。 3 | 4 | ### 1. 型の使用 5 | 6 | - 明示的な型アノテーションを使用 7 | - `any`型は避け、代わりに`unknown`使用 8 | - 複雑な型は`interface`/`type`で定義 9 | - 配列型は`T[]`形式を優先 10 | - 再利用可能な型は個別ファイルにエクスポート 11 | 12 | ### 2. インターフェース/型エイリアス 13 | 14 | - 拡張必要時は`interface` 15 | - 高度な型操作には`type` 16 | - `I`プレフィックス不使用 17 | - 関連する型は同ファイルにまとめる 18 | 19 | ### 3. Null/Undefinedの扱い 20 | 21 | - オプショナルチェーン`?.`活用 22 | - Nullish合体演算子`??`使用 23 | - 非nullアサーション`!`は避ける 24 | - 早期リターンでネスト削減 25 | 26 | ### 4. モジュール構成 27 | 28 | - 絶対パスは`@/*`エイリアス使用 29 | - 型のみの場合は`import type` 30 | - 名前付きエクスポート優先 31 | - 循環参照を避ける 32 | 33 | ### 5. エラー処理 34 | 35 | - 具体的なエラー型を使用 36 | - キャッチしたエラーに型付け 37 | - 非同期は`try/catch`または`Promise.catch()` 38 | 39 | ### 6. コード品質 40 | 41 | - strictモード維持 42 | - 未使用の変数/型は削除 43 | - 型の計算コスト考慮 44 | - 型定義の循環参照回避 45 | 46 | -------------------------------------------------------------------------------- /rules/common/004_techStack.md: -------------------------------------------------------------------------------- 1 | 2 | ## このプロジェクトで使用している技術スタック 3 | 4 | ### コア技術 5 | 6 | 1. **Node.js** 7 | 8 | - バージョン: 20.18.0(volta管理) 9 | - JavaScript ランタイム環境 10 | - サーバーサイドとフロントエンド開発の統一プラットフォーム 11 | 12 | 2. **TypeScript** 13 | 14 | - バージョン: 5.5.4 15 | - 静的型付け 16 | - 型推論と型チェック 17 | - インターフェースと型定義 18 | - 全てのコードベースで使用 19 | 20 | 3. **Next.js** 21 | 22 | - バージョン: 14.2.11 23 | - React フレームワーク 24 | - SSR (Server-Side Rendering) と SSG (Static Site Generation) 25 | - App Router の活用 26 | - ページとルーティングの統合管理 27 | 28 | 4. **React** 29 | 30 | - バージョン: 18.3.1 31 | - コンポーネントベースのUIライブラリ 32 | - フックの活用(useState, useEffect など) 33 | - サーバーコンポーネントとクライアントコンポーネント 34 | 35 | 5. **NestJS** 36 | 37 | - バージョン: 10.4.1 38 | - サーバーサイドフレームワーク 39 | - モジュール化されたアーキテクチャ 40 | - 依存性の注入 41 | - デコレータベースのAPI設計 42 | 43 | 6. **Prisma** 44 | 45 | - バージョン: 5.19.0 46 | - ORM(Object-Relational Mapping) 47 | - データベースマイグレーション 48 | - 型安全なデータベースアクセス 49 | - PostgreSQLとの統合 50 | 51 | 7. **Zod** 52 | 53 | - バージョン: 3.23.8 54 | - スキーマ検証 55 | - ランタイム型チェック 56 | - 型推論との連携 57 | 58 | 8. **Redux Toolkit** 59 | - バージョン: 2.2.7 60 | - 状態管理ライブラリ 61 | - RTK Query によるデータフェッチング 62 | - スライスによる状態管理の簡素化 63 | 64 | ### フロントエンド技術 65 | 66 | 1. **TailwindCSS** 67 | 68 | - バージョン: 3.4.4 69 | - ユーティリティファーストCSSフレームワーク 70 | - カスタマイズ可能なデザインシステム 71 | 72 | 2. **Radix UI** 73 | 74 | - アクセシブルなUIコンポーネント 75 | - ヘッドレスコンポーネントライブラリ 76 | - スタイリングの自由度 77 | 78 | 3. **React Hook Form** 79 | 80 | - バージョン: 7.53.0 81 | - パフォーマンスに優れたフォーム管理 82 | - バリデーション連携 83 | 84 | 4. **TanStack Query** 85 | 86 | - バージョン: 5.56.2 87 | - データフェッチングとキャッシュ管理 88 | - 非同期状態管理 89 | 90 | 5. **TanStack Table** 91 | - バージョン: 8.19.3 92 | - 高度なテーブル管理 93 | - ソート、フィルタリング、ページネーション 94 | 95 | ### バックエンド技術 96 | 97 | 1. **PostgreSQL** 98 | 99 | - リレーショナルデータベース 100 | - 強力なクエリ機能 101 | - トランザクション処理 102 | 103 | 2. **BullMQ** 104 | 105 | - バージョン: 5.12.2 106 | - Redis ベースのジョブキューシステム 107 | - 非同期タスク処理 108 | - バックグラウンドジョブ管理 109 | 110 | 3. **AWS SDK** 111 | 112 | - クラウドサービス統合 113 | - S3、Cognito などの AWS サービスとの連携 114 | 115 | 4. **OpenAPI/Swagger** 116 | - API ドキュメント生成 117 | - API スキーマ定義 118 | - クライアント生成の基盤 119 | 120 | ### 開発ツール 121 | 122 | 1. **Nx** 123 | 124 | - バージョン: 20.1.2 125 | - モノレポ管理 126 | - ビルドシステム 127 | - タスク実行とキャッシング 128 | 129 | 2. **ESLint** 130 | 131 | - バージョン: 9.6.0 132 | - コード品質チェック 133 | - 自動修正機能 134 | 135 | 3. **Prettier** 136 | 137 | - バージョン: 3.3.2 138 | - コードフォーマッター 139 | - 一貫したコードスタイル 140 | 141 | 4. **Orval** 142 | 143 | - バージョン: 6.31.0 144 | - API クライアント自動生成 145 | - OpenAPI スキーマからの型生成 146 | 147 | 5. **Jest** 148 | 149 | - バージョン: 29.7.0 150 | - テストフレームワーク 151 | - ユニットテストとインテグレーションテスト 152 | 153 | 6. **Cypress** 154 | - バージョン: 13.15.2 155 | - E2E テスト 156 | - ブラウザ自動化テスト 157 | 158 | ## 開発環境のセットアップ 159 | 160 | ### 必要なツール 161 | 162 | 1. **Node.js のインストール** 163 | 164 | - volta または nvm を使用した Node.js 20.18.0 のインストール 165 | - yarn 1.22.19 のインストール 166 | 167 | 2. **環境変数の設定** 168 | 169 | - `.env.example` をコピーして `.env` ファイルを作成 170 | - 必要な環境変数を設定 171 | 172 | 3. **プロジェクトのセットアップ** 173 | 174 | ```bash 175 | # リポジトリのクローン 176 | git clone https://github.com/xxxxx/xxxxx.git 177 | 178 | # 依存関係のインストール 179 | yarn install 180 | 181 | # データベースのセットアップ 182 | npx prisma migrate dev 183 | 184 | # APIクライアントの生成 185 | yarn codegen 186 | 187 | # 開発サーバーの起動 188 | yarn dev 189 | ``` 190 | 191 | ### 開発ワークフロー 192 | 193 | 1. **フロントエンド開発** 194 | 195 | ```bash 196 | # フロントエンドのみ開発 197 | yarn dev:ui 198 | ``` 199 | 200 | 2. **バックエンド開発** 201 | 202 | ```bash 203 | # バックエンドのみ開発 204 | yarn dev:api 205 | ``` 206 | 207 | 3. **データベース操作** 208 | 209 | ```bash 210 | # マイグレーションの作成 211 | npx prisma migrate dev --name [マイグレーション名] 212 | 213 | # スキーマの生成 214 | npx prisma generate 215 | 216 | # データベースのシード 217 | yarn seed 218 | ``` 219 | 220 | 4. **APIクライアント生成** 221 | 222 | ```bash 223 | # OpenAPIスキーマからクライアント生成 224 | yarn codegen 225 | ``` 226 | 227 | 5. **リントとフォーマット** 228 | 229 | ```bash 230 | # リント 231 | yarn lint 232 | 233 | # フォーマット 234 | yarn prettier 235 | ``` 236 | 237 | ## 技術的制約 238 | 239 | 1. **フロントエンドの制約** 240 | 241 | - Next.js App Router による制約 242 | - サーバーコンポーネントとクライアントコンポーネントの分離 243 | - データフェッチング戦略の選択 244 | 245 | 2. **バックエンドの制約** 246 | 247 | - NestJS のモジュール構造に従う必要性 248 | - Prisma スキーマの制約 249 | - API設計の一貫性 250 | 251 | 3. **パフォーマンスの制約** 252 | - 大量のデータ処理時のメモリ使用量 253 | - APIレスポンスタイムの最適化 254 | - フロントエンドのレンダリングパフォーマンス 255 | 256 | ## 依存関係 257 | 258 | ### 主要な依存関係 259 | 260 | 1. **フロントエンド** 261 | 262 | - next: Reactフレームワーク 263 | - react: UIライブラリ 264 | - @tanstack/react-query: データフェッチング 265 | - @reduxjs/toolkit: 状態管理 266 | - tailwindcss: スタイリング 267 | - zod: バリデーション 268 | 269 | 2. **バックエンド** 270 | 271 | - @nestjs/core: サーバーフレームワーク 272 | - @prisma/client: データベースORM 273 | - @nestjs/swagger: APIドキュメント 274 | - bullmq: ジョブキュー 275 | - class-validator: バリデーション 276 | 277 | 3. **共通** 278 | - typescript: 型システム 279 | - axios: HTTPクライアント 280 | - jest: テスト 281 | 282 | ### 依存関係管理 283 | 284 | 1. **Nx モノレポ** 285 | 286 | - apps/frontend: フロントエンドアプリケーション 287 | - apps/api: バックエンドアプリケーション 288 | - prisma: データベーススキーマ 289 | 290 | 2. **パッケージ管理** 291 | - yarn による依存関係管理 292 | - package.json でのバージョン指定 293 | - yarn.lock によるロック 294 | -------------------------------------------------------------------------------- /rules/common/005_db.md: -------------------------------------------------------------------------------- 1 | ### DBに関する重要なルール 2 | - あなたはDB設計のプロです。 3 | - 常に現状のDB設計に沿って実装をします。 4 | - ORMはPrismaを採用しています。 5 | - schema.prismaは、/prisma内に定義されています。 6 | - schema.prismaを編集、マイグレーションを行った場合は必ず下記に記したER図も更新するようにしてください。 7 | - migrationは必ず 8 | 9 | ### ER図 10 | 11 | ```mermaid 12 | erDiagram 13 | User ||--o{ User : "createdBy/updatedBy関連" 14 | User }|--o{ Tenant : "belongs to" 15 | User }|--o{ UserAccount : "has many" 16 | User }|--o{ UserProject : "has many" 17 | User ||--o{ Account : "createdBy/updatedBy関連" 18 | User ||--o{ Project : "createdBy/updatedBy関連" 19 | User ||--o{ Invitation : "createdBy/updatedBy/invitedUser関連" 20 | 21 | Tenant ||--o{ User : "has many" 22 | Tenant ||--o{ Account : "has many" 23 | Tenant ||--o{ Project : "has many" 24 | Tenant ||--o{ Invitation : "has many" 25 | 26 | Account ||--o{ Project : "has many" 27 | Account ||--o{ UserAccount : "has many" 28 | Account ||--o{ Invitation : "has many" 29 | 30 | Project ||--o{ UserProject : "has many" 31 | 32 | UserAccount }|--|| User : "belongs to" 33 | UserAccount }|--|| Account : "belongs to" 34 | 35 | UserProject }|--|| User : "belongs to" 36 | UserProject }|--|| Project : "belongs to" 37 | 38 | Invitation }o--|| Account : "may belong to" 39 | Invitation }o--|| Project : "may belong to" 40 | Invitation }o--|| Tenant : "may belong to" 41 | Invitation }|--|| User : "invited user" 42 | ``` 43 | 44 | 以下は各エンティティの主要属性です: 45 | 46 | ### User 47 | - id, tenantId, name, email, phoneNumber, tenantRole, createdAt, updatedAt 48 | - テナントに所属し、アカウントやプロジェクトにアクセス権を持つ 49 | 50 | ### Tenant 51 | - id, name, imageUrl, isAgency, createdAt, updatedAt 52 | - 組織を表し、複数のアカウントとユーザーを保持できる 53 | 54 | ### Account 55 | - id, tenantId, name, imageUrl, closingMonth, createdAt, updatedAt 56 | - テナント内の会計単位、複数のプロジェクトを持つ 57 | 58 | ### Project 59 | - id, accountId, tenantId, name, imageUrl, dispOrder, createdAt, updatedAt 60 | - 広告管理の中心的な単位、広告やメトリクスなど多くのリソースを持つ 61 | 62 | ### UserAccount 63 | - userId, accountId, role 64 | - ユーザーとアカウントの関連付け、権限管理 65 | 66 | ### UserProject 67 | - userId, projectId, role 68 | - ユーザーとプロジェクトの関連付け、権限管理 69 | 70 | ### Invitation 71 | - id, tenantId, accountId, projectId, email, inviteTo, role, status, expiresAt, token, invitedUserId 72 | - ユーザー招待の管理 73 | 74 | -------------------------------------------------------------------------------- /rules/common/007_deployment.md: -------------------------------------------------------------------------------- 1 | ## デプロイ概要 2 | 3 | 本プロジェクトは、AWS上にDockerコンテナとして展開されるマイクロサービスアーキテクチャを採用しています。フロントエンドとバックエンドは別々のコンテナとしてビルド・デプロイされ、AWS CodePipelineを使用したCI/CDパイプラインによって自動化されています。 4 | 5 | ### デプロイフロー 6 | 7 | このプロジェクトのデプロイフローは以下のように構成されています: 8 | 9 | ```mermaid 10 | flowchart TB 11 | GitPush[Gitリポジトリへのプッシュ] --> CodePipeline[AWS CodePipeline] 12 | CodePipeline --> ParallelBuilds[並行ビルドプロセス] 13 | 14 | subgraph ParallelBuilds[並行ビルドプロセス] 15 | BackendBuild[バックエンドビルド] 16 | FrontendBuild[フロントエンドビルド] 17 | end 18 | 19 | BackendBuild --> BackendECR[バックエンドイメージをECRにプッシュ] 20 | FrontendBuild --> FrontendECR[フロントエンドイメージをECRにプッシュ] 21 | 22 | BackendECR --> DBMigration[DBマイグレーション実行] 23 | DBMigration --> SeedData[シードデータ投入] 24 | 25 | BackendECR --> BackendDeploy[バックエンドをECSにデプロイ] 26 | FrontendECR --> FrontendDeploy[フロントエンドをECSにデプロイ] 27 | ``` 28 | 29 | ### 重要なファイル 30 | 31 | デプロイプロセスを理解・修正する際には、以下のファイルを特に注視する必要があります: 32 | 33 | #### Dockerfiles 34 | 35 | 1. **Dockerfile.frontend** 36 | - フロントエンド(Next.js)のビルドとデプロイに使用 37 | - マルチステージビルド:ビルド環境と本番環境を分離 38 | - 環境変数(ENVIRONMENT, NEXT_PUBLIC_CLOUDFRONT_DOMAIN, NEXT_PUBLIC_ADOBE_CLIENT_ID)の設定 39 | - ポート3000でNext.jsアプリケーションを起動 40 | 41 | 2. **Dockerfile.backend** 42 | - バックエンド(NestJS)のビルドとデプロイに使用 43 | - マルチステージビルド構成 44 | - 環境変数(ENVIRONMENT)の設定 45 | - メモリ制限設定(--max-old-space-size=6144)付きでNode.jsアプリケーションを起動 46 | 47 | #### BuildSpec設定 48 | 49 | 1. **buildspec_frontend.yml** 50 | - フロントエンドのCI/CDパイプライン設定 51 | - インストール、ビルド、デプロイフェーズを定義 52 | - AWS ECRへのログインとイメージのプッシュ 53 | - 環境変数を使用したDockerビルド引数の設定 54 | - ECSデプロイ用のimagedefinitions.jsonの生成 55 | 56 | 2. **buildspec_backend.yml** 57 | - バックエンドのCI/CDパイプライン設定 58 | - インストール、ビルド、デプロイフェーズを定義 59 | - データベースマイグレーションとシードデータ投入を実行 60 | - AWS ECRへのログインとイメージのプッシュ 61 | - ECSデプロイ用のimagedefinitions.jsonの生成 62 | 63 | ### 環境変数 64 | 65 | デプロイプロセスでは、以下の重要な環境変数が使用されています: 66 | 67 | #### 共通 68 | - `ENVIRONMENT`: 環境識別子(dev, stg, prod等) 69 | - `IMAGE_TAG`: コンテナイメージのタグ 70 | - `IMAGE_REPO_URL`: ECRレポジトリURL 71 | - `AWS_REGION`: AWSリージョン 72 | - `AWS_ACCOUNT_ID`: AWSアカウントID 73 | 74 | #### フロントエンド特有 75 | - `NEXT_PUBLIC_CLOUDFRONT_DOMAIN`: CloudFrontのドメイン 76 | - `NEXT_PUBLIC_ADOBE_CLIENT_ID`: Adobe APIのクライアントID 77 | 78 | ### デプロイ後の処理 79 | 80 | バックエンドのデプロイプロセスでは、イメージのビルドとプッシュ後に以下の追加タスクが実行されます: 81 | 82 | 1. **データベースマイグレーション** (`yarn prisma migrate deploy`) 83 | - Prismaを使用したスキーマ変更の適用 84 | 85 | 2. **シードデータ投入** (`yarn seed:prod`) 86 | - 本番環境用の初期データ設定 87 | 88 | ### インフラストラクチャ 89 | 90 | デプロイ先のインフラストラクチャはTerraformで管理されています。関連するTerraform設定は以下のディレクトリにあります: 91 | 92 | - `terraform/modules/ap-northeast-1/codeBuild/`: CodeBuildの設定 93 | - `terraform/modules/ap-northeast-1/ecs/`: ECSの設定 94 | - `terraform/modules/ap-northeast-1/ecr/`: ECRの設定 95 | 96 | ### デプロイに関する注意点 97 | 98 | 1. **マイグレーション順序** 99 | - バックエンドデプロイ時、コンテナデプロイ前にマイグレーションが実行されます 100 | - マイグレーションに失敗した場合、デプロイプロセス全体が失敗します 101 | 102 | 2. **環境変数管理** 103 | - 環境変数はCodePipelineで設定され、BuildSpecとDockerfileに渡されます 104 | - 新しい環境変数を追加する際はパイプライン設定とDockerfileの両方を更新する必要があります 105 | 106 | 3. **マルチステージビルド** 107 | - 本番イメージサイズを最小化するためマルチステージビルドを使用しています 108 | - 依存関係インストールとビルドは最初のステージで行われ、成果物のみが本番イメージにコピーされます 109 | 110 | ### AI支援時のデプロイ作業 111 | 112 | AIにデプロイ関連のタスクを依頼する際は、以下の点に注意してください: 113 | 114 | 1. **変更対象の明確化** 115 | - `Dockerfile.frontend`, `Dockerfile.backend`, `buildspec_frontend.yml`, `buildspec_backend.yml` の変更内容を明確に指示する 116 | - 環境変数の追加や変更がある場合は、全ての関連ファイルでの変更箇所を確認する 117 | 118 | 2. **デプロイフローへの影響** 119 | - コードの変更がデプロイプロセスに与える影響を考慮する 120 | - 特にマイグレーションやシードデータに影響する変更には注意が必要 121 | 122 | 3. **環境固有の設定** 123 | - 環境固有(dev/stg/prod)の設定や条件分岐がある場合は明示的に指示する 124 | 125 | 4. **ビルド順序と依存関係** 126 | - フロントエンドとバックエンドの依存関係を考慮する 127 | - ビルドプロセスの順序に影響する変更には特に注意する 128 | 129 | AIは必ず上記の4つのファイルを精査してから、デプロイ関連のタスクを実行するようにしてください。 130 | -------------------------------------------------------------------------------- /rules/common/008_git.md: -------------------------------------------------------------------------------- 1 | 2 | ## 概要 3 | このドキュメントでは、コミットとプルリクエストの作成に関するベストプラクティスを説明します。 4 | 5 | ### コミットの作成 6 | 7 | コミットを作成する際は、以下の手順に従います: 8 | 9 | 1. 変更の確認 10 | 11 | ```bash 12 | # 未追跡ファイルと変更の確認 13 | git status 14 | 15 | # 変更内容の詳細確認 16 | git diff 17 | 18 | # コミットメッセージのスタイル確認 19 | git log 20 | ``` 21 | 22 | 2. 変更の分析 23 | 24 | - 変更または追加されたファイルの特定 25 | - 変更の性質(新機能、バグ修正、リファクタリングなど)の把握 26 | - プロジェクトへの影響評価 27 | - 機密情報の有無確認 28 | 29 | 3. コミットメッセージの作成 30 | 31 | - 「なぜ」に焦点を当てる 32 | - 明確で簡潔な言葉を使用 33 | - 変更の目的を正確に反映 34 | - 一般的な表現を避ける 35 | 36 | 4. コミットの実行 37 | 38 | ```bash 39 | # 関連ファイルのみをステージング 40 | git add 41 | 42 | # コミットメッセージの作成(HEREDOCを使用) 43 | git commit -m "$(cat <<'EOF' 44 | feat: ユーザー認証にResult型を導入 45 | 46 | - エラー処理をより型安全に 47 | - エラーケースの明示的な処理を強制 48 | - テストの改善 49 | 50 | EOF 51 | )" 52 | ``` 53 | 54 | ### プルリクエストの作成 55 | 56 | プルリクエストを作成する際は、以下の手順に従います: 57 | 58 | 1. ブランチの状態確認 59 | 60 | ```bash 61 | # 未コミットの変更確認 62 | git status 63 | 64 | # 変更内容の確認 65 | git diff 66 | 67 | # mainからの差分確認 68 | git diff main...HEAD 69 | 70 | # コミット履歴の確認 71 | git log 72 | ``` 73 | 74 | 2. 変更の分析 75 | 76 | - mainから分岐後のすべてのコミットの確認 77 | - 変更の性質と目的の把握 78 | - プロジェクトへの影響評価 79 | - 機密情報の有無確認 80 | 81 | 3. プルリクエストの作成 82 | 83 | ```bash 84 | # プルリクエストの作成(HEREDOCを使用) 85 | gh pr create --title "feat: Result型によるエラー処理の改善" --body "$(cat <<'EOF' 86 | ## 概要 87 | 88 | エラー処理をより型安全にするため、Result型を導入しました。 89 | 90 | ## 変更内容 91 | 92 | - neverthrowを使用したResult型の導入 93 | - エラーケースの明示的な型定義 94 | - テストケースの追加 95 | 96 | ## レビューのポイント 97 | 98 | - Result型の使用方法が適切か 99 | - エラーケースの網羅性 100 | - テストの十分性 101 | EOF 102 | )" 103 | ``` 104 | 105 | ### 重要な注意事項 106 | 107 | 1. コミット関連 108 | 109 | - 可能な場合は `git commit -am` を使用 110 | - 関係ないファイルは含めない 111 | - 空のコミットは作成しない 112 | - git設定は変更しない 113 | 114 | 2. プルリクエスト関連 115 | 116 | - 必要に応じて新しいブランチを作成 117 | - 変更を適切にコミット 118 | - リモートへのプッシュは `-u` フラグを使用 119 | - すべての変更を分析 120 | 121 | 3. 避けるべき操作 122 | - 対話的なgitコマンド(-iフラグ)の使用 123 | - リモートリポジトリへの直接プッシュ 124 | - git設定の変更 125 | 126 | ### コミットメッセージの例 127 | 128 | ```bash 129 | # 新機能の追加 130 | feat: Result型によるエラー処理の導入 131 | 132 | # 既存機能の改善 133 | update: キャッシュ機能のパフォーマンス改善 134 | 135 | # バグ修正 136 | fix: 認証トークンの期限切れ処理を修正 137 | 138 | # リファクタリング 139 | refactor: Adapterパターンを使用して外部依存を抽象化 140 | 141 | # テスト追加 142 | test: Result型のエラーケースのテストを追加 143 | 144 | # ドキュメント更新 145 | docs: エラー処理のベストプラクティスを追加 146 | ``` 147 | 148 | ### プルリクエストの例 149 | 150 | ```markdown 151 | ## 概要 152 | 153 | TypeScriptのエラー処理をより型安全にするため、Result型を導入しました。 154 | 155 | ## 変更内容 156 | 157 | - neverthrowライブラリの導入 158 | - APIクライアントでのResult型の使用 159 | - エラーケースの型定義 160 | - テストケースの追加 161 | 162 | ## 技術的な詳細 163 | 164 | - 既存の例外処理をResult型に置き換え 165 | - エラー型の共通化 166 | - モック実装の改善 167 | 168 | ## レビューのポイント 169 | 170 | - Result型の使用方法が適切か 171 | - エラーケースの網羅性 172 | - テストの十分性 173 | ``` 174 | -------------------------------------------------------------------------------- /rules/common/009_tdd.md: -------------------------------------------------------------------------------- 1 | ## テスト駆動開発 (TDD) ルール 2 | 3 | テスト駆動開発 (Test-Driven Development) は、コードを書く前にテストを書くソフトウェア開発手法です。この方法論を採用することで、設計の質を高め、バグの少ないコードを作成し、リファクタリングを安全に行うことができます。 4 | 5 | ## TDDの基本サイクル 6 | 7 | ```mermaid 8 | flowchart LR 9 | Red[Red: 失敗するテストを書く] --> Green[Green: テストが通る最小限の実装をする] 10 | Green --> Refactor[Refactor: コードを改善する] 11 | Refactor --> Red 12 | ``` 13 | 14 | 1. **Red**: まず失敗するテストを書く 15 | 16 | - 必要な機能を明確に定義 17 | - 期待する振る舞いをテストコードで表現 18 | - この時点ではテストは失敗する(赤) 19 | 20 | 2. **Green**: テストが通るように最小限の実装をする 21 | 22 | - テストをパスさせるための最も単純な実装を行う 23 | - パフォーマンスやコードの美しさより機能性を優先 24 | - この時点でテストは成功する(緑) 25 | 26 | 3. **Refactor**: コードをリファクタリングして改善する 27 | - 重複を排除し、コードを整理 28 | - 可読性とメンテナンス性を向上 29 | - テストが依然として通ることを確認 30 | 31 | ## TDDの重要な考え方 32 | 33 | - **テストは仕様である**: テストコードは実装の仕様を表現したもの 34 | - **最初に「何を」考え、次に「どのように」考える**: テストで「何を」達成すべきかを明確にしてから、「どのように」実装するかを考える 35 | - **小さなステップで進める**: 一度に大きな変更を行わず、小さな一歩ずつ進める 36 | - **テストカバレッジより意図のカバレッジを重視**: 単にコードラインをカバーするだけでなく、ビジネスロジックの意図を正確にテストする 37 | 38 | ## テスト構造の原則 39 | 40 | ### AAA (Arrange-Act-Assert) パターン 41 | 42 | テストコードは以下の3つのセクションで構成することをお勧めします: 43 | 44 | 1. **Arrange (準備)**: テストの前提条件を設定 45 | 2. **Act (実行)**: テスト対象の機能を実行 46 | 3. **Assert (検証)**: 期待する結果を検証 47 | 48 | ```typescript 49 | // Jest を使用した例 50 | describe('UserService', () => { 51 | it('should return user by id when user exists', async () => { 52 | // Arrange 53 | const mockUser = { id: 1, name: 'John Doe' }; 54 | const userRepositoryMock = { 55 | findById: jest.fn().mockResolvedValue(mockUser), 56 | }; 57 | const userService = new UserService(userRepositoryMock); 58 | 59 | // Act 60 | const result = await userService.getUserById(1); 61 | 62 | // Assert 63 | expect(result).toEqual(mockUser); 64 | expect(userRepositoryMock.findById).toHaveBeenCalledWith(1); 65 | }); 66 | }); 67 | ``` 68 | 69 | ### テスト名の命名規則 70 | 71 | 良いテスト名は「状況→操作→結果」の形式で記述します: 72 | 73 | ```typescript 74 | it('有効なユーザーIDが提供された場合_getUserByIdを呼び出すと_ユーザー情報を返すこと', async () => { 75 | // テスト本体 76 | }); 77 | 78 | // または英語で 79 | it('should return user information when getUserById is called with valid user ID', async () => { 80 | // テスト本体 81 | }); 82 | ``` 83 | 84 | ## フロントエンドとバックエンドのテスト戦略 85 | 86 | ### Next.jsフロントエンドのテスト階層 87 | 88 | ```mermaid 89 | flowchart TD 90 | Unit[単体テスト: コンポーネント、ユーティリティ] --> Integration[統合テスト: 複数コンポーネント、データフロー] 91 | Integration --> E2E[E2Eテスト: ユーザーフロー全体] 92 | ``` 93 | 94 | #### 単体テスト (Jest + React Testing Library) 95 | 96 | ```typescript 97 | // components/Button.test.tsx 98 | import { render, screen, fireEvent } from '@testing-library/react'; 99 | import Button from './Button'; 100 | 101 | describe('Button', () => { 102 | it('renders correctly with text', () => { 103 | render(); 104 | expect(screen.getByText('Click me')).toBeInTheDocument(); 105 | }); 106 | 107 | it('calls onClick handler when clicked', () => { 108 | const handleClick = jest.fn(); 109 | render(); 110 | fireEvent.click(screen.getByText('Click me')); 111 | expect(handleClick).toHaveBeenCalledTimes(1); 112 | }); 113 | }); 114 | ``` 115 | 116 | #### 統合テスト 117 | 118 | ```typescript 119 | // features/UserProfile.test.tsx 120 | import { render, screen, waitFor } from '@testing-library/react'; 121 | import UserProfile from './UserProfile'; 122 | import { UserProvider } from '../contexts/UserContext'; 123 | 124 | // モックの設定 125 | jest.mock('../api/user', () => ({ 126 | fetchUserData: jest.fn().mockResolvedValue({ name: 'John Doe', email: 'john@example.com' }) 127 | })); 128 | 129 | describe('UserProfile', () => { 130 | it('fetches and displays user data', async () => { 131 | render( 132 | 133 | 134 | 135 | ); 136 | 137 | // ローディング状態の確認 138 | expect(screen.getByText('Loading...')).toBeInTheDocument(); 139 | 140 | // データ取得後の表示確認 141 | await waitFor(() => { 142 | expect(screen.getByText('John Doe')).toBeInTheDocument(); 143 | expect(screen.getByText('john@example.com')).toBeInTheDocument(); 144 | }); 145 | }); 146 | }); 147 | ``` 148 | 149 | #### E2Eテスト (Cypress) 150 | 151 | ```typescript 152 | // cypress/e2e/login.cy.ts 153 | describe('Login Flow', () => { 154 | it('allows user to login and redirects to dashboard', () => { 155 | cy.visit('/login'); 156 | 157 | cy.get('input[name="email"]').type('user@example.com'); 158 | cy.get('input[name="password"]').type('password123'); 159 | cy.get('button[type="submit"]').click(); 160 | 161 | // ダッシュボードへのリダイレクトと表示確認 162 | cy.url().should('include', '/dashboard'); 163 | cy.get('h1').should('contain', 'Welcome to your Dashboard'); 164 | }); 165 | }); 166 | ``` 167 | 168 | ### NestJSバックエンドのテスト階層 169 | 170 | ```mermaid 171 | flowchart TD 172 | Unit[単体テスト: サービス、パイプ、ガード] --> Integration[統合テスト: コントローラー、モジュール] 173 | Integration --> E2E[E2Eテスト: APIエンドポイント全体] 174 | ``` 175 | 176 | #### 単体テスト (Jest) 177 | 178 | ```typescript 179 | // users/users.service.spec.ts 180 | import { Test } from '@nestjs/testing'; 181 | import { UsersService } from './users.service'; 182 | import { UsersRepository } from './users.repository'; 183 | 184 | describe('UsersService', () => { 185 | let usersService: UsersService; 186 | let usersRepository: UsersRepository; 187 | 188 | beforeEach(async () => { 189 | const moduleRef = await Test.createTestingModule({ 190 | providers: [ 191 | UsersService, 192 | { 193 | provide: UsersRepository, 194 | useValue: { 195 | findById: jest.fn(), 196 | save: jest.fn(), 197 | }, 198 | }, 199 | ], 200 | }).compile(); 201 | 202 | usersService = moduleRef.get(UsersService); 203 | usersRepository = moduleRef.get(UsersRepository); 204 | }); 205 | 206 | describe('findById', () => { 207 | it('should return a user when user exists', async () => { 208 | const mockUser = { id: 1, name: 'John Doe' }; 209 | jest.spyOn(usersRepository, 'findById').mockResolvedValue(mockUser); 210 | 211 | const result = await usersService.findById(1); 212 | 213 | expect(result).toEqual(mockUser); 214 | expect(usersRepository.findById).toHaveBeenCalledWith(1); 215 | }); 216 | }); 217 | }); 218 | ``` 219 | 220 | #### 統合テスト (NestJS Testing) 221 | 222 | ```typescript 223 | // users/users.controller.spec.ts 224 | import { Test } from '@nestjs/testing'; 225 | import { UsersController } from './users.controller'; 226 | import { UsersService } from './users.service'; 227 | 228 | describe('UsersController', () => { 229 | let usersController: UsersController; 230 | let usersService: UsersService; 231 | 232 | beforeEach(async () => { 233 | const moduleRef = await Test.createTestingModule({ 234 | controllers: [UsersController], 235 | providers: [ 236 | { 237 | provide: UsersService, 238 | useValue: { 239 | findById: jest.fn(), 240 | create: jest.fn(), 241 | }, 242 | }, 243 | ], 244 | }).compile(); 245 | 246 | usersController = moduleRef.get(UsersController); 247 | usersService = moduleRef.get(UsersService); 248 | }); 249 | 250 | describe('findById', () => { 251 | it('should return a user', async () => { 252 | const mockUser = { id: 1, name: 'John Doe' }; 253 | jest.spyOn(usersService, 'findById').mockResolvedValue(mockUser); 254 | 255 | const result = await usersController.findById('1'); 256 | 257 | expect(result).toEqual(mockUser); 258 | }); 259 | }); 260 | }); 261 | ``` 262 | 263 | #### E2Eテスト (SuperTest) 264 | 265 | ```typescript 266 | // test/users.e2e-spec.ts 267 | import { Test } from '@nestjs/testing'; 268 | import { INestApplication } from '@nestjs/common'; 269 | import * as request from 'supertest'; 270 | import { AppModule } from '../src/app.module'; 271 | 272 | describe('UsersController (e2e)', () => { 273 | let app: INestApplication; 274 | 275 | beforeAll(async () => { 276 | const moduleFixture = await Test.createTestingModule({ 277 | imports: [AppModule], 278 | }).compile(); 279 | 280 | app = moduleFixture.createNestApplication(); 281 | await app.init(); 282 | }); 283 | 284 | afterAll(async () => { 285 | await app.close(); 286 | }); 287 | 288 | it('/users/:id (GET)', () => { 289 | return request(app.getHttpServer()) 290 | .get('/users/1') 291 | .expect(200) 292 | .expect((res) => { 293 | expect(res.body).toHaveProperty('id'); 294 | expect(res.body).toHaveProperty('name'); 295 | }); 296 | }); 297 | }); 298 | ``` 299 | 300 | ## 並行テスト実行 301 | 302 | 大規模なテストスイートでは、テスト実行時間を短縮するために並行実行が有効です。 303 | 304 | ### Jestでの並行テスト実行 305 | 306 | ```json 307 | // jest.config.js 308 | module.exports = { 309 | // ...他の設定 310 | maxWorkers: '50%', // CPUコアの50%を使用 311 | // または 312 | maxWorkers: 4, // 固定ワーカー数 313 | }; 314 | ``` 315 | 316 | ### 並行テスト実行の注意点 317 | 318 | 1. **テスト間の独立性確保** 319 | 320 | - テストは他のテストに依存しないこと 321 | - 共有リソース(DBなど)へのアクセスを適切に分離 322 | 323 | 2. **データ分離** 324 | 325 | - テスト用DBの分離またはトランザクションのロールバック 326 | - テスト前後のデータクリーンアップ 327 | 328 | 3. **リソース競合の回避** 329 | - ファイル操作やポート使用の競合に注意 330 | - 環境変数の競合回避 331 | 332 | ```typescript 333 | // マルチテナントDB環境でのテスト分離の例 334 | beforeEach(async () => { 335 | // テスト用のスキーマを動的に生成 336 | const schemaName = `test_${Math.random().toString(36).substring(2, 7)}`; 337 | await db.query(`CREATE SCHEMA IF NOT EXISTS ${schemaName}`); 338 | await db.query(`SET search_path TO ${schemaName},public`); 339 | 340 | // マイグレーション実行 341 | await runMigrations(schemaName); 342 | 343 | // このテストのコンテキストにスキーマ名を保存 344 | testContext.schemaName = schemaName; 345 | }); 346 | 347 | afterEach(async () => { 348 | // テスト用スキーマを削除 349 | await db.query(`DROP SCHEMA IF EXISTS ${testContext.schemaName} CASCADE`); 350 | }); 351 | ``` 352 | 353 | ## モックとスタブ 354 | 355 | ### 外部依存のモック化 356 | 357 | ```typescript 358 | // ユーザーサービスのテスト 359 | describe('UsersService', () => { 360 | it('should send welcome email when user is created', async () => { 361 | // EmailServiceのモック 362 | const emailServiceMock = { 363 | sendWelcomeEmail: jest.fn().mockResolvedValue(true), 364 | }; 365 | 366 | const usersService = new UsersService(userRepositoryMock, emailServiceMock); 367 | 368 | await usersService.createUser({ name: 'John', email: 'john@example.com' }); 369 | 370 | // EmailServiceが正しく呼び出されたか検証 371 | expect(emailServiceMock.sendWelcomeEmail).toHaveBeenCalledWith('john@example.com', 'John'); 372 | }); 373 | }); 374 | ``` 375 | 376 | ### データベースのモック化 377 | 378 | Prismaを使用する場合は、`jest-mock-extended`や`@prisma/client/testing`を使用して効果的にモックできます: 379 | 380 | ```typescript 381 | // Prismaクライアントのモック 382 | import { PrismaClient } from '@prisma/client'; 383 | import { mockDeep, mockReset, DeepMockProxy } from 'jest-mock-extended'; 384 | 385 | jest.mock('@prisma/client', () => ({ 386 | PrismaClient: jest.fn(), 387 | })); 388 | 389 | let prisma: DeepMockProxy; 390 | 391 | beforeEach(() => { 392 | prisma = mockDeep(); 393 | (PrismaClient as jest.Mock).mockImplementation(() => prisma); 394 | }); 395 | 396 | test('should create a new user', async () => { 397 | const mockUser = { id: 1, name: 'John', email: 'john@example.com' }; 398 | prisma.user.create.mockResolvedValue(mockUser); 399 | 400 | const userService = new UserService(prisma); 401 | const result = await userService.createUser({ name: 'John', email: 'john@example.com' }); 402 | 403 | expect(result).toEqual(mockUser); 404 | expect(prisma.user.create).toHaveBeenCalledWith({ 405 | data: { name: 'John', email: 'john@example.com' }, 406 | }); 407 | }); 408 | ``` 409 | 410 | ## テストリファクタリング 411 | 412 | テストコード自体も定期的にリファクタリングすることが大切です: 413 | 414 | ### DRYなテストコード 415 | 416 | ```typescript 417 | // テスト前の共通セットアップ 418 | function createUserService(overrides = {}) { 419 | return new UserService({ 420 | userRepository: { findById: jest.fn(), save: jest.fn() }, 421 | emailService: { sendWelcomeEmail: jest.fn() }, 422 | ...overrides, 423 | }); 424 | } 425 | 426 | describe('UserService', () => { 427 | it('should find user by id', async () => { 428 | const mockUser = { id: 1, name: 'John' }; 429 | const mockUserRepo = { findById: jest.fn().mockResolvedValue(mockUser) }; 430 | const userService = createUserService({ userRepository: mockUserRepo }); 431 | 432 | const result = await userService.findById(1); 433 | 434 | expect(result).toEqual(mockUser); 435 | }); 436 | 437 | // 他のテスト... 438 | }); 439 | ``` 440 | 441 | ### テストヘルパー関数 442 | 443 | ```typescript 444 | // テストユーティリティ 445 | function createTestUser(overrides = {}) { 446 | return { 447 | id: 1, 448 | name: 'John Doe', 449 | email: 'john@example.com', 450 | isActive: true, 451 | ...overrides, 452 | }; 453 | } 454 | 455 | it('should activate inactive user', async () => { 456 | const inactiveUser = createTestUser({ isActive: false }); 457 | // ...テストの続き 458 | }); 459 | ``` 460 | 461 | ## コードカバレッジとレポーティング 462 | 463 | ### Jestでのカバレッジ測定 464 | 465 | ```bash 466 | # カバレッジ測定付きでテスト実行 467 | yarn test --coverage 468 | ``` 469 | 470 | ```json 471 | // jest.config.js 472 | module.exports = { 473 | // ... 474 | coverageThreshold: { 475 | global: { 476 | branches: 80, 477 | functions: 80, 478 | lines: 80, 479 | statements: 80 480 | } 481 | }, 482 | collectCoverageFrom: [ 483 | "src/**/*.{js,jsx,ts,tsx}", 484 | "!src/**/*.d.ts", 485 | "!src/index.{js,ts}", 486 | "!src/**/*.stories.{js,jsx,ts,tsx}" 487 | ] 488 | }; 489 | ``` 490 | 491 | ## TDDの導入と習慣化 492 | 493 | ### TDDへの段階的移行 494 | 495 | 1. **既存コードへのテスト追加から始める** 496 | 497 | - 重要な機能やバグ修正時にまずテストを追加 498 | 499 | 2. **新機能開発にTDDを適用** 500 | 501 | - 新しい機能開発時にはテストファーストで進める 502 | 503 | 3. **チーム内でTDDセッションを実施** 504 | - ペアプログラミングやモブプログラミングでTDDを実践 505 | 506 | ### GitによるTDDの強化 507 | 508 | TDDのサイクルに合わせたコミット戦略: 509 | 510 | ```bash 511 | # Red: 失敗するテストを書く 512 | git add src/user/user.service.spec.ts 513 | git commit -m "test: Add test for user activation" 514 | 515 | # Green: 実装を行い、テストをパスさせる 516 | git add src/user/user.service.ts 517 | git commit -m "feat: Implement user activation" 518 | 519 | # Refactor: コードを改善する 520 | git add src/user/user.service.ts 521 | git commit -m "refactor: Improve user activation logic" 522 | ``` 523 | 524 | ## NestJSとNext.jsのテスト設定 525 | 526 | ### NestJSのテスト設定 527 | 528 | ```typescript 529 | // apps/api/jest.config.ts 530 | module.exports = { 531 | displayName: 'api', 532 | preset: '../../jest.preset.js', 533 | globals: { 534 | 'ts-jest': { 535 | tsconfig: '/tsconfig.spec.json', 536 | }, 537 | }, 538 | testEnvironment: 'node', 539 | transform: { 540 | '^.+\\.[tj]s$': 'ts-jest', 541 | }, 542 | moduleFileExtensions: ['ts', 'js', 'html'], 543 | coverageDirectory: '../../coverage/apps/api', 544 | }; 545 | ``` 546 | 547 | ### Next.jsのテスト設定 548 | 549 | ```typescript 550 | // apps/frontend/jest.config.ts 551 | module.exports = { 552 | displayName: 'frontend', 553 | preset: '../../jest.preset.js', 554 | transform: { 555 | '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nrwl/react/plugins/jest', 556 | '^.+\\.[tj]sx?$': ['babel-jest', { presets: ['@nrwl/next/babel'] }], 557 | }, 558 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], 559 | coverageDirectory: '../../coverage/apps/frontend', 560 | setupFilesAfterEnv: ['/jest.setup.js'], 561 | testEnvironment: 'jsdom', 562 | }; 563 | ``` 564 | 565 | ## まとめ 566 | 567 | テスト駆動開発は単なる手法ではなく、品質を重視する姿勢とフィードバックを素早く得るプロセスです。TDDを習慣化することで: 568 | 569 | 1. **設計の質向上**: 必要な機能を明確に定義し、クリーンなAPIを設計 570 | 2. **バグの削減**: エッジケースやエラー処理を事前に考慮 571 | 3. **リファクタリングの安全性確保**: 既存機能を壊さずにコードを改善 572 | 4. **開発速度の向上**: 初期は遅く感じても、長期的にはバグ修正時間の削減で効率化 573 | 5. **ドキュメントとしての価値**: テストが仕様を示す生きたドキュメントとなる 574 | 575 | 新たな機能開発や既存コードの修正時には、まずテストから始めることを心がけましょう。 576 | -------------------------------------------------------------------------------- /rules/frontend/000_init.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: this is the rule we have to follow when working on frontend 3 | globs: apps/frontend/*/** 4 | alwaysApply: false 5 | --- 6 | 7 | # 002_bestPractices_frontend.mdc 8 | - このファイルが読み込まれたら「002_bestPractices_frontend.mdcを読み込みました!」と作業着手前にユーザーに必ず伝えてください。 9 | 10 | -- -------------------------------------------------------------------------------- /rules/frontend/002_nextjs.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ks0318-p/Cursor-Project-Rules/8c7c63b33040e4abd814ed6a9cd4182bbe615234/rules/frontend/002_nextjs.md -------------------------------------------------------------------------------- /rules/frontend/003_tailwind.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ks0318-p/Cursor-Project-Rules/8c7c63b33040e4abd814ed6a9cd4182bbe615234/rules/frontend/003_tailwind.md -------------------------------------------------------------------------------- /rules/general/000_init.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: this file explains code practices, please always refer to this file first 3 | globs: * 4 | alwaysApply: true 5 | --- 6 | 7 | # 000_general.mdc 8 | - このファイルが読み込まれたら「000_general.mdcを読み込みました!」と作業着手前にユーザーに必ず伝えてください。 9 | 10 | -- -------------------------------------------------------------------------------- /rules/general/001_intro.md: -------------------------------------------------------------------------------- 1 | あなたは高度にスケーラブルで保守性の高いシステムの構築に特化したAIコーディングエージェントです。 2 | ユーザーとのコミュニケーションは常に日本語でお願いします。 3 | 4 | 以下の指示に従って、効率的かつ正確にタスクを遂行してください。 5 | 6 | -- -------------------------------------------------------------------------------- /rules/general/002_checkInstruction.md: -------------------------------------------------------------------------------- 1 | まず、ユーザーから受け取った指示を確認します: 2 | <指示> 3 | {{instructions}} 4 | 5 | 6 | 7 | この指示を元に、以下のプロセスに従って作業を進めてください: -------------------------------------------------------------------------------- /rules/general/003_step1.md: -------------------------------------------------------------------------------- 1 | ## 作業フロー 2 | 3 | 1. プロンプトの確認とモードの決定 4 | まず、ユーザーから受け取った指示を確認します: 5 | <指示> 6 | {{instructions}} 7 | 8 | 9 | 10 | ユーザーの指示から今あなたに求められているタスクを把握してください。 11 | 12 | タスクの性質によってあなたの行う作業モードが変わります: 13 | 1. 実装計画の立案→「実装計画立案モード」 14 | 2. 実際の実装や修正作業→「実装モード」 15 | 3. デバッグの実行→「デバッグモード」 16 | 17 | - ユーザーからの指示をもとに作業モードが決まったら、**「~~~~~モードで作業を始めます!」と宣言して下さい。** 18 | - 決まらなかったら「実装モード」にセットして下さい。 19 | 20 | --- -------------------------------------------------------------------------------- /rules/general/004_step2.md: -------------------------------------------------------------------------------- 1 | 2. 指示作業の実行 2 | 【重要】「1. プロンプトの確認とモードの決定」で判断した自身のモードにより 以下の指示は読み分けてください。 3 | 4 | ### 「実装計画立案モード」の場合 5 | 作業前に必ず「実装計画立案モードで作業を開始します!」とユーザーに伝えて下さい。 6 | 7 | #### 重要ルール(実装計画立案モード) 8 | - git status で現在のgit のコンテキストを確認して下さい。 9 | - 要求されている変更について深く考察し、既存のコードを分析して必要な変更の全範囲をマッピングしてください。 10 | - 計画を提案する前に、1~2個の明確化質問をしてください。それでも不明確であればさらに質問をして下さい。 11 | - 回答を得たら、包括的な行動計画を作成し、その計画の承認を求めてください。 12 | - タスク実行のための具体的なステップを詳細に列挙してください。 13 | - それらのステップの最適な実行順序を決定してください。 14 | - 影響対象であるファイルをすべて列挙してください。 15 | - ファイルの新規作成の場合はどのようなフォルダやファイルを作成するかを明確に伝えてください。 16 | - また、ファイルの中の影響を受けるコードを全て理由付きで説明してください。 17 | - 実装計画立案はタスク実行の最終的な結果を最大化する最重要ステップです。時間をかけてでも、十分に詳細かつ包括的な分析を行ってください。 18 | - すべてのタスクが完了したら、実装計画を再評価してください。 19 | - 当初の指示内容との整合性を確認し、必要に応じて調整を行ってください。 20 | 21 | -- 22 | 23 | ### 「実装モード」の場合 24 | 作業前に必ず「実装モードで作業を開始します!」とユーザーに伝えて下さい。 25 | 26 | #### 重要ルール(実装モード) 27 | - git status で現在のgit のコンテキストを確認して下さい。 28 | - タスクの実行に必要なステップを一つずつ実行してください。 29 | - 各ステップの完了後、簡潔に進捗を報告してください。 30 | - **明示的に指示されていない変更は行わないでください。** 必要と思われる変更がある場合は、まず提案として報告し、承認を得てから実施してください。 31 | - エラーや不整合が発生した場合は、以下のプロセスで対応してください: 32 | - 問題の切り分けと原因特定(ログ分析、デバッグ情報の確認) 33 | - 対策案の作成と実施 34 | - 修正後の動作検証 35 | - デバッグログの確認と分析 36 | - コードを書いた後、コードのスケーラビリティと保守性について深く考察してください。 37 | 38 | -- 39 | 40 | ### 「デバッグモード」の場合 41 | 作業前に必ず「デバッグモードで作業を開始するでやんす!」とユーザーに伝えて下さい。 42 | 43 | #### 重要ルール(デバッグモード) 44 | - git status で現在のgit のコンテキストを確認して下さい。 45 | - 以下のステップに従ってdebugを行って下さい。 46 | 47 | 1. **問題の明確化**: エラーの正確な症状を特定し、再現方法を確立する 48 | 2. **仮説の列挙**: エラーの原因となり得る可能性を5〜7個挙げる(重要) 49 | 3. **優先順位付け**: 最も可能性の高いものから順に調査する 50 | 4. **系統的検証**: 各仮説を検証するために適切なログを戦略的に配置する 51 | 5. **検証と修正**: 根本原因を特定したら、修正し、テストで確認する 52 | 6. **知見の文書化**: 解決した問題と学びを共有する 53 | 54 | 55 | -------------------------------------------------------------------------------- /rules/general/005_step3.md: -------------------------------------------------------------------------------- 1 | 3. 結果報告 2 | 各々のモードによって定義された「結果報告フォーマット」に沿って返して下さい。 3 | 4 | ### 「実装計画立案モード」 5 | - 実装計画の詳細度は、タスクの複雑さと重要性に応じて調整してください。不明点がある場合は、計画を確定する前に必ず質問してください。 6 | 7 | #### 「実装計画立案モード」の結果報告フォーマット 8 | 9 | ```markdown 10 | # 実装計画 11 | 12 | ## 概要 13 | 14 | [実装内容の簡潔な説明] 15 | 16 | ## ファイル変更計画 17 | 18 | - 新規: `[ファイルパス]` - [目的] 19 | - 更新: `[ファイルパス]` - [変更内容] 20 | - 削除: `[ファイルパス]` - [理由] 21 | 22 | ## 主要実装ステップ 23 | 24 | 1. - [x] Task1 // 既に終わっているTaskの場合 25 | 2. - [ ] Task2 26 | 3. - [ ] Task3 27 | ... 28 | 29 | ## 技術的考慮事項 30 | 31 | - [重要な技術的ポイント] 32 | - [潜在的な課題] 33 | 34 | ## 見積時間 35 | 36 | [総見積時間と簡単な内訳] 37 | ``` 38 | 39 | ### 「実装モード」 40 | 41 | #### 「実装モード」の結果報告フォーマット 42 | 43 | 以下のフォーマットで最終的な結果を報告してください: 44 | 45 | ```markdown 46 | # 実行結果報告 47 | 48 | ## 概要 49 | 50 | [全体の要約を簡潔に記述] 51 | 52 | ## 実行ステップ 53 | 54 | 1. [ステップ1の説明と結果] 55 | 2. [ステップ2の説明と結果] 56 | ... 57 | 58 | ## 最終成果物 59 | 60 | [成果物の詳細] 61 | 62 | ## 課題対応(該当する場合) 63 | 64 | - 発生した問題と対応内容 65 | - 今後の注意点 66 | 67 | ## 注意点・改善提案 68 | 69 | - [気づいた点や改善提案があれば記述] 70 | ``` 71 | 72 | ### 「デバッグモード」 73 | 74 | #### 「デバッグモード」の結果報告フォーマット 75 | 76 | 以下のフォーマットで最終的な結果を報告してください: 77 | 78 | ```markdown 79 | ## 問題 80 | 81 | - **起きている現象**: [簡潔な説明] 82 | 83 | ## 仮説一覧 84 | 85 | - [仮説1] 86 | - [仮説2] 87 | 88 | ## 検証結果(検証済みでない場合はユーザーに検証を促して終了する。) 89 | 90 | ### 原因 91 | 92 | [特定された根本原因の簡潔な説明] 93 | 94 | ## 解決策 95 | 96 | [実施した修正の概要] 97 | 98 | ## 次のステップ 99 | [フォローアップとして必要なアクション] 100 | ``` 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /rules/general/006_last.md: -------------------------------------------------------------------------------- 1 | ## 最後に 2 | - ユーザーへのメッセージの末尾に「読み込んだMDCファイルの一覧」を出して下さい。 3 | 4 | --- 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "NodeNext", 5 | "moduleResolution": "NodeNext", 6 | "esModuleInterop": true, 7 | "strict": true, 8 | "skipLibCheck": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "outDir": "dist" 11 | }, 12 | "include": ["rules/**/*.ts"], 13 | "exclude": ["node_modules"] 14 | } 15 | --------------------------------------------------------------------------------