Compare commits
5 Commits
chore/upda
...
updater-au
@@ -55,6 +55,7 @@
|
||||
- 托盘 `更多` 中新增 `关闭所有连接` 按钮
|
||||
- 新增左侧菜单栏的排序功能(右键点击左侧菜单栏)
|
||||
- 托盘 `打开目录` 中新增 `应用日志` 和 `内核日志`
|
||||
- 支持更新通道切换 (Stable / Autobuild)
|
||||
</details>
|
||||
|
||||
<details>
|
||||
|
||||
@@ -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()}`;
|
||||
}
|
||||
|
||||
@@ -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"),
|
||||
),
|
||||
|
||||
@@ -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 透明代理連接埠"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user