feat(release): prepare 1.1.0 candidate

This commit is contained in:
Yun Chan 2026-08-29 18:33:45 +09:00
parent 5a34f66981
commit 5205dcdfa9
736 changed files with 115667 additions and 12203 deletions

View file

@ -0,0 +1,145 @@
import { spawnSync } from 'node:child_process'
import { dirname, join, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import {
buildReleasePayload,
createImmutableVerificationSnapshot,
hashRegularFileStable,
readSmallFileStable,
releaseBoundaryFail,
signReleaseEvidence,
writeJsonCreateOnly,
} from './mobile-release-evidence-lib.mjs'
function parseArguments(argv) {
const allowed = new Set([
'aab',
'apk',
'bundletool',
'commit-sha',
'expected-admob-app-id',
'expected-upload-cert-sha256',
'expected-version-code',
'expected-version-name',
'git-ref',
'private-key',
'repository',
'run-attempt',
'run-id',
'runner-identity',
'snapshot-dir',
'tree-sha',
'workflow-identity',
])
const result = {}
for (let index = 0; index < argv.length; index += 2) {
const flag = argv[index]
const value = argv[index + 1]
if (!flag?.startsWith('--') || !value || value.startsWith('--')) {
releaseBoundaryFail('create_evidence_arguments_invalid')
}
const name = flag.slice(2)
if (!allowed.has(name) || Object.hasOwn(result, name)) {
releaseBoundaryFail(`create_evidence_argument_rejected_${name}`)
}
result[name] = value
}
for (const name of allowed) {
if (!result[name]) releaseBoundaryFail(`create_evidence_argument_missing_${name}`)
}
return result
}
const options = parseArguments(process.argv.slice(2))
const scriptDirectory = dirname(fileURLToPath(import.meta.url))
const verifier = resolve(scriptDirectory, 'verify-android-artifact.mjs')
const bundletool = resolve(options.bundletool)
const snapshot = createImmutableVerificationSnapshot({
apkPath: resolve(options.apk),
aabPath: resolve(options.aab),
destinationDirectory: resolve(options['snapshot-dir']),
verifierPath: verifier,
bundletoolPath: bundletool,
})
const verifierBefore = hashRegularFileStable(snapshot.verifierPath)
const bundletoolBefore = hashRegularFileStable(snapshot.bundletoolPath)
const verifierArguments = [
snapshot.verifierPath,
'--mode', 'release',
'--apk', snapshot.apkPath,
'--aab', snapshot.aabPath,
'--bundletool', snapshot.bundletoolPath,
'--expected-admob-app-id', options['expected-admob-app-id'],
'--expected-upload-cert-sha256', options['expected-upload-cert-sha256'],
'--expected-version-name', options['expected-version-name'],
'--expected-version-code', options['expected-version-code'],
]
const verificationRun = spawnSync(process.execPath, verifierArguments, {
encoding: 'utf8',
maxBuffer: 32 * 1024 * 1024,
windowsHide: true,
})
if (verificationRun.status !== 0) {
const detail = String(verificationRun.stderr || verificationRun.stdout)
.trim()
.replace(/\s+/g, '_')
.slice(0, 500)
releaseBoundaryFail(`release_artifact_verifier_failed_${detail}`)
}
const verifierAfter = hashRegularFileStable(snapshot.verifierPath)
const bundletoolAfter = hashRegularFileStable(snapshot.bundletoolPath)
if (verifierBefore.sha256 !== verifierAfter.sha256 || verifierBefore.bytes !== verifierAfter.bytes) {
releaseBoundaryFail('verifier_changed_during_verification')
}
if (bundletoolBefore.sha256 !== bundletoolAfter.sha256 || bundletoolBefore.bytes !== bundletoolAfter.bytes) {
releaseBoundaryFail('bundletool_changed_during_verification')
}
let verification
try {
verification = JSON.parse(verificationRun.stdout)
} catch {
releaseBoundaryFail('release_artifact_verifier_json_invalid')
}
const expected = {
versionName: options['expected-version-name'],
versionCode: options['expected-version-code'],
adMobAppId: options['expected-admob-app-id'],
signerSha256: options['expected-upload-cert-sha256'],
}
const provenance = {
repository: options.repository,
commitSha: options['commit-sha'],
treeSha: options['tree-sha'],
gitRef: options['git-ref'],
workflowIdentity: options['workflow-identity'],
runId: options['run-id'],
runAttempt: options['run-attempt'],
runnerIdentity: options['runner-identity'],
verifierSha256: verifierBefore.sha256,
bundletoolSha256: bundletoolBefore.sha256,
}
const payload = buildReleasePayload({
verification,
apkPath: snapshot.apkPath,
aabPath: snapshot.aabPath,
expected,
provenance,
})
const privateKeyPem = readSmallFileStable(options['private-key'], 64 * 1024)
const evidence = signReleaseEvidence(payload, privateKeyPem)
// Both files are create-only. A rerun must start from a clean build output,
// never overwrite evidence that may already have been consumed downstream.
writeJsonCreateOnly(join(snapshot.destination, 'release-artifact-verification.json'), verification)
writeJsonCreateOnly(join(snapshot.destination, 'release-artifact-evidence.json'), evidence)
process.stdout.write(`${JSON.stringify({
ok: true,
mode: payload.mode,
packageName: payload.packageName,
versionName: payload.versionName,
versionCode: payload.versionCode,
apkSha256: payload.apk.sha256,
aabSha256: payload.aab.sha256,
evidenceKeyId: evidence.signature.keyId,
provenance: payload.provenance,
})}\n`)