如何构建你的第一个 GitHub 工作流(下)

298 阅读5分钟

「这是我参与2022首次更文挑战的第27天,活动详情查看:2022首次更文挑战」。

我喜欢使用GitHub Actions。它们易于使用但功能强大。当我看到人们在使用他们来自动化不同的任务时有多么有创造力时,我感到兴奋。 如需阅读全文 ,请从上一篇开始 :如何构建你的第一个 GitHub 工作流(上)

5. 编写动作

在此阶段创建.gitignore文件很重要,以避免将不必要的文件推送到存储库。

我经常使用的一个很棒的工具是:https ://www.toptal.com/developers/gitignore

我的.gitignore文件是:

https://www.toptal.com/developers/gitignore/api/visualstudiocode,macos,node

创建一个特定于您的环境和项目的项目。

我们终于准备好创建我们的index.js文件了。这就是我们行动的所有逻辑所在。我们当然可以有一个更复杂的结构,但现在一个文件就可以了。

我已经注释了下面的所有代码,以便您逐步了解正在发生的事情。

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

const main = async () => {
  try {
    /**
     * We need to fetch all the inputs that were provided to our action
     * and store them in variables for us to use.
     **/
    const owner = core.getInput('owner', { required: true });
    const repo = core.getInput('repo', { required: true });
    const pr_number = core.getInput('pr_number', { required: true });
    const token = core.getInput('token', { required: true });

    /**
     * Now we need to create an instance of Octokit which will use to call
     * GitHub's REST API endpoints.
     * We will pass the token as an argument to the constructor. This token
     * will be used to authenticate our requests.
     * You can find all the information about how to use Octokit here:
     * https://octokit.github.io/rest.js/v18
     **/
    const octokit = new github.getOctokit(token);

    /**
     * We need to fetch the list of files that were changes in the Pull Request
     * and store them in a variable.
     * We use octokit.paginate() to automatically loop over all the pages of the
     * results.
     * Reference: https://octokit.github.io/rest.js/v18#pulls-list-files
     */
    const { data: changedFiles } = await octokit.rest.pulls.listFiles({
      owner,
      repo,
      pull_number: pr_number,
    });


    /**
     * Contains the sum of all the additions, deletions, and changes
     * in all the files in the Pull Request.
     **/
    let diffData = {
      additions: 0,
      deletions: 0,
      changes: 0
    };

    // Reference for how to use Array.reduce():
    // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce
    diffData = changedFiles.reduce((acc, file) => {
      acc.additions += file.additions;
      acc.deletions += file.deletions;
      acc.changes += file.changes;
      return acc;
    }, diffData);

    /**
     * Loop over all the files changed in the PR and add labels according 
     * to files types.
     **/
    for (const file of changedFiles) {
      /**
       * Add labels according to file types.
       */
      const fileExtension = file.filename.split('.').pop();
      switch(fileExtension) {
        case 'md':
          await octokit.rest.issues.addLabels({
            owner,
            repo,
            issue_number: pr_number,
            labels: ['markdown'],
          });
        case 'js':
          await octokit.rest.issues.addLabels({
            owner,
            repo,
            issue_number: pr_number,
            labels: ['javascript'],
          });
        case 'yml':
          await octokit.rest.issues.addLabels({
            owner,
            repo,
            issue_number: pr_number,
            labels: ['yaml'],
          });
        case 'yaml':
          await octokit.rest.issues.addLabels({
            owner,
            repo,
            issue_number: pr_number,
            labels: ['yaml'],
          });
      }
    }
/**
     * Create a comment on the PR with the information we compiled from the
     * list of changed files.
     */
    await octokit.rest.issues.createComment({
      owner,
      repo,
      issue_number: pr_number,
      body: `
        Pull Request #${pr_number} has been updated with: \n
        - ${diffData.changes} changes \n
        - ${diffData.additions} additions \n
        - ${diffData.deletions} deletions \n
      `
    });

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

// Call the main function to run the action
main();

6. 将我们的 Action 文件推送到 GitHub

让我们暂存、提交并将我们的文件推送到上游的主分支:

$ git status
On branch main
Your branch is up to date with 'origin/main'.

Untracked files:
  (use "git add <file>..." to include in what will be committed)
 .gitignore
 action.yml
 index.js
 package-lock.json
 package.json

nothing added to commit but untracked files present (use "git add" to track)

让我们添加所有要暂存的文件:

$ git add .

现在我们可以提交我们的更改:

$ git commit -m "Add main action structure"
[main 1fc5d18] Add main action structure
 5 files changed, 686 insertions(+)
 create mode 100644 .gitignore
 create mode 100644 action.yml
 create mode 100644 index.js
 create mode 100644 package-lock.json
 create mode 100644 package.json

并推动我们的改变:

$ git push origin main
Enumerating objects: 8, done.
Counting objects: 100% (8/8), done.
Delta compression using up to 16 threads
Compressing objects: 100% (7/7), done.
Writing objects: 100% (7/7), 5.82 KiB | 5.82 MiB/s, done.
Total 7 (delta 0), reused 0 (delta 0), pack-reused 0
To github.com:Link-/PR-metadata-action.git
   457fee2..1fc5d18  main -> main

7.如何测试我们的Action

为了让我们能够测试我们的操作,我们需要创建一个包。如果您在上一步中注意到,我们没有推送node_modules包含我们在构建文件时使用的包的index.js文件夹。

没有这些软件包,我们的行动将无法运行!为了解决这个问题,我们可以使用一个名为ncc的好工具。它将帮助我们创建一个文件,其中包含我们的代码和运行操作所需的所有包。

让我们从安装开始ncc

$ npm install @vercel/ncc

added 1 package, and audited 26 packages in 5s

found 0 vulnerabilities

编译我们的 JavaScript 就像运行一样简单:

$ ncc build index.js -o dist
ncc: Version 0.22.1
ncc: Compiling file index.js
530kB  dist/index.js
530kB  [845ms] - ncc 0.22.1

这将创建一个名为的新目录dist并创建一个名为的文件index.js,其中包含我们的代码和运行操作所需的所有包。

现在我们需要确保我们的action.yml文件包含正确的runs部分。您需要更换:

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

和:

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

让我们再次将我们的更改推送到上游(到我们的 GitHub 存储库)。确保我们的dist/文件夹不在.gitignore文件中:

$ git status
$ git add .
$ git commit -m "Add compiled action"
[main adfc4f0] Add compiled action
 4 files changed, 8505 insertions(+), 3 deletions(-)
 create mode 100644 dist/index.js
$ git push origin main

我们终于准备好创建我们的工作流程了!在相同或任何其他存储库(公共或私有无关紧要)中创建新工作流,如下所示:

mkdir -p .github/workflows
touch .github/workflows/pr-metadata.yaml

将以下工作流程复制到我们的pr-metadata.yaml文件中:

name: PR metadata annotation

on: 
  pull_request:
    types: [opened, reopened, synchronize]

jobs:

  annotate-pr:
    runs-on: ubuntu-latest
    name: Annotates pull request with metadata
    steps:
      - name: Annotate PR
        uses: link-/PR-metadata-action@main
        with:
          owner: ${{ github.repository_owner }}
          repo: ${{ github.event.repository.name }}
          pr_number: ${{ github.event.number }}
          token: ${{ secrets.GITHUB_TOKEN }}

完成所有这些步骤后,我们的存储库应如下所示:

构建你的第一个 github-final_repo

为了让我们测试这个工作流程,我们需要在我们的存储库中进行更改并创建一个拉取请求 (PR)。我们可以通过README.md直接在 GitHub 上编辑文件来做到这一点:

build-your-first-github_demo.gif

GitHub 操作最佳实践

最后,我想与你分享一些创建自定义操作时的最佳实践:

  • 采用单一职责原则。确保你的行动只做一件事。它将使你的代码更易于维护和测试。
  • 好好考虑一下你的操作界面(输入和输出)。通过减少可选输入的数量来保持界面简单明了。
  • 我们在本教程中没有这样做,但你需要验证您的操作输入! 大多数安全项目可以通过验证输入来消除。
  • 确保你的操作是幂等的,这意味着,如果你按顺序多次运行该操作,结果应该始终相同。在我们的例子中,动作应该执行并发表评论并添加标签,或者它应该优雅地退出。
  • 阅读并遵循这些 GitHub 文档中记录的安全强化最佳实践。
  • 如果你无法维护它,请不要创建新操作。在市场上搜索类似的操作并使用它们

结论

感谢读完这篇文章 ,希望你有所收获!