UNPKG

@capgo/cli

Version:
268 lines (267 loc) 12.8 kB
import type { MobileprovisionDetail } from '../mobileprovision-parser.js'; /** Standard locations Xcode writes provisioning profiles into. */ export declare const PROVISIONING_PROFILE_DIRS: readonly ["Library/Developer/Xcode/UserData/Provisioning Profiles", "Library/MobileDevice/Provisioning Profiles"]; export type IdentityType = 'distribution' | 'development' | 'unknown'; export interface SigningIdentity { /** SHA1 hash of the certificate, lowercase 40-char hex */ sha1: string; /** Full identity string from `security find-identity` (e.g. "Apple Distribution: Acme Corp (XYZ123ABCD)") */ name: string; /** Best-effort classification from the name prefix */ type: IdentityType; /** Human-readable team name extracted from the identity string */ teamName: string; /** Apple Team ID (10-char alphanumeric) extracted from the identity string */ teamId: string; } export interface DiscoveredProfile extends MobileprovisionDetail { /** Absolute path to the .mobileprovision file */ path: string; } export interface IdentityProfileMatch { identity: SigningIdentity; /** Profiles whose embedded developer certs include this identity's SHA1 */ profiles: DiscoveredProfile[]; } export interface ExportedP12 { /** Base64-encoded PKCS#12 blob containing the chosen identity's cert + private key */ base64: string; /** Auto-generated passphrase used to wrap the export */ passphrase: string; } export declare class MacOSSigningError extends Error { readonly cause?: unknown | undefined; constructor(message: string, cause?: unknown | undefined); } export declare class NotMacOSError extends MacOSSigningError { constructor(); } /** Returns `true` when running on macOS (Darwin). */ export declare function isMacOS(): boolean; /** * Run a subprocess and capture stdout/stderr/exit-code. * * Public so tests can inject a fake runner via the optional argument on * higher-level functions. Not intended for downstream callers. */ export interface SecurityRunResult { stdout: string; stderr: string; code: number | null; } export type SecurityRunner = (args: readonly string[]) => Promise<SecurityRunResult>; /** * Parse the human-readable output of `security find-identity -v -p codesigning`. * Each line looks like: * ` 1) <SHA1> "Apple Distribution: Acme Corp (XYZ123ABCD)"` * * Exported so unit tests can verify parsing without spawning a subprocess. */ export declare function parseFindIdentityOutput(stdout: string): SigningIdentity[]; /** * List all code-signing identities visible in the user's default Keychain. * Read-only — does NOT trigger any Keychain access prompt. * * @param runner Optional injection point for testing. Pass a fake to avoid * spawning the real `/usr/bin/security` binary. */ export declare function listSigningIdentities(runner?: SecurityRunner): Promise<SigningIdentity[]>; /** * Scan all standard Xcode provisioning-profile directories under the user's * home and return parsed metadata for every readable `.mobileprovision`. * * Read-only — pure filesystem reads, no Keychain interaction. * * Files that fail to parse are silently skipped (a teammate's malformed * profile shouldn't break the whole listing). * * @param homeDirOverride Optional override for HOME, used in tests. */ export declare function scanProvisioningProfiles(homeDirOverride?: string): Promise<DiscoveredProfile[]>; /** * Given a list of identities and profiles, return one match entry per * identity, populated with profiles whose embedded developer certs include * that identity's SHA1. * * Pure function — no I/O. */ export declare function matchIdentitiesToProfiles(identities: readonly SigningIdentity[], profiles: readonly DiscoveredProfile[]): IdentityProfileMatch[]; /** * Compare a provisioning profile's bundle id against the app's concrete bundle * id, honoring Apple's wildcard syntax. The mobileprovision parser leaves the * asterisk in place after stripping the team-id prefix, so a wildcard profile * arrives here as either the bare `*` (matches everything the team owns) or a * suffix wildcard like `com.example.*` (matches `com.example.<anything>`). * * Exported so the file-picker validation in the Ink UI can reuse the same * matching rule as `filterProfilesForApp` — otherwise a wildcard * `.mobileprovision` picked manually would be hard-rejected even though the * underlying profile is valid for the current app. */ export declare function bundleIdMatches(profileBundleId: string, appId: string): boolean; /** * Filter profiles that are actually usable for a given Capacitor app + iOS * distribution mode. Used by the import-existing flow to detect dead-end * situations where an identity has profiles for a different app or the wrong * distribution mode — in which case the no-match-recovery menu can offer * "fetch / create via Apple" instead of dropping the user at an empty picker. * * `importDistribution` is null/undefined when the user hasn't picked yet — * in that case any profileType is accepted. * * Bundle-id comparison goes through {@link bundleIdMatches} so wildcard * profiles (the norm for ad_hoc/enterprise teams that share one profile * across many apps) are accepted alongside literal-equality matches. Apple * never issues wildcard `app_store` profiles in practice, so when the caller * pins `importDistribution = 'app_store'` the conjunction naturally drops * any ad_hoc/enterprise wildcards that happen to be installed. */ export declare function filterProfilesForApp(profiles: readonly DiscoveredProfile[], appId: string, importDistribution: 'app_store' | 'ad_hoc' | null | undefined): DiscoveredProfile[]; /** * Generate a cryptographically random passphrase suitable for wrapping the * exported PKCS#12. 32 bytes of entropy → 64-char hex string. */ export declare function generateP12Passphrase(): string; /** * Bundle identifier of the App Store Connect key helper's CapgoAscKeyHelper.app. * Pinned in its designated requirement exactly like the keychain helper above — * same team, same Developer ID, different bundle id. Must match * cli/scripts/package-asc-key-helper-app.sh and publish_cli_helper.yml's sign step. */ export declare const ASC_KEY_HELPER_BUNDLE_IDENTIFIER = "app.capgo.asc-key-helper"; /** * Map a Node `process.arch` value to the matching helper package name, or * null when no precompiled helper exists for that architecture. */ export declare function helperPackageName(arch: string): string | null; /** * codesign designated requirement asserting: the exact helper bundle identifier * (defaults to the keychain helper's app.capgo.cli.helper), an Apple-rooted * chain, a Developer ID Application leaf cert (OID 1.2.840.113635.100.6.1.13), * and the given Apple Team ID as the signing team. The identifier clause is what * scopes the requirement to THIS binary — without it, any other binary signed * with Capgo's Developer ID cert (a future tool, a leaked artifact) would also * satisfy the check. Pass `bundleIdentifier` to scope it to a different Capgo * helper (e.g. the ASC key helper) signed with the same Developer ID + team. */ export declare function helperSignatureRequirement(teamId?: string, bundleIdentifier?: string): string; export interface CodesignRunner { (args: readonly string[]): Promise<SpawnResult>; } export interface ResolveHelperBinaryOptions { /** Override `process.arch` (tests). */ arch?: string; /** * Override module resolution (tests). Each resolver receives the package's * `package.json` specifier and must return its absolute path or throw. Pass an * ARRAY to test the fallback chain (each base is tried in order until one * resolves); a single function is treated as a one-element chain. */ resolve?: ((specifier: string) => string) | Array<(specifier: string) => string>; /** Override the codesign spawn (tests). */ codesignRunner?: CodesignRunner; /** Force the dev env-override gate (tests). Defaults to the build-time flag. */ allowEnvOverride?: boolean; /** * Project directory to ALSO resolve the helper package from, in addition to the * CLI's own node_modules. Lets a project-local `npm i @capgo/cli-helper-darwin-*` * be picked up even when the CLI runs from a global install or the MCP server * (which doesn't resolve from the user's project). Defaults to `process.cwd()`. * Ignored when `resolve` is provided (tests). */ cwd?: string; } /** * Locate the precompiled `helper` binary for this machine and verify its code * signature chains to Capgo's Developer ID before returning it. * * Resolution order: * 1. CAPGO_KEYCHAIN_HELPER_PATH (dev builds only — see the build-time flag) * 2. The arch-matching @capgo/cli-helper-darwin-* optional dependency * 3. Hard error with install guidance. There is no compile fallback. */ export declare function resolveHelperBinary(options?: ResolveHelperBinaryOptions): Promise<string>; export interface VerifyAppBundleSignatureOptions { /** * Bundle identifier to pin in the designated requirement (e.g. * app.capgo.cli.helper for the keychain helper, app.capgo.asc-key-helper for * the ASC key helper). Both are signed with the same Developer ID + team. */ bundleIdentifier: string; /** Human-readable name for the helper, used in the thrown error message. */ label?: string; /** Extra guidance appended to the error message (e.g. a reinstall hint). */ reinstallHint?: string; /** Override the codesign spawn (tests). */ codesignRunner?: CodesignRunner; } /** * Verify an app bundle's (or binary's) code signature against Capgo's designated * requirement (Apple-rooted chain + Developer ID Application leaf + Capgo Team ID * + the given bundle identifier). macOS validates the certificate chain and the * seal, so this also detects post-install tampering. Throws — never returns — * on any failure, so callers can verify-then-spawn safely. * * Shared by the keychain helper and the App Store Connect key helper: both ship * inside the same npm package, signed with the same Developer ID, distinguished * only by their bundle identifiers. */ export declare function verifyAppBundleSignature(bundlePath: string, options: VerifyAppBundleSignatureOptions): Promise<void>; /** * Output shape from the Swift helper's stdout — always emitted as one line of * JSON regardless of success or failure. See cli-helper/src/helper.swift for * the source of truth. */ interface SwiftHelperResult { ok: boolean; p12Path?: string; p12SizeBytes?: number; identityName?: string; errorCode?: 'INVALID_ARGS' | 'NO_IDENTITY' | 'USER_DENIED' | 'EXPORT_FAILED' | 'WRITE_FAILED' | 'FORBIDDEN_CALLER' | 'INTERNAL'; message?: string; osStatus?: number; } /** * Spawn an arbitrary command, capturing stdout/stderr/exit-code. Used for * `/usr/bin/codesign` and the precompiled Swift helper itself. */ interface SpawnResult { stdout: string; stderr: string; code: number | null; } export interface ExportP12Options { /** * Pre-resolved helper binary path. Used in tests to inject a fake binary; * in production this is computed automatically. Bypasses the signature * check — not reachable from user input. */ helperPathOverride?: string; /** Injection points for {@link resolveHelperBinary} (tests). */ resolveOptions?: ResolveHelperBinaryOptions; } /** * Export the chosen identity from the user's Keychain as a base64'd PKCS#12. * * Triggers exactly TWO macOS Keychain prompts on the user's first run for * a given identity (one for "access" ACL, one for "export" ACL). Both * decisions are cached when the user clicks "Always Allow", so subsequent * runs against the same identity from the same binary are silent. * * Internally runs the precompiled, signature-verified `helper keychain-export` * subcommand from the arch-matching `@capgo/cli-helper-darwin-*` package. * * @param targetSha1 SHA1 of the identity to export (from {@link listSigningIdentities}) * @param options See {@link ExportP12Options} */ export declare function exportP12FromKeychain(targetSha1: string, options?: ExportP12Options): Promise<ExportedP12>; /** * Parse the helper's JSON output. Tolerates: extra whitespace, trailing * newline, BOM. Throws a clear error if the output is unparsable — that * indicates the helper crashed without emitting JSON, which our Swift code * tries hard to never do (see cli-helper/src/helper.swift's top-level catch). * * Exported for tests. */ export declare function parseHelperJson(stdout: string, stderr: string, exitCode: number | null): SwiftHelperResult; export {};