flotilla/scripts/release/lib/appstore.mjs

89 lines
2.5 KiB
JavaScript
Raw Normal View History

import {sign} from "node:crypto"
import {copyFile, mkdir, mkdtemp, readFile, rm} from "node:fs/promises"
2026-09-18 17:44:35 +00:00
import {tmpdir} from "node:os"
import {join} from "node:path"
import {MINUTE, int, now} from "@welshman/lib"
2026-09-18 17:44:35 +00:00
import {run} from "./shell.mjs"
const encode = value => Buffer.from(JSON.stringify(value)).toString("base64url")
export const appStore = async ({keyId, issuerId, keyPath}) => {
const key = await readFile(keyPath, "utf-8")
// Tokens live at most 20 minutes and waiting on a build can take longer, so each call signs anew
const token = () => {
const header = encode({alg: "ES256", kid: keyId, typ: "JWT"})
const payload = encode({
iss: issuerId,
iat: now(),
exp: now() + int(15, MINUTE),
aud: "appstoreconnect-v1",
})
const signature = sign("sha256", Buffer.from(`${header}.${payload}`), {
key,
dsaEncoding: "ieee-p1363",
})
return `${header}.${payload}.${signature.toString("base64url")}`
}
const api = async (method, path, body) => {
const response = await fetch(`https://api.appstoreconnect.apple.com${path}`, {
method,
headers: {
Authorization: `Bearer ${token()}`,
...(body ? {"Content-Type": "application/json"} : {}),
},
body: body && JSON.stringify(body),
})
if (response.status === 204) {
return undefined
}
const result = await response.json()
if (!response.ok) {
const details = result.errors?.map(error => error.detail ?? error.title).join("; ")
throw new Error(`App Store Connect: ${details ?? JSON.stringify(result)}`)
}
return result
}
return {
api,
upload: async ipa => {
// altool only reads the api key from a `private_keys` directory beside its working directory
// or under $HOME, so give it a private one rather than leaving the key in the repo or home dir
const directory = await mkdtemp(join(tmpdir(), "flotilla-appstore-"))
try {
await mkdir(join(directory, "private_keys"))
await copyFile(keyPath, join(directory, "private_keys", `AuthKey_${keyId}.p8`))
await run(
"xcrun",
[
"altool",
"--upload-app",
"-f",
ipa,
"-t",
"ios",
"--apiKey",
keyId,
"--apiIssuer",
issuerId,
],
{cwd: directory},
)
} finally {
await rm(directory, {recursive: true, force: true})
}
},
2026-09-18 17:44:35 +00:00
}
}