diff --git a/.github/workflows/desktop-client-dev.yml b/.github/workflows/desktop-client-dev.yml index 5e4c5fe..3c2e2ac 100644 --- a/.github/workflows/desktop-client-dev.yml +++ b/.github/workflows/desktop-client-dev.yml @@ -44,6 +44,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: + fail-fast: false matrix: include: # 固定 runner 大版本,避免 -latest 自动迁移导致构建环境突变 diff --git a/desktop-client/scripts/electron-builder-green.config.js b/desktop-client/scripts/electron-builder-green.config.js index 7f023cd..39ce6a1 100644 --- a/desktop-client/scripts/electron-builder-green.config.js +++ b/desktop-client/scripts/electron-builder-green.config.js @@ -1,9 +1,3 @@ -function macNotarize() { - const teamId = process.env.APPLE_TEAM_ID; - if (!teamId) return false; - return { teamId }; -} - module.exports = { appId: "com.gogoshine.capcutmate", productName: "CapCut-Mate", @@ -11,6 +5,7 @@ module.exports = { output: "dist" }, afterPack: require("./afterPackWinIcon"), + afterSign: require("./notarize"), win: { icon: "assets/icons/logo.ico", artifactName: "capcut-mate-windows-x64-green.zip", @@ -25,7 +20,7 @@ module.exports = { gatekeeperAssess: false, entitlements: "assets/entitlements.mac.plist", entitlementsInherit: "assets/entitlements.mac.plist", - notarize: macNotarize() + notarize: false }, files: [ "!node_modules/**/*", diff --git a/desktop-client/scripts/electron-builder.config.js b/desktop-client/scripts/electron-builder.config.js index b7bd2f3..2dee3c0 100644 --- a/desktop-client/scripts/electron-builder.config.js +++ b/desktop-client/scripts/electron-builder.config.js @@ -1,13 +1,6 @@ /** * Electron Builder Configuration */ -function macNotarize() { - const teamId = process.env.APPLE_TEAM_ID; - // 本地无 Team ID 时跳过公证;CI 注入 APPLE_TEAM_ID + Apple ID 凭据后启用 - if (!teamId) return false; - return { teamId }; -} - module.exports = { appId: "com.gogoshine.capcutmate", productName: "剪映小助手", @@ -15,6 +8,7 @@ module.exports = { output: "dist" }, afterPack: require("./afterPackWinIcon"), + afterSign: require("./notarize"), files: [ "**/*", // "!node_modules", @@ -53,7 +47,8 @@ module.exports = { gatekeeperAssess: false, entitlements: "assets/entitlements.mac.plist", entitlementsInherit: "assets/entitlements.mac.plist", - notarize: macNotarize() + // 公证改由 afterSign(scripts/notarize.js)处理,便于重试 + notarize: false }, dmg: { background: null, diff --git a/desktop-client/scripts/notarize.js b/desktop-client/scripts/notarize.js new file mode 100644 index 0000000..60caa0d --- /dev/null +++ b/desktop-client/scripts/notarize.js @@ -0,0 +1,63 @@ +/** + * macOS 公证(带重试) + * 解决 Apple notarytool 偶发返回非 JSON 的 HTTP 错误导致 CI 失败。 + */ +const path = require("path"); +const { execFileSync } = require("child_process"); + +function loadNotarize() { + try { + return require("@electron/notarize"); + } catch (_) { + // electron-builder 自带的嵌套依赖 + return require("app-builder-lib/node_modules/@electron/notarize"); + } +} + +exports.default = async function notarizeAfterSign(context) { + const { electronPlatformName, appOutDir } = context; + if (electronPlatformName !== "darwin") return; + + const appleId = process.env.APPLE_ID; + const appleIdPassword = process.env.APPLE_APP_SPECIFIC_PASSWORD; + const teamId = process.env.APPLE_TEAM_ID; + if (!appleId || !appleIdPassword || !teamId) { + console.log("skip notarize: APPLE_ID / APPLE_APP_SPECIFIC_PASSWORD / APPLE_TEAM_ID not set"); + return; + } + + const { notarize } = loadNotarize(); + const appName = context.packager.appInfo.productFilename; + const appPath = path.join(appOutDir, `${appName}.app`); + + const maxAttempts = 5; + let lastError; + + for (let attempt = 1; attempt <= maxAttempts; attempt++) { + try { + console.log(`notarizing ${appPath} (attempt ${attempt}/${maxAttempts})`); + await notarize({ + tool: "notarytool", + appPath, + appleId, + appleIdPassword, + teamId + }); + console.log("notarization successful, stapling..."); + execFileSync("xcrun", ["stapler", "staple", "-v", appPath], { stdio: "inherit" }); + console.log("staple successful"); + return; + } catch (err) { + lastError = err; + const message = err && err.message ? err.message : String(err); + console.warn(`notarize attempt ${attempt} failed: ${message}`); + if (attempt < maxAttempts) { + const waitSec = attempt * 45; + console.log(`waiting ${waitSec}s before retry...`); + await new Promise((resolve) => setTimeout(resolve, waitSec * 1000)); + } + } + } + + throw lastError; +};