依存関係の管理

サイトの npm 依存関係に対するインストール時の動作、更新手順、サプライチェーン制御

npm 依存関係はコミット済みの package-lock.json によって固定され、インストール時にはレビュー済みのライフサイクルスクリプトのみが実行されます。 これらの制御の背景にある脅威モデルと根拠については、Supply-chain security を参照してください。

インストール時の動作

CI、devcontainer、Netlify は、ロック固定かつスクリプト無効でインストールし、その後、レビュー済みのフック1つだけを明示的に再有効化します。 それは固定された Hugo バイナリを取得する hugo-extended のリビルドです。 リビルドは scripts/rebuild-hugo-extended.mjs を通じて実行され、制限付きバックオフでフェッチをリトライし、HUGO_* インストーラーオーバーライドが設定されている間は実行を拒否します。 インストールではオプショナルな依存関係を保持します。npm はプラットフォーム固有のバイナリ(たとえば sass-embedded 内の Dart Sass コンパイラー)を os/cpu で選択されるオプショナルな依存関係として配布するため、省略するとビルドが壊れます。 環境ごとの詳細は以下のとおりです。

  • CI: npm run ci:min を実行します。 サイトをビルドするジョブは続けて npm run ci:prepare を実行します。
  • Devcontainer: npm run install:safe を実行します。 同じ動作を保証します。
  • Netlify: npm run install:safe を実行します。 Netlify のビルドコマンドによって、不活性な自動インストールの後、クリーンなワーキングツリーのチェックの間に実行されます。
    • ロックのドリフトやその他の Git で検出可能な変更があればビルドが失敗します。
    • インストールが変更していないパスでの失敗については、後述の古い Netlify ビルドキャッシュを参照してください。
  • ローカル: npm run install:safe、または標準の npm install を実行します。 標準の npm install は、package.json と一致する間はロックに従い、ライフサイクルスクリプトを無効化するかわりに許可リストでゲートします。 詳細はローカルセットアップを参照してください。

ネストされた Docsy テーマのセットアップも同じ動作に従います。 prepare ステップが Docsy 自身のロック固定かつスクリプト無効のテーマ依存関係インストールを呼び出します。

古い Netlify ビルドキャッシュ

Netlify はデプロイコンテキストごとにビルドキャッシュを保持します。

  • プロダクション用に1つ
  • Deploy Preview 用にヘッドブランチ名ごとに1つ。 そのブランチ名の最初のビルド時にプロダクションキャッシュからシードされます。 キャッシュの系統は明示的なクリアでのみ消滅します。 ブランチの削除は Netlify からは見えないため、同じ名前で再作成されたブランチ(リサイクルされたボットブランチ名を含む)は古いキャッシュに再接続されます。

各キャッシュにはリポジトリのクローンが含まれており、git サブモジュールを削除するコミットをチェックアウトすると、サブモジュールのワーキングツリーがそのまま残ります。 そのため、削除されたサブモジュールがキャッシュに乗って後のビルドに未追跡の残留物として混入し、クリーンなワーキングツリーのチェックに失敗する可能性があります。 デプロイログには ?? 接頭辞のステータス行としてそのパスが表示されます。

パスを .gitignore に追加するのではなく、該当するビルドキャッシュをクリアしてください。

  • プロダクション:
    • Deploys > Trigger deploy からキャッシュをクリアしてサイトをデプロイします。
  • Deploy Preview: 既にビルドされた各ブランチは独自のキャッシュコピーを保持しており、プロダクションのクリア後も影響を受けません。
    • PR の最新デプロイページから Retry > Clear cache and retry with latest branch commit でクリアします。 ブランチ全体を一括でクリアする方法はありません。

依存関係の更新

通常のバージョンアップは Renovate PR として届き、リリースクールダウンでゲートされ、更新タイプごとにグループ化されます。 patch グループはより長いクールダウンの後に自動マージされ、minor および major の更新はレビュー後にマージされます。 OpenTelemetry npm パッケージはかわりに1つの PR としてまとめて届きます。 既知の脆弱性修正はアラート駆動で届きます(セキュリティ更新)。 残りのケースは手動です。 いずれの場合も、再生成したロックファイルを package.json の変更と一緒にコミットしてください。

マニフェストの変更

package.json を手動で編集した場合でも、npm run update:packages ですべてのバージョンをアップした場合でも(提供されるバージョンにはリリースクールダウンが適用されます)、変更されたマニフェストに合わせてロックファイルを同期します。

npm install --package-lock-only --ignore-scripts

npm update(後述)とは異なり、このコマンドはマニフェスト編集の影響を受けるロックエントリのみを書き換えます。 ただし、編集したパッケージだけにとどまらない場合があります。 バンプされたパッケージが置き換えられると、そのパッケージ固有の推移的依存関係(他のパッケージが依存していないもの)が、範囲とクールダウンが許す最新バージョンに再解決される可能性があるため、ロックの差分全体をレビューしてください。

ロックファイルのマージコンフリクトも同じ方法で解決します。 main のバージョンを採用し、上記のコマンドを再実行してください。

スクリプトを持つパッケージ

allowScripts エントリを持つ(または必要とする)パッケージを追加または更新する場合、変更を行うコントリビューターは以下を行います。

  1. 新しいバージョンのライフサイクルスクリプトをレビューする。
  2. 結果を、依存関係の変更と一緒にコミットし、PR レビューで検証する。 必要なスクリプトは正確なバージョン承認として、不要なスクリプトは名前レベルの拒否(false、以降のバージョンアップ時に更新不要)として記録する。
  3. 直接的依存関係の新しい承認の場合、.github/renovate.jsonc で Puppeteer ルールを参考に(配置も含め)、そのパッケージに専用の Renovate グループを作成し、自動マージから除外する。 承認済みパッケージのすべてのバージョンアップには上記の手順が必要なため、その更新 PR は共有の patch/minor グループを止めることなくコントリビューターを待つ必要がある。

ピア結合パッケージ

OpenTelemetry npm パッケージ(@opentelemetry/*)のうち、SDK パッケージは @opentelemetry/api の狭いバージョン範囲のみを受け入れます。SDK 3.0 以降はその範囲は 1 つのマイナーバージョンに限られ、@opentelemetry/instrumentation も同じ範囲を共有しています。 エクスポーターおよび Web 自動計装は、自身のリリースの SDK パッケージに依存します。 協調リリースの一部だけが揃った状態では、API の境界をまたぐとロック再生成が ERESOLVE で失敗し、そうでなくてもロック内に重複した OpenTelemetry パッケージがネストされた状態で解決される可能性があります。

共有グループに任せると、更新タイプをまたぐリリース(API の minor を伴う SDK の major)が major PR と共有の minor グループに分かれ、失敗したロックが minor グループの無関係なバンプを止めてしまいます。 .github/renovate.jsonc の OpenTelemetry npm packages ルールは、更新タイプをまたいで @opentelemetry/* のすべての更新を1つの PR にグループ化します。 その PR は以下のとおりです。

  • 共有ポリシーに従ってマージされます: patch のみのセットは自動マージ、それ以外のセットはレビュー後にマージされます。
  • リリースを延期するためにクローズすることはできません: 異なるターゲットバージョンを持つ更新を含む PR は、次回の実行で再作成されます。
  • 一部だけの状態でグリーンになる場合があります: 更新がクールダウンを異なる実行でクリアする可能性があります(段階的なパブリッシュ、または patch メンバーのより長いクールダウン)。パッケージリストをリリースと照合し、残りは経過期間を超えた時点で Renovate が追加するのを待ってください。

Hugo

Hugo のバージョンを指定する設定は 2 つあります。

  • package.json の hugo-extended:正確なピン指定です。すべてのビルドが使用するバイナリであり、サイトが検証されている唯一のバージョンです。
  • module-template.yaml の module.hugoVersion.min:下限です。これを下回ると Hugo が警告を出し、ビルドログチェックがその警告を CI の失敗に変えます。

サイトが Hugo の変更に依存するようになると、下限が引き上げられます。 たとえば、マウントの files パターンは、Hugo 0.166.0 で修正された glob エンジン(**/ がゼロ個のディレクトリにマッチしなくなった)を前提としています。

Hugo をバージョンアップするには、以下の手順を実行します。

  1. 対象となる Hugo のバージョン範囲について Docsy のアップグレードガイドに沿って作業する。
  2. package.json の hugo-extended のバージョンを設定し、ロックファイルを同期する。 (npm run update:hugo はかわりに最新バージョンを、スクリプトを有効にした状態でインストールします。承認がすでにそのバージョンを指定していない限り、strict-allow-scripts によって失敗します。)
  3. allowScripts の承認を更新し、npm run install:safe を実行して、レビュー済みのフックを通じてピン指定されたバイナリを取得する。
  4. サイトがその範囲内の変更に依存するようになった場合は、hugoVersion.min を引き上げる。

推移的依存関係のリフレッシュ

ロックファイル全体を再解決するスケジュールはありません(解決は意図的)。 推移的依存関係をリフレッシュするには、リポジトリルートで以下をオンデマンドで実行します(ロックファイルは scripts/generate-community-data ワークスペースもカバーしています)。

npm update --package-lock-only --ignore-scripts

特定のパッケージだけをリフレッシュするには、名前を指定します。

npm update --package-lock-only --ignore-scripts PACKAGE_NAME

PACKAGE_NAME をリフレッシュするパッケージ名(複数指定可)に置き換えてください。

いずれの場合も、リフレッシュ後のロック全体をレビューしてください。 npm は選択されたバージョンが必要とするものも移動させます。 また、マニフェストの宣言された範囲に従うため、親が範囲を広げると新しい推移的メジャーバージョンが入る可能性があります。

リリースクールダウンが適用されますが、注意すべきエッジケースがあります。 満たせるバージョンがすべてクールダウンより新しい依存関係(正確なピン指定がよくあるケースです)は、いずれかのバージョンが経過期間を超えるまで解決全体が失敗します(ETARGET)。 レビュー済みで問題ないと判断した新しいリリースの場合は、その名前だけを除外します。 クールダウンはツリーの残りの部分には引き続き適用されます。

npm_config_min_release_age_exclude=PACKAGE_NAME \
  npm update --package-lock-only --ignore-scripts

除外は呼び出しごとに指定してください。 .npmrc に恒久的なエントリを追加すると、その名前に対するクールダウンが永続的に免除されます。

予期しないロックファイルの変更

依存関係を変更していないのにロックファイルが変更された場合(postinstall チェックがインストール時にこれを検出すると警告します)、ドリフトを示しています。 ロックファイルを復元し、リライトをコミットするのではなく調査してください。

セキュリティ更新

既知の脆弱性修正は、週次の更新 PR を待たずにアラート駆動で届きます。

  • GitHub の Dependabot security updates: リポジトリ側の設定(dependabot.yml は不要)で、直接的および推移的依存関係にパッチを適用できます。 npm の場合、ロックファイルだけでなく親のマニフェストエントリの書き換えが必要になることがあります。
  • Renovate の脆弱性アラート PR: 直接的依存関係に対して即座にオープンされます。

重複は意図的なものであり、まれに PR が重複することは許容されています。 スケジュールされたロックの再解決は設計上無効であるため、これらのアラート駆動のパスが推移的修正の唯一の自動化されたルートです。 そのためリポジトリ側の設定は有効のままにしています。 どちらも PR を出さない場合は、メンテナーがパッケージを名前で指定してリフレッシュします(手動ルート)。 2つのパスはリリースクールダウンに対して異なる挙動を示します。

  • Dependabot security updates は、すべてのリリース経過期間ゲート(.npmrc を含む)を意図的にオーバーライドします。 クールダウンより新しい修正バージョンがランドする可能性があり、その検証はレビューするメンテナーの責任です。
  • Renovate の PR は、ロックファイルを再生成する際に .npmrc のゲートの対象となるため、クールダウンより新しい修正は失敗したアーティファクト更新として届きます。 早期に採用するには、メンテナーがスコープ付き除外を実行する必要があります。

親の宣言された範囲が修正バージョンを除外している場合、リフレッシュでは到達できません。 修正を取り込めるリリースがあれば親をバンプしてください。そうでなければ、ルートに overrides エントリを追加し、監査の REVIEWED_OVERRIDES レジスターに行を登録して、ロックファイルを同期してください。 親の宣言された範囲が変わると監査が失敗し、再評価を促します。

サプライチェーン制御

サプライチェーン監査

サプライチェーン監査テスト scripts/supply-chain-audit.test.mjs は、以下の制御と各セキュリティオーバーライドを、test:local-tools の実行ごとにコミット済みファイルのみから検証します。 これにより、制御の退行はインシデントを待たずにテスト失敗として検出されます。 監査自体の検証原則については、その設計ページを参照してください。

PR で監査が失敗した場合、アサーションメッセージに期待される条件が記載されています。 よくあるケースは以下のとおりです。

  • allowScripts エントリを持つ依存関係をバージョンアップした場合。 スクリプトを持つパッケージに従ってください。 失敗メッセージにエントリが移行すべきバージョンが示されます。
  • セキュリティオーバーライドの親の範囲を変更した場合。 新しい範囲が修正バージョンを取り込めるなら、オーバーライドを削除し、ロックファイルを同期して、その行の firstFixed より古いコピーが残っていないことを確認した上で行を削除してください。取り込めなければ、行の parentRange を更新してください。
  • ボットがセキュリティオーバーライドをバンプした場合。 行の spec を更新し、バンプが新しいアドバイザリーに対応する場合は firstFixed とアドバイザリー ID も更新してください。
  • インストールパスのスクリプト、.npmrc、または netlify.toml を変更した場合。 その失敗こそが目的です。 監査はインストール面を固定し、変更のたびに意図的なレビューが行われるようにしています。 変更に合わせて対応するアサーションを更新し、PR にその理由を記載してください。

グリーンにするためだけにアサーションを緩和しないでください。 各アサーションはこのページの制御を強制しているため、まず変更によってどの制御が緩和されるかを把握してください。

監査のスコープ外:

  • GitHub ワークフローファイル: トリガーとトークン権限はワークフローごとにレビューされます(CI ワークフローを参照)
  • Renovate 設定(.github/renovate.jsonc): コードと同様にレビューされるが、監査による固定の対象外
  • Docsy テーマ自身の依存関係インストール(上流で監査済み)
  • インストール境界を越えたビルド側の npm スクリプト: サイト自身のコードを丸ごと実行し、通常の開発で変更される

リリースクールダウン

バージョン解決では、設定された最小経過期間より新しいリリースは無視されます。

  • 適用: .npmrc の min-release-age。 scripts/generate-community-data サブプロジェクトは独立したロックホームではなく npm ワークスペースであるため、ルートの .npmrc とロックファイルがその解決も管理します。
  • スコープ:
    • 影響を受けるのはバージョン解決操作のみです。 ロック固定インストール(npm ci)はバージョンを解決しません。
    • npm はプロジェクト設定をユーザー設定より優先するため、ユーザーの .npmrc でより厳しいクールダウンを設定していても、ここのプロジェクト値に緩和されます。 特定の呼び出しで自分の設定を維持するには、npm_config_min_release_age 環境変数を設定してください。 この変数は両方の設定より優先されます。
  • Renovate: 開く更新 PR に独自のクールダウンを適用します。 .github/renovate.jsonc の minimumReleaseAge で設定されます。 人間のレビューなしでマージされる更新にはより長い期間が設定されます。 プリセット提供の3日間の npm クールダウン(security:minimumReleaseAgeNpm)は、これらの期間をオーバーライドできないよう(その経過期間の免除を含め)除外されています。 注意: そのプリセットの上流での名前変更は、暗黙的にクールダウンを再適用させます。 Renovate が日付を判定できない更新タイプ(pin、replacement、rollback など)はクールダウンの対象外です。 それらの PR は通常どおりオープンされ、恒久的に保留の安定性ステータス(必須チェックではない)が表示される場合があり、通常のレビューがゲートとなります。

ライフサイクルスクリプト許可リスト

インストール時にパッケージのライフサイクルスクリプトが実行されるのは、その正確な名前とバージョンが allowScripts 許可リストに記載されている場合のみです。

  • 適用: package.json の allowScripts マップ。 .npmrc の strict-allow-scripts によりフェイルクローズドとなります。
  • 評価されるタイミング: スクリプト有効のインストール時のみです。 具体的には、インストール時の動作の hugo-extended リビルドステップ(npm がインストール済みツリー全体をチェックする)と、--ignore-scripts なしで実行されるローカルの npm install です。
  • 拒否:
    • false に設定されたエントリは、レビュー済みの拒否を記録します。 パッケージはインストールされますが、そのスクリプトはスキップされます。
    • 拒否は何も許可しないため、バージョンをまたいで名前単位でパッケージをカバーします。
  • ローカルでのマスキング: ユーザーの .npmrc で ignore-scripts=true を設定すると、通常のローカル npm install はスクリプト無効になるため、許可リストの評価をスキップします。 ただし、リビルドステップは引き続き許可リストを評価するため、npm run install:safe はユーザー設定に関係なくリポジトリの態勢をチェックします。

npm バージョンフロア

アクティブな npm が engines フロアより古い場合、インストールは失敗します。 engines フロアとは、上記の制御をサポートする最も古いバージョンです。

  • 適用:
    • package.json の engines がフロアを設定します。 フロアを下回る npm は、許可リストの適用機能の一部または全部、および推移的リフレッシュの除外が依存する min-release-age-exclude 設定を欠いています。 engines の横のコメントにリリースが記載されています。 allowScripts をサポートしないバージョンはこのフィールドを黙って無視するため、スクリプト有効のインストールではすべてのインストールスクリプトが実行されます。
    • .npmrc の engine-strict が npm の EBADENGINE 警告を拒否に変えます。
  • フロアポリシー:
    • npm が制御の適用ギャップを修正するたびにフロアは引き上げられます。
    • コミット済みの .nvmrc は、バンドルされている npm がフロアを満たす Node.js リリースを固定しているため、CI、Netlify、nvm 管理のローカルセットアップは構造上これを満たします。 Renovate がこの固定値を最新に保ちます。 (lts/* のようなフローティングな .nvmrc ではこれを保証できません。CI ランナーは古い可能性のあるキャッシュから解決します。)
  • Netlify:
    • Netlify の Node バンドルのデフォルト npm はフロアより古い場合があります。 netlify.toml の NPM_VERSION でフロアを満たすバージョンを固定しています。
    • 少なくともフロアが引き上げられた際にはこの固定値も更新してください。

不活性な Netlify 自動インストール

ビルド開始時の Netlify の自動インストールは、netlify.toml の NPM_FLAGS によって無効化されます。

  • --dry-run: npm はインストールが何を変更するかを解決してログに記録しますが、何も書き込みません。
  • --ignore-scripts: ライフサイクルスクリプトは、ドライランの副作用としてではなく、明示的な指示により無効化されたままになります。

スコープ: NPM_FLAGS は Netlify のビルド設定であり npm の設定ではありません。 自動インストールにのみ適用され、ビルドコマンドの npm 実行には適用されません。

多層防御: 実際のインストールは npm ci であり、node_modules を丸ごと置換します。 そのため、自動インストールやビルドキャッシュの node_modules 内の残留物は、クリーンなワーキングツリーのチェックからは見えない(Git で検出可能な変更のみを見る)にもかかわらず、ビルドには持ち越されません。

素の npx の禁止

リポジトリの設定(パッケージスクリプト、CI、ヘルパースクリプト、コントリビュータードキュメント)は、npx BIN としてバイナリを呼び出しません。 node_modules が古いか存在しない場合、npx は公開レジストリにフォールバックし、その名前を持つパッケージが何であれ実行します。 インストールプロンプトは防御になりません。 非インタラクティブなコンテキストではスキップされ、その他の場合は反射的に「はい」を誘います。 ローカライズされたコントリビュータードキュメントのコピーは、ドリフトトラッキングを通じてこのルールに追従します。

  • 代替手段:
    • パッケージスクリプトは、依存関係が提供するバイナリを直接呼び出します。 npm が node_modules/.bin を PATH に追加するため、バイナリが存在しなければレジストリへの通信なしに大きなエラーで失敗します。
    • その PATH エントリがないコンテキスト(ドキュメント、スタンドアロンスクリプト)では、npm exec --no -- BIN を使用します。 これはインストールを行いません。
  • 適用: レビューの規律による。 自動化されたチェックはありません。