JA Development Guides Debugging - aku11i/phantom GitHub Wiki
English | 日本語
このガイドでは、開発中のPhantomのトラブルシューティングのためのデバッグテクニックとツールについて説明します。
Node.jsには、Chrome DevToolsとVS Codeで動作する組み込みデバッガーが含まれています。
# インスペクター付きで開始
node --inspect dist/phantom.js create test
# インスペクター付きで開始し、最初の行でブレーク
node --inspect-brk dist/phantom.js create test
# 特定のポート
node --inspect=9229 dist/phantom.js list
-
--inspect
でNodeを開始 - Chromeを開いて
chrome://inspect
に移動 - Remote Targetの下の「inspect」をクリック
- DevToolsを使用してデバッグ
ソース: 推奨される.vscode/launch.json
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Debug Phantom CLI",
"skipFiles": ["<node_internals>/**"],
"program": "${workspaceFolder}/dist/phantom.js",
"args": ["create", "test-phantom"],
"outFiles": ["${workspaceFolder}/dist/**/*.js"],
"sourceMaps": true,
"console": "integratedTerminal"
},
{
"type": "node",
"request": "launch",
"name": "Debug Current Test",
"skipFiles": ["<node_internals>/**"],
"program": "${file}",
"args": ["--test"],
"console": "integratedTerminal"
},
{
"type": "node",
"request": "attach",
"name": "Attach to Process",
"port": 9229,
"skipFiles": ["<node_internals>/**"],
"sourceMaps": true
}
]
}
Phantomはesbuildでビルドされているため、ソースマップによりTypeScriptのデバッグが可能です:
# ソースマップ付きでビルド(デフォルト)
pnpm build
# TypeScriptを直接デバッグ
node --enable-source-maps --inspect dist/phantom.js
// ハンドラーにデバッグロギングを追加
export async function createHandler(args: string[]): Promise<void> {
console.log("createHandler called with:", args);
const [name, branch] = args;
console.log("Parsed:", { name, branch });
// 既存のコード...
}
Git操作をデバッグ:
// src/core/git/executor.ts内
export class GitExecutor {
execute(args: string[]): Result<string> {
console.log("Git command:", "git", args.join(" "));
const { status, stdout, stderr } = spawnSync("git", args);
console.log("Git exit code:", status);
console.log("Git stdout:", stdout);
console.log("Git stderr:", stderr);
// 既存のコード...
}
}
// パス構築をデバッグ
import { getWorktreePath } from "../paths.js";
const path = getWorktreePath(repoName, phantomName);
console.log("Resolved path:", {
repoName,
phantomName,
resultPath: path,
exists: existsSync(path)
});
// Result型をログするヘルパー
function debugResult<T>(result: Result<T>, label: string): void {
if (result.ok) {
console.log(`${label} - Success:`, result.value);
} else {
console.log(`${label} - Error:`, result.error.message);
console.error(result.error.stack);
}
}
// 使用方法
const result = await createWorktree(name);
debugResult(result, "createWorktree");
# 特定のテストファイルを実行
node --inspect --test src/core/worktree/create.test.ts
# パターンに一致するテストを実行
node --inspect --test --test-name-pattern="validation"
import { mock } from "node:test";
// モック呼び出しをログ
const mockExecute = mock.fn((args) => {
console.log("Mock called with:", args);
return Result.ok("mocked response");
});
// テスト後
console.log("Mock call count:", mockExecute.mock.calls.length);
console.log("All calls:", mockExecute.mock.calls);
import { test } from "node:test";
// デバッグのためにタイムアウトを増やす
test("slow operation", { timeout: 30000 }, async () => {
// ブレークポイントを設定し、タイムアウトなしでデバッグ
});
問題がどこで発生するか不明な場合:
console.log("Point A reached");
// コードの半分
console.log("Point B reached");
// もう半分
console.log("Point C reached");
// デバッグヘルパーを作成
function inspectState(label: string, state: any): void {
console.log(`\n=== ${label} ===`);
console.log(JSON.stringify(state, null, 2));
console.log("=================\n");
}
// コード全体で使用
inspectState("Before validation", { name, branch });
inspectState("After Git operation", { result, worktrees });
// エラー情報を強化
try {
await riskyOperation();
} catch (error) {
console.error("Operation failed");
console.error("Error:", error.message);
console.error("Stack:", error.stack);
console.error("Context:", { name, branch, path });
throw error;
}
// 環境ベースのデバッグ
const DEBUG = process.env.DEBUG_PHANTOM === "true";
if (DEBUG) {
console.log("Debug info:", data);
}
// またはデバッグ関数を使用
function debug(...args: any[]): void {
if (process.env.DEBUG_PHANTOM) {
console.log("[DEBUG]", ...args);
}
}
# worktree状態を確認
git worktree list --porcelain
# Git内部を確認
ls -la .git/worktrees/
# worktree設定を表示
cat .git/worktrees/phantom-name/gitdir
// Git worktreeの問題をデバッグ
async function debugWorktreeState(name: string): Promise<void> {
const git = new GitExecutor();
// すべてのworktreeをリスト
const listResult = git.execute(["worktree", "list", "--porcelain"]);
console.log("All worktrees:", listResult);
// 特定のworktreeを確認
const worktreePath = getWorktreePath(repoName, name);
console.log("Checking path:", worktreePath);
console.log("Path exists:", existsSync(worktreePath));
console.log("Is directory:", existsSync(worktreePath) &&
statSync(worktreePath).isDirectory());
// Gitディレクトリを確認
const gitDir = join(worktreePath, ".git");
console.log("Git dir:", gitDir);
console.log("Git file contents:",
existsSync(gitDir) ? readFileSync(gitDir, "utf-8") : "Not found");
}
// シンプルなタイミング
console.time("createWorktree");
const result = await createWorktree(name);
console.timeEnd("createWorktree");
// 詳細なタイミング
const start = performance.now();
const result = await operation();
const duration = performance.now() - start;
console.log(`操作に${duration.toFixed(2)}msかかりました`);
// メモリ使用量の追跡
function logMemoryUsage(label: string): void {
const usage = process.memoryUsage();
console.log(`メモリ (${label}):`);
console.log(` RSS: ${(usage.rss / 1024 / 1024).toFixed(2)} MB`);
console.log(` ヒープ使用: ${(usage.heapUsed / 1024 / 1024).toFixed(2)} MB`);
console.log(` ヒープ合計: ${(usage.heapTotal / 1024 / 1024).toFixed(2)} MB`);
}
logMemoryUsage("操作前");
await heavyOperation();
logMemoryUsage("操作後");
# プロファイリング付きで開始
node --prof dist/phantom.js create test
# ログを処理
node --prof-process isolate-*.log > profile.txt
# profile.txtでボトルネックを分析
// 詳細モードを追加
const VERBOSE = process.env.PHANTOM_VERBOSE === "true";
function verbose(message: string, data?: any): void {
if (VERBOSE) {
console.error(`[VERBOSE] ${message}`, data || "");
}
}
// 全体で使用
verbose("Creating worktree", { name, branch });
verbose("Git command", { cmd: args.join(" ") });
// 拡張エラークラス
class PhantomError extends Error {
constructor(
message: string,
public code: string,
public context: Record<string, any>
) {
super(message);
this.name = "PhantomError";
}
}
// 使用方法
throw new PhantomError(
"Failed to create worktree",
"WORKTREE_CREATE_FAILED",
{ name, branch, gitError: stderr }
);
デバッグユーティリティを作成:
// src/core/debug.ts
const DEBUG = process.env.DEBUG?.includes("phantom");
export function debug(namespace: string) {
return (...args: any[]) => {
if (DEBUG || process.env.DEBUG?.includes(namespace)) {
console.error(`[${namespace}]`, ...args);
}
};
}
// 使用方法
import { debug } from "../debug.js";
const log = debug("phantom:git");
log("Executing command", args);
// デバッグ用のきれいな出力
export function inspect(obj: any, label?: string): void {
if (label) console.log(`\n${label}:`);
console.dir(obj, {
depth: null,
colors: true,
compact: false
});
}
// ブレークポイントヘルパー
export function breakpoint(condition: boolean = true): void {
if (condition) {
debugger;
}
}
class ExecutionTracer {
private stack: string[] = [];
enter(name: string): void {
this.stack.push(name);
console.log("→".repeat(this.stack.length), name);
}
exit(name: string): void {
console.log("←".repeat(this.stack.length), name);
this.stack.pop();
}
}
const tracer = new ExecutionTracer();
tracer.enter("createWorktree");
// ... コード ...
tracer.exit("createWorktree");
function assertDefined<T>(
value: T | undefined,
message: string
): asserts value is T {
if (value === undefined) {
console.error("Assertion failed:", message);
console.trace();
throw new Error(message);
}
}
// 使用方法
assertDefined(worktreeName, "Worktree name is required");
// 異なるポイントで状態をキャプチャ
const stateHistory: any[] = [];
function captureState(label: string, state: any): void {
stateHistory.push({
timestamp: Date.now(),
label,
state: JSON.parse(JSON.stringify(state))
});
}
function dumpStateHistory(): void {
console.log("State History:");
stateHistory.forEach(({ timestamp, label, state }) => {
console.log(`\n[${new Date(timestamp).toISOString()}] ${label}:`);
console.log(JSON.stringify(state, null, 2));
});
}
// 悪い
console.log(result);
console.log(name);
// 良い
console.log("Worktree creation result:", result);
console.log("Processing phantom name:", name);
// デバッグコードが本番環境に到達しないようにする
if (process.env.NODE_ENV !== "production") {
console.log("Debug:", sensitiveData);
}
# 残っているデバッグステートメントを見つける
grep -r "console\." src/
grep -r "debugger" src/
# 開発では常にソースマップを有効にする
node --enable-source-maps dist/phantom.js
# デバッグ用にインラインソースマップでビルド
# (一時的にbuild.tsを変更)
sourcemap: "inline"
Phantomでの効果的なデバッグには以下が含まれます:
- 適切なツール - VS Codeデバッガー、Chrome DevTools
- 戦略的なロギング - 意味のあるデバッグ出力
- 状態の検査 - データフローの理解
- 分離 - 一度に1つのことをデバッグ
- クリーンアップ - コミット前にデバッグコードを削除
覚えておいてください:良いデバッグは系統的で忍耐強いものです。解決策に飛びつく前に、問題を理解するために時間をかけてください。