TypeScriptでGitHub Actionsカスタムアクション開発完全ガイド!CI/CDワークフローを効率化する実践的な作り方

GitHub Actionsカスタムアクションとは
GitHub Actionsのカスタムアクションは、繰り返し使用されるタスクを再利用可能なコンポーネントとして作成できる機能です。通常のワークフローファイルではステップごとに処理を記述しますが、カスタムアクションを使用することで複雑な処理をひとつのアクションにまとめることができます。
カスタムアクションには主に3つのタイプがあります:
# Docker コンテナアクション
runs:
using: 'docker'
image: 'Dockerfile'
# JavaScript アクション
runs:
using: 'node20'
main: 'dist/index.js'
# 複合アクション
runs:
using: 'composite'
steps:
- run: echo "Hello World"
shell: bash
特にJavaScriptアクション(TypeScriptで開発)は、高速に実行でき、GitHubのAPIとの連携が簡単で、デバッグしやすいため最も人気があります。開発したカスタムアクションは、同じリポジトリ内だけでなく、他のリポジトリからも使用できるため、チーム全体の開発効率を大幅に向上させることができます。
このトピックはこちらの書籍で勉強するのがおすすめ!
この記事の内容をさらに深く理解したい方におすすめの一冊です。実践的な知識を身につけたい方は、ぜひチェックしてみてください!
TypeScriptでカスタムアクション開発のメリット
TypeScriptでGitHub Actionsのカスタムアクションを開発することには、多くのメリットがあります:
型安全性と開発効率の向上
interface ActionInputs {
token: string;
repository: string;
branch?: string;
}
// 型チェックによりランタイムエラーを事前に防げます
function validateInputs(inputs: ActionInputs): boolean {
return inputs.token.length > 0 && inputs.repository.includes('/');
}
豊富なライブラリエコシステム
GitHub公式が提供する@actions/core
や@actions/github
といったライブラリを活用でき、複雑なAPI呼び出しやファイル操作を簡単に実装できます。
優れたデバッグ環境 Visual Studio Codeなどのエディタでブレークポイントを設定し、ローカル環境でステップ実行によるデバッグが可能です。これにより、GitHubにpushする前に問題を発見・修正できます。
保守性の高いコード TypeScriptの静的型チェックとコードの可読性により、長期的なメンテナンスが容易になります。また、リファクタリング時の安全性も大幅に向上します。
このトピックはこちらの書籍で勉強するのがおすすめ!
この記事の内容をさらに深く理解したい方におすすめの一冊です。実践的な知識を身につけたい方は、ぜひチェックしてみてください!
開発環境のセットアップ手順
TypeScriptでGitHub Actionsカスタムアクションを開発するための環境をセットアップしましょう。
必要なツールのインストール
# Node.js(推奨:LTS版)
# TypeScript
npm install -g typescript
# GitHub Actions用のライブラリ
npm init -y
npm install @actions/core @actions/github
npm install -D @types/node typescript ts-node @vercel/ncc
プロジェクト構造の作成
my-custom-action/
├── src/
│ └── main.ts
├── dist/
│ └── index.js
├── action.yml
├── package.json
└── tsconfig.json
TypeScript設定ファイル(tsconfig.json)
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
package.jsonの追加設定
{
"scripts": {
"build": "tsc",
"package": "ncc build dist/main.js --out dist",
"dev": "npm run build && npm run package"
}
}
@vercel/ncc
は、Node.jsアプリケーションを単一ファイルにバンドルするツールで、GitHub Actionsで使用する際に依存関係を含めてパッケージ化するために必要です。
このトピックはこちらの書籍で勉強するのがおすすめ!
この記事の内容をさらに深く理解したい方におすすめの一冊です。実践的な知識を身につけたい方は、ぜひチェックしてみてください!
基本的なカスタムアクションの実装例
実際にPull Requestにラベルを自動追加するカスタムアクションを作成してみましょう。
action.yml の定義
name: 'Auto Label PR'
description: 'Automatically adds labels to Pull Requests based on file changes'
inputs:
github-token:
description: 'GitHub token for API access'
required: true
label-rules:
description: 'JSON string of label rules'
required: true
default: '{"src/": "source-code", "docs/": "documentation"}'
outputs:
added-labels:
description: 'Labels that were added to the PR'
runs:
using: 'node20'
main: 'dist/index.js'
メインロジック(src/main.ts)
import * as core from '@actions/core';
import * as github from '@actions/github';
interface LabelRules {
[path: string]: string;
}
async function run(): Promise<void> {
try {
// 入力パラメータの取得
const token = core.getInput('github-token', { required: true });
const labelRulesInput = core.getInput('label-rules', { required: true });
const labelRules: LabelRules = JSON.parse(labelRulesInput);
// GitHub API クライアントの初期化
const octokit = github.getOctokit(token);
const context = github.context;
// Pull Request の変更ファイルを取得
const { data: files } = await octokit.rest.pulls.listFiles({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.payload.pull_request?.number || 0,
});
// ラベルの決定
const labelsToAdd = new Set<string>();
files.forEach(file => {
Object.keys(labelRules).forEach(path => {
if (file.filename.startsWith(path)) {
labelsToAdd.add(labelRules[path]);
}
});
});
// ラベルの追加
if (labelsToAdd.size > 0) {
await octokit.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request?.number || 0,
labels: Array.from(labelsToAdd),
});
core.setOutput('added-labels', Array.from(labelsToAdd).join(','));
core.info(`Added labels: ${Array.from(labelsToAdd).join(', ')}`);
} else {
core.info('No labels to add based on the current rules');
}
} catch (error) {
core.setFailed(`Action failed: ${error}`);
}
}
run();
ワークフローでの使用例
name: Auto Label PR
on:
pull_request:
types: [opened, synchronize]
jobs:
label:
runs-on: ubuntu-latest
steps:
- uses: your-username/auto-label-pr@v1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
label-rules: |
{
"src/": "source-code",
"docs/": "documentation",
"test/": "testing"
}
このカスタムアクションは、Pull Requestの変更ファイルのパスに基づいて、自動的に適切なラベルを追加します。
このトピックはこちらの書籍で勉強するのがおすすめ!
この記事の内容をさらに深く理解したい方におすすめの一冊です。実践的な知識を身につけたい方は、ぜひチェックしてみてください!
実践的なカスタムアクション開発のテクニック
プロダクションレベルのカスタムアクションを開発するための実践的なテクニックをご紹介します。
エラーハンドリングとロギング
import * as core from '@actions/core';
class ActionError extends Error {
constructor(message: string, public code: string) {
super(message);
this.name = 'ActionError';
}
}
async function safeApiCall<T>(
apiCall: () => Promise<T>,
errorMessage: string
): Promise<T> {
try {
return await apiCall();
} catch (error) {
core.error(`${errorMessage}: ${error}`);
throw new ActionError(errorMessage, 'API_ERROR');
}
}
// 使用例
const files = await safeApiCall(
() => octokit.rest.pulls.listFiles(params),
'Failed to fetch PR files'
);
設定の外部化とバリデーション
interface ActionConfig {
readonly githubToken: string;
readonly maxLabels: number;
readonly dryRun: boolean;
}
function getConfig(): ActionConfig {
const config = {
githubToken: core.getInput('github-token', { required: true }),
maxLabels: parseInt(core.getInput('max-labels') || '5'),
dryRun: core.getBooleanInput('dry-run'),
};
// バリデーション
if (config.maxLabels < 1 || config.maxLabels > 20) {
throw new ActionError('max-labels must be between 1 and 20', 'INVALID_CONFIG');
}
return config;
}
パフォーマンス最適化
// 並列処理でAPIコールを最適化
async function processFiles(files: any[], labelRules: LabelRules) {
const batchSize = 10;
const results = [];
for (let i = 0; i < files.length; i += batchSize) {
const batch = files.slice(i, i + batchSize);
const batchResults = await Promise.all(
batch.map(file => analyzeFileForLabels(file, labelRules))
);
results.push(...batchResults);
}
return results;
}
再利用可能なユーティリティ関数
// utils/github.ts
export class GitHubUtils {
constructor(private octokit: any, private context: any) {}
async getCurrentPR() {
if (!this.context.payload.pull_request) {
throw new ActionError('This action can only run on pull requests', 'INVALID_CONTEXT');
}
return this.context.payload.pull_request;
}
async addLabelsIfNotExist(labels: string[]) {
const existingLabels = await this.getExistingLabels();
const newLabels = labels.filter(label => !existingLabels.includes(label));
if (newLabels.length > 0) {
return this.addLabels(newLabels);
}
return [];
}
}
このトピックはこちらの書籍で勉強するのがおすすめ!
この記事の内容をさらに深く理解したい方におすすめの一冊です。実践的な知識を身につけたい方は、ぜひチェックしてみてください!
テストとデバッグの効率的な方法
カスタムアクションの品質を保つためには、適切なテストとデバッグが不可欠です。
ユニットテストの実装
// tests/main.test.ts
import * as core from '@actions/core';
import { run } from '../src/main';
// GitHub Actionsのモック
jest.mock('@actions/core');
jest.mock('@actions/github');
const mockCore = core as jest.Mocked<typeof core>;
describe('Auto Label PR Action', () => {
beforeEach(() => {
jest.clearAllMocks();
});
test('should add labels based on file changes', async () => {
// モックデータの設定
mockCore.getInput.mockImplementation((name: string) => {
switch (name) {
case 'github-token': return 'fake-token';
case 'label-rules': return '{"src/": "source-code"}';
default: return '';
}
});
// テスト実行
await run();
// アサーション
expect(mockCore.setOutput).toHaveBeenCalledWith('added-labels', 'source-code');
});
});
ローカルデバッグの設定
// scripts/debug.ts
import * as core from '@actions/core';
import { run } from '../src/main';
// ローカルテスト用の環境変数設定
process.env.INPUT_GITHUB_TOKEN = 'your-test-token';
process.env.INPUT_LABEL_RULES = JSON.stringify({
'src/': 'source-code',
'docs/': 'documentation'
});
// GitHub Contextのモック
const mockContext = {
repo: { owner: 'test-owner', repo: 'test-repo' },
payload: {
pull_request: { number: 123 }
}
};
// デバッグ実行
run().catch(error => {
console.error('Debug failed:', error);
});
実際のワークフローでのテスト
# .github/workflows/test-action.yml
name: Test Custom Action
on:
pull_request:
paths:
- 'action.yml'
- 'src/**'
- 'dist/**'
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Test action
uses: ./
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
label-rules: |
{
"src/": "test-source",
"docs/": "test-docs"
}
ログとデバッグ出力の活用
// デバッグレベルのログ出力
core.debug(`Processing ${files.length} files`);
core.info(`Labels to add: ${labelsToAdd.join(', ')}`);
core.warning('No matching files found for label rules');
// 条件付きデバッグ出力
if (core.isDebug()) {
core.debug(`Full file list: ${JSON.stringify(files, null, 2)}`);
}
適切なテストとデバッグ環境を整備することで、信頼性の高いカスタムアクションを効率的に開発できます。このガイドを参考に、あなたのCI/CDワークフローを効率化するカスタムアクションを作成してみてください。
このトピックはこちらの書籍で勉強するのがおすすめ!
この記事の内容をさらに深く理解したい方におすすめの一冊です。実践的な知識を身につけたい方は、ぜひチェックしてみてください!
おすすめコンテンツ
おすすめGitHub Actions2025/5/23GitHub Actionsワークフローが失敗する時の原因と解決法!初心者でも5分で修正できる実践ガイド
GitHub Actionsでワークフローが失敗して困っていませんか?よくあるエラーの原因と具体的な解決方法を、実際のコード例を交えて分かりやすく解説します。構文エラー、権限問題、依存関係のトラブルま...
続きを読む TypeScript2025/5/24ZodでTypeScriptの型安全バリデーション完全ガイド!フォーム検証からAPI連携まで実践例で学ぶ
TypeScriptのランタイムバリデーションライブラリZodの基本から実践まで完全解説。フォーム検証、API連携、Next.jsでの活用法を豊富なコード例で学べる初心者向けガイドです。
続きを読む IT技術2025/5/1TypeScript開発を劇的に効率化する13のベストプラクティス
TypeScriptプロジェクトの開発効率を高めるベストプラクティスを紹介します。プロジェクト設定から型活用テクニック、コードの最適化まで、実務で即役立つ具体例とともに解説し、TypeScriptの真...
続きを読む GitHub Copilot2025/5/29GitHub Copilotで開発効率を10倍にする実践的な使い方
GitHub Copilotを活用して開発効率を劇的に向上させる方法を解説します。インストールから基本的な使い方、高度な活用テクニックまで、実際のコード例を交えながら詳しく説明します。
続きを読む Vue.js2025/5/31Vue.js 3のComposition API完全ガイド!Reactユーザーでも5分で理解できる実践的な使い方
Vue.js 3の最新機能Composition APIを初心者向けに徹底解説。ReactのHooksとの比較、実際のコード例、よくあるエラーの解決法まで、実務で使える知識を分かりやすく紹介します。
続きを読む React2025/5/16TypeScriptでの型安全な状態管理:Zustandを使った実践的アプローチ
TypeScriptとZustandを組み合わせた型安全な状態管理の方法を学びましょう。シンプルでありながら強力な状態管理ライブラリZustandの基本から応用まで、実践的なコード例を交えて解説します...
続きを読む React2025/5/1Next.jsとTypeScriptで作る高速SSGブログ:初心者でも簡単に実装できる完全ガイド
Next.jsとTypeScriptを組み合わせたSSGブログの作り方を解説します。静的サイト生成の利点から実装方法、デプロイまで、初心者でも理解できるようステップバイステップで説明。コードサンプル付...
続きを読む TypeScript2025/5/16TypeScript非同期処理パターン完全ガイド:エラーハンドリングから並行処理まで
TypeScriptにおける非同期処理の基本から応用までを網羅。Promiseの使い方、async/awaitのベストプラクティス、エラーハンドリング、並行処理パターンまで実践的なコード例とともに解説...
続きを読む