mirror of
https://github.com/certd/certd.git
synced 2026-07-18 20:17:34 +08:00
perf: 增加稳定版,预览版区分,预览版是最新版,稳定版在预览版验证稳定后,再将预览版转为稳定版
This commit is contained in:
@@ -0,0 +1,84 @@
|
|||||||
|
name: stable-release
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
version:
|
||||||
|
description: "版本号(如 v1.42.5)"
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
make-stable:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Login to aliyun container Registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: registry.cn-shenzhen.aliyuncs.com
|
||||||
|
username: ${{ secrets.aliyun_cs_username }}
|
||||||
|
password: ${{ secrets.aliyun_cs_password }}
|
||||||
|
|
||||||
|
- name: Login to GitHub Packages
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.dockerhub_username }}
|
||||||
|
password: ${{ secrets.dockerhub_password }}
|
||||||
|
|
||||||
|
# stable 镜像:叠加 ENV 层
|
||||||
|
- name: Build and push stable images
|
||||||
|
run: |
|
||||||
|
echo "FROM greper/certd:${{ inputs.version }}
|
||||||
|
ENV certd_release_mode=stable" > Dockerfile.stable
|
||||||
|
docker buildx build \
|
||||||
|
--platform linux/amd64,linux/arm64 \
|
||||||
|
--push \
|
||||||
|
-f Dockerfile.stable \
|
||||||
|
-t registry.cn-shenzhen.aliyuncs.com/handsfree/certd:stable \
|
||||||
|
-t registry.cn-shenzhen.aliyuncs.com/handsfree/certd:${{ inputs.version }}-stable \
|
||||||
|
-t greper/certd:stable \
|
||||||
|
-t greper/certd:${{ inputs.version }}-stable \
|
||||||
|
-t ghcr.io/${{ github.repository }}:stable \
|
||||||
|
-t ghcr.io/${{ github.repository }}:${{ inputs.version }}-stable \
|
||||||
|
.
|
||||||
|
|
||||||
|
# slim-stable 镜像
|
||||||
|
- name: Build and push slim-stable images
|
||||||
|
run: |
|
||||||
|
echo "FROM greper/certd:${{ inputs.version }}-slim
|
||||||
|
ENV certd_release_mode=stable" > Dockerfile.slim-stable
|
||||||
|
docker buildx build \
|
||||||
|
--platform linux/amd64,linux/arm64 \
|
||||||
|
--push \
|
||||||
|
-f Dockerfile.slim-stable \
|
||||||
|
-t registry.cn-shenzhen.aliyuncs.com/handsfree/certd:slim-stable \
|
||||||
|
-t registry.cn-shenzhen.aliyuncs.com/handsfree/certd:${{ inputs.version }}-slim-stable \
|
||||||
|
-t greper/certd:slim-stable \
|
||||||
|
-t greper/certd:${{ inputs.version }}-slim-stable \
|
||||||
|
-t ghcr.io/${{ github.repository }}:slim-stable \
|
||||||
|
-t ghcr.io/${{ github.repository }}:${{ inputs.version }}-slim-stable \
|
||||||
|
.
|
||||||
|
|
||||||
|
- name: Set AtomGit release as stable
|
||||||
|
run: |
|
||||||
|
export ATOMGIT_TOKEN=${{ secrets.ATOMGIT_TOKEN }}
|
||||||
|
export VERSION=${{ inputs.version }}
|
||||||
|
npm run set-release-stable
|
||||||
+4
-2
@@ -19,7 +19,7 @@
|
|||||||
"devb": "lerna run dev-build",
|
"devb": "lerna run dev-build",
|
||||||
"i-all": "lerna link && lerna exec npm install ",
|
"i-all": "lerna link && lerna exec npm install ",
|
||||||
"publish": "pnpm run prepublishOnly2 && lerna publish --force-publish=pro/plus-core --conventional-commits && pnpm run afterpublishOnly ",
|
"publish": "pnpm run prepublishOnly2 && lerna publish --force-publish=pro/plus-core --conventional-commits && pnpm run afterpublishOnly ",
|
||||||
"publish2":" npm run pub_all && pnpm run afterpublishOnly",
|
"publish2": " npm run pub_all && pnpm run afterpublishOnly",
|
||||||
"afterpublishOnly": "pnpm run copylogs && time /t >trigger/build.trigger && git add ./trigger/build.trigger && git commit -m \"build: trigger build image\" && TIMEOUT /T 10 && pnpm run commitAll",
|
"afterpublishOnly": "pnpm run copylogs && time /t >trigger/build.trigger && git add ./trigger/build.trigger && git commit -m \"build: trigger build image\" && TIMEOUT /T 10 && pnpm run commitAll",
|
||||||
"transform-sql": "cd ./packages/ui/certd-server/db/ && node --experimental-json-modules transform.js",
|
"transform-sql": "cd ./packages/ui/certd-server/db/ && node --experimental-json-modules transform.js",
|
||||||
"plugin-doc-gen": "cd ./packages/ui/certd-server/ && pnpm run export-metadata",
|
"plugin-doc-gen": "cd ./packages/ui/certd-server/ && pnpm run export-metadata",
|
||||||
@@ -45,7 +45,9 @@
|
|||||||
"publish_to_atomgit": "node --experimental-json-modules ./scripts/publish-atomgit.js",
|
"publish_to_atomgit": "node --experimental-json-modules ./scripts/publish-atomgit.js",
|
||||||
"publish_to_gitee": "node --experimental-json-modules ./scripts/publish-gitee.js",
|
"publish_to_gitee": "node --experimental-json-modules ./scripts/publish-gitee.js",
|
||||||
"publish_to_github": "node --experimental-json-modules ./scripts/publish-github.js",
|
"publish_to_github": "node --experimental-json-modules ./scripts/publish-github.js",
|
||||||
"get_version": "node --experimental-json-modules ./scripts/version.js"
|
"get_version": "node --experimental-json-modules ./scripts/version.js",
|
||||||
|
"stable": "node ./scripts/stable.js",
|
||||||
|
"set-release-stable": "node ./scripts/set-release-stable.js"
|
||||||
},
|
},
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
ARG base_type=alpine
|
ARG base_type=alpine
|
||||||
|
|
||||||
# 根据 base_type 参数选择基础镜像系列
|
# 根据 base_type 参数选择基础镜像系列
|
||||||
FROM --platform=linux/amd64 node:22-alpine AS base-amd64-alpine
|
FROM --platform=linux/amd64 node:22-alpine AS base-amd64-alpine
|
||||||
@@ -99,6 +99,7 @@ RUN ARCH=$(uname -m) && \
|
|||||||
|
|
||||||
ENV TZ=Asia/Shanghai
|
ENV TZ=Asia/Shanghai
|
||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
|
ENV certd_release_mode=latest
|
||||||
ENV MIDWAY_SERVER_ENV=production
|
ENV MIDWAY_SERVER_ENV=production
|
||||||
|
|
||||||
RUN npm install -g pnpm@10.33.4
|
RUN npm install -g pnpm@10.33.4
|
||||||
|
|||||||
@@ -25,5 +25,7 @@ export default {
|
|||||||
expiringCerts: "Soon-to-Expire Certificates",
|
expiringCerts: "Soon-to-Expire Certificates",
|
||||||
supportedTasks: "Overview of Supported Deployment Tasks",
|
supportedTasks: "Overview of Supported Deployment Tasks",
|
||||||
changeLog: "Change Log",
|
changeLog: "Change Log",
|
||||||
|
stableRelease: "Stable",
|
||||||
|
latestRelease: "Preview",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -26,5 +26,7 @@ export default {
|
|||||||
expiringCerts: "最快到期证书",
|
expiringCerts: "最快到期证书",
|
||||||
supportedTasks: "已支持的部署任务总览",
|
supportedTasks: "已支持的部署任务总览",
|
||||||
changeLog: "更新日志",
|
changeLog: "更新日志",
|
||||||
|
stableRelease: "稳定版",
|
||||||
|
latestRelease: "预览版",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export type AppInfo = {
|
|||||||
version?: string;
|
version?: string;
|
||||||
time?: number;
|
time?: number;
|
||||||
deltaTime?: number;
|
deltaTime?: number;
|
||||||
|
releaseMode?: string;
|
||||||
};
|
};
|
||||||
export type SiteInfo = {
|
export type SiteInfo = {
|
||||||
title?: string;
|
title?: string;
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ export interface SettingState {
|
|||||||
version?: string;
|
version?: string;
|
||||||
time?: number;
|
time?: number;
|
||||||
deltaTime?: number;
|
deltaTime?: number;
|
||||||
|
releaseMode?: string;
|
||||||
};
|
};
|
||||||
productInfo: {
|
productInfo: {
|
||||||
notice?: string;
|
notice?: string;
|
||||||
@@ -109,6 +110,7 @@ export const useSettingStore = defineStore({
|
|||||||
version: "",
|
version: "",
|
||||||
time: 0,
|
time: 0,
|
||||||
deltaTime: 0,
|
deltaTime: 0,
|
||||||
|
releaseMode: "latest",
|
||||||
},
|
},
|
||||||
productInfo: {
|
productInfo: {
|
||||||
notice: "",
|
notice: "",
|
||||||
@@ -229,6 +231,9 @@ export const useSettingStore = defineStore({
|
|||||||
this.app.time = appInfo.time;
|
this.app.time = appInfo.time;
|
||||||
this.app.version = appInfo.version;
|
this.app.version = appInfo.version;
|
||||||
this.app.deltaTime = new Date().getTime() - this.app.time;
|
this.app.deltaTime = new Date().getTime() - this.app.time;
|
||||||
|
if (appInfo.releaseMode) {
|
||||||
|
this.app.releaseMode = appInfo.releaseMode;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
initSiteInfo(siteInfo: SiteInfo) {
|
initSiteInfo(siteInfo: SiteInfo) {
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="dashboard-user">
|
<div class="dashboard-user">
|
||||||
<div class="header-profile flex-wrap bg-white dark:bg-black">
|
<div class="header-profile flex-wrap bg-white dark:bg-black">
|
||||||
<div class="flex flex-1">
|
<div class="flex flex-1">
|
||||||
@@ -23,9 +23,9 @@
|
|||||||
<template v-if="userStore.isAdmin">
|
<template v-if="userStore.isAdmin">
|
||||||
<a-divider type="vertical" />
|
<a-divider type="vertical" />
|
||||||
<a-badge :dot="hasNewVersion">
|
<a-badge :dot="hasNewVersion">
|
||||||
<a-tag color="blue" class="flex-inline pointer mr-0" :title="t('certd.dashboard.latestVersion', { version: latestVersion })" @click="openUpgradeUrl()">
|
<a-tag color="blue" class="flex-inline pointer mr-0" :title="'v' + version + ' ' + (settingsStore.app.releaseMode === 'stable' ? '稳定版' : '预览版')" @click="openUpgradeUrl()">
|
||||||
<fs-icon icon="ion:rocket-outline" class="mr-5"></fs-icon>
|
<fs-icon icon="ion:rocket-outline" class="mr-5"></fs-icon>
|
||||||
v{{ version }}
|
v{{ version }}-{{ settingsStore.app.releaseMode }}
|
||||||
</a-tag>
|
</a-tag>
|
||||||
</a-badge>
|
</a-badge>
|
||||||
<a-divider type="vertical" />
|
<a-divider type="vertical" />
|
||||||
@@ -179,7 +179,6 @@ defineOptions({
|
|||||||
|
|
||||||
const version = ref(import.meta.env.VITE_APP_VERSION);
|
const version = ref(import.meta.env.VITE_APP_VERSION);
|
||||||
const latestVersion = ref("");
|
const latestVersion = ref("");
|
||||||
|
|
||||||
function isNewVersion(version: string, latestVersion: string) {
|
function isNewVersion(version: string, latestVersion: string) {
|
||||||
if (!latestVersion) {
|
if (!latestVersion) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
LEGO_VERSION=4.30.1
|
LEGO_VERSION=4.30.1
|
||||||
JKS_GO_VERSION=1.0.3
|
JKS_GO_VERSION=1.0.3
|
||||||
certd_plugin_loadmode=dev
|
certd_plugin_loadmode=dev
|
||||||
|
certd_release_mode=stable
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
import assert from "node:assert/strict";
|
import assert from "node:assert/strict";
|
||||||
|
|
||||||
import { normalizeReleaseVersion } from "./app-controller.js";
|
import { getReleaseMode, normalizeReleaseVersion } from "./app-controller.js";
|
||||||
|
|
||||||
describe("AppController.normalizeReleaseVersion", () => {
|
describe("AppController.normalizeReleaseVersion", () => {
|
||||||
it("normalizes AtomGit release tag names", () => {
|
it("normalizes AtomGit release tag names", () => {
|
||||||
@@ -15,3 +15,29 @@ describe("AppController.normalizeReleaseVersion", () => {
|
|||||||
assert.equal(normalizeReleaseVersion({ name: "v1.40.0" }), "1.40.0");
|
assert.equal(normalizeReleaseVersion({ name: "v1.40.0" }), "1.40.0");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("AppController.getReleaseMode", () => {
|
||||||
|
it("returns 'latest' when env var is not set", () => {
|
||||||
|
delete process.env.certd_release_mode;
|
||||||
|
assert.equal(getReleaseMode(), "latest");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns 'latest' when env var is empty", () => {
|
||||||
|
process.env.certd_release_mode = "";
|
||||||
|
assert.equal(getReleaseMode(), "latest");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns 'stable' when env var is 'stable'", () => {
|
||||||
|
process.env.certd_release_mode = "stable";
|
||||||
|
assert.equal(getReleaseMode(), "stable");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns 'latest' when env var is an unknown value", () => {
|
||||||
|
process.env.certd_release_mode = "unknown";
|
||||||
|
assert.equal(getReleaseMode(), "latest");
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
delete process.env.certd_release_mode;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -8,6 +8,10 @@ export function normalizeReleaseVersion(release: { tag_name?: string; name?: str
|
|||||||
return version.replace(/^v/i, "");
|
return version.replace(/^v/i, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getReleaseMode(): "stable" | "latest" {
|
||||||
|
return process.env.certd_release_mode === "stable" ? "stable" : "latest";
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
@Provide()
|
@Provide()
|
||||||
@@ -20,14 +24,30 @@ export class AppController extends BaseController {
|
|||||||
|
|
||||||
@Get("/latest", { description: Constants.per.authOnly })
|
@Get("/latest", { description: Constants.per.authOnly })
|
||||||
async latest(): Promise<any> {
|
async latest(): Promise<any> {
|
||||||
|
const mode = getReleaseMode();
|
||||||
try {
|
try {
|
||||||
const res = await http.request({
|
let latest = "";
|
||||||
url: "https://api.atomgit.com/api/v5/repos/certd/certd/releases/latest",
|
if (mode === "stable") {
|
||||||
method: "get",
|
// 稳定版: 查询被设为 latest 的正式版本
|
||||||
logRes: false,
|
const res = await http.request({
|
||||||
timeout: 5000,
|
url: "https://api.atomgit.com/api/v5/repos/certd/certd/releases/latest?type=latest",
|
||||||
});
|
method: "get",
|
||||||
const latest = normalizeReleaseVersion(res);
|
logRes: false,
|
||||||
|
timeout: 5000,
|
||||||
|
});
|
||||||
|
latest = normalizeReleaseVersion(res);
|
||||||
|
} else {
|
||||||
|
// 预览版: 获取最新一条 release(可能包含 pre-release)
|
||||||
|
const list = await http.request({
|
||||||
|
url: "https://api.atomgit.com/api/v5/repos/certd/certd/releases/?direction=desc&per_page=1",
|
||||||
|
method: "get",
|
||||||
|
logRes: false,
|
||||||
|
timeout: 5000,
|
||||||
|
});
|
||||||
|
if (Array.isArray(list) && list.length > 0) {
|
||||||
|
latest = normalizeReleaseVersion(list[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
return this.ok(latest);
|
return this.ok(latest);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
logger.error(e);
|
logger.error(e);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { SysInviteCommissionSetting } from "@certd/commercial-core";
|
|||||||
import { cloneDeep } from "lodash-es";
|
import { cloneDeep } from "lodash-es";
|
||||||
import { getVersion } from "../../utils/version.js";
|
import { getVersion } from "../../utils/version.js";
|
||||||
import { http } from "@certd/basic";
|
import { http } from "@certd/basic";
|
||||||
|
import { getReleaseMode } from "./app-controller.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
@@ -108,6 +109,7 @@ export class BasicSettingsController extends BaseController {
|
|||||||
app: {
|
app: {
|
||||||
time: new Date().getTime(),
|
time: new Date().getTime(),
|
||||||
version,
|
version,
|
||||||
|
releaseMode: getReleaseMode(),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,6 +74,27 @@ async function createRelease(versionTitle, content) {
|
|||||||
return response.data
|
return response.data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 设置 release 为预览版 (pre-release)
|
||||||
|
async function setReleaseAsPre(versionTitle, name, body) {
|
||||||
|
const response = await axios.request({
|
||||||
|
method: "PATCH",
|
||||||
|
url: `https://api.atomgit.com/api/v5/repos/certd/certd/releases/v${versionTitle}`,
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
},
|
||||||
|
params: {
|
||||||
|
access_token: AtomgitAccessToken
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
name: name,
|
||||||
|
body: body,
|
||||||
|
release_status: "pre"
|
||||||
|
},
|
||||||
|
})
|
||||||
|
console.log("setReleaseAsPre success")
|
||||||
|
return response.data
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取Release附件上传地址
|
* 获取Release附件上传地址
|
||||||
GET
|
GET
|
||||||
@@ -154,6 +175,7 @@ async function publishToAtomgit() {
|
|||||||
const { versionTitle, content } = getVersionContent()
|
const { versionTitle, content } = getVersionContent()
|
||||||
try{
|
try{
|
||||||
const release = await createRelease(versionTitle, content)
|
const release = await createRelease(versionTitle, content)
|
||||||
|
await setReleaseAsPre(versionTitle, `v${versionTitle}`, content)
|
||||||
const uploadUrl = await getUploadUrl(versionTitle)
|
const uploadUrl = await getUploadUrl(versionTitle)
|
||||||
const fileName = `./packages/ui/certd-client/ui.zip`
|
const fileName = `./packages/ui/certd-client/ui.zip`
|
||||||
const fileData = fs.createReadStream(fileName)
|
const fileData = fs.createReadStream(fileName)
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
const AtomgitAccessToken = process.env.ATOMGIT_TOKEN;
|
||||||
|
const Version = process.env.VERSION;
|
||||||
|
|
||||||
|
if (!AtomgitAccessToken) {
|
||||||
|
console.error("错误: 请设置环境变量 ATOMGIT_TOKEN");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Version) {
|
||||||
|
console.error("错误: 请设置环境变量 VERSION (如 v1.42.5)");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const API_BASE = "https://api.atomgit.com/api/v5/repos/certd/certd";
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
try {
|
||||||
|
// GET 当前 release
|
||||||
|
const getResp = await axios.request({
|
||||||
|
method: "GET",
|
||||||
|
url: `${API_BASE}/releases/${encodeURIComponent(Version)}`,
|
||||||
|
params: { access_token: AtomgitAccessToken },
|
||||||
|
});
|
||||||
|
|
||||||
|
const release = getResp.data;
|
||||||
|
console.log(`Current release: ${release.tag_name}, status: ${release.release_status}`);
|
||||||
|
|
||||||
|
if (release.release_status === "latest") {
|
||||||
|
console.log("Already stable, skipping");
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// PATCH 设为 latest
|
||||||
|
const patchResp = await axios.request({
|
||||||
|
method: "PATCH",
|
||||||
|
url: `${API_BASE}/releases/${encodeURIComponent(Version)}`,
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
params: { access_token: AtomgitAccessToken },
|
||||||
|
data: {
|
||||||
|
name: release.name,
|
||||||
|
body: release.body,
|
||||||
|
release_status: "latest",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`Done! ${Version} is now stable: status=${patchResp.data.release_status}`);
|
||||||
|
} catch (error) {
|
||||||
|
if (error?.response?.data) {
|
||||||
|
console.error("请求失败:", JSON.stringify(error.response.data, null, 2));
|
||||||
|
} else {
|
||||||
|
console.error("请求失败:", error.message || error);
|
||||||
|
}
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
@@ -0,0 +1,160 @@
|
|||||||
|
import axios from "axios";
|
||||||
|
import { createInterface } from "node:readline/promises";
|
||||||
|
import { stdin as input, stdout as output } from "node:process";
|
||||||
|
|
||||||
|
const AtomgitAccessToken = process.env.ATOMGIT_TOKEN;
|
||||||
|
|
||||||
|
if (!AtomgitAccessToken) {
|
||||||
|
console.error("错误: 请设置环境变量 ATOMGIT_TOKEN");
|
||||||
|
console.error(" $env:ATOMGIT_TOKEN='your_token' (Windows PowerShell)");
|
||||||
|
console.error(" 或 export ATOMGIT_TOKEN=your_token (Linux/Mac)");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const API_BASE = "https://api.atomgit.com/api/v5/repos/certd/certd";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取发布版本列表
|
||||||
|
*/
|
||||||
|
async function listReleases() {
|
||||||
|
const response = await axios.request({
|
||||||
|
method: "GET",
|
||||||
|
url: `${API_BASE}/releases`,
|
||||||
|
params: {
|
||||||
|
access_token: AtomgitAccessToken,
|
||||||
|
direction: "desc",
|
||||||
|
per_page: 10,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 触发 GitHub Actions workflow_dispatch 来执行稳定版发布
|
||||||
|
*/
|
||||||
|
async function triggerWorkflow(version) {
|
||||||
|
const token = process.env.GH_TOKEN;
|
||||||
|
if (!token) {
|
||||||
|
console.error("错误: 请设置环境变量 GH_TOKEN");
|
||||||
|
console.error(" $env:GH_TOKEN='your_token' (Windows PowerShell)");
|
||||||
|
console.error(" 需要在 https://github.com/settings/tokens 生成具有 repo 权限的 token");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await axios.request({
|
||||||
|
method: "POST",
|
||||||
|
url: "https://api.github.com/repos/certd/certd/actions/workflows/stable-release.yml/dispatches",
|
||||||
|
headers: {
|
||||||
|
"Accept": "application/vnd.github.v3+json",
|
||||||
|
"Authorization": `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
ref: "v2-dev",
|
||||||
|
inputs: {
|
||||||
|
version: version,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`
|
||||||
|
✓ 已触发 GitHub Actions workflow`);
|
||||||
|
console.log(` 版本: ${version}`);
|
||||||
|
console.log(` 请前往 https://github.com/certd/certd/actions/workflows/stable-release.yml 查看执行状态`);
|
||||||
|
} catch (error) {
|
||||||
|
const status = error?.response?.status;
|
||||||
|
const msg = error?.response?.data?.message || error.message;
|
||||||
|
if (status === 404) {
|
||||||
|
console.error(`
|
||||||
|
请求失败 (404): 工作流文件 stable-release.yml 尚未推送到远程仓库`);
|
||||||
|
console.error(" 请先提交并推送代码到 v2-dev 分支:");
|
||||||
|
console.error(" git add .");
|
||||||
|
console.error(' git commit -m "feat: add stable-release workflow"');
|
||||||
|
console.error(" git push");
|
||||||
|
} else {
|
||||||
|
console.error(`
|
||||||
|
请求失败 (${status}): ${msg}`);
|
||||||
|
}
|
||||||
|
console.error(" 请检查 GH_TOKEN 是否具有 actions:write 权限");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化输出版本列表
|
||||||
|
*/
|
||||||
|
function printReleases(releases) {
|
||||||
|
console.log("\n");
|
||||||
|
console.log("序号 | Tag 版本 | 名称 | 状态");
|
||||||
|
console.log("------|--------------------|-----------------------------------|--------");
|
||||||
|
releases.forEach((r, i) => {
|
||||||
|
const idx = String(i + 1).padEnd(4);
|
||||||
|
const tag = (r.tag_name || "").padEnd(18);
|
||||||
|
const name = (r.name || "").substring(0, 33).padEnd(33);
|
||||||
|
const status = releaseStatusLabel(r.release_status);
|
||||||
|
console.log(`${idx} | ${tag} | ${name} | ${status}`);
|
||||||
|
});
|
||||||
|
console.log("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
function releaseStatusLabel(status) {
|
||||||
|
if (status === "latest") {
|
||||||
|
return "稳定版";
|
||||||
|
}
|
||||||
|
if (status === "pre") {
|
||||||
|
return "预览版";
|
||||||
|
}
|
||||||
|
return status || "-";
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
try {
|
||||||
|
console.log("======= 获取发布版本列表... =======");
|
||||||
|
const releases = await listReleases();
|
||||||
|
|
||||||
|
if (!releases || releases.length === 0) {
|
||||||
|
console.error("没有找到任何发布版本");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
printReleases(releases);
|
||||||
|
|
||||||
|
const rl = createInterface({ input, output });
|
||||||
|
|
||||||
|
// 请用户选择序号
|
||||||
|
const answer = await rl.question("请输入序号 (1-" + releases.length + "): ");
|
||||||
|
const idx = parseInt(answer.trim(), 10);
|
||||||
|
if (isNaN(idx) || idx < 1 || idx > releases.length) {
|
||||||
|
console.error("请输入有效序号 (1-" + releases.length + ")");
|
||||||
|
rl.close();
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
const selected = releases[idx - 1];
|
||||||
|
const tag = selected.tag_name;
|
||||||
|
|
||||||
|
// 确认
|
||||||
|
const confirm = await rl.question(`确定要将 ${tag} 设置为稳定版(Stable)吗?(y/N): `);
|
||||||
|
rl.close();
|
||||||
|
|
||||||
|
if (confirm.trim().toLowerCase() !== "y") {
|
||||||
|
console.log("已取消");
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 触发 GitHub Actions workflow
|
||||||
|
console.log(`
|
||||||
|
正在触发稳定版发布流程: ${tag}`);
|
||||||
|
await triggerWorkflow(tag);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
if (error?.response?.data) {
|
||||||
|
console.error("请求失败:", JSON.stringify(error.response.data, null, 2));
|
||||||
|
} else {
|
||||||
|
console.error("请求失败:", error.message || error);
|
||||||
|
}
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
Reference in New Issue
Block a user