# 画面設計書 14-publish

## 概要

本ドキュメントは、Bun CLIの `bun publish` コマンドの画面設計書です。npmパッケージをnpmレジストリに公開する機能の仕様を定義します。

### 本画面の処理概要

`bun publish` コマンドは、ローカルのnpmパッケージをnpmレジストリ（または互換レジストリ）に公開する機能を提供します。tarball作成、認証、OTP対応などnpm publishと同等の機能を持ちます。

**業務上の目的・背景**：ライブラリやツールを開発した後、npmエコシステムに公開して他の開発者が利用できるようにするために使用します。Bunの高速なパッケージング処理により、公開作業を効率化します。

**画面へのアクセス方法**：公開したいパッケージのディレクトリで `bun publish` を実行、または既存のtarballを指定して `bun publish path/to/package.tgz` を実行します。

**主要な操作・処理内容**：
1. package.jsonの読み込みとバリデーション
2. tarballの作成（またはtarball読み込み）
3. SHA1/SHA512ハッシュの計算
4. npmレジストリへの認証（Bearer/Basic認証、OTP対応）
5. PUTリクエストでパッケージを公開
6. publish/postpublishスクリプトの実行

**画面遷移**：単独で実行されるCLIコマンドです。公開完了後、公開されたパッケージ情報がターミナルに表示されます。

**権限による表示制御**：レジストリへの認証情報（npm token、OTP）が必要です。スコープ付きパッケージではアクセス権限（public/restricted）の設定が可能です。

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 22 | bun publish | 主機能 | パッケージのnpmレジストリへの公開処理 |
| 41 | npmレジストリ | 主機能 | レジストリへのパッケージアップロード |

## 画面種別

コマンド実行（進捗表示、成功/エラーメッセージ出力）

## URL/ルーティング

```
bun publish [tarball-path] [options]
```

## 入出力項目

### 入力項目（コマンドライン引数）

| 項目名 | 型 | 必須 | 説明 |
|--------|-----|------|------|
| tarball-path | string | いいえ | 既存tarballのパス（省略時は現在のワークスペースをパック） |
| --tag | string | いいえ | 公開時のdist-tag（デフォルト: latest） |
| --access | string | いいえ | アクセス権限（public/restricted） |
| --otp | string | いいえ | ワンタイムパスワード |
| --dry-run | boolean | いいえ | 実際には公開せずに処理を確認 |
| --tolerate-republish | boolean | いいえ | 同一バージョン再公開を許容 |
| --ignore-scripts | boolean | いいえ | publish/postpublishスクリプトをスキップ |

### 出力項目

| 項目名 | 型 | 説明 |
|--------|-----|------|
| パック情報 | string | パッケージに含まれるファイル一覧 |
| サマリー | string | ファイル数、サイズ、shasum、integrity |
| 公開情報 | string | Tag、Access、Registry情報 |
| 成功メッセージ | string | 公開されたパッケージ名@バージョン |

## 表示項目

### パック進捗表示

```
packed {size} {filename}
packed {size} {filename}
...
```

### サマリー表示

```
Total files: {n}
Packed size: {size}
Unpacked size: {size}
Shasum: {sha1}
Integrity: {sha512}
Tag: {tag}
Access: {access}
Registry: {url}
```

### 成功時出力

```
 + {package-name}@{version}
```

## イベント仕様

### 1-ディレクトリからの公開（引数なし）

コマンド実行時の処理フロー：

1. `PublishCommand.exec()` がエントリーポイントとして呼び出される
2. コマンドライン引数を解析し、PackageManagerを初期化
3. 位置引数がなければ `Context.fromWorkspace()` でワークスペースをパック
4. tarballを作成してSHA1/SHA512ハッシュを計算
5. `publish()` 関数でレジストリに公開
6. publish/postpublishスクリプトを実行

### 2-tarballからの公開（引数あり）

1. 位置引数がある場合、`Context.fromTarballPath()` でtarballを読み込み
2. tarball内のpackage.jsonを解析
3. 同様にハッシュ計算、公開処理を実行

### 3-OTP認証フロー

1. 401レスポンスでOTPが必要な場合、www-authenticateヘッダーを確認
2. WebベースOTPの場合、authUrlを開いてdoneUrlをポーリング
3. クラシックOTPの場合、ターミナルでOTP入力を受け付け
4. OTP付きで再度PUTリクエスト

## データベース更新仕様

### 操作別データベース影響一覧

本コマンドはnpmレジストリへの書き込みを行います。

| 操作（イベント） | 対象 | 操作種別 | 概要 |
|----------------|------|---------|------|
| パッケージ公開 | npmレジストリ | PUT | パッケージメタデータとtarballのアップロード |
| dist-tag設定 | npmレジストリ | UPDATE | 指定タグに公開バージョンを設定 |

## メッセージ仕様

| メッセージID | 種別 | メッセージ内容 | 表示条件 |
|-------------|------|---------------|----------|
| PUBLISH_001 | 情報 | bun publish v{version} | コマンド開始時 |
| PUBLISH_002 | 成功 | + {name}@{version} | 公開成功時 |
| PUBLISH_003 | エラー | missing `name` string in package.json | name不在 |
| PUBLISH_004 | エラー | missing `version` string in package.json | version不在 |
| PUBLISH_005 | エラー | attempted to publish a private package | private:trueの場合 |
| PUBLISH_006 | エラー | unable to restrict access to unscoped package | スコープなしでrestricted |
| PUBLISH_007 | エラー | missing authentication (run `bunx npm login`) | 認証情報なし |
| PUBLISH_008 | エラー | login is not allowed from your IP address | IPアドレス制限 |
| PUBLISH_009 | 警告 | Registry already knows about version {v}; skipping | 再公開スキップ時 |
| PUBLISH_010 | 情報 | This operation requires a one-time password. | OTP要求時 |

## 例外処理

| 例外条件 | 処理内容 | エラーコード |
|---------|---------|------------|
| package.json未検出 | エラーメッセージを表示して終了 | 1 |
| name/versionフィールド不在 | エラーメッセージを表示して終了 | 1 |
| private: trueの場合 | エラーメッセージを表示して終了 | 1 |
| 認証情報なし | エラーメッセージを表示して終了 | 1 |
| HTTP 400-5xx | レスポンスエラーを表示して終了 | 1 |
| 同一バージョン既存 | tolerate-republishでなければエラー | 1 |

## 備考

- publishConfigはpackage.json内で設定可能（tag、access）
- --dry-runでは実際のPUTリクエストをスキップ
- binフィールドはnpm形式に正規化される
- tarball内のパスはpackage/プレフィックスで統一
- SHA512はintegrity形式（sri）で出力

---

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

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

### 推奨読解順序

#### Step 1: データ構造を理解する

publishコマンドで使用される主要なコンテキスト構造体を理解します。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | publish_command.zig | `src/cli/publish_command.zig` | Context構造体（L2-21）：公開に必要な情報を保持 |

**読解のコツ**: Contextはジェネリック型で、`directory_publish`パラメータによりワークスペースからの公開とtarballからの公開を区別します。

#### Step 2: エントリーポイントを理解する

処理の起点となるexec関数を確認します。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | publish_command.zig | `src/cli/publish_command.zig` | exec関数（L290-448）：メイン処理ロジック |

**主要処理フロー**:
1. **L291-292**: バージョン情報を含むヘッダーを出力
2. **L294-304**: コマンドライン引数を解析しPackageManagerを初期化
3. **L307-354**: 位置引数によりtarballパスかワークスペースかを判断
4. **L337, L384**: publish関数を呼び出し
5. **L400-447**: publish/postpublishスクリプトを実行

#### Step 3: Context構築処理を理解する

tarballまたはワークスペースからContextを構築する処理を読み解きます。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | publish_command.zig | `src/cli/publish_command.zig` | fromTarballPath（L34-234）：tarballからのContext構築 |
| 3-2 | publish_command.zig | `src/cli/publish_command.zig` | fromWorkspace（L240-286）：ワークスペースからのContext構築 |

**主要処理フロー**:
- **L47-51**: tarballバイトを読み込み
- **L54-129**: Archive.Iteratorでtarball内容をイテレート
- **L133-182**: package.jsonを解析して検証
- **L184-196**: SHA1/SHA512ハッシュを計算
- **L198-208**: normalizedPackageでパッケージ情報を正規化

#### Step 4: publish処理を理解する

実際のレジストリへの公開処理を読み解きます。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | publish_command.zig | `src/cli/publish_command.zig` | publish関数（L531-730）：レジストリへのPUT処理 |

**主要処理フロー**:
- **L535-539**: 認証情報の確認
- **L541-555**: tolerate-republishによる既存バージョンチェック
- **L572-573**: リクエストボディとヘッダーを構築
- **L597-618**: PUT /packageでパッケージを公開
- **L620-730**: レスポンス処理とOTPフロー

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

```
PublishCommand.exec()
    │
    ├─ PackageManager.CommandLineArguments.parse(.publish)
    │
    ├─ PackageManager.init()
    │
    ├─ [tarballパス指定時]
    │      └─ Context.fromTarballPath()
    │             ├─ File.readFrom() - tarball読み込み
    │             ├─ Archive.Iterator.init() - tar展開
    │             ├─ JSON.parsePackageJSONUTF8() - package.json解析
    │             ├─ SHA1/SHA512計算
    │             └─ normalizedPackage() - パッケージ情報正規化
    │
    ├─ [ワークスペース指定時]
    │      └─ Context.fromWorkspace()
    │             └─ Pack.pack() - tarball作成
    │
    ├─ publish()
    │      ├─ checkPackageVersionExists() [tolerate-republish時]
    │      ├─ constructPublishRequestBody()
    │      ├─ constructPublishHeaders()
    │      ├─ AsyncHTTP.initSync().sendSync() - PUT request
    │      └─ [OTP必要時]
    │             ├─ getOTP() - OTP取得
    │             └─ 再度PUT request
    │
    └─ Run.runPackageScriptForeground() - publish/postpublishスクリプト
```

### データフロー図

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

package.json ─────────────▶ fromWorkspace/fromTarballPath ─────┐
または                                                         │
tarball                                                        │
                                                               ▼
                           ┌──────────────────────────────────────┐
                           │ Context                              │
                           │  - package_name                      │
                           │  - package_version                   │
                           │  - tarball_bytes                     │
                           │  - shasum (SHA1)                     │
                           │  - integrity (SHA512)                │
                           └──────────────────────────────────────┘
                                              │
                                              ▼
認証情報 ───────────────▶ constructPublishHeaders() ─────────────┐
(token/auth)                                                    │
                                              ┌─────────────────┘
                                              ▼
                           constructPublishRequestBody()
                                              │
                                              ▼
                                    PUT {registry}/{package}
                                              │
                                   ┌──────────┴──────────┐
                                   │                     │
                              [成功]                 [401 OTP要求]
                                   │                     │
                                   │               getOTP()
                                   │                     │
                                   │               再PUT request
                                   │                     │
                                   └─────────┬───────────┘
                                             ▼
                                    [成功メッセージ]
                                    + package@version
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| publish_command.zig | `src/cli/publish_command.zig` | ソース | publishコマンドのメイン実装 |
| pack_command.zig | `src/cli/pack_command.zig` | ソース | tarball作成（Pack.pack） |
| npm.zig | `src/install/npm.zig` | ソース | npmレジストリ通信、responseError |
| install.zig | `src/install/install.zig` | ソース | PackageManager、スコープ管理 |
| http.zig | `src/http.zig` | ソース | AsyncHTTP、HeaderBuilder |
| sha.zig | `src/sha.zig` | ソース | SHA1、SHA512ハッシュ計算 |
| open.zig | `src/open.zig` | ソース | ブラウザでURLを開く（WebベースOTP） |
