# Process Exit 處理修正 - 2025-10-12
## 📋 概要
修復命令列參數處理中的 process exit 問題,確保 `--version` 和 `--help` 參數正確退出,同時保證 MCP 伺服器能夠正常啟動並持續運行。
## 🎯 問題描述
### 症狀
1. **版本參數測試失敗**
- `tests/e2e/cross-platform.test.ts` 中的 `應正確處理版本參數` 測試失敗
- 退出代碼為 `null` 而非預期的 `0`
- 測試錯誤訊息:`版本檢查失敗,退出代碼: null`
2. **MCP 端到端測試失敗**
- `tests/e2e/build-and-package.test.ts` 中 4 個 MCP 流程測試失敗
- 伺服器過早退出,無法回應 JSON-RPC 請求
- 測試項目:
- `應該正確處理 MCP 工具列表查詢`
- `應該正確處理假期查詢`
- `應該正確處理錯誤情況`
- `記憶體洩漏測試:多次請求後記憶體應該穩定`
### 根本原因
在 `src/index.ts` 中:
1. **初始實作問題**
```typescript
// 處理版本和幫助選項
if (args.showVersion) {
showVersion();
return; // ❌ 只 return,沒有 exit
}
if (args.showHelp) {
showHelp();
return; // ❌ 只 return,沒有 exit
}
```
2. **第一次修正嘗試(失敗)**
```typescript
// 啟動應用程式
main().then(() => {
process.exit(0); // ❌ 導致 MCP 伺服器過早退出
});
```
這導致:
- `--version` 正確退出
- 但 MCP 伺服器在 `server.run()` 後也立即退出
- 無法處理 MCP 請求
## ✅ 解決方案
### 最終實作
1. **在 showVersion() 和 showHelp() 中直接 exit**
```typescript
function showVersion(): void {
try {
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
console.error(`Taiwan Holiday MCP Server v${packageJson.version}`);
console.error(`Node.js ${process.version}`);
console.error(`Platform: ${process.platform} ${process.arch}`);
} catch (error) {
console.error('Taiwan Holiday MCP Server (版本資訊不可用)');
}
process.exit(0); // ✅ 直接退出
}
function showHelp(): void {
console.error(`...幫助資訊...`);
process.exit(0); // ✅ 直接退出
}
```
2. **main() 函數保持簡單**
```typescript
async function main(): Promise<void> {
try {
const args = parseArgs();
// 處理版本和幫助選項
if (args.showVersion) {
showVersion(); // 會直接 exit(0)
return;
}
if (args.showHelp) {
showHelp(); // 會直接 exit(0)
return;
}
// ... 其他初始化和啟動伺服器
await server.run(); // 保持運行
} catch (error) {
console.error('Taiwan Holiday MCP 伺服器啟動失敗:', error);
process.exit(1);
}
}
// 啟動應用程式
main(); // ✅ 不加 .then(() => exit(0))
```
### 設計考量
**為何在函數內部呼叫 exit 而非在 main() 中?**
1. **語意清晰**:showVersion() 和 showHelp() 的語意就是「顯示資訊並結束程式」
2. **控制流明確**:避免在 async 函數鏈中處理 exit,減少時序問題
3. **避免競態條件**:確保輸出完成後才退出
4. **維護簡單**:main() 函數保持簡單的流程控制
## 📊 測試結果
### 修復前
```
Test Suites: 2 failed, 18 passed, 20 total
Tests: 5 failed, 2 skipped, 441 passed, 448 total
```
失敗測試:
- cross-platform.test.ts: 1 個失敗
- build-and-package.test.ts: 4 個失敗
### 修復後
```
Test Suites: 20 passed, 20 total
Tests: 2 skipped, 446 passed, 448 total
Coverage: Statements: 92.27%, Branches: 82.24%, Functions: 89.80%, Lines: 92.34%
```
### 穩定性驗證
連續 3 次完整測試運行:
```
Run 1/3: Test Suites: 20 passed, Tests: 446 passed ✅
Run 2/3: Test Suites: 20 passed, Tests: 446 passed ✅
Run 3/3: Test Suites: 20 passed, Tests: 446 passed ✅
```
**結論:100% 穩定,無間歇性失敗**
## 🔍 技術細節
### Process Exit 行為分析
1. **正常流程(MCP 伺服器)**
```
main() → parseArgs() → setupEnvironment() → server.run()
→ [伺服器持續運行,等待 stdio 輸入]
```
2. **--version 流程**
```
main() → parseArgs() → showVersion() → process.exit(0)
→ [程式正常退出,exit code = 0]
```
3. **--help 流程**
```
main() → parseArgs() → showHelp() → process.exit(0)
→ [程式正常退出,exit code = 0]
```
### Child Process Exit Code 捕獲
測試中的關鍵點:
```typescript
child.on('close', (code) => {
if (code === 0) {
resolve(output);
} else {
reject(new Error(`退出代碼: ${code}`));
}
});
```
- 當 async 函數僅 `return` 時,child process 可能收到 `null` 作為 exit code
- 明確呼叫 `process.exit(0)` 確保 child process 收到正確的退出代碼
## 📝 相關檔案
### 修改檔案
- `src/index.ts`:修正 showVersion()、showHelp() 和 main() 函數
### 測試檔案
- `tests/e2e/cross-platform.test.ts`:版本參數測試
- `tests/e2e/build-and-package.test.ts`:MCP 端到端測試
### 文件更新
- `CHANGELOG.md`:新增修正記錄
- `README.md`:更新測試覆蓋率數據
- `DEVELOPMENT.md`:新增測試指引和注意事項
## 💡 經驗教訓
1. **Process Exit 處理需謹慎**
- 在 async 函數中處理 exit 容易產生時序問題
- 直接在同步函數中 exit 更可靠
2. **測試快取問題**
- Jest 快取可能導致修改後測試仍然失敗
- 建議測試前先執行 `npm test -- --clearCache`
3. **分離關注點**
- 顯示資訊並退出 vs. 啟動服務並保持運行
- 這是兩種完全不同的執行路徑,應該明確區分
4. **穩定性驗證重要性**
- 單次測試通過不代表穩定
- 需要多次運行確認無間歇性失敗
## 🎯 後續建議
1. **CI/CD 增強**
- 考慮在 CI 中執行 3 次測試確保穩定性
- 每次測試前清除快取
2. **測試文件化**
- 在 DEVELOPMENT.md 中記錄測試最佳實踐
- 提醒開發者注意 process exit 處理
3. **程式碼審查檢查清單**
- 確認 process exit 處理正確
- 驗證測試穩定性(多次運行)
- 檢查是否有快取問題
## 📊 影響評估
### 正面影響
- ✅ 所有測試通過,100% 穩定
- ✅ 修復 5 個失敗測試
- ✅ 改善程式碼清晰度
- ✅ 增強測試信心
### 風險評估
- ⚠️ 無已知風險
- ✅ 完全向後相容
- ✅ 不影響現有功能
### 效能影響
- 無影響(exit 行為優化)
## 🏁 總結
這次修正解決了一個看似簡單但實際上涉及 Node.js process 生命週期、async 函數執行時序和 child process 通訊的複雜問題。通過將 `process.exit()` 呼叫移至具體的功能函數中,而非在 async 執行鏈中處理,我們確保了:
1. 命令列工具(--version, --help)能正確退出並返回正確的 exit code
2. MCP 伺服器能正常啟動並持續運行
3. 所有測試穩定通過(100% 通過率,連續 3 次驗證)
4. 程式碼更加清晰和易於維護
---
**作者**: Claude Code
**日期**: 2025-10-12
**標籤**: #bug-fix #testing #process-exit #stability