Compare commits

..

5 Commits

4 changed files with 32 additions and 177 deletions

View File

@@ -55,6 +55,7 @@
- 托盘 `更多` 中新增 `关闭所有连接` 按钮
- 新增左侧菜单栏的排序功能(右键点击左侧菜单栏)
- 托盘 `打开目录` 中新增 `应用日志``内核日志`
- 支持更新通道切换 (Stable / Autobuild)
</details>
<details>

View File

@@ -8,9 +8,9 @@
* - A full semver version (e.g., 1.2.3, v1.2.3, 1.2.3-beta, v1.2.3-rc.1)
* - A tag: "alpha", "beta", "rc", "autobuild", "autobuild-latest", or "deploytest"
* - "alpha", "beta", "rc": Appends the tag to the current base version (e.g., 1.2.3-beta)
* - "autobuild": Appends a timestamped autobuild tag (e.g., 1.2.3-autobuild.1022.r2+cc39b2)
* - "autobuild-latest": Appends an autobuild tag with latest Tauri commit (e.g., 1.2.3-autobuild.1022.r2+a1b2c3d)
* - "deploytest": Appends a timestamped deploytest tag (e.g., 1.2.3-deploytest.1022.r2+cc39b2)
* - "autobuild": Appends a timestamped autobuild tag (e.g., 1.2.3-autobuild.0610.cc39b2.r2)
* - "autobuild-latest": Appends an autobuild tag with latest Tauri commit (e.g., 1.2.3-autobuild.0610.a1b2c3d.r2)
* - "deploytest": Appends a timestamped deploytest tag (e.g., 1.2.3-deploytest.0610.cc39b2.r2)
*
* Examples:
* pnpm release-version 1.2.3
@@ -101,22 +101,6 @@ function getLocalDatePart() {
* @returns {string|null}
*/
function getRunIdentifier() {
const runNumber = process.env.GITHUB_RUN_NUMBER;
if (runNumber && /^[0-9]+$/.test(runNumber)) {
const runNum = Number.parseInt(runNumber, 10);
if (!Number.isNaN(runNum)) {
const base = `r${runNum.toString(36)}`;
const attempt = process.env.GITHUB_RUN_ATTEMPT;
if (attempt && /^[0-9]+$/.test(attempt)) {
const attemptNumber = Number.parseInt(attempt, 10);
if (!Number.isNaN(attemptNumber) && attemptNumber > 1) {
return `${base}${attemptNumber.toString(36)}`;
}
}
return base;
}
}
const attempt = process.env.GITHUB_RUN_ATTEMPT;
if (attempt && /^[0-9]+$/.test(attempt)) {
const attemptNumber = Number.parseInt(attempt, 10);
@@ -125,6 +109,14 @@ function getRunIdentifier() {
}
}
const runNumber = process.env.GITHUB_RUN_NUMBER;
if (runNumber && /^[0-9]+$/.test(runNumber)) {
const runNum = Number.parseInt(runNumber, 10);
if (!Number.isNaN(runNum)) {
return `r${runNum.toString(36)}`;
}
}
return null;
}
@@ -161,25 +153,6 @@ function generateChannelSuffix({
return segments.join(".");
}
/**
* 为 autobuild 渠道构建版本片段
* @param {Object} options
* @param {"current"|"tauri"} [options.commitSource="current"]
* @returns {{date: string, run: string, metadata: string}}
*/
function generateAutobuildComponents({ commitSource = "current" } = {}) {
const date = getLocalDatePart();
const run = getRunIdentifier() ?? `manual${Date.now().toString(36)}`;
const commitHash =
commitSource === "tauri" ? getLatestTauriCommit() : getGitShortCommit();
return {
date,
run,
metadata: commitHash || "nogit",
};
}
/**
* 验证版本号格式
* @param {string} version
@@ -353,17 +326,23 @@ async function main(versionArg) {
const baseVersion = getBaseVersion(currentVersion);
if (versionArg.toLowerCase() === "autobuild") {
// 格式: 2.3.0-autobuild.1022.r2+cc39b2
const parts = generateAutobuildComponents({ commitSource: "tauri" });
newVersion = `${baseVersion}-autobuild.${parts.date}.${parts.run}+${parts.metadata}`;
// 格式: 2.3.0-autobuild.0610.cc39b2.r2
newVersion = `${baseVersion}-autobuild.${generateChannelSuffix({
includeCommit: true,
commitSource: "tauri",
})}`;
} else if (versionArg.toLowerCase() === "autobuild-latest") {
// 格式: 2.3.0-autobuild.1022.r2+a1b2c3d (使用最新 Tauri 提交)
const parts = generateAutobuildComponents({ commitSource: "tauri" });
newVersion = `${baseVersion}-autobuild.${parts.date}.${parts.run}+${parts.metadata}`;
// 格式: 2.3.0-autobuild.0610.a1b2c3d.r2 (使用最新 Tauri 提交)
newVersion = `${baseVersion}-autobuild.${generateChannelSuffix({
includeCommit: true,
commitSource: "tauri",
})}`;
} else if (versionArg.toLowerCase() === "deploytest") {
// 格式: 2.3.0-deploytest.1022.r2+cc39b2
const parts = generateAutobuildComponents({ commitSource: "tauri" });
newVersion = `${baseVersion}-deploytest.${parts.date}.${parts.run}+${parts.metadata}`;
// 格式: 2.3.0-deploytest.0610.cc39b2.r2
newVersion = `${baseVersion}-deploytest.${generateChannelSuffix({
includeCommit: true,
commitSource: "tauri",
})}`;
} else {
newVersion = `${baseVersion}-${versionArg.toLowerCase()}`;
}

View File

@@ -5,101 +5,6 @@ import fetch from "node-fetch";
import { resolveUpdateLog, resolveUpdateLogDefault } from "./updatelog.mjs";
const SEMVER_REGEX =
/v?\d+(?:\.\d+){2}(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?/g;
const STRICT_SEMVER_REGEX =
/^\d+(?:\.\d+){2}(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
const stripLeadingV = (version) =>
typeof version === "string" && version.startsWith("v")
? version.slice(1)
: version;
const preferCandidate = (current, candidate) => {
if (!candidate) return current;
if (!current) return candidate;
const candidateHasPre = /[-+]/.test(candidate);
const currentHasPre = /[-+]/.test(current);
if (candidateHasPre && !currentHasPre) return candidate;
if (candidateHasPre === currentHasPre && candidate.length > current.length) {
return candidate;
}
return current;
};
const extractBestSemver = (input) => {
if (typeof input !== "string") return null;
const matches = input.match(SEMVER_REGEX);
if (!matches) return null;
return matches
.map(stripLeadingV)
.reduce((best, candidate) => preferCandidate(best, candidate), null);
};
const splitIdentifiers = (segment) =>
segment
.split(/[^0-9A-Za-z-]+/)
.map((part) => part.trim())
.filter(Boolean);
const sanitizeSuffix = (value, fallbackLabel) => {
if (!value) return fallbackLabel;
const trimmed = value.trim();
if (!trimmed) return fallbackLabel;
const [preRelease = "", metadata] = trimmed.split("+", 2);
const normalizedPre = splitIdentifiers(preRelease).join(".") || fallbackLabel;
const normalizedMeta = metadata ? splitIdentifiers(metadata).join(".") : "";
return normalizedMeta ? `${normalizedPre}+${normalizedMeta}` : normalizedPre;
};
const ensureSemverCompatibleVersion = (
version,
{ channel, releaseTag, fallbackLabel },
) => {
const trimmed = stripLeadingV(version ?? "").trim();
if (!trimmed) return null;
if (STRICT_SEMVER_REGEX.test(trimmed)) {
return trimmed;
}
if (channel === "autobuild") {
const normalizedSuffix = sanitizeSuffix(trimmed, fallbackLabel ?? channel);
const fallback = `0.0.0-${normalizedSuffix}`;
console.warn(
`[${channel}] Normalized non-semver version "${trimmed}" from release "${releaseTag}" to "${fallback}"`,
);
return fallback;
}
throw new Error(
`[${channel}] Derived version "${trimmed}" is not semver compatible for release "${releaseTag}"`,
);
};
const resolveReleaseVersion = (release) => {
const sources = [
release?.name,
release?.tag_name,
release?.body,
...(Array.isArray(release?.assets)
? release.assets.map((asset) => asset?.name)
: []),
];
return sources.reduce((best, source) => {
const candidate = extractBestSemver(source);
return preferCandidate(best, candidate);
}, null);
};
// Add stable update JSON filenames
const UPDATE_TAG_NAME = "updater";
const UPDATE_JSON_FILE = "update.json";
@@ -229,40 +134,13 @@ async function processRelease(github, options, channelConfig) {
});
const releaseTagName = release.tag_name ?? tagName;
const resolvedVersion = resolveReleaseVersion(release);
if (!resolvedVersion) {
throw new Error(
`[${channelName}] Failed to determine semver version from release "${releaseTagName}"`,
);
}
console.log(
`[${channelName}] Preparing update metadata from release "${releaseTagName}"`,
);
console.log(
`[${channelName}] Resolved release version: ${resolvedVersion}`,
);
const semverCompatibleVersion = ensureSemverCompatibleVersion(
resolvedVersion,
{
channel: channelName,
releaseTag: releaseTagName,
fallbackLabel: channelName,
},
);
if (semverCompatibleVersion !== resolvedVersion) {
console.log(
`[${channelName}] Normalized updater version: ${semverCompatibleVersion}`,
);
}
const updateData = {
version: semverCompatibleVersion,
original_version: resolvedVersion,
tag_name: releaseTagName,
name: releaseTagName,
notes: await resolveUpdateLog(releaseTagName).catch(() =>
resolveUpdateLogDefault().catch(() => "No changelog available"),
),

View File

@@ -59,7 +59,7 @@
"Proxy detail": "展示節點細節",
"Profiles": "訂閱",
"Update All Profiles": "更新所有訂閱",
"Update Channel": "更新道",
"Update Channel": "更新道",
"Update Channel Stable": "Stable",
"Update Channel Autobuild": "Autobuild",
"View Runtime Config": "查看執行時訂閱",
@@ -220,7 +220,6 @@
"Settings": "設定",
"System Setting": "系統設定",
"Tun Mode": "虛擬網路介面卡模式",
"TUN requires Service Mode or Admin Mode": "虛擬網路介面卡模式需要服務模式或管理員模式",
"Install Service": "安裝服務",
"Install Service failed": "安裝服務失敗",
"Uninstall Service": "解除安裝服務",
@@ -308,7 +307,6 @@
"Socks Port": "SOCKS 代理連接埠",
"Http Port": "HTTP(S) 代理連接埠",
"Redir Port": "Redir 透明代理連接埠",
"TPROXY Port": "TPROXY 透明代理連接埠",
"Port settings saved": "連結埠設定已儲存",
"Failed to save port settings": "連結埠設定儲存失敗",
"External": "外部控制",
@@ -429,6 +427,8 @@
"Uninstalling Service...": "服務解除安裝中...",
"Service Installed Successfully": "已成功安裝服務",
"Service Uninstalled Successfully": "已成功解除安裝服務",
"Proxy Daemon Duration Cannot be Less than 1 Second": "代理守護間隔時間不得低於 1 秒",
"Invalid Bypass Format": "無效的代理繞過格式",
"Waiting for service to be ready...": "等待服務就緒...",
"Service not ready, retrying attempt {count}/{total}...": "服務未就緒,正在重試 {{count}}/{{total}} 次...",
"Failed to check service status, retrying attempt {count}/{total}...": "檢查服務狀態失敗,正在重試 {{count}}/{{total}} 次...",
@@ -439,8 +439,6 @@
"Fallback core restart also failed: {message}": "被園內核重新啟動也失敗了:{{message}}",
"Service is ready and core restarted": "服務已就緒,內核已重啟",
"Core restarted. Service is now available.": "內核已重啟,服務已就緒",
"Proxy Daemon Duration Cannot be Less than 1 Second": "代理守護間隔時間不得低於 1 秒",
"Invalid Bypass Format": "無效的代理繞過格式",
"Clash Port Modified": "Clash 連結埠已修改",
"Port Conflict": "連結埠衝突",
"Restart Application to Apply Modifications": "重新啟動 Verge 以套用修改",
@@ -716,6 +714,5 @@
"Menu reorder mode": "選單排序模式",
"Unlock menu order": "解鎖選單排序",
"Lock menu order": "鎖定選單排序",
"Open App Log": "應用程式日誌",
"Open Core Log": "內核日誌"
"TPROXY Port": "TPROXY 透明代理連接埠"
}