GitHub Actions 自定义动作 #

自定义动作允许你封装可重用的功能。本节介绍如何创建JavaScript和Docker类型的动作。

动作类型 #

JavaScript Action #

使用Node.js编写,执行速度最快。

Docker Action #

使用Docker容器,环境隔离性好。

Composite Action #

组合多个步骤,适合简单封装。

JavaScript Action #

目录结构 #

text
my-action/
├── action.yml
├── index.js
├── package.json
└── node_modules/

action.yml #

yaml
name: 'My Action'
description: 学习如何创建JavaScript和Docker类型的自定义动作,封装可重用的功能。
author: 'Your Name'

inputs:
  my-input:
    description: 'Input description'
    required: true
    default: 'default value'

outputs:
  my-output:
    description: 'Output description'

runs:
  using: 'node20'
  main: 'dist/index.js'

branding:
  icon: 'check-circle'
  color: 'green'

index.js #

javascript
const core = require('@actions/core');
const github = require('@actions/github');

try {
  const myInput = core.getInput('my-input');
  console.log(`Input: ${myInput}`);
  
  const myOutput = 'output value';
  core.setOutput('my-output', myOutput);
  
  console.log(`Event: ${github.context.eventName}`);
  console.log(`Repo: ${github.context.repo.owner}/${github.context.repo.repo}`);
} catch (error) {
  core.setFailed(error.message);
}

package.json #

json
{
  "name": "my-action",
  "version": "1.0.0",
  "main": "index.js",
  "dependencies": {
    "@actions/core": "^1.10.1",
    "@actions/github": "^6.0.0"
  },
  "devDependencies": {
    "@vercel/ncc": "^0.38.1"
  },
  "scripts": {
    "build": "ncc build index.js -o dist"
  }
}

构建和打包 #

bash
npm install
npm run build

使用工具包 #

javascript
const core = require('@actions/core');
const github = require('@actions/github');
const exec = require('@actions/exec');
const io = require('@actions/io');
const toolCache = require('@actions/tool-cache');
const artifact = require('@actions/artifact');
const cache = require('@actions/cache');

// 输入输出
const input = core.getInput('input', { required: true });
core.setOutput('output', 'value');
core.setSecret('secret-value');

// 环境变量
core.exportVariable('MY_VAR', 'value');

// 路径
core.addPath('/path/to/bin');

// 调试
core.debug('Debug message');
core.warning('Warning message');
core.error('Error message');

// 组输出
core.startGroup('Group name');
console.log('Inside group');
core.endGroup();

// 掩码
core.setSecret('secret-value');

// 执行命令
await exec.exec('node', ['--version']);

// 文件操作
await io.cp('source', 'dest');
await io.mv('source', 'dest');
await io.rmRF('path');

// 下载工具
const toolPath = await toolCache.downloadTool('https://example.com/tool');

// 制品
const artifactClient = artifact.create();
await artifactClient.uploadArtifact('artifact-name', ['file1', 'file2'], '.');

// 缓存
await cache.saveCache(['path'], 'cache-key');
await cache.restoreCache(['path'], 'cache-key');

Docker Action #

目录结构 #

text
my-docker-action/
├── action.yml
├── Dockerfile
└── entrypoint.sh

action.yml #

yaml
name: 'My Docker Action'
description: 学习如何创建JavaScript和Docker类型的自定义动作,封装可重用的功能。
author: 'Your Name'

inputs:
  my-input:
    description: 'Input description'
    required: true
    default: 'default'

outputs:
  my-output:
    description: 'Output description'

runs:
  using: 'docker'
  image: 'Dockerfile'
  args:
    - ${{ inputs.my-input }}
  env:
    MY_ENV: value

branding:
  icon: 'box'
  color: 'blue'

Dockerfile #

dockerfile
FROM alpine:3.18

RUN apk add --no-cache bash curl jq

COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh

ENTRYPOINT ["/entrypoint.sh"]

entrypoint.sh #

bash
#!/bin/bash

set -e

# 获取输入
INPUT="${1}"
echo "Input: $INPUT"

# 执行任务
result=$(curl -s https://api.example.com/data | jq -r '.value')

# 设置输出
echo "my-output=$result" >> $GITHUB_OUTPUT

# 设置环境变量
echo "MY_VAR=value" >> $GITHUB_ENV

# 添加到PATH
echo "$HOME/mybin" >> $GITHUB_PATH

使用自定义动作 #

本地动作 #

yaml
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - uses: ./.github/actions/my-action
        with:
          my-input: value

发布到Marketplace #

  1. 创建Git标签
bash
git tag -a v1.0.0 -m "Release v1.0.0"
git push origin v1.0.0
  1. 在GitHub上发布Release

  2. 动作会自动出现在Marketplace

从其他仓库使用 #

yaml
steps:
  - uses: owner/repo@v1
    with:
      my-input: value

完整示例 #

API调用动作 #

javascript
// action.yml
name: 'API Call'
description: 学习如何创建JavaScript和Docker类型的自定义动作,封装可重用的功能。
inputs:
  url:
    description: 'API URL'
    required: true
  method:
    description: 'HTTP method'
    required: false
    default: 'GET'
  token:
    description: 'Auth token'
    required: false
outputs:
  response:
    description: 'API response'
runs:
  using: 'node20'
  main: 'dist/index.js'
javascript
// index.js
const core = require('@actions/core');
const github = require('@actions/github');

async function run() {
  try {
    const url = core.getInput('url', { required: true });
    const method = core.getInput('method') || 'GET';
    const token = core.getInput('token');
    
    const headers = {
      'Content-Type': 'application/json'
    };
    
    if (token) {
      headers['Authorization'] = `Bearer ${token}`;
    }
    
    const response = await fetch(url, {
      method,
      headers
    });
    
    const data = await response.json();
    
    core.setOutput('response', JSON.stringify(data));
    core.info(`API call successful: ${response.status}`);
  } catch (error) {
    core.setFailed(error.message);
  }
}

run();

文件处理动作 #

yaml
# action.yml
name: 'File Processor'
description: 学习如何创建JavaScript和Docker类型的自定义动作,封装可重用的功能。
inputs:
  files:
    description: 'Files to process'
    required: true
  output-dir:
    description: 'Output directory'
    required: false
    default: 'output'
outputs:
  processed-files:
    description: 'Number of processed files'
runs:
  using: 'docker'
  image: 'Dockerfile'
  args:
    - ${{ inputs.files }}
    - ${{ inputs.output-dir }}
dockerfile
# Dockerfile
FROM alpine:3.18

RUN apk add --no-cache bash

COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh

ENTRYPOINT ["/entrypoint.sh"]
bash
#!/bin/bash
# entrypoint.sh

FILES="$1"
OUTPUT_DIR="$2"

mkdir -p "$OUTPUT_DIR"

count=0
for file in $FILES; do
  if [ -f "$file" ]; then
    cp "$file" "$OUTPUT_DIR/"
    ((count++))
  fi
done

echo "processed-files=$count" >> $GITHUB_OUTPUT

测试动作 #

单元测试 #

javascript
// index.test.js
const core = require('@actions/core');
const run = require('./index.js');

jest.mock('@actions/core');

test('should set output', async () => {
  core.getInput.mockReturnValue('test-value');
  
  await run();
  
  expect(core.setOutput).toHaveBeenCalledWith('my-output', 'output value');
});

本地测试 #

bash
# 设置环境变量
export INPUT_MY-INPUT="test-value"

# 运行动作
node dist/index.js

使用act测试 #

bash
# 安装act
brew install act

# 运行工作流
act -j test

最佳实践 #

1. 使用TypeScript #

typescript
import * as core from '@actions/core';
import * as github from '@actions/github';

interface ActionInputs {
  myInput: string;
}

function getInputs(): ActionInputs {
  return {
    myInput: core.getInput('my-input', { required: true })
  };
}

async function run(): Promise<void> {
  try {
    const inputs = getInputs();
    core.info(`Input: ${inputs.myInput}`);
    core.setOutput('my-output', 'value');
  } catch (error) {
    if (error instanceof Error) {
      core.setFailed(error.message);
    }
  }
}

run();

2. 添加错误处理 #

javascript
try {
  // 动作逻辑
} catch (error) {
  core.setFailed(error.message);
}

3. 添加日志 #

javascript
core.debug('Debug message');
core.info('Info message');
core.warning('Warning message');
core.error('Error message');

4. 使用语义化版本 #

bash
git tag v1.0.0
git tag v1
git push origin v1.0.0 v1

5. 添加README #

markdown
# My Action

Description of my action.

## Inputs

| Input | Description | Required | Default |
|-------|-------------|----------|---------|
| my-input | Input description | true | '' |

## Outputs

| Output | Description |
|--------|-------------|
| my-output | Output description |

## Usage

```yaml
- uses: owner/my-action@v1
  with:
    my-input: value
text

## 下一步学习

- [组合动作](/docs/github-actions/actions/composite-actions) - 学习Composite Action
- [Marketplace动作](/docs/github-actions/actions/marketplace) - 探索更多动作
- [实战案例](/docs/github-actions/practice/ci-pipeline) - 查看完整示例

## 小结

- JavaScript Action执行速度最快
- Docker Action环境隔离性好
- 使用工具包简化开发
- 使用TypeScript提高代码质量
- 添加测试确保质量
- 使用语义化版本管理
最后更新:2026-03-28