Как написать GitHub Action на TypeScript

Как написать GitHub Action на TypeScript

В этой статье я решил собрать самую основную информацию, необходимую для написания простейшего действия GitHub с использованием TypeScript.
Эта статья в первую очередь предназначена для новичков, тех, кто никогда не использовал экшены GitHub, но хочет быстро приступить к работе. Тем не менее, даже если у вас уже есть подобный опыт, но вы, например, не использовали ncc, вы можете найти в нем что-то полезное. Что ж приступим к изучению как написать GitHub Action на TypeScript.

Введение в GitHub Actions

В основе GitHub Actions лежит концепция Workflow — типа рабочего процесса, который запускается определенными триггерами. Репозиторий может содержать либо один рабочий процесс, либо несколько отдельных рабочих процессов, которые запускаются при разных условиях.

Для того чтобы добавить рабочий процесс, вам необходимо создать два или три файла yml в репозитории в директории.github / workflow. Наиболее простым файлом может быть такой:

name: Hello
on: [push]

jobs:
  Hello:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v2

      - name: Hello
        run: echo "Hello, GitHub Actions!"

В этом случае мы указываем, что хотим запустить рабочий процесс при отправке в ветку. Вы также можете сделать это по расписанию, вручную через пользовательский интерфейс или с помощью какого-либо другого триггера, например, при публикации общего ресурса или создании заявки.

Рабочий процесс можно найти на вкладке Actions в меню GitHub и посмотреть детальный отчет по нему.

Каждое задание состоит из одного или нескольких джобов. В данном случае это джоб Hello. В большинстве случаев задания не связаны между собой и могут выполняться одновременно на нескольких машинах, но при необходимости возможно определить зависимости между ними.

Каждый джоб имеет набор действий, которые необходимо выполнить последовательно на одной виртуальной машине. При этом каждый такой шаг может либо выполнить любую команду с помощью run, либо инициировать какое-то действие с помощью uses.

Эти действия являются отдельной частью рабочего процесса и могут быть использованы в нескольких проектах. Их может быть три типа: докер-контейнер, действие JavaScript или составные действия(которые, по сути, являются просто набором других действий, которые можно использовать повторно), а также. В этой статье мы рассмотрим вариант действия JavaScript, но для удобства напишем его на TypeScript.

(Более подробную информацию по возможностям рабочих процессов можно найти в документации)

Размещение действий в репозитории

В зависимости от потребностей действия можно разместить в репозитории в нескольких местах:

  • В подпапке .github/actions. Такой способ обычно используется когда вы хотите использовать эти действия из того же репозитория. В этом случае ссылаться на них необходимо по пути без указания ветки или тега:

    steps:
      - uses: ./.github/actions/{path}
  • В произвольном месте репозитория. Например, можно разместить несколько действий в корне репозитория, каждое в своей подпапке. Такой способ хорошо подходит, если вы хотите сделать что-то вроде личной библиотеки с набором действий, которые собираетесь использовать из разных проектов. В этом случае ссылаться на такие действия нужно по названию репозитория, пути и ветке или тегу:

    steps:
      - uses: {owner}/{repo}/{path}@{ref}
  • В корне репозитория. Такой способ позволяет разместить в одном репозитории только одно действие, и обычно используется если вы хотите позже опубликовать его в Marketplace. В этом случае ссылаться на такие действия нужно по названию репозитория и ветке или тегу:

    steps:
      - uses: {owner}/{repo}@{ref}
    
    

Действие на TypeScript

В качестве примера создадим очень простое действие, которое просто выводит сообщение Hello, GitHub Actions!. Для разработки действия нам потребуется установленная версия Node.js (я использовал v14.15.5).

Создадим в репозитории папку .github/actions. В ней создадим подпапку hello, в которой будем далее создавать все файлы, относящиеся к нашему действию. Нам потребуется создать всего четыре файла.

Файл action.yml:

name: Hello
description: Greet someone
runs:
  using: node12
  main: dist/index.js
inputs:
  Name:
    description: Who to greet
    required: true

Этот файл содержит метаданные нашей кампании. Благодаря своему присутствию GitHub понимает, что папка не содержит случайных файлов, а содержит некоторые действия, которые можно загрузить и выполнить.

Мы также указываем, что это именно JavaScript код, а не докер контейнер и говорим о точке входа: файл dist/index2.js. В данный момент у нас нет этого файла, но он появится в скором времени.

Набор входных и выходных параметров можно указать в этом файле, а также в соответствующем разделе. Этот случай является примером того что можно передать в качестве параметра имя для приветствия.

Более подробная информация сдесь.

Создаём файл package.json:

{
  "private": true,
  "scripts": {
    "build": "npx ncc build ./src/main.ts"
  },
  "dependencies": {
    "@actions/core": "^1.2.7",
    "@actions/exec": "^1.0.4",
    "@actions/github": "^4.0.0",
    "@actions/glob": "^0.1.2",
    "@types/node": "^14.14.41",
    "@vercel/ncc": "^0.28.3",
    "typescript": "^4.2.4"
  }
}

Это файл по умолчанию для Node.js. Чтобы не иметь никаких бесполезных атрибутов, таких как автор, лицензия и т. д., Вы можете просто указать, что пакет является частным.

В скриптах мы указываем один сценарий сборки, который будет запускать утилиту ncc. Эта утилита принимает файл src / main.ts в качестве входных данных и создает файл dist / index.js, который является точкой входа для нашего действия. Я вернусь к этой утилите чуть позже.

В скрипте не нужен npx и можно сделать просто "build": "ncc build ./src/main.ts".

В качестве зависимостей мы указываем typescript и @types/node для работы TypeScript. Зависимость @vercel/ncc нужна для работы ncc.

Зависимости @actions/* опциональны и являются частью GitHub Actions Toolkit — набора пакетов для разработки действий (я перечислил самые на мой взгляд полезные, но далеко не все):

  • Зависимость @actions/core понадобится вам вероятнее всего. Там содержится базовый функционал по выводу логов, чтению параметров действия и т.п. (документация)

  • Зависимость @actions/exec нужна для запуска других процессов, например dotnet. (документация)

  • Зависимость @actions/github нужна для взаимодействия с GitHub API. (документация)

  • Зависимость @actions/glob нужна для поиска файлов по маске. (документация)

Стоит отметить также то обстоятельство,что фактически наша работа будет компиляцией в один dist / index. js через ncc, все наши зависимость будут зависимостями времени разработки,т е лучше размещать ее как можно ближе к времени разработки, чем в зависимости, а в зависимости, или в devDependencies. Но это не так важно, т.к. все эти зависимости просто-напросто не будут использоваться во время выполнения.

Создаём файл tsconfig.json:

{
  "compilerOptions": {
    "target": "ES6",
    "module": "CommonJS",
    "moduleResolution": "Node",
    "strict": true
  }
}

В этом нет ничего сложного. В этом минимальном файле всё отлично, включая нормальную подсветку синтаксиса и IntelliSense в программе Visual Studio Code.

Создаём файл src/main.ts:

import * as core from '@actions/core'
async function main() {
  try {

    const name = core.getInput('Name');
    core.info(`Hello, ${name}`);

  } catch (error) {
    core.setFailed(error.message)
  }
}

main();

Здесь мы используем синхронную или асинкронную функцию main, которая вызывается в этом же файле. (Название функции также может быть произвольным).

Обертка тела функции должна быть обернута в блок try-catch для того, чтобы в случае любых ошибок сборку в GitHub можно было прервать. Без этого она всегда будет считаться успешной.

В данном конкретном случае мы также используем пакет @actions/core для чтения параметров и вывода сообщения в лог.

Восстановление пакетов из npm

Когда все файлы созданы, нам нужно восстановить все пакеты из npm. Затем, перейдя в папку действий (не в корень репозитория), выполните команду:

npm install

А вот здесь есть один момент. Все сценарии, включая зависимости, должны быть в одном репозитории, чтобы gitHub мог выполнить их правильно. Это означает, что нам, по существу, предлагается переместить папку node-modules в репозитории, что, на мой взгляд, является не очень хорошим решением.

Можно воспользоваться пакетом @vercel/nscc, который позволяет собрать js и ts-файлы в один единственый js-файл, который еще можно закоммитить в репозитарий.

Поскольку мы уже указали скрипт для сборки в файле package.json, нам осталось только запустить команду:

npm run build

В результате мы получим файл dist / index.js, который нужно будет зафиксировать в репозитории вместе с другими файлами. 

Тестируем действие

Создайте файл рабочего процесса в папке. github / workflows. Для того чтобы изменить его, мы сделаем так что он будет запускаться не по нажатию, а вручную.

name:Hello
on:
  workflow_dispatch:
    inputs:
      Name:
        description: Who to greet
        required: true
        default: 'GitHub Actions'
jobs:
  Hello:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v2

      - name: Hello
        uses: ./.github/actions/hello
        with:
          Name: ${{ github.event.inputs.Name }}

Здесь настройками workflow_dispat описывается форма в интерфейсе GitHub, в которую пользователь сможет ввести данные. Сдесь будет одно единственное поле для ввода имени. 

По окончании процесса, который мы запустили через событие, данные будут переданы в действие, которое мы запускаем с помощью параметра github-event.Name.

А также потому что действие тоже находится в репозитории, где находится рабочий процесс, нам обязательно нужно сделать checkout, чтобы все рабочие скрипты были доступны в рабочей папке.

После того, как мы запустим наш рабочий процесс, мы можем перейти к интерфейсу GitHub, на странице Actions выбрать наш рабочий процесс и запустить его исполнение, указав параметры:

После того, как мы запустим рабочий процесс, можем зайти в него и посмотреть, что получилось:

Настройка GitHooks 

В общем, все нормально, мы создали экшен с TypeScript и запустили его из рабочего процесса. 

Каждый раз, когда мы будем изменять код действия нам нужно не забыть вызвать команду:

npm run build

Мне кажется что такой ручной запуск будет постоянно приводить в ситуацию “я изменил код и отправил его на сервер а для меня ничего не изменилось. Я забыл переписать файлы.»

Есть несколько способов решить эту проблему. Вам не нужно беспокоиться и действительно каждый раз начинать сборку своими руками. В некоторых проектах люди делают это тщательно и запускают специальный рабочий процесс с помощью тех же действий GitHub, которые для каждой фиксации пытаются воссоздать действие и зафиксировать изменения.

Я решил проблему легче для себя и просто добавил гит хук, который автоматически восстанавливает все действия в репозитории для каждой фиксации. Для этого вам необходимо создать в репозитории папку .githooks и в ней файл предварительного подтверждения в следующем формате:

#!/bin/sh

for action in $(find ".github/actions" -name "action.yml"); do
    action_path=$(dirname $action)
    action_name=$(basename $action_path)

    echo "Building \"$action_name\" action..."
    pushd "$action_path" >/dev/null
    npm run build
    git add "dist/index.js"
    popd >/dev/null
    echo
done

На этом этапе мы собираем все файлы action.yml в папку .github / actions, а также для всех найденных файлов запускаем сборки из их папки.Внимание. В дальнейшем мы не будем задумываться о том, чтобы что-то менять в своих действиях, все будет происходить автоматически.

Чтобы хуки из папки .githooks запускались, нам необходимо поменять конфигурацию для текущего репозитория:

git config core.hooksPath .githooks

Или можно сделать это глобально (я сделал именно так):

git config --global core.hooksPath .githooks

Заключение

С помощью этой статьи я постарался максимально подробно описать все, что необходимо знать начинающему программисту для начала работы в GitHub и написания сценариев автоматизации на TypeScript.

Click to rate this post!
[Total: 0 Average: 0]

Leave a reply:

Your email address will not be published.