【実務・中級編】GitHub Actionsの「Script step」を使い倒す!Node.js不要でOSコマンドを自在に操るGitHub Actions Toolkitの使い方 – バージョン管理・CI/CD活用バイブル

GitHub Actionsの「Script step」を極限まで使い倒す:Node.js不要でOSコマンドとAPIを自在に操るGitHub Actions Toolkitの真髄

テックリードの〇〇だ。日々のCI/CDパイプライン運用、ご苦労様。

「ちょっとした条件分岐を入れたいだけなのに、わざわざ別リポジトリにカスタムアクションを切るのは面倒だ」
「PRに自動でコメントをつけたいだけなのに、Node.jsのセットアップやモジュールのビルド(`npm run build`)でCIの秒数が削られていく……」

こんなフラストレーションを抱えていないか?
世の中の多くのチュートリアルは「とりあえず動くYAML」を書かせることだけに終始し、パイプラインの速度や保守性をどう爆上げするかという本質に触れていない。

今回は、GitHub Actionsに標準組み込みされている最強の武器、`actions/github-script` を用いて、外部スクリプトファイルなしでワークフローを極限まで最適化するプロの技を授ける。Node.jsの面倒なボイラープレートから解放され、OSコマンドとGitHub APIをシームレスに融合させる実践知を見ていこう。

—

1. なぜ `actions/github-script` なのか?

一般的なCI/CDでは、ちょっとしたAPI操作やファイル処理を行うためだけに、PythonやNode.jsの独立したスクリプトを用意し、依存関係(`package.json`など)を管理しがちだ。しかし、これはコンテキストスイッチを生み、メンテナンスコストを跳ね上げる。

`actions/github-script` は、ワークフローのYAML内に直接JavaScript(TypeScript記法も可)を埋め込み、認証済みのGitHubクライアント(`octokit`)と、強力なユーティリティ(`context`)をノータイムで利用できる神アクションだ。

さらに、内部でNode.jsランタイムがすでに動いているため、開発者が`actions/setup-node`を叩いて環境構築する手間すらない。

—

2. 現場で即効性のある実践テクニック

ここからは、実務で即座にコピペして使える、洗練されたパターンを紹介する。

テクニック①:PRの変更ファイル群に応じた動的ラベル付け

「特定の拡張子やパスが変更されたら、自動でラベルを付与してレビュアーのルーティングを最適化したい」
これは、外部スクリプトなしで数行のJSを書くだけで実装できる。

name: Dynamic Labeler
on:
pull_request:
types: [opened, synchronize]

jobs:
label-pr:
runs-on: ubuntu-latest
steps:

  • uses: actions/github-script@v7

with:
# 組み込みのgithubオブジェクトとcontextオブジェクトがそのまま使える
script: |
const { owner, repo, number } = context.issue;

// このPRで変更されたファイルの一覧を取得
const files = await github.rest.pulls.listFiles({
owner,
repo,
pull_number: number,
});

const changedFiles = files.data.map(f => f.filename);
const labelsToAdd = [];

// インフラ系の変更があれば ‘infra’ ラベル
if (changedFiles.some(f => f.startsWith(‘terraform/’) || f.endsWith(‘.tf’))) {
labelsToAdd.push(‘infra’);
}

// フロントエンドの変更があれば ‘frontend’ ラベル
if (changedFiles.some(f => f.startsWith(‘src/client/’))) {
labelsToAdd.push(‘frontend’);
}

if (labelsToAdd.length > 0) {
await github.rest.issues.addLabels({
owner,
repo,
issue_number: number,
labels: labelsToAdd,
});
console.log(`Successfully added labels: ${labelsToAdd.join(‘, ‘)}`);
}

テクニック②:OSコマンドの出力をJSの変数としてキャプチャする高度な連携

`actions/github-script` は単にAPIを叩くだけではない。Node.jsの `exec` などをうまく使うことで、Bashコマンドの実行結果をJSの世界に引き込み、条件制御を行うことができる。

name: Smart CI Gatekeeper
on: [push]

jobs:
check-commit-message:
runs-on: ubuntu-latest
steps:

  • uses: actions/checkout@v4
  • name: Analyze Commit & Run Conditional Logic

uses: actions/github-script@v7
with:
script: |
const { execSync } = require(‘child_process’);

// 直近のコミットメッセージを取得
const latestCommitMsg = execSync(‘git log -1 –pretty=format:%s’).toString();
console.log(`Latest Commit: ${latestCommitMsg}`);

// [WIP] が含まれていたら特定の処理をスキップさせるフラグを立てる等
if (latestCommitMsg.includes(‘[WIP]’)) {
core.warning(‘Work In Progress detected. Skipping strict compliance checks.’);
// GitHub Actionsの環境変数を出力して後続のステップで活用
core.exportVariable(‘SKIP_COMPLIANCE’, ‘true’);
} else {
core.exportVariable(‘SKIP_COMPLIANCE’, ‘false’);
}

—

3. チーム開発における設計思想とベストプラクティス

YAMLの中に長大なJavaScriptコードをベタ書きするのは、可読性を下げる最悪のアンチパターンだ。「数行のグルーピング処理」を超えた複雑なロジックを書きたくなったら、以下の構成ルールを厳守してほしい。

1. 外部スクリプトのモジュール化(`.github/scripts/` の活用)

複雑な処理は、リポジトリ内の専用ディレクトリにプレーンなJSファイルとして置き、`github-script` の `script` 引数から `require` で読み込むのがスマートだ。

ディレクトリ構成例:

.github/
├── workflows/
│ └── ci.yml
└── scripts/
└── notify-slack.js

`ci.yml` での呼び出し:

  • uses: actions/github-script@v7

with:
script: |
// リポジトリ内のスクリプトを直接読み込んで実行
const script = require(‘${{ github.workspace }}/.github/scripts/notify-slack.js’);
await script({ github, context, core });

このアプローチの最大のメリットは、VSCodeの補完(IntelliSense)やESLint、Unitテスト(Jestなど)の恩恵をそのまま受けられる点にある。CIのYAMLファイルを修正するたびにコミットをプッシュして動作確認する、という無駄なデバッグ地獄から完全に脱却できる。

—

4. プロが教える「隠れたハックと注意点」

秘技:エラーハンドリングと再試行(Retries)のインライン化

GitHub APIは稀にレートリミットや一時的な5xxエラーを返す。堅牢なパイプラインを作るためには、スクリプト内でラップ関数を用意するのが鉄則だ。

  • uses: actions/github-script@v7

with:
script: |
async function retry(fn, retries = 3, delay = 1000) {
try {
return await fn();
} catch (error) {
if (retries <= 0) throw error; core.warning(`API call failed. Retrying in ${delay}ms... Error: ${error.message}`); await new Promise(res => setTimeout(res, delay));
return retry(fn, retries – 1, delay 2);
}
}

// 堅牢なAPIコール
await retry(async () => {
await github.rest.repos.createCommitStatus({
owner: context.repo.owner,
repo: context.repo.repo,
sha: context.sha,
state: ‘success’,
context: ‘Custom CI/CD Gate’,
});
});

—

まとめ:CI/CDの速度と自由度は「書き方」で決まる

外部のアクションに頼りすぎると、バージョンアップの追従に追われ、ブラックボックス化したパイプラインに苦しむことになる。

`actions/github-script` を使いこなし、YAMLとJavaScriptの境界線をシームレスに往来できるようになれば、「こんな自動化がしたかったのに、適すアクションがない」という言い訳は二度とできなくなる。

今すぐ、あなたのリポジトリの冗長なステップをこの方法に置き換えてみてほしい。パイプラインが劇的に軽くなり、開発スピードが一段上のステージへ加速するはずだ。

タイトルとURLをコピーしました