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 #
- 创建Git标签
bash
git tag -a v1.0.0 -m "Release v1.0.0"
git push origin v1.0.0
-
在GitHub上发布Release
-
动作会自动出现在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