release(main): 同步 dev 最新候选改动
Storage Persistence Guard / storage-persistence-audit (push) Has been cancelled
Client Quality Gates / Shared client and Web (push) Has been cancelled
Client Quality Gates / macOS Desktop (push) Has been cancelled
Client Quality Gates / Shared client and Web (pull_request) Has been cancelled
Client Quality Gates / macOS Desktop (pull_request) Has been cancelled
Storage Persistence Guard / storage-persistence-audit (pull_request) Has been cancelled
Storage Persistence Guard / storage-persistence-audit (push) Has been cancelled
Client Quality Gates / Shared client and Web (push) Has been cancelled
Client Quality Gates / macOS Desktop (push) Has been cancelled
Client Quality Gates / Shared client and Web (pull_request) Has been cancelled
Client Quality Gates / macOS Desktop (pull_request) Has been cancelled
Storage Persistence Guard / storage-persistence-audit (pull_request) Has been cancelled
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const frontendDir = fileURLToPath(new URL("../", import.meta.url));
|
||||
const rootDir = resolve(frontendDir, "..");
|
||||
const packageInfo = JSON.parse(await readFile(resolve(frontendDir, "package.json"), "utf8"));
|
||||
const failures = [];
|
||||
|
||||
const env = process.env;
|
||||
const fullShaPattern = /^[0-9a-f]{40}$/i;
|
||||
const expectedTag = `v${packageInfo.version}`;
|
||||
const requiredSecretLikeEnv = [
|
||||
"TAURI_SIGNING_PRIVATE_KEY",
|
||||
"TAURI_SIGNING_PRIVATE_KEY_PASSWORD",
|
||||
"APPLE_ID",
|
||||
"APPLE_PASSWORD",
|
||||
"APPLE_TEAM_ID",
|
||||
];
|
||||
|
||||
const fail = (message) => failures.push(message);
|
||||
const assert = (condition, message) => {
|
||||
if (!condition) fail(message);
|
||||
};
|
||||
|
||||
const git = (args) =>
|
||||
execFileSync("git", args, {
|
||||
cwd: rootDir,
|
||||
encoding: "utf8",
|
||||
stdio: ["ignore", "pipe", "ignore"],
|
||||
}).trim();
|
||||
|
||||
const gitMaybe = (args) => {
|
||||
try {
|
||||
return git(args);
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
const requireEnv = (name) => {
|
||||
assert(Boolean(env[name]), `${name} must be configured for signed desktop release readiness.`);
|
||||
};
|
||||
|
||||
const validateBaseUrl = () => {
|
||||
const raw = env.DESKTOP_UPDATE_BASE_URL;
|
||||
requireEnv("DESKTOP_UPDATE_BASE_URL");
|
||||
if (!raw) return;
|
||||
|
||||
let url;
|
||||
try {
|
||||
url = new URL(raw.endsWith("/") ? raw : `${raw}/`);
|
||||
} catch (error) {
|
||||
fail(`DESKTOP_UPDATE_BASE_URL is invalid: ${error.message}`);
|
||||
return;
|
||||
}
|
||||
|
||||
assert(url.protocol === "https:", "DESKTOP_UPDATE_BASE_URL must use HTTPS.");
|
||||
assert(url.username === "" && url.password === "", "DESKTOP_UPDATE_BASE_URL must not include credentials.");
|
||||
assert(!/[?&]token=/i.test(url.search), "DESKTOP_UPDATE_BASE_URL must not include token query parameters.");
|
||||
assert(
|
||||
url.pathname.includes(packageInfo.version),
|
||||
`DESKTOP_UPDATE_BASE_URL must include the immutable version segment ${packageInfo.version}.`,
|
||||
);
|
||||
};
|
||||
|
||||
const headSha = gitMaybe(["rev-parse", "HEAD"]);
|
||||
const exactTag = gitMaybe(["describe", "--tags", "--exact-match", "HEAD"]);
|
||||
const status = gitMaybe(["status", "--porcelain"]);
|
||||
|
||||
assert(Boolean(headSha), "Current Git commit cannot be resolved.");
|
||||
assert(exactTag === expectedTag, `Current commit must be exactly tagged ${expectedTag}; found ${exactTag || "<none>"}.`);
|
||||
assert(status === "", "Release readiness requires a clean working tree.");
|
||||
|
||||
assert(env.VITE_BUILD_CHANNEL === "release", "VITE_BUILD_CHANNEL must be release.");
|
||||
assert(fullShaPattern.test(env.VITE_BUILD_COMMIT || ""), "VITE_BUILD_COMMIT must be the full release commit SHA.");
|
||||
if (headSha && env.VITE_BUILD_COMMIT) {
|
||||
assert(env.VITE_BUILD_COMMIT === headSha, "VITE_BUILD_COMMIT must match the current release commit.");
|
||||
}
|
||||
|
||||
for (const name of requiredSecretLikeEnv) {
|
||||
requireEnv(name);
|
||||
}
|
||||
|
||||
assert(
|
||||
Boolean(env.APPLE_CERTIFICATE || env.APPLE_SIGNING_IDENTITY),
|
||||
"APPLE_CERTIFICATE or APPLE_SIGNING_IDENTITY must be configured for macOS signing.",
|
||||
);
|
||||
if (env.APPLE_CERTIFICATE) {
|
||||
requireEnv("APPLE_CERTIFICATE_PASSWORD");
|
||||
}
|
||||
|
||||
validateBaseUrl();
|
||||
|
||||
if (failures.length > 0) {
|
||||
console.error(`Desktop release readiness check failed:\n${failures.map((item) => ` - ${item}`).join("\n")}`);
|
||||
process.exitCode = 1;
|
||||
} else {
|
||||
console.log("Desktop release readiness check passed.");
|
||||
}
|
||||
Reference in New Issue
Block a user