Skip to content
ABFree Studio

Turborepo + GitHub Actions + CF Pages:打造完美的持续部署流程

背景

我有多个前端项目使用Turborepo管理起来的,之前一直关联在Vercel进行部署,Vercel确实也挺好用,但是平台限制和朋友推荐让我想拿其中两个项目做一次测试。

为了使开发,预览,发布流程更加丝滑,迁移到Cloudflare Pages还需要解决下面几个问题:

  1. 怎么使用Git Tag管理多个项目的发布、预览版本
  2. 怎么使用Github Action自动化打包、部署到Cloudflare Page平台

如何使用Git Tag管理多项目,多版本

Vercel在每次提交都会帮我们构建一个Preview版本,Cloudflare暂时没有这个功能,但是实现起来也不难。

首先我们要定义好一个规范:什么情况下才触发构建?构建Preview还是Production?

这里我使用了Tag来触发构建,Tag规范如下:

Terminal window
//* @前面代表了某个项目
// 后面代表了版本
// -rc 代表是否是Preview版本

这样我们在需要发布的时候可以再当前版本打一个Tag,然后推送,Github Action根据流水线脚本进行构建,Github Action的触发条件如下:

.github/workflows/deploy.yml
on:
push:
tags:
- '*@*.*.*-rc' # 匹配预发布版本
- '*@*.*.*' # 匹配正式版本

因为我们要在脚本区分项目、版本、是否为预览,所以要加上解析Tag的脚本方便后续的判断,即下面steps里面的的 Parse Tag

.github/workflows/deploy.yml
jobs:
deploy-prod:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup Node.js
uses: pnpm/action-setup@v4
- name: Parse tag
id: parse-tag
run: |
TAG=${{ github.ref_name }}
PROJECT=$(echo $TAG | cut -d'@' -f1)
VERSION=$(echo $TAG | cut -d'@' -f2)
if [[ $TAG == *-rc ]]; then
STAGE="preview"
else
STAGE="main"
fi
echo "::set-output name=project::$PROJECT"
echo "::set-output name=version::$VERSION"
echo "::set-output name=stage::$STAGE"

此段脚本中包含了Setup Node.js用来搭建Node+Pnpm环境,代码Checkout为了在构建机下载代码,这两步是后续脚本执行的必要条件。

使用Turborepo来构建指定项目

上面的步骤中我们已经获取到了project名称,我们可以使用Turborepo自带的过滤npx turbo build --filter=project能力来对指定项目进行构建,脚本如下

- name: Install dependencies & Build
run: pnpm install && npx turbo build --filter=${{ steps.parse-tag.outputs.project }}

此时,我们已经构建好了我们的dist(取决你的项目)产物,只剩下最后一步了,发布~

在Github Action中使用Wrangler部署到Cloudflare

Cloudflare推荐我们使用 https://github.com/cloudflare/wrangler-action来做部署发布,脚本如下:

- name: Deploy Worker
uses: cloudflare/wrangler-action@v3
with:
packageManager: pnpm
workingDirectory: ./apps/${{ steps.parse-tag.outputs.project }}
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: pages deploy dist --project-name=${{ steps.parse-tag.outputs.project }} --branch=${{ steps.parse-tag.outputs.stage }}

这里刚好提一下Parse tag阶段的STAGESTAGE用于区分PreviewProduction

CLOUDFLARE_API_TOKEN,CLOUDFLARE_ACCOUNT_ID这两个secrets需要你到Github仓库里面配置,如何获取可以看文章最后的Tips。

img.png

恭喜你,已经完成自动构建和发布。

等等!我完全不记得我上一个tag是什么了怎么办?

再优化一下打Tag的过程吧

我们将版本分为["major", "minor", "patch"],当我想发布的时候,我需要手动管理版本,我想要脚本帮我自动创建一个自增tag。

scripts/auto-tags.js
#!/usr/bin/env node
const { program } = require("commander");
const inquirer = require("inquirer");
const semver = require("semver");
const simpleGit = require("simple-git");
const git = simpleGit();
program
.version("1.0.0")
.description("A version tag generator for your projects");
program.parse(process.argv);
async function getLatestTag(project) {
const tags = await git.tags();
const projectTags = tags.all
.filter((tag) => tag.startsWith(`${project}@`))
.map((tag) => {
const version = tag.split("@")[1];
return { tag, version };
})
.filter((tag) => semver.valid(tag.version))
.sort((a, b) => semver.rcompare(a.version, b.version));
return projectTags.length > 0 ? projectTags[0].version : "0.0.0";
}
async function main() {
// 如果本地有未提交的更改,直接退出
const status = await git.status();
if (status.files.length > 0) {
console.log("Please commit your changes before creating a new tag.");
return;
}
const { project } = await inquirer.prompt([
{
type: "list",
name: "project",
message: "Please select a project:",
choices: ["exportx", "abfree-com"],
},
]);
const currentVersion = await getLatestTag(project);
console.log(`Current Version: ${currentVersion}`);
const { versionType } = await inquirer.prompt([
{
type: "list",
name: "versionType",
message: "Please select a version type:",
choices: ["major", "minor", "patch"],
},
]);
const newVersion = semver.inc(currentVersion, versionType);
const { isRC } = await inquirer.prompt([
{
type: "confirm",
name: "isRC",
message: "Is this a release candidate?",
default: true,
},
]);
let newTag;
if (isRC) {
newTag = `${project}@${newVersion}-rc`;
} else {
newTag = `${project}@${newVersion}`;
}
console.log(`The new tag will be: ${newTag}`);
const { confirm } = await inquirer.prompt([
{
type: "confirm",
name: "confirm",
message: "Confirm to create the new tag?",
default: true,
},
]);
if (confirm) {
await git.addTag(newTag);
console.log(`Tag ${newTag} has been created successfully! 🎉`);
// 推送到远程仓库
await git.pushTags();
console.log(`Tag ${newTag} has been pushed to the remote repository! 🚀`);
} else {
console.log("The tag creation has been cancelled.");
}
}
main().catch(console.error);

我们顺便把它加入到package.json吧,

{
"scripts": {
"tag": "node scripts/auto-tags.js"
}
}

运行 pnpm tag

img_1.png

大功告成~

当然这样生成Tag在协作模式可能会有些问题,比如两个同事分别执行了这个脚本,则会出现tag冲突,有2个方式可能会帮到你:

  • 打tag前先git pull,生成tag后立即git push
  • 放github action打tag

感谢您的阅读,如果你有更好的想法我们可以一起交流沟通~

小广告:

ExportX超快的在线压缩工具,支持Figma Plugin!

ABFree 我的个人博客,记录一些技术和生活的点滴~

Tips

如何找到我的CLOUDFLARE_API_TOKEN

CLOUDFLARE_ACCOUNT_ID:

img_2.png

CLOUDFLARE_API_TOKEN:

https://dash.cloudflare.com/profile/api-tokens


© 2024 ABFree Co. All rights reserved.