前言
最近在刷LeetCode上的题,想着怎么着得搞个文档记录下题目、思路跟解法吧,刚好前阵子使用GitHub的Actions
,结合 vuepress
,将文章自动部署到了GitHub的pages上。那不如也在GitHub上建个算法的文档呗。
但是算法的解题方法跟思路不一定只有几个,不同语言的实现方式也是不尽相同的,既如此,那就针对不同的算法题创建对应的issue作为评论区,以供大佬们展示各自的骚操作,这样一来,岂不是美滋儿滋儿~
但是手动创建这些个issues显然不够优雅,既然已经尝过 Actions
的甜头了,那这个活儿就交给它去做吧!
(Actions过来下!来活儿了!)
注:本文主要分享如何利用Actions自动创建issues,关于如果自动部署文档的方法,具体可以参考我的上一篇文章:
https://juejin.cn/post/7029247758582153252
开始
想要完成如题所述的功能,主要有两个步骤:
1. 创建issue
2. 通过Actions,将创建issue的过程自动化
创建issue
这里创建issue的方式肯定不是手动去GitHub的项目下创建。
这里推荐一个库: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,可以去账户设置中新建一个
文件创建完成后,在控制台中尝试运行下,看看是否已经可以创建issue了。
首先在 package.json
的 scripts
中添加 create:issue
命令
scripts: {
"create:issue": "node utils/createIssues.js"
}
然后执行
yarn create:issue
执行完脚本后,到GitHub相应的项目目录下,点开 Issues
选项:
当当~ 此时我们已经可以看到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会提供一些模板供我们使用,这里我们选择默认的即可:
新建完成后,会默认在我们的项目下生成一个 .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并没有被成功创建!此时不禁张飞问号:
原因是什么呢?我们点开控制台看下问题所在:
简而言之,就是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
选项中:
之后对脚本文件稍作修改,加入 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列表的方式:
我们再次对 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,完美解决这个问题~~
结语
行文至此,我们已经完成了通过GitHub Actions自动创建issues的功能。
有关于此文的项目跟源码,感兴趣的小伙伴可以戳这里~。欢迎小伙伴儿们star,并在issues中留下自己的骚操作~
此文中所提到的文档地址也已部署,可以戳这里~