ci: 添加双仓库发布工作流与按规范编译打包脚本
This commit is contained in:
parent
b2ded73632
commit
fb16df5314
82
.github/workflows/release.yml
vendored
Normal file
82
.github/workflows/release.yml
vendored
Normal file
@ -0,0 +1,82 @@
|
||||
name: Build and Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
- 'V*'
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build Release Package
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- goos: linux
|
||||
goarch: amd64
|
||||
archive_ext: tar.gz
|
||||
- goos: linux
|
||||
goarch: arm64
|
||||
archive_ext: tar.gz
|
||||
- goos: windows
|
||||
goarch: amd64
|
||||
archive_ext: zip
|
||||
steps:
|
||||
- name: Checkout Source
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.26.x'
|
||||
cache-dependency-path: backend/go.sum
|
||||
|
||||
- name: Determine Version
|
||||
id: version_step
|
||||
run: |
|
||||
if [ -n "${{ github.ref_name }}" ] && [[ "${{ github.ref_name }}" =~ ^[vV][0-9] ]]; then
|
||||
echo "VERSION=${{ github.ref_name }}" >> $GITHUB_ENV
|
||||
else
|
||||
PKG_VER=$(node -p "require('./frontend/package.json').version")
|
||||
echo "VERSION=v${PKG_VER#v}" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
- name: Build Frontend
|
||||
run: |
|
||||
cd frontend
|
||||
npm ci
|
||||
npm run build
|
||||
|
||||
- name: Package Release
|
||||
run: |
|
||||
node deploy/package.mjs --target=${{ matrix.goos }}_${{ matrix.goarch }} --version=${{ env.VERSION }}
|
||||
echo "PACKAGE_FILE=Scert_${{ env.VERSION }}_${{ matrix.goos }}_${{ matrix.goarch }}.${{ matrix.archive_ext }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Scert_${{ env.VERSION }}_${{ matrix.goos }}_${{ matrix.goarch }}
|
||||
path: |
|
||||
out/${{ env.PACKAGE_FILE }}
|
||||
out/${{ env.PACKAGE_FILE }}.sha256
|
||||
|
||||
- name: Upload to Release (Tag or Release event)
|
||||
if: github.event_name == 'release' || startsWith(github.ref, 'refs/tags/')
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: |
|
||||
out/${{ env.PACKAGE_FILE }}
|
||||
out/${{ env.PACKAGE_FILE }}.sha256
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
132
deploy/package.mjs
Normal file
132
deploy/package.mjs
Normal file
@ -0,0 +1,132 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import crypto from "node:crypto";
|
||||
import { execFileSync } from "node:child_process";
|
||||
|
||||
const rootDir = process.cwd();
|
||||
const backendDir = path.join(rootDir, "backend");
|
||||
const deployDir = path.join(rootDir, "deploy");
|
||||
const frontendDist = path.join(rootDir, "frontend", "dist");
|
||||
const outDir = path.join(rootDir, "out");
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
let targetArg = args.find(a => a.startsWith("--target="))?.replace("--target=", "");
|
||||
let versionArg = args.find(a => a.startsWith("--version="))?.replace("--version=", "");
|
||||
|
||||
if (!versionArg) {
|
||||
const pkg = JSON.parse(fs.readFileSync(path.join(rootDir, "frontend", "package.json"), "utf8"));
|
||||
versionArg = pkg.version.startsWith("v") || pkg.version.startsWith("V") ? pkg.version : `v${pkg.version}`;
|
||||
} else {
|
||||
if (!versionArg.startsWith("v") && !versionArg.startsWith("V")) {
|
||||
versionArg = `v${versionArg}`;
|
||||
}
|
||||
}
|
||||
|
||||
const targets = targetArg ? [targetArg] : ["linux_amd64", "linux_arm64", "windows_amd64", "darwin_amd64", "darwin_arm64"];
|
||||
|
||||
fs.mkdirSync(outDir, { recursive: true });
|
||||
|
||||
for (const target of targets) {
|
||||
const [goos, goarch] = target.split("_");
|
||||
if (!goos || !goarch) continue;
|
||||
|
||||
const pkgName = `Scert_${versionArg}_${goos}_${goarch}`;
|
||||
const stageDir = path.join(outDir, pkgName);
|
||||
fs.rmSync(stageDir, { recursive: true, force: true });
|
||||
fs.mkdirSync(stageDir, { recursive: true });
|
||||
|
||||
const ext = goos === "windows" ? ".exe" : "";
|
||||
const binaries = [
|
||||
{ name: `scert${ext}`, pkg: "./cmd/scert" },
|
||||
{ name: `scert-admin${ext}`, pkg: "./cmd/scert-admin" }
|
||||
];
|
||||
|
||||
if (goos === "linux") {
|
||||
binaries.push({ name: "scert-nginx", pkg: "./cmd/scert-nginx" });
|
||||
binaries.push({ name: "scert-storage", pkg: "./cmd/scert-storage" });
|
||||
}
|
||||
|
||||
console.log(`Building Go binaries for ${goos}/${goarch}...`);
|
||||
const ldflags = `-s -w -X 'git.pcux.cn/xuans/Scert/backend/internal/buildinfo.Version=${versionArg.replace(/^v/i, "")}'`;
|
||||
|
||||
for (const bin of binaries) {
|
||||
const outBin = path.join(stageDir, bin.name);
|
||||
execFileSync("go", ["build", "-trimpath", "-ldflags", ldflags, "-o", outBin, bin.pkg], {
|
||||
cwd: backendDir,
|
||||
env: {
|
||||
...process.env,
|
||||
GOOS: goos,
|
||||
GOARCH: goarch,
|
||||
CGO_ENABLED: "0"
|
||||
},
|
||||
stdio: "inherit"
|
||||
});
|
||||
}
|
||||
|
||||
// Copy Web frontend dist
|
||||
const webDir = path.join(stageDir, "web");
|
||||
fs.mkdirSync(webDir, { recursive: true });
|
||||
fs.cpSync(frontendDist, webDir, { recursive: true });
|
||||
|
||||
// Copy Deploy configurations
|
||||
if (fs.existsSync(path.join(deployDir, "README.md"))) {
|
||||
fs.copyFileSync(path.join(deployDir, "README.md"), path.join(stageDir, "部署说明.md"));
|
||||
}
|
||||
|
||||
if (goos === "linux") {
|
||||
if (fs.existsSync(path.join(deployDir, "install-linux.sh"))) {
|
||||
fs.copyFileSync(path.join(deployDir, "install-linux.sh"), path.join(stageDir, "install-linux.sh"));
|
||||
}
|
||||
if (fs.existsSync(path.join(deployDir, "systemd"))) {
|
||||
fs.cpSync(path.join(deployDir, "systemd"), path.join(stageDir, "systemd"), { recursive: true });
|
||||
}
|
||||
if (fs.existsSync(path.join(deployDir, "nginx"))) {
|
||||
fs.cpSync(path.join(deployDir, "nginx"), path.join(stageDir, "nginx"), { recursive: true });
|
||||
}
|
||||
} else if (goos === "windows") {
|
||||
if (fs.existsSync(path.join(deployDir, "install-windows.ps1"))) {
|
||||
fs.copyFileSync(path.join(deployDir, "install-windows.ps1"), path.join(stageDir, "install-windows.ps1"));
|
||||
}
|
||||
}
|
||||
|
||||
// Generate SHA256SUMS
|
||||
const allFiles = [];
|
||||
function collect(dir, base) {
|
||||
for (const item of fs.readdirSync(dir, { withFileTypes: true })) {
|
||||
const full = path.join(dir, item.name);
|
||||
const rel = path.join(base, item.name).replace(/\\/g, "/");
|
||||
if (item.isDirectory()) {
|
||||
collect(full, rel);
|
||||
} else if (item.isFile() && item.name !== "SHA256SUMS") {
|
||||
const hash = crypto.createHash("sha256").update(fs.readFileSync(full)).digest("hex");
|
||||
allFiles.push(`${hash} ${rel}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
collect(stageDir, "");
|
||||
allFiles.sort();
|
||||
fs.writeFileSync(path.join(stageDir, "SHA256SUMS"), allFiles.join("\n") + "\n", "utf8");
|
||||
|
||||
// Archive
|
||||
const archiveExt = goos === "windows" ? "zip" : "tar.gz";
|
||||
const archiveName = `${pkgName}.${archiveExt}`;
|
||||
const archivePath = path.join(outDir, archiveName);
|
||||
fs.rmSync(archivePath, { force: true });
|
||||
|
||||
console.log(`Packaging ${archiveName}...`);
|
||||
if (archiveExt === "tar.gz") {
|
||||
execFileSync("tar", ["-czf", archivePath, "-C", outDir, pkgName], { stdio: "inherit" });
|
||||
} else {
|
||||
// PowerShell Compress-Archive or tar
|
||||
try {
|
||||
execFileSync("tar", ["-a", "-c", "-f", archivePath, "-C", outDir, pkgName], { stdio: "inherit" });
|
||||
} catch {
|
||||
execFileSync("powershell", ["-Command", `Compress-Archive -Path '${stageDir}' -DestinationPath '${archivePath}' -Force`], { stdio: "inherit" });
|
||||
}
|
||||
}
|
||||
|
||||
// Compute archive sha256
|
||||
const archiveHash = crypto.createHash("sha256").update(fs.readFileSync(archivePath)).digest("hex");
|
||||
fs.writeFileSync(`${archivePath}.sha256`, `${archiveHash} ${archiveName}\n`, "utf8");
|
||||
console.log(`Finished ${archiveName} (${archiveHash})`);
|
||||
}
|
||||
@ -3674,8 +3674,8 @@ P0-05 从 `[!]` 调整为 `[~]`,并未标记完成;P0-04 剩余门禁和全
|
||||
|
||||
## 2026年10月18日 双仓库发布工作流与按规范编译包生成
|
||||
|
||||
- [~] 需求确认:为 GitHub 与 git.pcux.cn 双仓库创建统一的 CI/CD Release 工作流,实现多架构自动编译打包与发布,产物严格按照“项目名+版本+架构”(如 Scert_v1.52.1_linux_amd64.tar.gz / Scert_v1.52.1_windows_amd64.zip)进行命名。
|
||||
- [ ] 编写跨平台打包辅助能力,支持包含可执行程序、Web资源、服务部署模板配置及哈希清单。
|
||||
- [ ] 创建 .github/workflows/release.yml 自动化工作流,覆盖 linux/amd64、linux/arm64、windows/amd64 等平台矩阵,支持 tag 触发与 workflow_dispatch 手动触发。
|
||||
- [ ] 执行前端单元测试(357项)与构建检查,确保工具链与代码健全。
|
||||
- [ ] 提交代码并双重推送到 Gitea (git.pcux.cn) 与 GitHub (github.com)。
|
||||
- [✓] 需求确认:为 GitHub 与 git.pcux.cn 双仓库创建统一的 CI/CD Release 工作流,实现多架构自动编译打包与发布,产物严格按照“项目名+版本+架构”(如 Scert_v1.52.1_linux_amd64.tar.gz / Scert_v1.52.1_windows_amd64.zip)进行命名。
|
||||
- [✓] 编写跨平台打包辅助脚本(deploy/package.mjs),支持针对特定平台及架构进行编译,并整合前端构建产物(web/)、部署脚本与配置文件,自动计算 SHA256SUMS,生成对应的 tar.gz / zip 压缩包及校验文件。
|
||||
- [✓] 创建 .github/workflows/release.yml 自动化工作流,构建矩阵覆盖 linux/amd64、linux/arm64、windows/amd64 等平台,支持推送 tag、发布 release 以及 workflow_dispatch 手动触发,并自动上传 Release 资产与 Artifacts。
|
||||
- [✓] 执行前端单元测试(357 项测试全绿通过,357 pass, 0 fail)与生产打包构建检查。
|
||||
- [✓] 提交代码并双重推送到 Gitea (git.pcux.cn) 与 GitHub (github.com)。
|
||||
Loading…
Reference in New Issue
Block a user