Files
kegel/store/asc.js
marcuspaico 2d74b7d152
Some checks failed
Build & Release APK / build (push) Has been cancelled
chore(store): App Store Connect API client and listing push script
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WontRuPqKcod9XK4ZN9MXv
2026-09-07 16:56:15 +10:00

51 lines
2.2 KiB
JavaScript

#!/usr/bin/env node
// Minimal App Store Connect API client (ES256 JWT via node:crypto, no deps).
//
// node store/asc.js get /v1/apps/6809350427
// node store/asc.js patch /v1/appInfos/ID '{"data":{...}}'
//
// Reads the key from eas.json submit.production.ios (ascApiKeyPath/Id/IssuerId).
const fs = require("fs");
const path = require("path");
const crypto = require("crypto");
const eas = JSON.parse(fs.readFileSync(path.join(__dirname, "..", "eas.json"), "utf8"));
const cfg = eas.submit.production.ios;
const APP_ID = cfg.ascAppId;
const BASE = "https://api.appstoreconnect.apple.com";
function b64url(buf) {
return Buffer.from(buf).toString("base64").replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
}
function token() {
const now = Math.floor(Date.now() / 1000);
const header = b64url(JSON.stringify({ alg: "ES256", kid: cfg.ascApiKeyId, typ: "JWT" }));
const payload = b64url(JSON.stringify({ iss: cfg.ascApiKeyIssuerId, iat: now, exp: now + 19 * 60, aud: "appstoreconnect-v1" }));
const key = fs.readFileSync(cfg.ascApiKeyPath, "utf8");
const sig = crypto.sign("sha256", Buffer.from(`${header}.${payload}`), { key, dsaEncoding: "ieee-p1363" });
return `${header}.${payload}.${b64url(sig)}`;
}
async function api(method, p, body, extraHeaders = {}) {
const res = await fetch(p.startsWith("http") ? p : BASE + p, {
method,
headers: { Authorization: `Bearer ${token()}`, "Content-Type": "application/json", ...extraHeaders },
body: body === undefined ? undefined : typeof body === "string" ? body : JSON.stringify(body),
});
const text = await res.text();
let json;
try { json = text ? JSON.parse(text) : null; } catch { json = text; }
if (!res.ok) throw new Error(`${method} ${p} -> ${res.status}\n${JSON.stringify(json, null, 2)}`);
return json;
}
module.exports = { api, APP_ID, token };
if (require.main === module) {
const [, , method, p, body] = process.argv;
if (!method || !p) { console.error("usage: asc.js <get|post|patch|delete> <path> [json]"); process.exit(1); }
api(method.toUpperCase(), p, body).then((r) => console.log(JSON.stringify(r, null, 2))).catch((e) => { console.error(e.message); process.exit(1); });
}