# 機能設計書 36-nodejsオプション

## 概要

本ドキュメントは、CoffeeScript CLIの`--nodejs`オプションの機能設計を記述する。このオプションは、CoffeeScriptの実行時に使用されるNode.jsプロセスに対して、直接オプションを渡す機能を提供する。

### 本機能の処理概要

**業務上の目的・背景**：Node.jsランタイムには、メモリ制限、デバッグポート、V8オプションなど、様々な実行時設定がある。CoffeeScriptスクリプトを実行する際にこれらのNode.jsオプションを指定できるようにすることで、高度なデバッグ、パフォーマンスチューニング、メモリ管理が可能になる。

**機能の利用シーン**：
- `--inspect`や`--inspect-brk`でデバッガを有効にしてCoffeeScriptをデバッグしたい場合
- `--max-old-space-size`でヒープサイズを増やして大規模なコンパイル/実行を行いたい場合
- `--harmony`フラグで最新のJavaScript機能を有効にしたい場合
- `--expose-gc`でガベージコレクションを手動制御したい場合
- `--prof`でV8プロファイラを有効にしたい場合

**主要な処理内容**：
1. `--nodejs`オプションの引数を取得
2. 引数をスペースで分割してNode.jsオプション配列を生成
3. 現在のプロセス引数から`--nodejs`とその引数を除去
4. `child_process.spawn`で新しいNode.jsプロセスを起動
5. Node.jsオプションと残りの引数を結合して新プロセスに渡す
6. シグナル（SIGINT, SIGTERM）をフォワード
7. 子プロセスの終了コードを親プロセスに伝播

**関連システム・外部連携**：Node.jsランタイム、child_processモジュール

**権限による制御**：特になし（ただしNode.jsオプションによっては特権が必要な場合がある）

## 関連画面

本機能はCLI機能のため、画面との直接的な関連はない。

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | - | - | - |

## 機能種別

プロセス管理 / ランタイム設定

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| --nodejs | String | No | Node.jsに渡すオプション（スペース区切りで複数指定可） | 有効なNode.jsオプションであること |

### 入力例

```bash
# デバッガ有効化
coffee --nodejs "--inspect" script.coffee

# ヒープサイズ増加
coffee --nodejs "--max-old-space-size=4096" script.coffee

# 複数オプション
coffee --nodejs "--inspect --max-old-space-size=4096" script.coffee
```

### 入力データソース

コマンドライン引数（`process.argv`経由）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| exitCode | Number | 子プロセスの終了コード |

### 出力先

子プロセスの標準出力/標準エラーは親プロセスにそのまま継承される

## 処理フロー

### 処理シーケンス

```
1. オプション解析
   └─ opts.nodejsにNode.jsオプション文字列を格納
2. forkNode関数の呼び出し
   └─ opts.nodejsが存在する場合、run関数から早期リターン
3. Node.jsオプションの分割
   └─ opts.nodejs.split(/\s+/)でスペース区切りで配列化
4. 引数リストの再構成
   └─ process.argv[1..]から--nodejsとその引数を除去
5. 子プロセスのspawn
   └─ process.execPath（現在のnodeバイナリ）を使用
   └─ nodeArgs.concat(args)で引数を結合
6. シグナルフォワーディングの設定
   └─ SIGINT, SIGTERMを子プロセスに転送
7. 終了コードの伝播
   └─ 子プロセスのexitイベントでprocess.exit(code)
```

### フローチャート

```mermaid
flowchart TD
    A[coffee --nodejs ARGS script.coffee] --> B[parseOptions]
    B --> C[opts.nodejs = ARGS]
    C --> D{opts.nodejs 存在?}
    D -->|No| E[通常処理へ]
    D -->|Yes| F[forkNode 呼び出し]
    F --> G[nodeArgs = opts.nodejs.split /\s+/]
    G --> H[args = process.argv 1..]
    H --> I[args から --nodejs と引数を除去]
    I --> J[spawn process.execPath, nodeArgs.concat args]
    J --> K[stdio: 0, 1, 2 継承]
    K --> L[cwd: process.cwd]
    L --> M[env: process.env]
    M --> N[シグナルハンドラ設定]
    N --> O[SIGINT/SIGTERM を p.kill に転送]
    O --> P[p.on 'exit', code]
    P --> Q[process.exit code]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-36-01 | 早期リターン | --nodejsオプションが指定された場合、他の処理より先にforkNodeが実行される | --nodejs指定時 |
| BR-36-02 | スペース分割 | --nodejsの引数はスペースで分割されて個別のオプションとして扱われる | 常時 |
| BR-36-03 | 引数除去 | 新プロセスの引数リストから--nodejsとその引数は除去される | 常時 |
| BR-36-04 | シグナル転送 | SIGINT, SIGTERMシグナルは子プロセスに転送される | 常時 |
| BR-36-05 | 終了コード伝播 | 子プロセスの終了コードが親プロセスの終了コードになる | 常時 |
| BR-36-06 | 環境継承 | 子プロセスはcwd、envを親プロセスから継承する | 常時 |

### 計算ロジック

**引数除去ロジック**:
```coffeescript
args = process.argv[1..]                    # 最初の引数（nodeパス）を除く
args.splice args.indexOf('--nodejs'), 2     # --nodejsとその引数（次の要素）を除去
```

## データベース操作仕様

本機能はデータベース操作を行わない。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| SPAWN_ERROR | Error | 子プロセスの起動に失敗 | Node.jsのインストールを確認 |
| - | 子プロセスエラー | 無効なNode.jsオプションが指定された | オプション名を確認する |

### リトライ仕様

リトライは行わない。

## トランザクション仕様

該当なし。

## パフォーマンス要件

- 子プロセス起動のオーバーヘッドが発生する
- stdio継承により入出力のパフォーマンスへの影響は最小限

## セキュリティ考慮事項

- 任意のNode.jsオプションを渡せるため、セキュリティに影響するオプション（`--allow-natives-syntax`等）に注意
- 子プロセスは親プロセスと同じ権限で実行される

## 備考

**よく使用されるNode.jsオプション例**:

| オプション | 説明 |
|-----------|------|
| --inspect | デバッグポート9229でインスペクタを有効化 |
| --inspect-brk | 最初の行でブレークしてデバッグ開始 |
| --max-old-space-size=N | V8ヒープの最大サイズをNMBに設定 |
| --harmony | 実験的なJavaScript機能を有効化 |
| --expose-gc | global.gc()を利用可能に |
| --prof | V8プロファイラを有効化 |
| --trace-warnings | 警告のスタックトレースを表示 |

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: オプション定義を理解する

まず、--nodejsオプションがどのように定義されているかを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | command.coffee | `src/command.coffee` | SWITCHESでの--nodejsオプション定義 |

**主要処理フロー**:
- **46行目**: `--nodejs [ARGS]`オプションの定義（`'pass options directly to the "node" binary'`）
- 短縮形なし（`--nodejs`のみ）
- `[ARGS]`は引数を取ることを示す

#### Step 2: run関数でのforkNode呼び出しを理解する

opts.nodejsが設定された場合の早期リターンを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | command.coffee | `src/command.coffee` | run関数でのforkNode呼び出し |

**主要処理フロー**:
- **97行目**: `return forkNode() if opts.nodejs` - opts.nodejsが存在する場合、即座にforkNodeを呼び出してリターン

**読解のコツ**: この早期リターンにより、--nodejsオプションは他のすべてのオプションより優先される。実際のコンパイル/実行は子プロセスで行われる。

#### Step 3: forkNode関数を理解する

子プロセス起動の核心部分を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | command.coffee | `src/command.coffee` | forkNode関数の実装 |

**主要処理フロー**:
- **531-542行目**: forkNode関数全体
  ```coffeescript
  forkNode = ->
    nodeArgs = opts.nodejs.split /\s+/
    args     = process.argv[1..]
    args.splice args.indexOf('--nodejs'), 2
    p = spawn process.execPath, nodeArgs.concat(args),
      cwd:        process.cwd()
      env:        process.env
      stdio:      [0, 1, 2]
    for signal in ['SIGINT', 'SIGTERM']
      process.on signal, do (signal) ->
        -> p.kill signal
    p.on 'exit', (code) -> process.exit code
  ```

**各行の解説**:
- **532行目**: `opts.nodejs.split /\s+/` - スペースで分割してNode.jsオプション配列を生成
- **533行目**: `process.argv[1..]` - process.argvの2番目以降（nodeパスを除く）
- **534行目**: `args.splice args.indexOf('--nodejs'), 2` - --nodejsとその次の引数を除去
- **535-538行目**: `spawn`で子プロセスを起動
  - `process.execPath`: 現在のnodeバイナリのパス
  - `nodeArgs.concat(args)`: Node.jsオプション + 残りの引数
  - `cwd`: 現在の作業ディレクトリを継承
  - `env`: 現在の環境変数を継承
  - `stdio: [0, 1, 2]`: stdin/stdout/stderrを継承（親プロセスと直結）
- **539-541行目**: シグナルハンドラの設定
  - `do (signal) -> -> p.kill signal`: クロージャでsignalをキャプチャ
- **542行目**: 子プロセス終了時に同じ終了コードで親も終了

**読解のコツ**:
- `do (signal) ->` は即時実行関数で、ループ変数をキャプチャするCoffeeScriptのイディオム
- `stdio: [0, 1, 2]` は `stdio: 'inherit'` と同等

#### Step 4: child_processモジュールのspawn関数を理解する

Node.js標準のspawn関数の動作を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | command.coffee | `src/command.coffee` | spawn関数のimport |

**主要処理フロー**:
- **13行目**: `{spawn, exec} = require 'child_process'` - child_processモジュールからspawnをインポート

### プログラム呼び出し階層図

```
exports.run (command.coffee:72)
    │
    ├─ parseOptions (command.coffee:445)
    │      └─ opts.nodejs = "--inspect" (例)
    │
    └─ opts.nodejs チェック (command.coffee:97)
           │
           └─ forkNode() (command.coffee:531)
                  │
                  ├─ opts.nodejs.split(/\s+/)
                  │      └─ nodeArgs = ["--inspect"]
                  │
                  ├─ process.argv[1..]
                  │      └─ args = ["coffee", "--nodejs", "--inspect", "script.coffee"]
                  │
                  ├─ args.splice(indexOf('--nodejs'), 2)
                  │      └─ args = ["coffee", "script.coffee"]
                  │
                  ├─ spawn(process.execPath, nodeArgs.concat(args), options)
                  │      └─ 子プロセス: node --inspect coffee script.coffee
                  │
                  ├─ process.on('SIGINT', ...)
                  │      └─ p.kill('SIGINT')
                  │
                  ├─ process.on('SIGTERM', ...)
                  │      └─ p.kill('SIGTERM')
                  │
                  └─ p.on('exit', code)
                         └─ process.exit(code)
```

### データフロー図

```
[入力]                           [処理]                              [出力]

process.argv                 forkNode                       子プロセス
["node",                        │                               │
 "coffee",                      │                               │
 "--nodejs",        ───▶    引数分解・再構成     ───▶      spawn
 "--inspect",                   │                           process.execPath
 "script.coffee"]               │                           ["--inspect",
                                │                            "coffee",
                                │                            "script.coffee"]
                                │
                                ▼                               │
process.stdin/out/err    stdio: [0, 1, 2]    ───▶      継承（直結）
                                │
                                ▼                               │
SIGINT/SIGTERM          シグナルハンドラ      ───▶      p.kill(signal)
                                │
                                ▼                               │
                         p.on('exit', code)   ───▶      process.exit(code)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| command.coffee | `src/command.coffee` | ソース | CLI本体、forkNode関数 |
| child_process | Node.js組み込み | モジュール | spawn関数（子プロセス起動） |
