# @boost-jp/nagio-visual-diff-uploader

> 画面差分（ビジュアルリグレッションテスト）のスナップショットを Nagio backend にアップロードする CLI

Latest version **0.5.0** (published 2026-09-24) · MIT license · 0 weekly downloads

## Install

```sh
npm install @boost-jp/nagio-visual-diff-uploader
pnpm add @boost-jp/nagio-visual-diff-uploader
yarn add @boost-jp/nagio-visual-diff-uploader
bun add @boost-jp/nagio-visual-diff-uploader
```

Provides the command `nagio-visual-diff`.

## Health

**Score 70/100 (B)** — status: active.

Positive: has types; esm support; no vulnerabilities; recently updated; high maintenance score; high quality score.

Warnings: low downloads; pre 1.0.

## Facts

| | |
|---|---|
| Version | 0.5.0 |
| Published | 2026-09-24 |
| First published | 2026-09-24 |
| Weekly downloads | 0 |
| License | MIT |
| TypeScript types | bundled |
| Module format | ESM + CommonJS |
| Dependencies | 0 |
| Unpacked size | 270.2 KB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| Maintainers | yuuuutsk |

## Links

- npm: https://www.npmjs.com/package/@boost-jp/nagio-visual-diff-uploader
- Repository: https://github.com/boost-jp/nagio
- Homepage: https://nagio.studio
- Issues: https://github.com/boost-jp/nagio/issues
- npm.io page: https://npm.io/package/@boost-jp/nagio-visual-diff-uploader

## Recent versions

- 0.5.0 (latest) — 2026-09-24
- 0.4.0 — 2026-09-24

## README

# @boost-jp/nagio-visual-diff-uploader

VisualDiff（ビジュアルリグレッションテスト）のスナップショット（PNG）を nagio backend にアップロードする CLI です。
Playwright / Storybook / 任意の PNG 出力ディレクトリなど、スクリーンショットを吐くツールを問わず使えるようにするため、
`@boost-jp/playwright`（Playwright reporter）とは独立したパッケージにしています。

## なぜ別パッケージなのか

- VisualDiff は画像アップロードのため sha256 計算・生バイナリ送信が必要で、`@boost-jp/playwright` の「依存ゼロ」方針を守れなくなる可能性がある
- 失敗時に CI を落とすデフォルトの挙動を reporter とは変えたい（ネットワーク/認証エラー以外では CI を落とさない。差分の合否判定は GitHub Check Run「画面差分」で行う）
- Playwright 専用ではなく、Storybook や任意の PNG ディレクトリからも使いたい

並行数制限（`src/concurrency-limit.ts`）のような小さいユーティリティは、`@boost-jp/playwright` を import せず、このパッケージ内に自己完結で実装しています（パッケージ間の依存を増やさないため）。

## 設定ファイル

`nagio-visual.config.json` に書いておくと、CLI 実行のたびに同じ設定を使い回せます。
「CI では通るのに手元では設定が違う」という実行環境ごとのズレを防ぐ、
Chromatic の `chromatic.config.json` と同じ考え方です。

```json
{
  "app": "my-app",
  "env": "production",
  "dir": ".generated/visual-diff",
  "viewports": {
    "visual-chrome": "desktop-1920x1080",
    "visual-iphone-15": "mobile-393x852"
  }
}
```

- 置き場所は cwd から親へ遡って探す。`--config <path>` で明示もできる
  （明示したのに見つからない場合はエラーにする。黙って無視されるのが最も困るため）
- **トークンは書かないこと。** このファイルはコミットされる。`--token` か
  環境変数 `NAGIO_VD_TOKEN` で渡す
- 優先順位は **フラグ > 環境変数 > 設定ファイル > 既定値**。その場の指定が常に勝つ
- 未知のキーはエラーにする。綴り間違い（`viewPorts` など）を黙って無視すると
  「設定したのに効かない」という最も気づきにくい壊れ方をするため

### `env`（送信先の環境）

送信先を**環境名**で指定できる。URL を覚えたり設定ファイルに書き写したりしなくてよく、
ドメインが変わってもこのパッケージを上げれば追随する。

| 環境名 | 説明 |
|---|---|
| `production`（既定） | **空**。本番環境がまだ無いため |
| `dev01` | 共有 dev 環境（社外からは利用できません） |
| `local` | `http://localhost:8181` |

**既定の `production` は URL が空**にしてある。環境の指定漏れで dev へ書き込む事故を
防ぐためで、何も指定しなければ「送信先が決まっていません」というエラーで止まる。

解決順は **`--base-url` > `NAGIO_VD_BASE_URL` > 設定ファイルの `baseUrl` > 環境名**。
URL の直接指定が常に勝つ（環境名は省略記法という位置づけ）。

### `viewports`

「撮影ディレクトリ直下のサブディレクトリ名 → viewport 名」の対応。指定すると
**サブディレクトリごとに分けてアップロードする**（1 回のアップロードには viewport を
1 つしか指定できないため）。

Playwright の project ごとに viewport が違い、同じファイル名で撮ることがあるので、
撮影側は project 名のサブディレクトリに書き出す（`@boost-jp/playwright` の
`expect` が `NAGIO_VD_CAPTURE_DIR` 配下にその形で出す）。

`--viewport` を明示した場合は分割せず、`--dir` 全体を 1 回で送る。

## インストール

**このパッケージは npmjs.com で公開しています。**

```bash
npm install -D @boost-jp/nagio-visual-diff-uploader
```

## 使い方

```
nagio-visual-diff upload --app <visual_diff_app_key> --dir <dir> --base-url <url> --token <token> [options]
```

### 必須

| オプション | 説明 |
|---|---|
| `--app <key>` | VisualDiff アプリの key（例: `web-visual`）。`--suite` は非推奨のエイリアスとして使えるが `--app` への移行を推奨する（両方指定するとエラー） |
| `--dir <path>` | PNG を探すディレクトリ（再帰走査） |
| `--base-url <url>` | Nagio backend（connect-go）のベース URL（`NAGIO_VD_BASE_URL` でも指定可、フラグが優先）。**既定値は無い**（指定漏れに気づかず意図しない環境へ書き込む事故を防ぐため） |
| `--token <token>` | API トークン（`NAGIO_VD_TOKEN` でも指定可、フラグが優先）。**未指定ならエラーで終了**（exit code 1） |

### 任意

| オプション | デフォルト | 説明 |
|---|---|---|
| `--branch <branch>` | `GITHUB_HEAD_REF` \|\| `GITHUB_REF_NAME` \|\| `"local"` | ブランチ名 |
| `--commit <sha>` | `GITHUB_SHA` \|\| `"unknown"` | コミット SHA |
| `--message <text>` | 空文字 | コミットメッセージ |
| `--pr <number>` | `0` | PR 番号 |
| `--repository <owner/repo>` | `GITHUB_REPOSITORY` 環境変数（GitHub Actions が自動設定） | リポジトリ識別子。**現時点では backend には送信されません**（GitHub 連携の受け側の設計が未確定のため、CLI 側で受け取れる状態にしてある予約中の値です。ログには出力されます） |
| `--viewport <name>` | `"default"` | 全スナップショット共通の viewport 名 |
| `--browser <name>` | `"chromium"` | ブラウザ名 |
| `--concurrency <n>` | `5` | アップロード（およびファイルメタデータ計算）の並行数 |
| `--timeout-ms <n>` | `15000` | 1 リクエストあたりのタイムアウト（ms）。`CompleteBuildV1` を含む全リクエスト共通 |

`--fail-on-diff` / 設定ファイルの `failOnDiff` は **0.4.0 で廃止しました**。指定するとエラー終了します。差分の合否は GitHub Check Run「画面差分」で判定してください。

### 動作フロー

1. `--dir` を再帰走査して `*.png` を列挙する（0 件ならエラーで終了）
2. 各ファイルの sha256・width/height（PNG ヘッダから直接パース）・byteSize を計算する
3. `VisualDiffService.CreateBuildV1` でビルドを作成する
4. `VisualDiffService.ReserveSnapshotsV1` で全スナップショットを予約する（`uploadRequired: false` の分は既にストレージに同一ハッシュの画像がある = アップロード不要）
5. `uploadRequired: true` のエントリだけ、対応するファイルを `POST {baseUrl}/visual-diff/upload` に生バイナリで送る（`--concurrency` で並行数を制限。1 件失敗しても他は続行し、最後に失敗件数をまとめて報告。全件失敗した場合のみ exit code 1）
6. `VisualDiffService.CompleteBuildV1` を呼ぶ。backend は差分計算（pixelmatch）を待たず、ビルドを `comparing`（計算中）にして即座に応答する（画像差分計算の非同期化。0.4.0）。CLI はこれを待たず終了する
7. サマリを標準出力に出す

```
[nagio-visual-diff] ビルド <visualDiffBuildId> を作成しました（app=web-visual, branch=..., commit=...）
[nagio-visual-diff] 12 件中 3 件はアップロード済みのため送信をスキップしました
[nagio-visual-diff] 9 件をアップロードしました（失敗 0 件）
[nagio-visual-diff] ビルドが完了しました: status=COMPARING, diffCount=0, unreviewedDiffCount=0
[nagio-visual-diff] 比較は非同期に行われます。結果は GitHub の Check Run「画面差分」か nagio のビルド詳細で確認してください。
```

## エラーハンドリング

- `--token` 未指定、または backend が 401 相当（connect の `unauthenticated`）を返した場合は、はっきりしたエラーメッセージを stderr に出し **exit code 1** で終了します（CI を落とします）
- `CreateBuildV1` が 404 相当（connect の `not_found`）を返した場合、backend の応答自体は「対象が見つかりませんでした」としか言いません（越境アクセスと不存在を区別しない存在オラクル対策のため）。CLI 側で `アプリ "<--app の値>" が存在しないか、アクセス権がありません。Web UI で作成してください` という案内を追加します（アプリは CLI からは自動作成されません。事前に Web UI から Editor 権限で作成が必要です）
- connect のエラーレスポンス（`{"code": "...", "message": "..."}` の JSON、HTTP ステータスも 4xx/5xx）はパースしてメッセージに含めます
- ネットワークエラー・タイムアウトも非ゼロ終了します
- ビルドが正常に完了しさえすれば（`comparing` を含む。diff の有無に関わらず）exit code は 0 です。差分の合否は GitHub Check Run「画面差分」で判定してください

## 環境変数

| 変数 | 説明 |
|---|---|
| `NAGIO_VD_BASE_URL` | `--base-url` の代替。フラグが優先 |
| `NAGIO_VD_TOKEN` | `--token` の代替。フラグが優先 |
| `GITHUB_HEAD_REF` / `GITHUB_REF_NAME` | `--branch` 省略時の既定値解決に使用 |
| `GITHUB_SHA` | `--commit` 省略時の既定値解決に使用 |
| `GITHUB_REPOSITORY` | `--repository` 省略時の既定値解決に使用（GitHub Actions が自動設定する `owner/repo`）。現時点では backend には送信されません |

## CI（GitHub Actions）連携例

```yaml
- name: Upload VisualDiff snapshots
  run: node dist/cli.js upload --app web-visual --dir ./test-results/screenshots
  env:
    NAGIO_VD_BASE_URL: ${{ vars.NAGIO_VD_BASE_URL }}
    NAGIO_VD_TOKEN: ${{ secrets.NAGIO_VD_TOKEN }}
```

`NAGIO_VD_TOKEN` は GitHub Actions の secrets（`vars` ではなく `secrets`）で管理してください（API トークンは漏洩するとプロジェクトへの権限を持つ機密情報です）。

diff の有無で CI を落とすことはありません（人がレビューする前提のワークフローのため）。差分の合否は GitHub Check Run「画面差分」で確認してください。

## 冪等性について

`CreateBuildV1` は呼び出すたびに**新しいビルド**を作成します（backend 側でビルドの再利用はしません）。そのため、同じ `--dir` に対して CLI を複数回実行すると、その都度新しいビルドが作られます。
「未送信分だけ再送する」冪等性は、あくまで「同一ビルドに対して `ReserveSnapshotsV1` を再度呼び、既にアップロード済み（同一ハッシュ）のスナップショットは `uploadRequired: false` になり送信をスキップする」という粒度のものです。

**同一ビルドへの再送（再実行時に新規ビルドを作らず、指定した既存ビルドに追記する）は Phase 1 のスコープ外です。** 必要になった場合は `--build-id` のようなオプションを別途追加してください。

## status サブコマンド

**未実装です。** 現状は `upload` サブコマンドのみサポートしています。

## エクスポート

CLI 以外に、ライブラリとしても利用できます。

```ts
import {
  VisualDiffClient,
  VisualDiffApiError,
  isUnauthenticated,
  sha256File,
  parsePngSize,
  readPngSize,
  scanPngFiles,
  mapWithConcurrencyLimit,
  waitForVisualStability,
} from "@boost-jp/nagio-visual-diff-uploader";
```

- `VisualDiffClient`: `VisualDiffService`（connect-go）を叩く fetch ベースの薄いクライアント（`createBuild` / `reserveSnapshots` / `completeBuild` / `uploadSnapshot`）
- `sha256File(path)`: ファイルを sha256 でハッシュ化（ストリーム読み込み）
- `parsePngSize(buffer)` / `readPngSize(path)`: PNG の width/height を追加 npm 依存なしで取得（`image-size` 等は未使用。PNG シグネチャ + IHDR チャンクを直接パース）
- `scanPngFiles(dir)`: ディレクトリを再帰走査して `*.png` を列挙し、`--dir` からの相対パス（拡張子なし・区切りは `/`）を `testName` として決定する
- `mapWithConcurrencyLimit(items, limit, fn)`: 簡易な並行数制限ユーティリティ
- `waitForVisualStability(page, options?)`: 画面差分の撮影前にフォント読み込み・画像読み込みの完了を待ち、短い静止時間を置く簡易ヘルパ（`docs/visual-diff-setup.md` §2.5）。`@playwright/test` を直接依存に持たないよう、`evaluate`/`waitForTimeout` だけを持つ最小限の構造型（`StabilityPage`）を受け取ります。Playwright の `Page` はそのまま渡せます。

  ```ts
  import { test, expect } from "@playwright/test";
  import { waitForVisualStability } from "@boost-jp/nagio-visual-diff-uploader";

  test("商品一覧が正しく表示される", async ({ page }) => {
    await page.goto("/products");
    await waitForVisualStability(page);
    await expect(page).toHaveScreenshot("products-list.png");
  });
  ```

---
_Source: https://npm.io/package/@boost-jp/nagio-visual-diff-uploader · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
