@sitepong/fastnode
v0.2.2
Published
A complete Node.js/TypeScript port of Fastlane — iOS & Android build automation, code signing, and app distribution
Downloads
94
Maintainers
Readme
@sitepong/fastnode
A Node.js/TypeScript toolkit for iOS & Android build automation, code signing, and App Store / TestFlight distribution. Inspired by Fastlane, rewritten in TypeScript with no Ruby dependency.
Install
npm install @sitepong/fastnodeQuick start
As a library
import { AppStoreConnectClient } from "@sitepong/fastnode/client";
import { createCertificate } from "@sitepong/fastnode/cert";
const client = new AppStoreConnectClient({
keyId: process.env.APP_STORE_CONNECT_API_KEY_ID!,
issuerId: process.env.APP_STORE_CONNECT_API_KEY_ISSUER_ID!,
privateKey: process.env.APP_STORE_CONNECT_API_KEY!,
});
const cert = await createCertificate({
client,
type: "IOS_DISTRIBUTION",
outputDir: "./certs",
});
console.log("Issued cert:", cert.certificate.id);CLI
npx @sitepong/fastnode --help
npx @sitepong/fastnode cert --development
npx @sitepong/fastnode sigh --bundle-id com.example.appModules
Each module can be imported separately:
| Module | Purpose |
|---|---|
| @sitepong/fastnode/client | App Store Connect REST client (JWT) + Apple Developer Portal client (session) |
| @sitepong/fastnode/cert | Create, list, revoke signing certificates |
| @sitepong/fastnode/sigh | Create and download provisioning profiles |
| @sitepong/fastnode/match | Sync certs + profiles across machines via git/S3/local storage |
| @sitepong/fastnode/produce | Create App Store Connect apps and bundle IDs, manage capabilities |
| @sitepong/fastnode/pilot | Upload builds to TestFlight, manage beta groups, set changelog |
| @sitepong/fastnode/deliver | Upload metadata, screenshots, submit to App Review |
| @sitepong/fastnode/pem | Create APNS push notification certificates |
| @sitepong/fastnode/gym | Build and archive iOS apps via xcodebuild |
| @sitepong/fastnode/scan | Run tests via xcodebuild, parse xcresult |
| @sitepong/fastnode/snapshot | Capture localized screenshots across devices |
| @sitepong/fastnode/trainer | Parse .xcresult / .plist test output → JUnit XML |
| @sitepong/fastnode/precheck | Static-analyze App Store metadata against review rules |
| @sitepong/fastnode/frameit | Composite screenshots into device frames |
Apple sign-in & one-click setup
fastnode ships a complete "sign in with your Apple Developer Apple ID
and provision everything" flow built on top of the same SIRP+2FA login
fastlane's spaceship uses. Given an email + password and a 2FA code,
it produces a fully-populated credential set without ever touching the
Apple Developer Portal UI.
Resumable login (HTTP-friendly 2FA)
import {
beginLogin,
submitTwoFactorCode,
requestSmsCode,
} from "@sitepong/fastnode/client";
// Step 1: SIRP. Returns either a logged-in session or a pending state
// describing the 2FA challenge.
const result = await beginLogin({ email, password });
if (result.kind === "logged_in") {
// No 2FA on this account — go straight to provisioning
console.log("Logged in:", result.session.userEmail);
} else {
// Prompt the user for the 6-digit code Apple pushed to their device
// (or call requestSmsCode() to fall back to SMS)
const session = await submitTwoFactorCode({
pending: result.pending,
code: "123456",
source: "device", // or "phone" if the user opted into SMS
});
}beginLogin and submitTwoFactorCode are split so the 2FA prompt can
be served by an HTTP request loop (e.g. a wizard in a web app), not
just an interactive CLI. The legacy loginWithAppleId(...) is still
available for one-shot CLI use.
One-click orchestration
import {
oneClickListTeams,
oneClickInventory,
oneClickSetup,
} from "@sitepong/fastnode";
// 1. List developer-portal teams (the alphanumeric teamIds, e.g. "W44274RVJ2")
const teams = await oneClickListTeams(session);
// 2. Inspect the team's current state — bundle ids, dist cert capacity,
// existing push certs, profiles. Used to pre-populate UI / detect the
// 3-distribution-cert limit before trying to provision.
const inventory = await oneClickInventory(session, teams[0].teamId);
// 3. Provision everything in one call
const result = await oneClickSetup({
session,
teamId: teams[0].teamId,
bundleIdentifier: "com.example.myapp",
appName: "My App",
options: {
generateApnsAuthKey: true, // .p8 APNs auth key (modern push)
generateAscApiKey: true, // .p8 ASC API key (TestFlight uploads)
enableSignInWithApple: true, // bundle capability via REST API
enablePushNotifications: true,
revokeCertIds: [], // dist cert ids to revoke first
},
onProgress: (event) => {
// structured step events for live UI feedback
if (event.type === "step_done") console.log("✓", event.label);
},
});
console.log({
bundleId: result.bundle_identifier,
ascKeyId: result.asc_key_id, // e.g. "MRA2DX6Y6W"
ascIssuerId: result.asc_issuer_id, // e.g. "cfadaf94-44ca-4c2c-..."
ascP8: result.asc_api_key_base64, // base64 .p8 PEM
distP12: result.dist_cert_base64, // base64 PKCS#12
certPwd: result.cert_password,
profile: result.provisioning_profile_base64,
apnsKey: result.additional_certs.find(c => c.cert_type === "apns_key"),
});Under the hood oneClickSetup runs:
- Initialize the developer-portal CSRF context
- (Optional) revoke the user's existing distribution cert(s) — Apple caps a team at 3 active distribution certs and 1 per user
- Create or reuse the bundle id
- Generate an RSA CSR + create a fresh distribution certificate, bundle
it with the private key into a PKCS#12 (.p12) using
node-forge - Create an App Store provisioning profile, picking a non-colliding
date-stamped name (e.g.
My App App Store 2026-04-14,… (1),… (2)if the same day collides) - (Optional) create an APNs Authentication Key (.p8) via the developer
portal's
account/auth/key/v2/createendpoint and download the PEM immediately viaaccount/auth/key/download - (Optional) create an App Store Connect API key (.p8) via the iris
admin API at
iris/v1/apiKeys, then download the .p8 + issuer id via the JSON-API sparse fieldset trick (?fields[apiKeys]=privateKey&include=provider) reverse-engineered from@expo/apple-utils - (Optional) flip Push Notifications and Sign in with Apple capabilities on the bundle id via the JWT-authenticated REST API (the legacy portal capability endpoint Apple removed)
Failures at any optional step are converted into structured warnings rather than halting the orchestration, so the credential is always saved with whatever could be obtained.
PortalClient and IrisClient
Two lower-level clients are exported alongside the high-level orchestrator for direct use:
import { PortalClient, IrisClient } from "@sitepong/fastnode/client";
// Apple Developer Portal — session-cookie auth, used by the legacy
// account/* endpoints (bundle ids, certs, profiles, devices, key
// management for service-tied auth keys like APNs / DeviceCheck).
const portal = new PortalClient(session, "W44274RVJ2");
await portal.init();
const apnsKey = await portal.createApnsAuthKey("My App APNs");
console.log(apnsKey.pem); // -----BEGIN PRIVATE KEY-----...
// App Store Connect iris admin API — same session cookies, used for
// the modern PUBLIC_API (App Store Connect) keys that show up under
// "Users and Access > Integrations > Keys".
const iris = new IrisClient(session);
const ascKey = await iris.createApiKey({
nickname: "My App SitePong",
roles: ["APP_MANAGER"],
allAppsVisible: true,
keyType: "PUBLIC_API",
});
const downloaded = await iris.fetchApiKeyPrivateKey(ascKey.id);
console.log(downloaded?.pem, downloaded?.issuerId);Both clients deliberately stay close to the wire format — they do not
hide the JSON-API envelope or Apple's per-section CSRF quirks. The
high-level oneClickSetup is where the ergonomics live.
Environment
The library pulls App Store Connect credentials from:
APP_STORE_CONNECT_API_KEY_ID— the 10-character key IDAPP_STORE_CONNECT_API_KEY_ISSUER_ID— the issuer UUID (omit for individual keys)APP_STORE_CONNECT_API_KEY— the-----BEGIN PRIVATE KEY-----PEM contents
Requirements
- Node.js ≥ 20
- macOS for Xcode-driven operations (
gym,scan,snapshot,trainer) xcodebuild,security, andcodesignon PATH for code-signing flows
License
MIT
