前言

最近在刷LeetCode上的题,想着怎么着得搞个文档记录下题目、思路跟解法吧,刚好前阵子使用GitHub的Actions,结合 vuepress ,将文章自动部署到了GitHub的pages上。那不如也在GitHub上建个算法的文档呗。

但是算法的解题方法跟思路不一定只有几个,不同语言的实现方式也是不尽相同的,既如此,那就针对不同的算法题创建对应的issue作为评论区,以供大佬们展示各自的骚操作,这样一来,岂不是美滋儿滋儿~

image.png

但是手动创建这些个issues显然不够优雅,既然已经尝过 Actions 的甜头了,那这个活儿就交给它去做吧!

(Actions过来下!来活儿了!)

注:本文主要分享如何利用Actions自动创建issues,关于如果自动部署文档的方法,具体可以参考我的上一篇文章:
https://juejin.cn/post/7029247758582153252

开始

想要完成如题所述的功能,主要有两个步骤:

1. 创建issue
2. 通过Actions,将创建issue的过程自动化

创建issue

这里创建issue的方式肯定不是手动去GitHub的项目下创建。

image.png

这里推荐一个库:octokit。这个库提供了操作GitHub的能力,在这里,我们使用它来帮助我们创建issue。

当然,它所提供的能力不只是可以操作GitHub,官方所给的描述如下:

The `octokit` package integrates the three main Octokit libraries
1.  **API client** (REST API requests, GraphQL API queries, Authentication)
2.  **App client** (GitHub App & installations, Webhooks, OAuth)
3.  **Action client** (Pre-authenticated API client for single repository)

有兴趣的小伙伴可以戳链接自行探究,这里不做过多赘述~

第一步:老规矩,既然要用到它,那就先安装一把:

yarn add -D octokit

第二步:新建创建issue的执行文件

将刚才安装的 octokit 引入进来,开始编写创建issue的逻辑。

// utils/createIssues.js

const { Octokit } = require("octokit")
// 初始化,这里需要传入GitHub账号的token
const octokit = new Octokit({ auth: 'your token' })

octokit.rest.issues.create({
  owner: "your name", // GitHub账户名
  repo: "your project name" // 项目名称
  title: '删除排序数组中的重复项', // issue标题
  body: '关于删除排序数组中的重复项的更多解法,欢迎在issue中讨论~' // issue描述
})

初始化 octokit 的时候,需要GitHub账户的token,可以去账户设置中新建一个

image.png

文件创建完成后,在控制台中尝试运行下,看看是否已经可以创建issue了。
首先在 package.jsonscripts 中添加 create:issue 命令

scripts: {
  "create:issue": "node utils/createIssues.js"
}

然后执行

yarn create:issue

执行完脚本后,到GitHub相应的项目目录下,点开 Issues 选项:

image.png

当当~ 此时我们已经可以看到issue被成功创建!至此,我们第一个功能点就已经完成了!(撒花~)

通过Actions,将创建issue的过程自动化

现在,我们需要把创建issue的过程自动化。在这里,我们通过官方提供的创建Javascript的actions的方式来完成这个功能。

第一步:创建操作元数据文件

在项目根目录下,创建 action.yml文件:

name: 'GET TOKEN' # 名称
description: 'Get secrets token' # 描述
runs:
  using: 'node12' # 指定在node12的环境中执行
  main: 'utils/createIssues.js' # 指定需要执行的文件

第二步:新建Action

在GitHub项目中,选择 Actions, 新建一个workflow。在Actions界面,Git会提供一些模板供我们使用,这里我们选择默认的即可:

image.png
新建完成后,会默认在我们的项目下生成一个 .github/workflows/blank.yml 文件,用于配置脚本, 我们将其稍作修改:

# action名称
name: CREATE ISSUES

# action执行时机
on:
  # 在push时执行
  push:
    # 分支指定为master
    branches: [ master ]
  # 在PR阶段执行
  pull_request:
    # 分支指定为master
    branches: [ master ]
# 执行的任务列表
jobs:
  build:
    # 在ubuntu-latest平台执行
    runs-on: ubuntu-latest

    # 执行步骤
    steps:
      # 执行名称
      - name: Checkout
        # checkout代码
        uses: actions/checkout@v2
      - name: Install packages
        # 安装包
        run: npm install
      - name: Create an issue
        uses: ./ # 使用根目录下的action.yml

第三步:验证 && 发现问题

action创建完成并提交后,我们切到GitHub的 Actions 选项,等待脚本执行结束,此时再切换到 Issues 选项,此时发现,issue并没有被成功创建!此时不禁张飞问号:

image.png

原因是什么呢?我们点开控制台看下问题所在:

image.png
简而言之,就是token已经失效了!

那token为什么会失效呢?问题出在我们创建issue的 utils/createIssues.js 上。在这个文件里,我们初始化 octokit 时所传入的token是明文传入的,在上传执行action的时候,token会立即失效。

第四步:解决问题

不懂就问:那要怎么第三步中的问题呢?

答:既然问题出在明文传入token上,那咱们就不明文传入token呗!

不懂再问:那如何在 utils/createIssue.js 中以不明文的方式获取到token呢?

答:官方提供了一个工具包:@actions/core
通过这个包,我们可以获取到action中传过来的内容。

解决问题第一式:老规矩,既然要用它,那我们就安装它!
yarn add -D @actions/core
解决问题第二式:修改action执行脚本

既然说要通过action来获取token,那我们首先要让它抛出来才行。

回到用户设置界面,我们重新生成一个token,并将这个token设置到 项目设置中的 Secrets 选项中:

image.png

之后对脚本文件稍作修改,加入 with 配置

 # 省略前方好多代码...
      - name: Create an issue
        uses: ./ # 使用根目录下的action.yml
        with:
          token: ${{ secrets.TOKEN }} # 将TOKEN抛出
解决问题第三式:修改action.yml文件,定义输入项,用于接受token
name: 'GET TOKEN' # 名称
description: 'Get secrets token' # 描述
# 定义输入项
inputs:
  token:  # 输入项的名称
    description: 'Get secrets token' # 输入项的描述
    required: true # 输入项是否必须
runs:
  using: 'node12' # 指定在node12的环境中执行
  main: 'utils/createIssues.js' # 指定需要执行的文件
解决问题第四式:设置token

utils/createIssues.js 文件中添加 @actions/core 的引用,并获取token:

const { Octokit } = require("octokit")
const core = require('@actions/core')

const auth = core.getInput('token')
const octokit = new Octokit({ auth })

octokit.rest.issues.create({
  owner: "your name", // GitHub账户名
  repo: "your project name" // 项目名称
  title: '删除排序数组中的重复项', // issue标题
  body: '关于删除排序数组中的重复项的更多解法,欢迎在issue中讨论~' // issue描述
})

而后提交修改项,再次查看GitHub中的 Actions 选项,发现脚本运行无误,且 Issues 中也已经出现了新建的issue。

行文至此,功能完成已告一段落~

思考

通过上面的操作,我们现在已经实现了自动创建issue的功能,但是功能还不够完美,主要还存在以下两个问题:

1. 每次更新完文档,我必须手动修改issue的title与body,来保证我创建issue的正确性,这显然是不行的;
2. 每次重新执行,我如何保证issue没有被重复创建呢?

那该如何解决呢?

针对问题1:

既然不想手动,那就自动嘛~在 docs/.vuepress/config.js 中,我们可以获取到sidebar的配置,通过这个配置,我必然有方法可以获取到标题信息。

在这里,我借助了一个库:markdown-to-ast。这个库可以将markdown文件内容转换成 AST,我可以借此获取到标题内容:

const vuepressConfig = require("../docs/.vuepress/config")
const fs = require("fs")
const path = require("path")
const { parse } = require("markdown-to-ast")

const baseDir = '/notes/exercises/'
// 获取习题文件
const { themeConfig: { sidebar } } = vuepressConfig
const exercisesSidebar = sidebar[`${baseDir}`]
const exercisesFiles = exercisesSidebar.reduce((files, item) => {
 if ( item && item.children ) {
  files.push(...item.children)
 }
 return files
}, [])
// 获取标题与文件路径对应关系
const docs = exercisesFiles.map(fileDir => {
 const content = fs.readFileSync(path.resolve(__dirname, '../', `docs${baseDir}${fileDir}`), {
  encoding: 'utf-8'
 })
 const { children } = parse(content)
 const titleConfig = children.filter(ch => ch.type === 'Header' && ch.depth === 1)
 let title = ''
 if (titleConfig && titleConfig.length) title = titleConfig[0].raw.replace('# ', '')
 return {
  title,
  fileDir
 }
})

module.exports = { exercisesSidebar, exercisesFiles, docs }

之后我只需要在 utils/createIssues.js 中引入 docs,遍历获取 title 即可:

const { Octokit } = require("octokit");
const core = require('@actions/core');
const { docs } = require('./getAllDocs')

const auth = core.getInput('token')
const octokit = new Octokit({ auth })

const titles = docs.filter(doc => doc.title)

titles.forEach(title => {
 octokit.rest.issues.create({
  owner: "your name",
  repo: "your project name",
  title,
  body: `关于${title}的更多解法,欢迎在issue中讨论~`
 });
})

针对问题2:

那又该如何避免同名issue重复创建的问题呢?

如果我可以获取到当前已经被创建的活跃issues列表,然后做下筛选剔除,不就可以解决这个问题了么?

那我该如何获取到当前已创建的活跃issues呢?

答案是使用上文中我们提到的 octokit 库。它提供了获取活跃issues列表的方式:

image.png
我们再次对 utils/createIssues.js 文件做些修改:

const { Octokit } = require("octokit");
const core = require('@actions/core');
const { docs } = require('./getAllDocs')

const auth = core.getInput('token')
const octokit = new Octokit({ auth })

// 定义项目通用参数
const REPO_INFO = {
 owner: "your name",
 repo: "your project name"
}
// 获取所有issues
async function getAllIssues () {
 return await octokit.paginate(octokit.rest.issues.listForRepo, {
  ...REPO_INFO,
  per_page: 100,
 })
}
getAllIssues().then(data => {
 if (data && data.length) {
  let issuesList = data.map(issue => issue.title)
  // 获取没有创建issue的标题
  const titles = docs.filter(doc => !issuesList.includes(doc.title))
  // 将没有创建issue的算法都创建对应的issue
  titles.forEach(item => {
   const { title } = item
   octokit.rest.issues.create({
    ...REPO_INFO,
    title,
    body: `关于${title}的更多解法,欢迎在issue中讨论~`
   });
  })
 }
}).catch(err => { console.log(err) })

这样一来,我们就可以过滤到已经创建过的issues,完美解决这个问题~~

image.png

结语

行文至此,我们已经完成了通过GitHub Actions自动创建issues的功能。

有关于此文的项目跟源码,感兴趣的小伙伴可以戳这里~。欢迎小伙伴儿们star,并在issues中留下自己的骚操作~

此文中所提到的文档地址也已部署,可以戳这里~

参考文档 && 视频

崔大的教学视频~

利用Git Actions部署vuepress

创建Javascript的actions

octokit

@actions/core

markdown-to-ast