使用 TypeScript 创建自己的 GitHub Action

什么是 GitHub Action?

GitHub Action 允许用户简单地运行脚本,并在存储库上自动运行作业或工作流。这些作业和工作流可以由合并到分支、拉取请求、推送更新等事件触发。

我们可以使用 GitHub Actions 自动化的活动包括每次推送到主分支时的 linting、测试、构建和项目部署。

下面是使用 Typescript 创建自己的 GitHub Action 的分步过程!让我们从设置我们的项目开始吧!

项目设置

设置项目环境是必不可少的第一步。将此存储库用作启动器,这是 Chris 用于演示的。

首先,运行git clone https://github.com/ktrz/gh-action-js-marathon将存储库克隆到本地目录。

然后,使用 进入目录cd gh-action-js-marathon,使用 npm 安装依赖项,并运行构建脚本。要在目录中初始化节点项目,请运行 npm 命令:

npm init

此时,您将接受这些属性。

但是,由于该项目使用 TypeScript,因此必须先将其编译为 JavaScript,然后才能构建入口点,即dist/index.js.

在此之后,您将继续安装开发依赖项,例如@types/node. 为此,请运行:

npm i -D @types/node

要设置 TypeScript 项目,请包含一个tsconfig.json以让 TypeScript 知道源文件的位置以及输出目录的位置。

您可以使用以下配置和该项目的一些额外配置。

tsconfig.json文件

{
    "compilerOptions": {
      "target": "es2019",
      "module": "commonjs",
      "strict": true,
      "esModuleInterop": true,
      "skipLibCheck": true,
      "forceConsistentCasingInFileNames": true,
      "outDir": "dist",
      "lib": ["esnext"]
    },
    "references": [
      {
        "path": "./tsconfig.build.json"
      }
    ],
    "exclude": ["node_modules"]
  }

该行"outDir": "dist"告诉 TypeScript 编译器输出文件应该进入dist目录。

tsconfig.build.json文件

{
    "extends": "./tsconfig.json",
    "compilerOptions": {
      "types": ["node"]
    },
    "include": ["src/**/*.ts"],
    "exclude": ["src/**/*.spec.ts"]
  }

这些行"include": ["src/**/*.ts"], and "exclude": ["src/**/*.spec.ts"]告诉 TypeScript 编译器忽略任何以 结尾的文件名,并在目录spec.ts中包含以结尾的每个文件。.ts``src

下一步是编辑 package.json 文件,将测试脚本替换为构建脚本"build": "tsc -p tsconfig.build.json"

要测试构建脚本,请创建一个src目录,然后创建一个index.ts包含以下代码的文件:

console.log(‘Hello World!’)

然后,运行构建 TypeScript 代码的脚本npm run build,并为项目生成一个 JavaScript 文件。

要清理,请添加一个gitignore文件。对文件使用以下内容:

node_modules
dist
.idea
.vscode

GitHub Action设置

继续创建一个 GitHub Action 文件,它是一个名为action.yml. 这将定义项目和 GitHub Action 配置的入口文件是什么。

action.yml文件

name: 'Consonant vowel ratio'
author: 'Chris Trzesniewski'
description: 'Custom GH action with TypeScript'

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

属性nameauthor和上方的详细信息description包括yml操作文件的元数据。

run属性是配置中最重要的部分。它通知开发人员使用什么来运行动作。对于这个项目,它是节点 16。

Action 的目录与构建脚本目录相同,即dist/index.js. 在这种情况下,如果我们构建 Action 并将其部署到存储库上的单独分支,这应该允许任何人指向存储库并使用该 Action。

出于本次培训的目的,我们将所有这些设置在一个目录中。这将是.github/workflow存储库中的一个目录,我们将在其中使用以下配置创建一个测试操作:

.github/workflow/test.yml文件

name: Test
on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main

jobs:
  run-action:
    name: Run action
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v2

      - name: Use Node.js 16.x
        uses: actions/setup-node@v2
        with:
          node-version: 16.x
          cache: 'npm'
          cache-dependency-path: package-lock.json

      - name: Install dependencies
        run: npm ci

      - name: Build
        run: npm run build

      - name: Run my action
        uses: ./

使用此处的属性,我们首先需要name为 Action 定义一个,即Test.

然后,我们定义触发工作流的内容,即on属性的来源。在此之后,我们将创建推送到主分支或针对主分支创建的拉取请求。

在 jobs 属性上,我们定义了一个我们想要运行的作业的映射。这将具有 name 属性,用于显示我们正在运行的名称。在这里,我们要运行 GitHub 提供的 Linux 虚拟机环境。

接下来,查看存储库:- uses: actions/checkout@v2. 这是 GitHub 提供的开箱即用的 GitHub Action。

然后,我们建立一个节点环境来运行我们项目的实际脚本就行了uses: actions/setup-node@v2。这将安装node-version 16,然后缓存 npm,因此在后续运行中不会花费很长时间。

使用命令安装项目依赖npm ci,和平时一样npm install。但是,npm ci用于安装所有确切的版本依赖项,或来自包锁的 devDependencies。

现在是时候使用 run 属性构建我们的项目了run: npm run build

最后,我们将指向项目中的一个目录。对于我们的例子,它是当前工作目录,GitHub 会在action.yml那里找到该文件。

GitHub 将能够识别我们在 action.yml 中定义的入口点。

要测试拉取请求触发器,您可以创建另一个分支并调用它feat/setup-action。发布新分支,导航到远程存储库,然后创建拉取请求。这应该触发工作流运行。

GitHub Action依赖项

现在是时候从 GitHub 安装一些依赖项了。这些依赖项允许您从 GitHub Runner 检索有关触发操作的事件的信息。

运行 npm 安装命令npm install @actions/core @actions/github

编辑src/index.ts文件以将其替换为以下代码:

import { getInput } from "@actions/core";

type GithubContext = typeof context;

const inputName = getInput("name");

greet(inputName);

function greet(name: string, repoUrl: string) {
  console.log(`'Hello ${name}! You are running a GH Action in ${repoUrl}'`);
}

现在,您应该修改action.yml文件,并添加以下代码。

最终文件应如下所示:

name: 'Consonant vowel ratio'
author: 'Chris Trzesniewski'
description: 'Custom GH action with TypeScript'

inputs:
  name:
    description: 'Name to greet'
    required: false
    default: 'JS Marathon viewers'

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

提交更改并推送到远程存储库。工作流将触发并再次运行。你应该看到’Hello JS Marahon viewers!’就行了Run my action

这是默认值。但是您也可以通过编辑.github/workflow/test.yml文件来为操作提供值。

最终文件应如下所示:


name: Test
on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main

jobs:
  run-action:
    name: Run action
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v2

      - name: Use Node.js 16.x
        uses: actions/setup-node@v2
        with:
          node-version: 16.x
          cache: 'npm'
          cache-dependency-path: package-lock.json

      - name: Install dependencies
        run: npm ci

      - name: Build
        run: npm run build

      - name: Run my action
        uses: ./
        with:
          name: 'This Dot Labs'

提交代码,然后推送到远程存储库。

在第 4 行Run my action,您将看到发给赞助商的新消息。

检索数据

要从 GitHub 检索数据,我们将使用第二个依赖项。这一次,修改src/index.ts. 该文件应如下所示:

mport { getInput } from "@actions/core";
import { context, getOctokit } from "@actions/github";
import dedent from 'dedent'

type GithubContext = typeof context;

const inputName = getInput("name");

greet(inputName, getRepoUrl(context));

function greet(name: string, repoUrl: string) {
  console.log(`'Hello ${name}! You are running a GH Action in ${repoUrl}'`);
}

function getRepoUrl({ repo, serverUrl }: GithubContext): string {
  return `${serverUrl}/${repo.owner}/${repo.repo}`;
}

导入上下文,这是将由 Action 运行器提供的对象,以获取有关我们将针对其运行此工作流的存储库的基本数据,以及触发该操作的有效负载。

要了解有关此内容的更多信息,请查看有关Webhook 事件和有效负载的 GitHub 文档。

要获取 repo URL,请创建一个getRepoUrl接受 git 上下文的函数。这将解构属性reposerverUrl,并且该函数将返回存储库的远程 URL。

GitHub 差异

使用 GitHub API,我们可以访问当前头部和拉取请求所引用的目标分支之间的差异。

已安装的依赖项带有一个OctoKit模块。我们还需要 GitHub 令牌才能正常工作。返回该src/index.ts文件,并将其修改为如下所示的文件。

mport { getInput } from "@actions/core";
import { context, getOctokit } from "@actions/github";
import dedent from 'dedent'

type GithubContext = typeof context;

const inputName = getInput("name");
const ghToken = getInput("ghToken");

greet(inputName, getRepoUrl(context));

getDiff().then(files => {
    console.log(dedent(`
    Your PR diff:
    ${JSON.stringify(files, undefined, 2)}
    `))
})

function greet(name: string, repoUrl: string) {
  console.log(`'Hello ${name}! You are running a GH Action in ${repoUrl}'`);
}

function getRepoUrl({ repo, serverUrl }: GithubContext): string {
  return `${serverUrl}/${repo.owner}/${repo.repo}`;
}

async function getDiff() {
  if (ghToken && context.payload.pull_request) {
      const octokit = getOctokit(ghToken)

      const result = await octokit.rest.repos.compareCommits({
          repo: context.repo.repo,
          owner: context.repo.owner,
          head: context.payload.pull_request.head.sha,
          base: context.payload.pull_request.base.sha,
          per_page: 100
      })

      return result.data.files || []
  }

  return []
}

让我们来看看 getDiff 函数。

首先,检查您是否拥有 GitHub 令牌,以及上下文负载是否存在。

然后,为 GitHub REST API 访问声明并分配 octokit。然后,调用 compareCommits 方法,并从 GitHub 上下文传递属性。然后,返回结果文件或空数组。

如果您没有,则令牌或拉取请求将返回一个空数组。

将文件修改action.yml为如下所示以访问 GitHub 令牌:

name: 'Consonant vowel ratio'
author: 'Chris Trzesniewski'
description: 'Custom GH action with TypeScript'

inputs:
  name:
    description: 'Name to greet'
    required: false
    default: 'JS Marathon viewers'
  ghToken:
    description: 'Github access token'
    required: false

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

最后,修改.github/workflow/test.yml传递 GitHub 令牌:

name: Test
on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main

jobs:
  run-action:
    name: Run action
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v2

      - name: Use Node.js 16.x
        uses: actions/setup-node@v2
        with:
          node-version: 16.x
          cache: 'npm'
          cache-dependency-path: package-lock.json

      - name: Install dependencies
        run: npm ci

      - name: Build
        run: npm run build

      - name: Run my action
        uses: ./
        with:
          name: 'This Dot Labs'
          ghToken: ${{ secrets.GITHUB_TOKEN }}

现在,提交代码并推送到远程存储库以测试最终结果。

结论

GitHub Actions 是在您的项目中实施 CI/CD 的强大工具,无论大小如何,都可用于在部署和暂存之前测试代码。