Learn (Almost) Anything
КаталогСкачать курс

Автоматизация в GitHub

Структура Node-based action: `@actions/core`, `@actions/github`, packaging, runtime compatibility, outputs, error handling, logging и release artifacts.

ruЭнциклопедия33 урока6 модулей9
GitHub actionsАвтоматизация в GitHubМодель workflowсостояние и артефактыматрицы и runtimeрелизы и environmentsПереиспользование и собственные actionsgovernance и эксплуатацияgithubactions

JavaScript actions

Структура Node-based action: `@actions/core`, `@actions/github`, packaging, runtime compatibility, outputs, error handling, logging и release artifacts.

JavaScript actions

JavaScript action — это custom action, который выполняется прямо на runner через Node.js runtime, указанный в action.yml. В отличие от Composite actions, здесь логика живёт не как набор YAML-steps, а как JavaScript-код: можно нормально обрабатывать JSON, вызывать GitHub API, валидировать inputs, строить outputs и делать аккуратный error handling. В отличие от Docker container actions, JavaScript action не тащит свой Linux-контейнер и может работать на Ubuntu, Windows и macOS runners, если код не зависит от внешних бинарников.

Практическое правило: JavaScript action нужен, когда shell уже стал хрупким. Например, action должна прочитать pull request labels, найти изменённые packages, выставить output deploy-needed и оставить понятный лог. Такой код проще сопровождать в Node.js, чем в многострочном bash с jq, кавычками и платформенными отличиями.

flowchart TD A[Caller workflow step: uses owner/action@ref] --> B[action.yml] B --> C{runs.using} C -->|node20 / node24| D[Runner запускает dist/index.js] D --> E[@actions/core: inputs, outputs, logs] D --> F[@actions/github: context + Octokit API] E --> G[Step outputs и annotations] F --> H[GitHub API с правами job token]
flowchart TD
  A[Caller workflow step: uses owner/action@ref] --> B[action.yml]
  B --> C{runs.using}
  C -->|node20 / node24| D[Runner запускает dist/index.js]
  D --> E[@actions/core: inputs, outputs, logs]
  D --> F[@actions/github: context + Octokit API]
  E --> G[Step outputs и annotations]
  F --> H[GitHub API с правами job token]
Как JavaScript action связывает metadata, Node runtime, Toolkit и права caller job.

Структура repo

Минимальный JavaScript action repo обычно выглядит так:

my-js-action/
  action.yml
  package.json
  package-lock.json
  src/
    index.js
  dist/
    index.js
  README.md

action.yml — публичный контракт action. Он описывает имя, inputs, outputs и то, какой файл запускать:

name: "Detect changed package"
description: "Find a changed workspace package and expose it as an output"

inputs:
  token:
    description: "GitHub token for API calls"
    required: true
  base-ref:
    description: "Base git ref to compare against"
    required: false
    default: "main"

outputs:
  package:
    description: "Detected package name, or empty string"
  changed:
    description: "true when a package changed"

runs:
  using: "node24"
  main: "dist/index.js"

На момент актуальной GitHub Docs для JavaScript actions поддерживаются node20 и node24 в runs.using. Если action уже опубликована на старом runtime, migration лучше делать как обычный breaking-risk change: проверить зависимости, собрать новый dist, выпустить tag и явно описать совместимость. Это связано с Versioning и maintenance actions, а не только с синтаксисом YAML.

@actions/core: inputs, outputs и статус

Пакет @actions/core — основной слой между вашим Node-кодом и GitHub Actions workflow commands. Через него читают inputs, пишут outputs, управляют логами и помечают action как failed.

const core = require("@actions/core");

async function run() {
  try {
    const baseRef = core.getInput("base-ref") || "main";
    const token = core.getInput("token", { required: true });

    core.info(`Comparing against ${baseRef}`);

    const detectedPackage = "web";
    core.setOutput("package", detectedPackage);
    core.setOutput("changed", detectedPackage.length > 0 ? "true" : "false");

    core.notice(`Detected package: ${detectedPackage}`);
  } catch (error) {
    core.setFailed(error instanceof Error ? error.message : String(error));
  }
}

run();

Важная деталь: required: true в metadata описывает контракт, но надёжную runtime-валидацию лучше делать в коде через core.getInput("name", { required: true }). Outputs для JavaScript action объявляются в action.yml, но значение задаёт код через core.setOutput. Это отличается от composite action, где metadata мапит output через value: ${{ steps... }}. В caller workflow результат читается обычным способом, как в Step outputs и job outputs:

steps:
  - uses: actions/checkout@v6
  - id: detect
    uses: acme/detect-changed-package@v1
    with:
      token: ${{ github.token }}
      base-ref: main

  - run: echo "Package: ${{ steps.detect.outputs.package }}"

@actions/github: context и API

Пакет @actions/github даёт два главных инструмента: github.context и authenticated Octokit client. context похож по смыслу на данные из Contexts и expressions, но доступен уже внутри Node-кода: repo, owner, event payload, ref, actor, SHA.

const core = require("@actions/core");

async function run() {
  try {
    const github = await import("@actions/github");
    const token = core.getInput("token", { required: true });
    const octokit = github.getOctokit(token);
    const { owner, repo } = github.context.repo;
    const pullNumber = github.context.payload.pull_request?.number;

    if (!pullNumber) {
      core.info("Not a pull_request event; nothing to inspect.");
      core.setOutput("changed", "false");
      return;
    }

    const { data: files } = await octokit.rest.pulls.listFiles({
      owner,
      repo,
      pull_number: pullNumber,
      per_page: 100
    });

    const changed = files.some((file) => file.filename.startsWith("packages/web/"));
    core.setOutput("changed", changed ? "true" : "false");
  } catch (error) {
    core.setFailed(error instanceof Error ? error.message : String(error));
  }
}

run();

Права здесь задаёт не action, а caller job. Если API-запросу нужно читать PR files, создавать comments или писать checks, workflow должен выдать соответствующие Permissions и GITHUB_TOKEN. Action не должна молча предполагать broad token: лучше документировать минимальные permissions в README и падать с понятной ошибкой, если API вернул 403.

Logging: полезный шум, а не дамп payload

Для логов используйте core.info, core.debug, core.notice, core.warning, core.error, core.startGroup и core.endGroup. debug виден только при включённом debug logging, поэтому туда можно отправлять диагностические детали. warning и notice хороши для аннотаций, которые должны быть видны в UI workflow run.

Не печатайте целиком github.context.payload в production action. Payload может быть большим, шумным и содержать данные из untrusted источников: PR title, branch name, issue body, commit message. Это та же граница доверия, что в Secure use и threat model и pull_request_target и untrusted PR. Secrets GitHub старается маскировать, но action всё равно не должна превращать логи в свалку входных данных.

Если action создаёт человеческий отчёт, лучше писать короткий summary через workflow commands или отдельный файл, который caller загрузит как artifact. Для больших отчётов используйте Artifacts и reports, а не outputs.

Packaging: почему dist обычно коммитят

JavaScript action запускается из Git ref, который указал пользователь: owner/repo@v1, @v1.2.3 или SHA. Runner не обязан выполнять npm install перед запуском action. Поэтому published JavaScript action обычно содержит собранный dist/index.js вместе с зависимостями, упакованными bundler-ом. В современных шаблонах GitHub для JavaScript action это часто отдельная команда вроде npm run bundle, после которой action.yml указывает на dist/index.js.

Это непривычно для обычного Node.js package, где dist часто не коммитят. Для action repo логика другая: release artifact — это сам git tag с metadata и runnable dist. Если tag указывает на commit без собранного файла, consumer увидит падение уже в своём workflow.

ИнтерактивRelease-путь JavaScript action#int-1
Шаг 1 из 41. Описать контракт
В `action.yml` задаются `inputs`, `outputs` и `runs`. Для JavaScript action `runs.using` выбирает Node runtime, а `runs.main` указывает на runnable файл, обычно `dist/index.js`.
runs:
  using: "node24"
  main: "dist/index.js"
Короткий walkthrough того, что должно попасть в опубликованный tag, чтобы action запускалась у других repos без `npm install`.

Production-friendly release flow обычно такой:

name: release

on:
  release:
    types: [published]

jobs:
  verify:
    runs-on: ubuntu-24.04
    permissions:
      contents: read
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-node@v4
        with:
          node-version: 24
          cache: npm
      - run: npm ci
      - run: npm test
      - run: npm run bundle
      - run: git diff --exit-code dist/index.js

Последняя команда защищает от типичной ошибки: source изменили, а bundled dist забыли обновить. В реальном release workflow ещё добавляют semver tags (v1, v1.2, v1.2.3) и правила публикации, но это уже отдельная тема Versioning и maintenance actions.

Когда JavaScript action не подходит

JavaScript action не должна становиться универсальным deploy-сервисом с собственной политикой доступа. Job-level решения — environment, approvals, cloud credentials, permissions, matrix — должны оставаться в workflow или Reusable workflows. Action может помочь вычислить данные и выполнить узкую операцию, но не должна прятать критическую CD-политику от ревьюера.

Если action требует системные пакеты, конкретную версию Linux library или тяжёлый CLI, лучше посмотреть на Docker container actions. Если логика — всего лишь три shell-команды с понятными inputs, Composite actions будут дешевле и прозрачнее. Если вы берёте готовую action из чужого repo, применяются те же правила выбора и pinning, что в Marketplace actions и pinning.

See also

Внешние справки: GitHub Docs по creating a JavaScript action, metadata syntax, custom actions, release and maintain actions, а также официальный repo actions/toolkit.

Быстрое повторение #rc-1

Когда JavaScript action обычно лучше composite action с shell-steps?

Показать ответ

Когда логика стала хрупкой для shell: нужно нормально обрабатывать JSON, ходить в GitHub API, валидировать inputs, выставлять outputs и делать аккуратный error handling в Node.js.

Источники

  1. Creating a JavaScript action - GitHub Docs
  2. Metadata syntax reference - GitHub Docs
  3. About custom actions - GitHub Docs
  4. Releasing and maintaining actions - GitHub Docs
  5. actions/toolkit - GitHub
  6. actions/javascript-action - GitHub