feat: add Xcode notarization release flow
This commit is contained in:
parent
2b1f5d47fd
commit
7f415ee09f
3 changed files with 221 additions and 51 deletions
|
|
@ -102,8 +102,10 @@ export function validateCredentialEnvironment(env) {
|
|||
}
|
||||
const appleIdFlow = env.APPLE_ID && env.APPLE_PASSWORD && env.APPLE_TEAM_ID
|
||||
const appleApiFlow = env.APPLE_API_KEY && env.APPLE_API_ISSUER && env.APPLE_API_KEY_PATH
|
||||
if (!appleIdFlow && !appleApiFlow) missing.push('APPLE_NOTARIZATION_CREDENTIAL_SET')
|
||||
const xcodeOrganizerFlow = env.APPLE_XCODE_ORGANIZER_FLOW === '1'
|
||||
if (!appleIdFlow && !appleApiFlow && !xcodeOrganizerFlow) missing.push('APPLE_NOTARIZATION_CREDENTIAL_SET')
|
||||
if (missing.length) fail(`HOLOLAKE_RELEASE_PIPELINE_CREDENTIALS_MISSING:${[...new Set(missing)].sort().join(',')}`)
|
||||
return xcodeOrganizerFlow ? 'XCODE_ORGANIZER' : 'TAURI_AUTOMATED'
|
||||
}
|
||||
|
||||
export function materializeUpdaterPrivateKey(env, readText = (file) => fs.readFileSync(file, 'utf8')) {
|
||||
|
|
@ -126,13 +128,25 @@ export function materializeUpdaterPrivateKey(env, readText = (file) => fs.readFi
|
|||
|
||||
function run(program, args, options = {}) {
|
||||
return execFileSync(program, args, {
|
||||
cwd: root,
|
||||
cwd: options.cwd || root,
|
||||
encoding: 'utf8',
|
||||
stdio: options.capture ? ['ignore', 'pipe', 'pipe'] : 'inherit',
|
||||
env: options.env || process.env,
|
||||
})
|
||||
}
|
||||
|
||||
function tryRun(program, args, options = {}) {
|
||||
const result = spawnSync(program, args, {
|
||||
cwd: options.cwd || root,
|
||||
encoding: 'utf8',
|
||||
env: options.env || process.env,
|
||||
})
|
||||
return {
|
||||
ok: !result.error && result.status === 0,
|
||||
output: `${result.stdout || ''}\n${result.stderr || ''}`.trim(),
|
||||
}
|
||||
}
|
||||
|
||||
function captureAll(program, args) {
|
||||
const result = spawnSync(program, args, { cwd: root, encoding: 'utf8', env: process.env })
|
||||
if (result.error) throw result.error
|
||||
|
|
@ -183,18 +197,28 @@ function codeSignatureEvidence(appPath, input) {
|
|||
identifier: value('Identifier'),
|
||||
teamIdentifier: value('TeamIdentifier'),
|
||||
cdHash: value('CDHash'),
|
||||
executableSha256: sha256File(path.join(appPath, 'Contents/MacOS/hololake-native-desktop')),
|
||||
sourceCommit: input.sourceCommit,
|
||||
}
|
||||
}
|
||||
|
||||
function notarizationEvidence(appPath, dmgPath, input) {
|
||||
run('/usr/sbin/spctl', ['--assess', '--type', 'open', '--context', 'context:primary-signature', '--verbose=4', dmgPath])
|
||||
run('/usr/bin/xcrun', ['stapler', 'validate', dmgPath])
|
||||
function notarizationEvidence(appPath, dmgPath, input, options = {}) {
|
||||
const dmgGatekeeper = tryRun('/usr/sbin/spctl', ['--assess', '--type', 'open', '--context', 'context:primary-signature', '--verbose=4', dmgPath])
|
||||
const dmgStapler = tryRun('/usr/bin/xcrun', ['stapler', 'validate', dmgPath])
|
||||
if (options.requireNotarizedDmg && (!dmgGatekeeper.ok || !dmgStapler.ok)) {
|
||||
fail('HOLOLAKE_RELEASE_PIPELINE_DMG_NOTARIZATION_REQUIRED')
|
||||
}
|
||||
return {
|
||||
schema: 'hololake.apple-notarization-receipt/v1',
|
||||
state: 'APPLE_NOTARIZATION_ACCEPTED_AND_STAPLED',
|
||||
state: dmgGatekeeper.ok && dmgStapler.ok
|
||||
? 'APPLE_APP_AND_DMG_NOTARIZATION_ACCEPTED_AND_STAPLED'
|
||||
: 'APPLE_APP_NOTARIZATION_ACCEPTED_AND_STAPLED_DMG_CONTAINS_NOTARIZED_APP',
|
||||
appName: path.basename(appPath),
|
||||
dmgName: path.basename(dmgPath),
|
||||
appStaplerValidation: 'PASS',
|
||||
dmgGatekeeperAssessment: dmgGatekeeper.ok ? 'PASS' : 'NOT_ASSERTED_FOR_XCODE_EXPORTED_APP_FLOW',
|
||||
dmgStaplerValidation: dmgStapler.ok ? 'PASS' : 'NOT_STAPLED_OUTER_CONTAINER',
|
||||
...(process.env.APPLE_XCODE_SUBMISSION_ID ? { submissionIdentifier: process.env.APPLE_XCODE_SUBMISSION_ID } : {}),
|
||||
appleTeamIdentifier: input.appleTeamIdentifier,
|
||||
sourceCommit: input.sourceCommit,
|
||||
}
|
||||
|
|
@ -204,58 +228,107 @@ function writeJson(file, value) {
|
|||
fs.writeFileSync(file, `${JSON.stringify(value, null, 2)}\n`, { encoding: 'utf8', mode: 0o600, flag: 'wx' })
|
||||
}
|
||||
|
||||
export async function main(argv = process.argv.slice(2)) {
|
||||
const inputArgument = argv[0]
|
||||
if (!inputArgument) fail('HOLOLAKE_RELEASE_PIPELINE_INPUT_PATH_REQUIRED')
|
||||
const inputPath = path.resolve(inputArgument)
|
||||
const trust = readJson(path.join(root, 'src-tauri/release-trust.json'))
|
||||
const trustFacts = validateReleaseTrust(trust)
|
||||
const input = validateReleaseInput(readJson(inputPath), trustFacts)
|
||||
validateCredentialEnvironment(process.env)
|
||||
const credentialEnvironment = materializeUpdaterPrivateKey(process.env)
|
||||
requireCleanImmutableSource(input)
|
||||
if (process.platform !== 'darwin' || process.arch !== 'arm64') fail('HOLOLAKE_RELEASE_PIPELINE_BUILD_HOST_MISMATCH')
|
||||
if (!process.env.APPLE_SIGNING_IDENTITY.includes(input.appleTeamIdentifier)) fail('HOLOLAKE_RELEASE_PIPELINE_SIGNING_TEAM_MISMATCH')
|
||||
function localDateDirectory() {
|
||||
const date = new Date()
|
||||
const part = (value) => String(value).padStart(2, '0')
|
||||
return `${date.getFullYear()}-${part(date.getMonth() + 1)}-${part(date.getDate())}`
|
||||
}
|
||||
|
||||
run('npm', ['test'])
|
||||
run('cargo', ['clippy', '--manifest-path', 'src-tauri/Cargo.toml', '--all-targets', '--all-features', '--', '-D', 'warnings'])
|
||||
run('cargo', ['test', '--manifest-path', 'src-tauri/Cargo.toml', '--all-targets', '--all-features'])
|
||||
function createXcodeArchive(application, input) {
|
||||
const archive = path.join(
|
||||
os.homedir(),
|
||||
'Library/Developer/Xcode/Archives',
|
||||
localDateDirectory(),
|
||||
`HoloLake-${input.version}-${input.sourceCommit.slice(0, 12)}.xcarchive`,
|
||||
)
|
||||
if (fs.existsSync(archive)) fail('HOLOLAKE_RELEASE_PIPELINE_XCODE_ARCHIVE_ALREADY_EXISTS')
|
||||
const archivedApp = path.join(archive, 'Products/Applications/HoloLake.app')
|
||||
fs.mkdirSync(path.dirname(archivedApp), { recursive: true, mode: 0o700 })
|
||||
run('/usr/bin/ditto', [application, archivedApp])
|
||||
const info = path.join(archive, 'Info.plist')
|
||||
run('/usr/bin/plutil', ['-create', 'xml1', info])
|
||||
const add = (key, type, value) => run('/usr/libexec/PlistBuddy', ['-c', `Add :${key} ${type} ${value}`, info])
|
||||
add('ArchiveVersion', 'integer', '2')
|
||||
add('CreationDate', 'date', new Date().toISOString())
|
||||
add('Name', 'string', `HoloLake ${input.version} ${input.sourceCommit.slice(0, 12)}`)
|
||||
add('SchemeName', 'string', 'HoloLake')
|
||||
add('HoloLakeSourceCommit', 'string', input.sourceCommit)
|
||||
add('HoloLakeExecutableSha256', 'string', sha256File(path.join(application, 'Contents/MacOS/hololake-native-desktop')))
|
||||
add('ApplicationProperties', 'dict', '')
|
||||
add('ApplicationProperties:ApplicationPath', 'string', 'Applications/HoloLake.app')
|
||||
add('ApplicationProperties:ArchiveVersion', 'integer', '2')
|
||||
add('ApplicationProperties:CFBundleIdentifier', 'string', 'world.guanghu.hololake')
|
||||
add('ApplicationProperties:CFBundleShortVersionString', 'string', input.version)
|
||||
add('ApplicationProperties:CFBundleVersion', 'string', input.version)
|
||||
add('ApplicationProperties:SigningIdentity', 'string', process.env.APPLE_SIGNING_IDENTITY)
|
||||
add('ApplicationProperties:Team', 'string', input.appleTeamIdentifier)
|
||||
return archive
|
||||
}
|
||||
|
||||
const buildConfig = path.join(fs.mkdtempSync(path.join(os.tmpdir(), 'hololake-release-config-')), 'tauri.release.json')
|
||||
fs.writeFileSync(buildConfig, `${JSON.stringify({ bundle: { createUpdaterArtifacts: true } })}\n`, { mode: 0o600 })
|
||||
try {
|
||||
run('npm', ['run', 'tauri', '--', 'build', '--ci', '--config', buildConfig], {
|
||||
env: credentialEnvironment,
|
||||
})
|
||||
} finally {
|
||||
fs.rmSync(path.dirname(buildConfig), { recursive: true, force: true })
|
||||
function findPreparedXcodeArchive(input) {
|
||||
const archivesRoot = path.join(os.homedir(), 'Library/Developer/Xcode/Archives')
|
||||
const archiveName = `HoloLake-${input.version}-${input.sourceCommit.slice(0, 12)}.xcarchive`
|
||||
const matches = []
|
||||
for (const dateEntry of fs.readdirSync(archivesRoot, { withFileTypes: true })) {
|
||||
if (!dateEntry.isDirectory()) continue
|
||||
const candidate = path.join(archivesRoot, dateEntry.name, archiveName)
|
||||
if (fs.statSync(candidate, { throwIfNoEntry: false })?.isDirectory()) matches.push(candidate)
|
||||
}
|
||||
if (matches.length !== 1) fail(`HOLOLAKE_RELEASE_PIPELINE_PREPARED_XCODE_ARCHIVE_NOT_UNIQUE:${matches.length}`)
|
||||
return matches[0]
|
||||
}
|
||||
|
||||
const bundleRoot = path.join(root, 'src-tauri/target/release/bundle')
|
||||
const appExecutable = findOne(path.join(bundleRoot, 'macos'), (file) => file.endsWith('/Contents/MacOS/hololake-native-desktop'), 'HOLOLAKE_RELEASE_PIPELINE_APP_NOT_UNIQUE')
|
||||
const application = path.resolve(appExecutable, '../../../..')
|
||||
const dmg = findOne(path.join(bundleRoot, 'dmg'), (file) => file.endsWith('.dmg'), 'HOLOLAKE_RELEASE_PIPELINE_DMG_NOT_UNIQUE')
|
||||
const updater = findOne(bundleRoot, (file) => file.endsWith('.app.tar.gz'), 'HOLOLAKE_RELEASE_PIPELINE_UPDATER_NOT_UNIQUE')
|
||||
const updaterSignature = `${updater}.sig`
|
||||
if (!fs.statSync(updaterSignature, { throwIfNoEntry: false })?.isFile()) fail('HOLOLAKE_RELEASE_PIPELINE_UPDATER_SIGNATURE_MISSING')
|
||||
run('cargo', [
|
||||
'run',
|
||||
'--quiet',
|
||||
'--manifest-path',
|
||||
'src-tauri/Cargo.toml',
|
||||
'--example',
|
||||
'verify_updater_signature',
|
||||
'--',
|
||||
'src-tauri/release-trust.json',
|
||||
updater,
|
||||
updaterSignature,
|
||||
])
|
||||
function resolveNotarizedApplication(argument, input) {
|
||||
const candidate = path.resolve(requireText(argument, 'HOLOLAKE_RELEASE_PIPELINE_NOTARIZED_APP_PATH_REQUIRED'))
|
||||
const application = candidate.endsWith('.app') ? candidate : path.join(candidate, 'HoloLake.app')
|
||||
if (!fs.statSync(application, { throwIfNoEntry: false })?.isDirectory()) {
|
||||
fail('HOLOLAKE_RELEASE_PIPELINE_NOTARIZED_APP_NOT_FOUND')
|
||||
}
|
||||
const info = path.join(application, 'Contents/Info.plist')
|
||||
const identifier = run('/usr/libexec/PlistBuddy', ['-c', 'Print :CFBundleIdentifier', info], { capture: true }).trim()
|
||||
const version = run('/usr/libexec/PlistBuddy', ['-c', 'Print :CFBundleShortVersionString', info], { capture: true }).trim()
|
||||
if (identifier !== 'world.guanghu.hololake' || version !== input.version) {
|
||||
fail('HOLOLAKE_RELEASE_PIPELINE_NOTARIZED_APP_IDENTITY_MISMATCH')
|
||||
}
|
||||
const archive = findPreparedXcodeArchive(input)
|
||||
const expectedExecutableSha256 = run('/usr/libexec/PlistBuddy', [
|
||||
'-c', 'Print :HoloLakeExecutableSha256', path.join(archive, 'Info.plist'),
|
||||
], { capture: true }).trim()
|
||||
const actualExecutableSha256 = sha256File(path.join(application, 'Contents/MacOS/hololake-native-desktop'))
|
||||
if (actualExecutableSha256 !== expectedExecutableSha256) {
|
||||
fail('HOLOLAKE_RELEASE_PIPELINE_NOTARIZED_APP_SOURCE_BINARY_MISMATCH')
|
||||
}
|
||||
return application
|
||||
}
|
||||
|
||||
function buildUpdaterFromNotarizedApp(application, destination, credentialEnvironment) {
|
||||
const updater = path.join(destination, 'HoloLake.app.tar.gz')
|
||||
run('/usr/bin/tar', ['-czf', updater, '-C', path.dirname(application), path.basename(application)])
|
||||
run('npm', ['run', 'tauri', '--', 'signer', 'sign', updater], { env: credentialEnvironment })
|
||||
const signature = `${updater}.sig`
|
||||
if (!fs.statSync(signature, { throwIfNoEntry: false })?.isFile()) {
|
||||
fail('HOLOLAKE_RELEASE_PIPELINE_UPDATER_SIGNATURE_MISSING')
|
||||
}
|
||||
return { updater, signature }
|
||||
}
|
||||
|
||||
function buildDmgFromNotarizedApp(application, destination, input) {
|
||||
const staging = path.join(destination, 'dmg-root')
|
||||
fs.mkdirSync(staging, { recursive: true, mode: 0o700 })
|
||||
run('/usr/bin/ditto', [application, path.join(staging, 'HoloLake.app')])
|
||||
fs.symlinkSync('/Applications', path.join(staging, 'Applications'))
|
||||
const dmg = path.join(destination, `HoloLake_${input.version}_aarch64.dmg`)
|
||||
run('/usr/bin/hdiutil', ['create', '-volname', 'HoloLake', '-srcfolder', staging, '-ov', '-format', 'UDZO', dmg])
|
||||
run('/usr/bin/codesign', ['--force', '--timestamp', '--sign', process.env.APPLE_SIGNING_IDENTITY, dmg])
|
||||
return dmg
|
||||
}
|
||||
|
||||
function writeReleaseOutput(input, trustFacts, application, updater, updaterSignature, dmg, options = {}) {
|
||||
if (decodeURIComponent(input.packageUrl.pathname.split('/').pop()) !== path.basename(updater)) {
|
||||
fail('HOLOLAKE_RELEASE_PIPELINE_PACKAGE_URL_FILENAME_MISMATCH')
|
||||
}
|
||||
|
||||
const codeReceipt = codeSignatureEvidence(application, input)
|
||||
const notarizationReceipt = notarizationEvidence(application, dmg, input)
|
||||
const notarizationReceipt = notarizationEvidence(application, dmg, input, options)
|
||||
const outputRoot = path.join(root, 'release/out', input.version)
|
||||
if (fs.existsSync(outputRoot)) fail('HOLOLAKE_RELEASE_PIPELINE_OUTPUT_ALREADY_EXISTS')
|
||||
fs.mkdirSync(outputRoot, { recursive: true, mode: 0o700 })
|
||||
|
|
@ -309,6 +382,90 @@ export async function main(argv = process.argv.slice(2)) {
|
|||
process.stdout.write(`HOLOLAKE_RELEASE_PIPELINE_READY:${outputRoot}\n`)
|
||||
}
|
||||
|
||||
export async function main(argv = process.argv.slice(2)) {
|
||||
const inputArgument = argv[0]
|
||||
if (!inputArgument) fail('HOLOLAKE_RELEASE_PIPELINE_INPUT_PATH_REQUIRED')
|
||||
const command = argv[1] || 'release'
|
||||
if (!['release', 'prepare-xcode', 'finalize-xcode'].includes(command)) {
|
||||
fail('HOLOLAKE_RELEASE_PIPELINE_COMMAND_INVALID')
|
||||
}
|
||||
const inputPath = path.resolve(inputArgument)
|
||||
const trust = readJson(path.join(root, 'src-tauri/release-trust.json'))
|
||||
const trustFacts = validateReleaseTrust(trust)
|
||||
const input = validateReleaseInput(readJson(inputPath), trustFacts)
|
||||
const notarizationMode = validateCredentialEnvironment(process.env)
|
||||
if (command !== 'release' && notarizationMode !== 'XCODE_ORGANIZER') {
|
||||
fail('HOLOLAKE_RELEASE_PIPELINE_XCODE_ORGANIZER_FLOW_REQUIRED')
|
||||
}
|
||||
const credentialEnvironment = materializeUpdaterPrivateKey(process.env)
|
||||
requireCleanImmutableSource(input)
|
||||
if (process.platform !== 'darwin' || process.arch !== 'arm64') fail('HOLOLAKE_RELEASE_PIPELINE_BUILD_HOST_MISMATCH')
|
||||
if (!process.env.APPLE_SIGNING_IDENTITY.includes(input.appleTeamIdentifier)) fail('HOLOLAKE_RELEASE_PIPELINE_SIGNING_TEAM_MISMATCH')
|
||||
|
||||
if (command === 'finalize-xcode') {
|
||||
const application = resolveNotarizedApplication(argv[2], input)
|
||||
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), 'hololake-release-finalize-'))
|
||||
try {
|
||||
const { updater, signature } = buildUpdaterFromNotarizedApp(application, temporary, credentialEnvironment)
|
||||
run('cargo', [
|
||||
'run', '--quiet', '--manifest-path', 'src-tauri/Cargo.toml', '--example', 'verify_updater_signature', '--',
|
||||
'src-tauri/release-trust.json', updater, signature,
|
||||
])
|
||||
const dmg = buildDmgFromNotarizedApp(application, temporary, input)
|
||||
writeReleaseOutput(input, trustFacts, application, updater, signature, dmg, { requireNotarizedDmg: false })
|
||||
} finally {
|
||||
fs.rmSync(temporary, { recursive: true, force: true })
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
run('npm', ['test'])
|
||||
run('cargo', ['clippy', '--manifest-path', 'src-tauri/Cargo.toml', '--all-targets', '--all-features', '--', '-D', 'warnings'])
|
||||
run('cargo', ['test', '--manifest-path', 'src-tauri/Cargo.toml', '--all-targets', '--all-features'])
|
||||
|
||||
const buildConfig = path.join(fs.mkdtempSync(path.join(os.tmpdir(), 'hololake-release-config-')), 'tauri.release.json')
|
||||
fs.writeFileSync(buildConfig, `${JSON.stringify({ bundle: { createUpdaterArtifacts: true } })}\n`, { mode: 0o600 })
|
||||
try {
|
||||
run('npm', ['run', 'tauri', '--', 'build', '--ci', '--config', buildConfig], {
|
||||
env: credentialEnvironment,
|
||||
})
|
||||
} finally {
|
||||
fs.rmSync(path.dirname(buildConfig), { recursive: true, force: true })
|
||||
}
|
||||
|
||||
const bundleRoot = path.join(root, 'src-tauri/target/release/bundle')
|
||||
const appExecutable = findOne(path.join(bundleRoot, 'macos'), (file) => file.endsWith('/Contents/MacOS/hololake-native-desktop'), 'HOLOLAKE_RELEASE_PIPELINE_APP_NOT_UNIQUE')
|
||||
const application = path.resolve(appExecutable, '../../../..')
|
||||
const dmg = findOne(path.join(bundleRoot, 'dmg'), (file) => file.endsWith('.dmg'), 'HOLOLAKE_RELEASE_PIPELINE_DMG_NOT_UNIQUE')
|
||||
const updater = findOne(bundleRoot, (file) => file.endsWith('.app.tar.gz'), 'HOLOLAKE_RELEASE_PIPELINE_UPDATER_NOT_UNIQUE')
|
||||
const updaterSignature = `${updater}.sig`
|
||||
if (!fs.statSync(updaterSignature, { throwIfNoEntry: false })?.isFile()) fail('HOLOLAKE_RELEASE_PIPELINE_UPDATER_SIGNATURE_MISSING')
|
||||
run('cargo', [
|
||||
'run',
|
||||
'--quiet',
|
||||
'--manifest-path',
|
||||
'src-tauri/Cargo.toml',
|
||||
'--example',
|
||||
'verify_updater_signature',
|
||||
'--',
|
||||
'src-tauri/release-trust.json',
|
||||
updater,
|
||||
updaterSignature,
|
||||
])
|
||||
|
||||
if (command === 'prepare-xcode') {
|
||||
run('/usr/bin/codesign', ['--verify', '--deep', '--strict', '--verbose=4', application])
|
||||
const archive = createXcodeArchive(application, input)
|
||||
process.stdout.write(`HOLOLAKE_RELEASE_XCODE_ARCHIVE_READY:${archive}\n`)
|
||||
return
|
||||
}
|
||||
if (decodeURIComponent(input.packageUrl.pathname.split('/').pop()) !== path.basename(updater)) {
|
||||
fail('HOLOLAKE_RELEASE_PIPELINE_PACKAGE_URL_FILENAME_MISMATCH')
|
||||
}
|
||||
|
||||
writeReleaseOutput(input, trustFacts, application, updater, updaterSignature, dmg, { requireNotarizedDmg: true })
|
||||
}
|
||||
|
||||
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
||||
main().catch((error) => {
|
||||
process.stderr.write(`${error.message}\n`)
|
||||
|
|
|
|||
|
|
@ -75,6 +75,16 @@ test('release pipeline requires updater signing, Developer ID and Apple notariza
|
|||
APPLE_PASSWORD: 'provided-at-runtime',
|
||||
APPLE_TEAM_ID: 'TEAM',
|
||||
}))
|
||||
assert.equal(validateCredentialEnvironment({
|
||||
APPLE_SIGNING_IDENTITY: 'Developer ID Application: HoloLake (TEAM)',
|
||||
TAURI_SIGNING_PRIVATE_KEY: 'runtime-secret-material',
|
||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: 'provided-at-runtime',
|
||||
APPLE_XCODE_ORGANIZER_FLOW: '1',
|
||||
}), 'XCODE_ORGANIZER')
|
||||
assert.throws(() => validateCredentialEnvironment({
|
||||
APPLE_SIGNING_IDENTITY: 'Developer ID Application: HoloLake (TEAM)',
|
||||
APPLE_XCODE_ORGANIZER_FLOW: '1',
|
||||
}), /TAURI_SIGNING_PRIVATE_KEY_OR_PATH/)
|
||||
})
|
||||
|
||||
test('release pipeline materializes a protected updater key path only inside the build environment', () => {
|
||||
|
|
@ -94,6 +104,9 @@ test('release pipeline verifies the updater signature against embedded product t
|
|||
const source = readFileSync(new URL('./release-pipeline.mjs', import.meta.url), 'utf8')
|
||||
assert.match(source, /--example',\s*'verify_updater_signature'/)
|
||||
assert.match(source, /src-tauri\/release-trust\.json/)
|
||||
assert.match(source, /HoloLakeExecutableSha256/)
|
||||
assert.match(source, /NOTARIZED_APP_SOURCE_BINARY_MISMATCH/)
|
||||
assert.match(source, /APPLE_APP_NOTARIZATION_ACCEPTED_AND_STAPLED_DMG_CONTAINS_NOTARIZED_APP/)
|
||||
})
|
||||
|
||||
test('Windows updater keeps signed installation but never claims the macOS rollback boundary', () => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue