@capgo/cli
Version:
A CLI to upload to capgo servers
489 lines (488 loc) • 22.7 kB
TypeScript
import type { InstallCommand, PackageManagerRunner, PackageManagerType } from '@capgo/find-package-manager';
import type { SemVer } from '@std/semver';
import type { Buffer } from 'node:buffer';
import type { UploadSpinner } from './bundle/reporter';
import type { CapacitorConfig, ExtConfigPairs } from './config';
import type { Compatibility, CompatibilityDetails, NativePackage } from './schemas/common';
import type { Organization } from './types/api';
import { trimTrailingSlashes } from './shared/trim-trailing-slashes';
export { trimTrailingSlashes };
export declare const baseKey = ".capgo_key";
export declare const baseKeyV2 = ".capgo_key_v2";
export declare const baseKeyPub = ".capgo_key.pub";
export declare const baseKeyPubV2 = ".capgo_key_v2.pub";
export declare const defaultHost = "https://capgo.app";
export declare const defaultFileHost = "https://files.capgo.app";
export declare const defaultApiHost = "https://api.capgo.app";
export declare const defaultHostWeb = "https://console.capgo.app";
/** Build a console web-app URL (settings, builds, connect, etc.). */
export declare function consoleWebUrl(path?: string): string;
export declare const UPLOAD_TIMEOUT = 120000;
export declare const ALERT_UPLOAD_SIZE_BYTES: number;
export declare const MAX_UPLOAD_LENGTH_BYTES: number;
export declare const MAX_CHUNK_SIZE_BYTES: number;
export declare const TUS_UPLOAD_RETRY_DELAYS: number[];
export declare const MAX_MANIFEST_ENTRIES = 10000;
/** User-facing error when a delta/manifest upload exceeds MAX_MANIFEST_ENTRIES. */
export declare function deltaManifestTooLargeMessage(fileCount: number): string;
/**
* Stable `name` for the error thrown when the standard (non-TUS) upload hits its
* wall-clock timeout. Kept typed and constant so error tracking groups every
* occurrence into a single issue instead of one duplicate per CLI version.
*/
export declare const UPLOAD_TIMEOUT_ERROR_NAME = "BundleUploadTimeoutError";
/** User-facing error when the standard HTTP PUT upload exceeds its wall-clock timeout. */
export declare function uploadTimeoutMessage(timeoutMs: number): string;
export declare const PACKNAME = "package.json";
/** Stable PostHog fingerprint for incomplete Capgo server config before the API client is created. */
export type ArrayElement<ArrayType extends readonly unknown[]> = ArrayType extends readonly (infer ElementType)[] ? ElementType : never;
export type { Organization };
export declare const regexSemver: RegExp;
/**
* Format an error for display. If it's a security policy error,
* returns a human-readable message with actionable steps.
*/
export declare function formatError(error: any): string;
/**
* Render the safe, diagnostic part of an error chain for --verbose output.
*
* tus-js-client stores its underlying transport error in `causingError`, while
* native fetch/Undici uses `cause`. Only known diagnostic fields are included;
* request objects and headers are deliberately ignored, then the result passes
* through the CLI's secret redactor as a final safeguard.
*/
export declare function formatVerboseError(error: unknown): string;
type TagKey = Lowercase<string>;
/** Tag Type */
type Tags = Record<TagKey, string | number | boolean>;
/**
* Options for publishing analytics events
*/
interface TrackOptions {
/**
* Channel name
* example: "waitlist"
*/
channel: string;
/**
* Event name
* example: "User Joined"
*/
event: string;
/**
* Event description
* example: "joe@example.com joined waitlist"
*/
description?: string;
/**
* User ID
* example: "user-123"
*/
user_id?: string;
/**
* Organization ID for actor-scoped tracking.
*/
org_id?: string;
/**
* Tracking payload contract version.
*/
tracking_version?: number;
/**
* Event tags
* example: { username: "mattie" }
*/
tags?: Tags;
/**
* Event timestamp
*/
timestamp?: number | Date;
}
export declare function wait(ms: number): Promise<unknown>;
interface PromptInteractivityOptions {
silent?: boolean;
stdinIsTTY?: boolean;
stdoutIsTTY?: boolean;
ci?: boolean;
}
export declare function canPromptInteractively({ silent, stdinIsTTY, stdoutIsTTY, ci, }?: PromptInteractivityOptions): boolean;
interface CanOpenExternalUrlOptions {
ci?: boolean;
stdinIsTTY?: boolean;
stdoutIsTTY?: boolean;
display?: string | undefined;
platform?: NodeJS.Platform;
}
/** Whether the CLI may spawn a desktop browser for upgrade/docs links. */
export declare function canOpenExternalUrl(options?: CanOpenExternalUrlOptions): boolean;
/** Best-effort browser open; skipped in CI/headless and when xdg-open is missing. */
export declare function openExternalUrl(url: string): Promise<void>;
export declare function projectIsMonorepo(dir: string): boolean;
export declare function findRoot(dir: string): string;
export declare function getPackageScripts(f?: string, file?: string | undefined): Record<string, string>;
export declare function getBundleVersion(f?: string, file?: string | undefined): string;
/**
* Get the actual installed version of a package from node_modules (not from package.json)
* Uses multiple resolution strategies to find the installed version:
* 1. require.resolve - Works with all package managers
* 2. Walk up node_modules - Handles hoisted dependencies in monorepos
* 3. Native config files (iOS/Android) - For @capgo/capacitor-updater only
* 4. Fallback to declared version in package.json
*
* @param packageName - The package name to check
* @param rootDir - The root directory of the project
* @param packageJsonPath - Optional custom package.json path provided by user (takes priority if provided)
*/
export declare function getInstalledVersion(packageName: string, rootDir?: string, packageJsonPath?: string): Promise<string | null>;
export declare function getAllPackagesDependencies(f?: string, file?: string | undefined): Promise<Map<string, string>>;
export declare function getDeclaredPackageVersionMap(f?: string, file?: string | undefined): Promise<Map<string, string>>;
export declare const NO_CAPACITOR_CONFIG_MESSAGE = "No capacitor config file found, run `cap init` first";
export declare function getConfig(silent?: boolean): Promise<ExtConfigPairs>;
/** Loads the source config that a subsequent mutation will write. */
export declare function getConfigForWrite(silent?: boolean): Promise<ExtConfigPairs>;
export declare function updateConfigbyKey(key: string, newConfig: any): Promise<ExtConfigPairs>;
export declare function updateConfigUpdater(newConfig: any): Promise<ExtConfigPairs>;
export declare function getLocalConfig(silent?: boolean): Promise<{
host: string;
hostWeb: string;
hostFilesApi: string;
hostApi: string;
}>;
interface CapgoHostOverride {
apiHost?: string;
filesHost?: string;
}
/**
* Pin the Capgo API for this CLI process (--api-host / --files-host). Every host
* lookup (HTTP calls, remote config, files config, TUS uploads) then uses it, so
* tests and self-hosted backends never leak traffic to Capgo cloud. Library code
* uses withCapgoHosts instead so concurrent clients never share a host.
*/
export declare function setCapgoHostOverride(hosts: CapgoHostOverride): void;
/**
* Run `action` with its own Capgo hosts. Empty hosts mean Capgo defaults (env,
* capacitor config, cloud): a scope never inherits the CLI process pin.
*/
export declare function withCapgoHosts<T>(hosts: CapgoHostOverride, action: () => T): T;
interface CapgoConfig {
host: string;
hostWeb: string;
hostFilesApi: string;
hostApi: string;
minCliVersion?: string;
minCliVersionReason?: string;
}
export declare function getRemoteConfig(silent?: boolean, signal?: AbortSignal, apikey?: string): Promise<{
host: string;
hostWeb: string;
hostFilesApi: string;
hostApi: string;
minCliVersion?: string;
minCliVersionReason?: string;
}>;
export interface CapgoFilesConfig {
partialUpload: boolean;
partialUploadForced: boolean;
TUSUpload: boolean;
TUSUploadForced: boolean;
maxUploadLength: number;
maxChunkSize: number;
alertUploadSize: number;
}
export declare function getRemoteFileConfig(apikey?: string): Promise<CapgoFilesConfig>;
export declare function formatCapgoApiErrorBody(body: unknown): string;
export declare function channelUpdatePackageCliError(error: unknown): Promise<string | null>;
/** Options that select the Capgo API. */
export interface CapgoHostOptions {
/** Capgo API base URL (`--api-host`): any Capgo backend, e.g. `https://api.example.com` or `http://127.0.0.1:8787`. */
apiHost?: string;
/** Capgo files API base URL (`--files-host`); defaults to apiHost when apiHost is set. */
filesHost?: string;
}
/** Validate a Capgo API base URL: https (http only on loopback), no credentials/query. */
export declare function normalizeCapgoApiHost(host: string): string;
export interface CapgoApiHosts {
/** Capgo API base URL. */
apiHost: string;
/** Capgo files API base URL (same as apiHost when self-hosting on Edge Functions). */
filesHost: string;
}
/**
* Resolve Capgo API hosts: explicit apiHost/filesHost, then the process override,
* env and capacitor config (see applyCapgoHostOverride), then Capgo cloud.
*/
export declare function resolveCapgoApiHosts(apiHost?: string, silent?: boolean, filesHost?: string): Promise<CapgoApiHosts>;
export interface CapgoCliInvokeOptions {
apikey: string;
method?: string;
body?: unknown;
/** Capgo cloud files worker; same as the API host when self-hosting */
useFilesHost?: boolean;
/** Capgo API base URL; defaults to capacitor config / Capgo cloud. */
apiHost?: string;
signal?: AbortSignal;
/** Set false for telemetry-internal calls so perf events never recurse. */
instrument?: boolean;
}
export declare function getCapgoCliHttpStatus(error: unknown): number | undefined;
export declare function readCapgoCliApiErrorPayload(error: unknown): Promise<{
error?: string;
message?: string;
moreInfo?: Record<string, unknown>;
} | null>;
export declare function formatCapgoCliInvokeError(error: unknown): Promise<string>;
/**
* Call the Capgo HTTP API. Every request carries the API key and the `capgo_api`
* version header (see buildCliRequestHeaders); the backend owns all data access.
*/
export declare function invokeCapgoCliApi<T = any>(path: string, options: CapgoCliInvokeOptions): Promise<{
data: T | null;
error: Error | null;
}>;
export declare function resolveCapgoPublicApiHost(options?: {
apiHost?: string;
}, silent?: boolean): Promise<string>;
/**
* Connection to the Capgo HTTP API. The CLI never queries the database directly:
* every read/write goes through Capgo HTTP endpoints via `invokeCapgoCliApi`.
*/
export interface CapgoClient extends CapgoApiHosts {
apikey: string;
}
export declare function createCapgoClient(apikey: string, apiHost?: string, silent?: boolean, signal?: AbortSignal, filesHost?: string): Promise<CapgoClient>;
export declare function fetchCliMembers2faStatus(apikey: string, orgId: string, httpOptions?: CliHttpOptions): Promise<Array<{
'user_id': string;
'2fa_enabled': boolean;
}>>;
export declare function fetchCliMembersPasswordPolicyStatus(apikey: string, orgId: string, httpOptions?: CliHttpOptions): Promise<Array<{
user_id: string;
email: string;
first_name: string;
last_name: string;
password_policy_compliant: boolean;
}>>;
export declare function findSavedKeySilent(): string | undefined;
export declare function findSavedKey(quiet?: boolean, report?: (message: string) => void): string;
export declare function getContentType(filename: string): string;
export declare function findProjectType(options?: {
quiet?: boolean;
packageJsonPath?: string;
}): Promise<string>;
export declare function findMainFileForProjectType(projectType: string, isTypeScript: boolean, rootDir?: string): string | null;
export declare function findBuildCommandForProjectType(projectType: string): Promise<"build" | "generate">;
export declare function findMainFile(silent?: boolean, rootDir?: string): Promise<string>;
export declare function uploadUrl(apikey: string, appId: string, name: string, options?: {
apiHost?: string;
}): Promise<string>;
export declare const BROTLI_MIN_UPDATER_VERSION_V5 = "5.10.0";
export declare const BROTLI_MIN_UPDATER_VERSION_V6 = "6.25.0";
export declare const BROTLI_MIN_UPDATER_VERSION_V7 = "7.0.30";
export declare function isDeprecatedPluginVersion(parsedPluginVersion: SemVer, minFive?: string, minSix?: string, minSeven?: string): boolean;
export declare function generateManifest(path: string): Promise<{
file: string;
hash: string;
}[]>;
export type manifestType = Awaited<ReturnType<typeof generateManifest>>;
export declare function zipFile(filePath: string): Promise<Buffer>;
export declare function zipFileUnix(filePath: string): Buffer<ArrayBufferLike>;
export declare function zipFileWindows(filePath: string): Promise<Buffer>;
export declare function appAddHintMessage(appId: string): string;
export declare function isAppNotFoundError(error: unknown): boolean;
export declare function uploadTUS(apikey: string, data: Buffer, orgId: string, appId: string, name: string, spinner: UploadSpinner, localConfig: CapgoConfig, chunkSize: number): Promise<boolean>;
export declare function deletedFailedVersion(apikey: string, appId: string, name: string, options?: {
apiHost?: string;
}): Promise<void>;
export interface VersionManifestEntry {
file_name: string;
s3_path: string;
file_hash: string;
file_size_receipt?: string;
}
/**
* Writes delta manifest rows through the backend (file_size stays 0 until R2 size lookup).
* Prefer this over writing app_versions.manifest jsonb — old CLIs still use that legacy path.
*/
export declare function setVersionManifest(apikey: string, appId: string, name: string, manifest: VersionManifestEntry[], options?: {
apiHost?: string;
}): Promise<void>;
type SendEventPayload = TrackOptions & {
nonPersonTags?: Record<string, unknown>;
} & ({
notifyConsole: true;
icon?: string;
} | {
notifyConsole?: false;
icon?: never;
});
export declare function sendEvent(capgkey: string, payload: SendEventPayload, verbose?: boolean, signal?: AbortSignal, apiHost?: string, redirect?: RequestInit['redirect']): Promise<void>;
export declare function show2FADeniedError(organizationName?: string): never;
/** Capgo API selection for helpers that are not handed a CapgoClient. */
export interface CliHttpOptions {
apiHost?: string;
}
/** API key plus optional host: a CapgoClient, or command options before a client exists. */
export interface CapgoApiTarget {
apikey: string;
apiHost?: string;
filesHost?: string;
}
/** Call the Capgo API with a client's key and host. */
export declare function invokeCliHttpFromClient<T>(client: CapgoApiTarget, path: string, options?: {
method?: string;
body?: unknown;
query?: Record<string, string | undefined>;
useFilesHost?: boolean;
signal?: AbortSignal;
}): Promise<{
data: T | null;
error: Error | null;
}>;
/** Host options matching a Capgo client, for helpers that take CliHttpOptions. */
export declare function hostOptionsFromClient(client: CapgoClient): CliHttpOptions;
/**
* Organizations visible to the API key. With `permission`, each row carries
* `allowed`: whether the key holds that permission in the org.
*/
export declare function fetchOrganizations(apikey: string, httpOptions?: CliHttpOptions, permission?: string): Promise<Array<Organization & {
allowed?: boolean;
}>>;
export interface CliOrganizationDetails {
name: string;
management_email?: string;
created_by?: string;
enforcing_2fa: boolean;
password_policy_config: {
enabled: boolean;
min_length: number;
require_uppercase: boolean;
require_number: boolean;
require_special: boolean;
} | null;
require_apikey_expiration: boolean;
max_apikey_expiration_days: number | null;
enforce_hashed_api_keys: boolean;
}
export declare function fetchCliOrganization(apikey: string, orgId: string, httpOptions?: CliHttpOptions): Promise<CliOrganizationDetails>;
export declare function getOrganizationListWithPermission(client: CapgoClient, apikey: string, permissionKey: string, httpOptions?: CliHttpOptions): Promise<{
allOrganizations: Organization[];
allowedOrganizations: Organization[];
}>;
export declare function getOrganizationWithPermission(client: CapgoClient, apikey: string, permissionKey: string): Promise<Organization>;
export declare function resolveUserIdFromApiKey(client: CapgoClient, apikey: string, silent?: boolean, httpOptions?: CliHttpOptions): Promise<string>;
interface CliPermissionScope {
orgId?: string | null;
appId?: string | null;
channelId?: number | null;
}
/** Ask the backend which of these RBAC permissions the client key holds in one scope. */
export declare function fetchCliPermissions(client: CapgoClient, permissionKeys: string[], scope?: CliPermissionScope): Promise<Record<string, boolean>>;
export declare function hasCliPermission(client: CapgoClient, _apikey: string, permissionKey: string, scope?: CliPermissionScope, silent?: boolean): Promise<boolean>;
export declare function assertCliPermission(client: CapgoClient, apikey: string, permissionKey: string, scope?: CliPermissionScope, options?: {
message?: string;
silent?: boolean;
}): Promise<void>;
export declare function assertOrgPermission(client: CapgoClient, apikey: string, permissionKey: string, orgId: string, message: string, silent: boolean): Promise<void>;
export declare function getOrganizationId(apikey: string, appId: string, options?: {
apiHost?: string;
}): Promise<string>;
export declare function getHumanDate(createdA: string | null): string;
export declare function getPMAndCommand(): {
pm: PackageManagerType;
command: InstallCommand;
installCommand: string;
runner: PackageManagerRunner;
};
export declare function setPMAndCommand(next: {
pm: PackageManagerType;
command: InstallCommand;
runner: PackageManagerRunner;
}): void;
export declare function getNativeProjectResetAdvice(platformRunner: string, nativePlatform: 'ios' | 'android'): {
summary: string;
command: string;
};
export declare function getLocalDependencies(packageJsonPath: string | undefined, nodeModulesString: string | undefined): Promise<{
name: string;
version: string;
requested_version?: string;
native: boolean;
ios_checksum?: string;
android_checksum?: string;
}[]>;
export declare function getRemoteDependencies(apikey: string, appId: string, channel: string, httpOptions?: CliHttpOptions): Promise<Map<string, NativePackage>>;
export declare function getRemoteChecksums(apikey: string, appId: string, channel: string, httpOptions?: CliHttpOptions): Promise<string | null>;
export type { NativePackage } from './schemas/common';
export declare function convertNativePackages(nativePackages: NativePackage[]): Map<string, NativePackage>;
export type { Compatibility, CompatibilityDetails } from './schemas/common';
export declare function getAppId(appId: string | undefined, config: CapacitorConfig | undefined): any;
/**
* Check if a package is compatible and return detailed reasons if not
*/
export declare function getCompatibilityDetails(pkg: Compatibility): CompatibilityDetails;
/**
* Simple compatibility check (backward compatible)
*/
export declare function isCompatible(pkg: Compatibility): boolean;
export declare function checkCompatibilityCloud(apikey: string, appId: string, channel: string, packageJsonPath: string | undefined, nodeModules: string | undefined, httpOptions?: CliHttpOptions): Promise<{
finalCompatibility: {
name: string;
localVersion?: string | undefined;
remoteVersion?: string | undefined;
localRequestedVersion?: string | undefined;
remoteRequestedVersion?: string | undefined;
localIosChecksum?: string | undefined;
remoteIosChecksum?: string | undefined;
localAndroidChecksum?: string | undefined;
remoteAndroidChecksum?: string | undefined;
}[];
localDependencies: {
name: string;
version: string;
requested_version?: string;
native: boolean;
ios_checksum?: string;
android_checksum?: string;
}[];
}>;
export declare function checkCompatibilityNativePackages(apikey: string, appId: string, channel: string, nativePackages: NativePackage[], httpOptions?: CliHttpOptions): Promise<{
finalCompatibility: {
name: string;
localVersion?: string | undefined;
remoteVersion?: string | undefined;
localRequestedVersion?: string | undefined;
remoteRequestedVersion?: string | undefined;
localIosChecksum?: string | undefined;
remoteIosChecksum?: string | undefined;
localAndroidChecksum?: string | undefined;
remoteAndroidChecksum?: string | undefined;
}[];
localDependencies: {
name: string;
version: string;
requested_version?: string | undefined;
ios_checksum?: string | undefined;
android_checksum?: string | undefined;
}[];
}>;
export interface IosUpdaterSyncValidationResult {
shouldCheck: boolean;
valid: boolean;
details: string[];
}
/**
* Validate whether the iOS native project is correctly synced for capacitor-updater.
*
* `shouldCheck` is `false` when no iOS project is present or no updater signals are detected
* (no dependency declaration, installed package, or native references). `shouldCheck` is `true`
* as soon as any signal indicates updater should be wired, then both dependency definitions
* (`Podfile` or SPM `Package.swift`) and generated native outputs (`Podfile.lock`,
* `capacitor.plugins.json`, or `ios/App/App/capacitor.config.json`) must include
* updater markers for `valid` to be `true`.
*/
export declare function validateIosUpdaterSync(rootDir?: string, packageJsonPath?: string): IosUpdaterSyncValidationResult;
interface PromptAndSyncOptions {
validateIosUpdater?: boolean;
packageJsonPath?: string;
}
export type IosUpdaterSyncValidation = ReturnType<typeof validateIosUpdaterSync>;
export declare function throwIfIosUpdaterSyncInvalid(syncValidation: IosUpdaterSyncValidation, platformRunner: string, onFailure?: () => void): void;
export declare function promptAndSyncCapacitor(isInit?: boolean, orgId?: string, apikey?: string, options?: PromptAndSyncOptions): Promise<void>;