| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869 |
- import { createHash } from 'node:crypto'
- import { execFileSync } from 'node:child_process'
- import { createReadStream } from 'node:fs'
- import { readFile, stat, writeFile } from 'node:fs/promises'
- import { basename, join, resolve } from 'node:path'
- import { pathToFileURL } from 'node:url'
- import { load } from 'js-yaml'
- export function releaseSigningOptions(env = process.env) {
- if (!env.CSC_NAME?.startsWith('Developer ID Application:'))
- throw new Error('正式自动更新包需要 CSC_NAME 指定 Developer ID Application 签名身份')
- const apiKey = env.APPLE_API_KEY && env.APPLE_API_KEY_ID && env.APPLE_API_ISSUER
- const appleId = env.APPLE_ID && env.APPLE_APP_SPECIFIC_PASSWORD && env.APPLE_TEAM_ID
- const keychainProfile = env.APPLE_KEYCHAIN_PROFILE?.trim()
- if (!apiKey && !appleId && !keychainProfile) throw new Error('请配置 Apple 公证凭据:APPLE_KEYCHAIN_PROFILE,APPLE_API_KEY/APPLE_API_KEY_ID/APPLE_API_ISSUER,或 APPLE_ID/APPLE_APP_SPECIFIC_PASSWORD/APPLE_TEAM_ID')
- // electron-builder selects the certificate type itself and rejects its prefix
- // in the identity qualifier. Enforce a distribution identity, then strip it.
- const identity = env.CSC_NAME.slice('Developer ID Application:'.length).trim()
- if (!identity) throw new Error('CSC_NAME 缺少分发签名身份名称')
- return { forceCodeSigning: true, mac: { identity, type: 'distribution', notarize: true } }
- }
- async function digest(file, algorithm, encoding) {
- const hash = createHash(algorithm)
- for await (const chunk of createReadStream(file)) hash.update(chunk)
- return hash.digest(encoding)
- }
- export async function verifyUpdateArtifacts(directory, version, arch = 'arm64') {
- const metadata = load(await readFile(join(directory, 'latest-mac.yml'), 'utf8'))
- if (metadata?.version !== version || !Array.isArray(metadata.files)) throw new Error('自动更新清单版本无效')
- const required = [`Yaoyao-${version}-${arch}.zip`, `Yaoyao-${version}-${arch}.dmg`]
- for (const name of required) {
- const matches = metadata.files.filter(file => file.url === name)
- if (matches.length !== 1) throw new Error(`自动更新清单缺少唯一附件:${name}`)
- }
- const names = ['latest-mac.yml']
- for (const file of metadata.files) {
- if (typeof file.url !== 'string' || basename(file.url) !== file.url || !required.includes(file.url)) throw new Error('自动更新清单包含非本次发布的附件')
- const fullPath = join(directory, file.url), info = await stat(fullPath)
- if (file.size !== info.size || !info.size || !info.isFile()) throw new Error(`安装包大小无效:${file.url}`)
- if (file.sha512 !== await digest(fullPath, 'sha512', 'base64')) throw new Error(`安装包 SHA-512 不匹配:${file.url}`)
- names.push(file.url)
- const blockmap = `${file.url}.blockmap`
- if (!(await stat(join(directory, blockmap))).size) throw new Error(`差分更新文件为空:${blockmap}`)
- names.push(blockmap)
- }
- return names
- }
- export async function packageDesktopRelease() {
- const signing = releaseSigningOptions()
- const root = resolve(import.meta.dirname, '..')
- const { version } = JSON.parse(await readFile(join(root, 'package.json'), 'utf8'))
- const { build, Platform, Arch } = await import('electron-builder')
- await build({ projectDir: root, targets: Platform.MAC.createTarget(['dmg', 'zip'], Arch.arm64),
- publish: 'never', config: signing })
- const directory = join(root, 'desktop-release')
- const names = await verifyUpdateArtifacts(directory, version)
- const app = join(directory, 'mac-arm64', '夭夭完整版.app')
- const feed = load(await readFile(join(app, 'Contents/Resources/app-update.yml'), 'utf8'))
- if (feed.provider !== 'github' || feed.owner !== 'Lsamien' || feed.repo !== 'yaoyao') throw new Error('安装包中的更新源不正确')
- execFileSync('codesign', ['--verify', '--deep', '--strict', app], { stdio: 'inherit' })
- execFileSync('xcrun', ['stapler', 'validate', app], { stdio: 'inherit' })
- const sums = await Promise.all(names.map(async name => `${await digest(join(directory, name), 'sha256', 'hex')} ${name}`))
- await writeFile(join(directory, 'SHA256SUMS.txt'), sums.join('\n') + '\n')
- console.log(`正式桌面产物已验证:${[...names, 'SHA256SUMS.txt'].join('、')}。尚未上传发布。`)
- }
- if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) await packageDesktopRelease()
|