@capgo/cli 8.14.0 → 8.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/README.md +29 -0
  2. package/dist/index.js +648 -644
  3. package/dist/package.json +4 -2
  4. package/dist/src/ai/analyze.d.ts +2 -1
  5. package/dist/src/build/cwd.d.ts +7 -0
  6. package/dist/src/build/mobileprovision-parser.d.ts +2 -0
  7. package/dist/src/build/onboarding/apple-access.d.ts +26 -0
  8. package/dist/src/build/onboarding/apple-api.d.ts +15 -0
  9. package/dist/src/build/prescan/checks/android-keystore.d.ts +3 -0
  10. package/dist/src/build/prescan/checks/android-manifest.d.ts +14 -0
  11. package/dist/src/build/prescan/checks/android-project.d.ts +28 -0
  12. package/dist/src/build/prescan/checks/blob-limit.d.ts +8 -0
  13. package/dist/src/build/prescan/checks/credentials.d.ts +2 -0
  14. package/dist/src/build/prescan/checks/ios-certs.d.ts +11 -0
  15. package/dist/src/build/prescan/checks/ios-plist.d.ts +3 -0
  16. package/dist/src/build/prescan/checks/ios-profiles.d.ts +21 -0
  17. package/dist/src/build/prescan/checks/shared-remote.d.ts +3 -0
  18. package/dist/src/build/prescan/checks/shared.d.ts +4 -0
  19. package/dist/src/build/prescan/checks/store-access.d.ts +20 -0
  20. package/dist/src/build/prescan/command.d.ts +52 -0
  21. package/dist/src/build/prescan/context.d.ts +15 -0
  22. package/dist/src/build/prescan/engine.d.ts +7 -0
  23. package/dist/src/build/prescan/gradle.d.ts +65 -0
  24. package/dist/src/build/prescan/manifest.d.ts +61 -0
  25. package/dist/src/build/prescan/prompt.d.ts +15 -0
  26. package/dist/src/build/prescan/registry.d.ts +2 -0
  27. package/dist/src/build/prescan/report.d.ts +6 -0
  28. package/dist/src/build/prescan/types.d.ts +50 -0
  29. package/dist/src/build/prescan/upload-intent.d.ts +3 -0
  30. package/dist/src/schemas/build.d.ts +3 -0
  31. package/dist/src/schemas/sdk.d.ts +2 -0
  32. package/dist/src/sdk.js +436 -402
  33. package/package.json +4 -2
package/dist/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@capgo/cli",
3
3
  "type": "module",
4
- "version": "8.14.0",
4
+ "version": "8.15.0",
5
5
  "description": "A CLI to upload to capgo servers",
6
6
  "author": "Martin martin@capgo.app",
7
7
  "license": "Apache 2.0",
@@ -65,6 +65,7 @@
65
65
  "check-posix-paths": "node test/check-posix-paths.js",
66
66
  "generate-docs": "node dist/index.js generate-docs README.md",
67
67
  "test:bundle": "bun test/test-bundle.mjs",
68
+ "test:prescan": "bun test test/prescan/",
68
69
  "test:functional": "bun test/test-functional.mjs",
69
70
  "test:semver": "bun test/test-semver-validation.mjs",
70
71
  "test:version-edge-cases": "bun test/test-version-validation.mjs",
@@ -114,6 +115,7 @@
114
115
  "test:esm-sdk": "node test/test-sdk-esm.mjs",
115
116
  "test:auth-session": "bun test/test-auth-session.mjs",
116
117
  "test:mcp": "node test/test-mcp.mjs",
118
+ "test:mcp-no-key-handshake": "node test/test-mcp-no-key-handshake.mjs",
117
119
  "test:version-detection": "node test/test-get-installed-version.mjs",
118
120
  "test:version-detection:setup": "./test/fixtures/setup-test-projects.sh",
119
121
  "test:platform-paths": "bun test/test-platform-paths.mjs",
@@ -154,7 +156,7 @@
154
156
  "test:ios-verify-app": "bun test/test-ios-verify-app.mjs",
155
157
  "test:platform-flow-contract": "bun test/test-platform-flow-contract.mjs",
156
158
  "test:tail-engine-shared": "bun test/test-tail-engine-shared.mjs",
157
- "test": "bun run build && bun run test:helper-dce && bun run test:version-detection:setup && bun run test:bundle && bun run test:functional && bun run test:semver && bun run test:version-edge-cases && bun run test:regex && bun run test:upload && bun run test:fail-on-incompatible && bun run test:credentials && bun run test:credentials-validation && bun run test:android-service-account-validation && bun run test:build-zip-filter && bun run test:checksum && bun run test:build-needed && bun run test:ci-prompts && bun run test:ci-secrets && bun run test:android-onboarding-progress && bun run test:onboarding-telemetry && bun run test:v2-event-migration && bun run test:analytics && bun run test:analytics-error-category && bun run test:analytics-org-resolver && bun run test:supabase-perf && bun run test:preview-qr && bun run test:mcp-analytics && bun run test:mcp-instructions && bun run test:mcp-live-update-onboarding && bun run test:mcp-stdout-guard && bun run test:mcp-platform-select && bun run test:mcp-explain-scopes && bun run test:mcp-oauth-reopen && bun run test:mcp-broker-oauth && bun run test:mcp-broker-session && bun run test:mcp-credentials-manage && bun run test:mcp-resume-prompt && bun run test:mcp-build-job && bun run test:mcp-build-tools && bun run test:app-created-source && bun run test:doctor-analytics && bun run test:posthog-exception && bun run test:build-platform-selection && bun run test:onboarding-recovery && bun run test:onboarding-progress && bun run test:onboarding-run-targets && bun run test:run-device-command && bun run test:init-app-conflict && bun run test:init-guardrails && bun run test:init-replay && bun run test:prompt-preferences && bun run test:esm-sdk && bun run test:mcp && bun run test:auth-session && bun run test:version-detection && bun run test:platform-paths && bun run test:project-type-detection && bun run test:payload-split && bun run test:manifest-path-encoding && bun run test:macos-signing && bun run test:asc-key-protocol && bun run test:apple-api-import-helpers && bun run test:apple-api-verify-key && bun run test:bundle-id-detector && bun run test:apple-api-app-list && bun run test:app-verification && bun run test:pbxproj-parser && bun run test:ai-log-capture && bun run test:ai-analyze-flow && bun run test:cicd-failure-help && bun run test:ai-sse-parser && bun run test:ai-render-markdown && bun run test:ai-stream-markdown && bun run test:ai-onboarding-mode && bun run test:ai-fit && bun run test:platform-layout && bun run test:frame-fit && bun run test:onboarding-min-size && bun run test:min-size-gate && bun run test:shell-size-gate && bun run test:build-log-sanitize && bun run test:build-output-viewport && bun run test:diff-viewer-viewport && bun run test:build-complete-exit && bun run test:ai-analyze-stream && bun run test:support-mailto && bun run test:support-redact && bun run test:support-internal-log && bun run test:support-help-menu && bun run test:support-contact && bun run test:support-bundle-files && bun run test:self-update && bun run test:update-prompt && bun run test:apple-api-cert-create && bun run test:android-tail-engine && bun run test:android-tail-render && bun run test:android-tail-routing && bun run test:dev-gate-stripped && bun run test:frame-fit-ios-shared && bun run test:ios-confirm-app-id && bun run test:ios-create-new && bun run test:ios-e2e && bun run test:ios-flow-contract && bun run test:ios-import-discovery && bun run test:ios-import-export && bun run test:ios-import-pickers && bun run test:ios-import-recovery && bun run test:ios-recovery && bun run test:ios-resume && bun run test:ios-tail-handoff && bun run test:ios-tui-render && bun run test:p8-error && bun run test:ios-tui-routing && bun run test:ios-updater-sync-validation && bun run test:ios-verify-app && bun run test:platform-flow-contract && bun run test:tail-engine-shared",
159
+ "test": "bun run build && bun run test:helper-dce && bun run test:version-detection:setup && bun run test:bundle && bun run test:functional && bun run test:semver && bun run test:version-edge-cases && bun run test:regex && bun run test:upload && bun run test:fail-on-incompatible && bun run test:credentials && bun run test:credentials-validation && bun run test:android-service-account-validation && bun run test:build-zip-filter && bun run test:checksum && bun run test:build-needed && bun run test:ci-prompts && bun run test:ci-secrets && bun run test:android-onboarding-progress && bun run test:onboarding-telemetry && bun run test:v2-event-migration && bun run test:analytics && bun run test:analytics-error-category && bun run test:analytics-org-resolver && bun run test:supabase-perf && bun run test:preview-qr && bun run test:mcp-analytics && bun run test:mcp-instructions && bun run test:mcp-live-update-onboarding && bun run test:mcp-stdout-guard && bun run test:mcp-platform-select && bun run test:mcp-explain-scopes && bun run test:mcp-oauth-reopen && bun run test:mcp-broker-oauth && bun run test:mcp-broker-session && bun run test:mcp-credentials-manage && bun run test:mcp-resume-prompt && bun run test:mcp-build-job && bun run test:mcp-build-tools && bun run test:app-created-source && bun run test:doctor-analytics && bun run test:posthog-exception && bun run test:build-platform-selection && bun run test:onboarding-recovery && bun run test:onboarding-progress && bun run test:onboarding-run-targets && bun run test:run-device-command && bun run test:init-app-conflict && bun run test:init-guardrails && bun run test:init-replay && bun run test:prompt-preferences && bun run test:esm-sdk && bun run test:mcp && bun run test:mcp-no-key-handshake && bun run test:auth-session && bun run test:version-detection && bun run test:platform-paths && bun run test:project-type-detection && bun run test:payload-split && bun run test:manifest-path-encoding && bun run test:macos-signing && bun run test:asc-key-protocol && bun run test:apple-api-import-helpers && bun run test:apple-api-verify-key && bun run test:bundle-id-detector && bun run test:apple-api-app-list && bun run test:app-verification && bun run test:pbxproj-parser && bun run test:ai-log-capture && bun run test:ai-analyze-flow && bun run test:cicd-failure-help && bun run test:ai-sse-parser && bun run test:ai-render-markdown && bun run test:ai-stream-markdown && bun run test:ai-onboarding-mode && bun run test:ai-fit && bun run test:platform-layout && bun run test:frame-fit && bun run test:onboarding-min-size && bun run test:min-size-gate && bun run test:shell-size-gate && bun run test:build-log-sanitize && bun run test:build-output-viewport && bun run test:diff-viewer-viewport && bun run test:build-complete-exit && bun run test:ai-analyze-stream && bun run test:support-mailto && bun run test:support-redact && bun run test:support-internal-log && bun run test:support-help-menu && bun run test:support-contact && bun run test:support-bundle-files && bun run test:self-update && bun run test:update-prompt && bun run test:apple-api-cert-create && bun run test:android-tail-engine && bun run test:android-tail-render && bun run test:android-tail-routing && bun run test:dev-gate-stripped && bun run test:frame-fit-ios-shared && bun run test:ios-confirm-app-id && bun run test:ios-create-new && bun run test:ios-e2e && bun run test:ios-flow-contract && bun run test:ios-import-discovery && bun run test:ios-import-export && bun run test:ios-import-pickers && bun run test:ios-import-recovery && bun run test:ios-recovery && bun run test:ios-resume && bun run test:ios-tail-handoff && bun run test:ios-tui-render && bun run test:p8-error && bun run test:ios-tui-routing && bun run test:ios-updater-sync-validation && bun run test:ios-verify-app && bun run test:platform-flow-contract && bun run test:tail-engine-shared && bun run test:prescan",
158
160
  "test:build-platform-selection": "bun test/test-build-platform-selection.mjs",
159
161
  "test:ai-log-capture": "bun test/test-ai-log-capture.mjs",
160
162
  "test:ai-analyze-flow": "bun test/test-ai-analyze-flow.mjs",
@@ -1,7 +1,8 @@
1
- export type AnalyzeBehavior = 'show_menu' | 'ask_then_menu' | 'auto_upload' | 'skip';
1
+ export type AnalyzeBehavior = 'ask_then_menu' | 'auto_upload' | 'skip';
2
2
  export interface DecideInput {
3
3
  isTTY: boolean;
4
4
  aiAnalyticsFlag: boolean;
5
+ sendLogsFlag: boolean;
5
6
  }
6
7
  export declare function decideAnalyzeBehavior(input: DecideInput): AnalyzeBehavior;
7
8
  export declare const CI_FAILURE_TIP = "Build failed. Tip: re-run with --ai-analytics for an AI-powered diagnosis, or --send-logs to upload the build logs to Capgo support.";
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Run an async function with the process working directory temporarily set to `dir`.
3
+ *
4
+ * NOTE: `process.chdir()` is global, so this uses a simple in-process queue to avoid
5
+ * concurrent calls interfering with each other.
6
+ */
7
+ export declare function withCwd<T>(dir: string, fn: () => Promise<T>): Promise<T>;
@@ -34,6 +34,8 @@ export declare function parseMobileprovisionFromBase64(base64Content: string): M
34
34
  * - SHA1 of each embedded developer certificate (used for cert↔profile matching)
35
35
  */
36
36
  export declare function parseMobileprovisionDetailed(filePath: string): MobileprovisionDetail;
37
+ /** Base64 variant of {@link parseMobileprovisionDetailed} (profiles stored in CAPGO_IOS_PROVISIONING_MAP). */
38
+ export declare function parseMobileprovisionDetailedFromBase64(base64Content: string): MobileprovisionDetail;
37
39
  /**
38
40
  * Buffer-based variant of {@link parseMobileprovisionDetailed} — parses the raw
39
41
  * .mobileprovision bytes directly instead of reading a path. Used by the iOS
@@ -0,0 +1,26 @@
1
+ export interface AssertAscAccessOptions {
2
+ keyId: string;
3
+ issuerId: string;
4
+ /** The decoded .p8 PEM string (forge.util.decode64(APPLE_KEY_CONTENT)). */
5
+ p8Pem: string;
6
+ /** Project bundle id; when present we confirm it is reachable by the key. */
7
+ bundleId?: string;
8
+ signal?: AbortSignal;
9
+ /** Per-request timeout. Defaults to 7s (under the engine's 10s race). */
10
+ timeoutMs?: number;
11
+ /** Test-only injection point. Defaults to globalThis.fetch. */
12
+ fetchImpl?: typeof fetch;
13
+ }
14
+ export type AscAccessResult = {
15
+ ok: true;
16
+ } | {
17
+ ok: false;
18
+ kind: 'no-app-access' | 'auth-error' | 'network';
19
+ message: string;
20
+ };
21
+ /**
22
+ * Probe App Store Connect for app access. Never throws - every failure shape is
23
+ * returned as `{ ok: false, kind }` so the prescan check can self-classify the
24
+ * severity (auth-error -> error, no-app-access -> error, network -> info).
25
+ */
26
+ export declare function assertAscAccess(opts: AssertAscAccessOptions): Promise<AscAccessResult>;
@@ -8,6 +8,21 @@ export declare class AppleApiHttpError extends Error {
8
8
  readonly code?: string;
9
9
  constructor(status: number, message: string, code?: string);
10
10
  }
11
+ export declare const ASC_AGREEMENTS_MESSAGE: string;
12
+ export declare const ASC_KEY_REJECTED_MESSAGE: string;
13
+ export interface AscAuthClassification {
14
+ is401or403: boolean;
15
+ isAgreements: boolean;
16
+ status?: number;
17
+ code?: string;
18
+ message: string;
19
+ }
20
+ /**
21
+ * Classify a thrown App Store Connect error into the shared 401/403 + agreements
22
+ * buckets. Returns is401or403=false for anything else (network/5xx/etc) so
23
+ * callers route those to their own network/info handling.
24
+ */
25
+ export declare function classifyAscAuthError(err: any): AscAuthClassification;
11
26
  /**
12
27
  * Verify the API key works and try to detect the team ID from existing certificates.
13
28
  * Throws on 401/403 with a user-friendly message.
@@ -0,0 +1,3 @@
1
+ import type { PrescanCheck } from '../types';
2
+ export declare const keystoreOpens: PrescanCheck;
3
+ export declare const keystoreExpiry: PrescanCheck;
@@ -0,0 +1,14 @@
1
+ import type { PrescanCheck } from '../types';
2
+ export declare const manifestWellFormed: PrescanCheck;
3
+ export declare const manifestTagTypo: PrescanCheck;
4
+ export declare const manifestNamespaceUri: PrescanCheck;
5
+ export declare const manifestMissingPrefix: PrescanCheck;
6
+ export declare const manifestExportedMissing: PrescanCheck;
7
+ export declare const manifestMultipleUsesSdk: PrescanCheck;
8
+ export declare const manifestDuplicateComponent: PrescanCheck;
9
+ export declare const manifestUniquePermission: PrescanCheck;
10
+ export declare const manifestHardcodedDebuggable: PrescanCheck;
11
+ export declare const manifestMockLocation: PrescanCheck;
12
+ export declare const manifestExportedUnprotected: PrescanCheck;
13
+ export declare const manifestQueryAllPackages: PrescanCheck;
14
+ export declare const manifestDeeplinkValid: PrescanCheck;
@@ -0,0 +1,28 @@
1
+ import type { PrescanCheck } from '../types';
2
+ export declare const cordovaVarsPresent: PrescanCheck;
3
+ export declare const gradlePropsHeuristics: PrescanCheck;
4
+ export declare const playSaJson: PrescanCheck;
5
+ /**
6
+ * Brace-counted extraction of the body of the first `keyword { ... }` block.
7
+ * A greedy regex would capture to the LAST `}` in the file, swallowing
8
+ * buildTypes/dependencies/etc. into the flavor list.
9
+ */
10
+ export declare function extractBraceBlock(source: string, keyword: string): string | null;
11
+ /**
12
+ * Names of the direct (depth-1) child blocks of a Gradle DSL block body.
13
+ * Handles Groovy `demo { ... }` and Kotlin DSL `create("demo") { ... }`,
14
+ * `register("demo") { ... }`, and `val demo by creating { ... }`.
15
+ */
16
+ export declare function childBlockNames(block: string): string[];
17
+ export declare const flavorExists: PrescanCheck;
18
+ export declare const agp8PackageAttr: PrescanCheck;
19
+ export declare const applicationIdPresent: PrescanCheck;
20
+ export declare const capacitorBuildGradleApplied: PrescanCheck;
21
+ export declare const gradleWrapperPresent: PrescanCheck;
22
+ export declare const flavorDimensions: PrescanCheck;
23
+ export declare const googleServicesFile: PrescanCheck;
24
+ export declare const localPropertiesCommitted: PrescanCheck;
25
+ export declare const sdkFloors: PrescanCheck;
26
+ export declare const targetSdkPlay: PrescanCheck;
27
+ export declare const minSdkCapacitor: PrescanCheck;
28
+ export declare const versionFields: PrescanCheck;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Local checks parse credential blobs synchronously (node-forge), so the
3
+ * engine's async timeout cannot bound them: a corrupted or accidentally huge
4
+ * saved credential would freeze the CLI for minutes with multi-GB memory
5
+ * amplification. Refuse anything over a sane ceiling before decoding.
6
+ */
7
+ export declare const MAX_CREDENTIAL_B64_CHARS: number;
8
+ export declare function assertCredentialBlobSize(base64: string, what: 'certificate' | 'keystore'): void;
@@ -0,0 +1,2 @@
1
+ import type { PrescanCheck } from '../types';
2
+ export declare const credentialsSaved: PrescanCheck;
@@ -0,0 +1,11 @@
1
+ import type { PrescanCheck } from '../types';
2
+ import forge from 'node-forge';
3
+ export interface OpenedP12 {
4
+ cert: forge.pki.Certificate;
5
+ sha1: string;
6
+ }
7
+ /** Open the saved P12; throws on wrong password / garbage. Exported for reuse by pairing check. */
8
+ export declare function openP12(base64: string, password: string): OpenedP12;
9
+ export declare const p12Opens: PrescanCheck;
10
+ export declare const p12Expiry: PrescanCheck;
11
+ export declare const ascKeyValid: PrescanCheck;
@@ -0,0 +1,3 @@
1
+ import type { PrescanCheck } from '../types';
2
+ export declare const SCHEME_RE: RegExp;
3
+ export declare const infoplistSanity: PrescanCheck;
@@ -0,0 +1,21 @@
1
+ import type { PrescanCheck, ScanContext } from '../types';
2
+ /**
3
+ * One entry of CAPGO_IOS_PROVISIONING_MAP. The serialized shape (produced by
4
+ * buildProvisioningMap in src/build/credentials-command.ts) is
5
+ * `{ [bundleId]: { profile: base64, name: string } }` — keyed by the bundle id
6
+ * the profile is assigned to cover.
7
+ */
8
+ export interface MappedProfile {
9
+ /** bundle id this profile is assigned to cover */
10
+ bundleId: string;
11
+ /** base64-encoded .mobileprovision content */
12
+ base64: string;
13
+ /** profile display name extracted at save time */
14
+ name?: string;
15
+ }
16
+ export declare function parseProvisioningMap(ctx: ScanContext): MappedProfile[];
17
+ export declare const profileExpiry: PrescanCheck;
18
+ export declare const profileBundleMatch: PrescanCheck;
19
+ export declare const profileTypeVsMode: PrescanCheck;
20
+ export declare const certProfilePairing: PrescanCheck;
21
+ export declare const targetsCovered: PrescanCheck;
@@ -0,0 +1,3 @@
1
+ import type { PrescanCheck } from '../types';
2
+ export declare const apikeyPermission: PrescanCheck;
3
+ export declare const appExists: PrescanCheck;
@@ -0,0 +1,4 @@
1
+ import type { PrescanCheck } from '../types';
2
+ export declare const capSyncStale: PrescanCheck;
3
+ export declare const nodeLinkerLayout: PrescanCheck;
4
+ export declare const bundleIdConsistency: PrescanCheck;
@@ -0,0 +1,20 @@
1
+ import type { ValidateOptions, ValidationResult } from '../../onboarding/android/service-account-validation.js';
2
+ import type { AscAccessResult, AssertAscAccessOptions } from '../../onboarding/apple-access.js';
3
+ import type { PrescanCheck } from '../types';
4
+ /** Injectable validator type so tests can supply a fake without any network. */
5
+ type PlayValidator = (opts: ValidateOptions) => Promise<ValidationResult>;
6
+ type AscAsserter = (opts: AssertAscAccessOptions) => Promise<AscAccessResult>;
7
+ /**
8
+ * android/play-sa-access factory. Accepts the (injectable) service-account
9
+ * validator so the check is fully hermetic under test.
10
+ */
11
+ export declare function makePlaySaAccess(validator: PlayValidator): PrescanCheck;
12
+ /**
13
+ * ios/asc-key-access factory. Accepts the (injectable) App Store Connect access
14
+ * asserter so the check is fully hermetic under test.
15
+ */
16
+ export declare function makeAscKeyAccess(asserter: AscAsserter): PrescanCheck;
17
+ /** Wired checks (real validators) appended to the registry. */
18
+ export declare const playSaAccess: PrescanCheck;
19
+ export declare const ascKeyAccess: PrescanCheck;
20
+ export {};
@@ -0,0 +1,52 @@
1
+ import type { OutcomeOptions, PrescanReport, Severity } from './types';
2
+ export interface PrescanCommandOptions {
3
+ platform?: string;
4
+ path?: string;
5
+ apikey?: string;
6
+ androidFlavor?: string;
7
+ iosDist?: 'app_store' | 'ad_hoc';
8
+ json?: boolean;
9
+ failOnWarnings?: boolean;
10
+ ignoreFatal?: boolean;
11
+ verbose?: boolean;
12
+ supaHost?: string;
13
+ supaAnon?: string;
14
+ /**
15
+ * pre-merged credentials (CLI flags + env + saved file) when invoked from
16
+ * build request's gate — the scan must validate the exact set the build
17
+ * will use, not a fresh saved-file/env merge.
18
+ */
19
+ credentials?: Record<string, string>;
20
+ }
21
+ export declare function validateFlags(opts: Pick<PrescanCommandOptions, 'failOnWarnings' | 'ignoreFatal'>): void;
22
+ export declare function exitCodeFor(counts: Record<Severity, number>, opts: OutcomeOptions): number;
23
+ export interface PrescanExecution {
24
+ report: PrescanReport;
25
+ /** apikey actually used for the scan (flag or saved key); undefined when remote checks were skipped */
26
+ apikey?: string;
27
+ }
28
+ /** Shared scan runner used by both the standalone command and build request's gate. */
29
+ export declare function executePrescan(appId: string | undefined, options: PrescanCommandOptions): Promise<PrescanExecution>;
30
+ export declare function prescanCommand(appId: string | undefined, options: PrescanCommandOptions): Promise<void>;
31
+ export interface PrescanGateOptions {
32
+ enabled: boolean;
33
+ ignoreFatal?: boolean;
34
+ failOnWarnings?: boolean;
35
+ /** test seam; defaults to canPromptInteractively() (via resolveWarningGate) at call time */
36
+ interactive?: boolean;
37
+ silent?: boolean;
38
+ /**
39
+ * Output sink for the report / crash notice. Callers that own the terminal
40
+ * (Ink onboarding, SDK, MCP stdio) pass their BuildLogger here; raw clack
41
+ * writes would corrupt their rendering or the JSON-RPC stdout channel.
42
+ * Defaults to clack when not silent, and to no output when silent.
43
+ */
44
+ print?: (msg: string) => void;
45
+ warn?: (msg: string) => void;
46
+ }
47
+ /**
48
+ * Used by build request. Runs the scan via the provided thunk, prints the report,
49
+ * and resolves to 'proceed' | 'block'. NEVER throws: a crashing scanner proceeds with a notice
50
+ * (the scanner must never be worse than no scanner).
51
+ */
52
+ export declare function runPrescanGate(opts: PrescanGateOptions, scan: () => Promise<PrescanReport>): Promise<'proceed' | 'block'>;
@@ -0,0 +1,15 @@
1
+ import type { SupabaseClient } from '@supabase/supabase-js';
2
+ import type { Database } from '../../types/supabase.types';
3
+ import type { Platform, ScanContext } from './types';
4
+ export interface BuildScanContextArgs {
5
+ appId?: string;
6
+ platform: Platform;
7
+ projectDir: string;
8
+ distributionMode?: 'app_store' | 'ad_hoc';
9
+ androidFlavor?: string;
10
+ apikey?: string;
11
+ supabase?: SupabaseClient<Database>;
12
+ /** pre-merged credentials when called from build request (avoids double work) */
13
+ credentials?: Record<string, string>;
14
+ }
15
+ export declare function buildScanContext(args: BuildScanContextArgs): Promise<ScanContext>;
@@ -0,0 +1,7 @@
1
+ import type { OutcomeOptions, PrescanCheck, PrescanOutcome, PrescanReport, ScanContext } from './types';
2
+ interface EngineOptions {
3
+ checkTimeoutMs?: number;
4
+ }
5
+ export declare function runPrescan(ctx: ScanContext, checks: PrescanCheck[], options?: EngineOptions): Promise<PrescanReport>;
6
+ export declare function decideOutcome(report: Pick<PrescanReport, 'counts'>, options: OutcomeOptions): PrescanOutcome;
7
+ export {};
@@ -0,0 +1,65 @@
1
+ export declare function readTextIfExists(path: string): string | null;
2
+ /** Parse android/gradle.properties into a key→value map (ignores comments/blank lines). */
3
+ export declare function gradleProperties(projectDir: string): Record<string, string>;
4
+ /** Count `include ':…'` modules in android/capacitor.settings.gradle (proxy for plugin module count). */
5
+ export declare function settingsGradleModuleCount(projectDir: string): number;
6
+ export declare function appBuildGradle(projectDir: string): string | null;
7
+ export declare function gradleApplicationId(projectDir: string): string | null;
8
+ export interface EffectiveApplicationId {
9
+ /** The resolved package name, or null when the base id is unknown. */
10
+ packageName: string | null;
11
+ /**
12
+ * True when a productFlavors block exists (or a flavor was requested) but the
13
+ * effective applicationId for that flavor cannot be resolved unambiguously
14
+ * from static parsing. Callers should NOT emit a blocking error in this case.
15
+ */
16
+ ambiguous: boolean;
17
+ }
18
+ /**
19
+ * Resolve the package name the build will actually upload, accounting for the
20
+ * active product flavor's `applicationId` override / `applicationIdSuffix` on
21
+ * top of defaultConfig. A store-access probe must target this final package,
22
+ * not the unflavored defaultConfig id, or it 404s on a valid flavored upload.
23
+ *
24
+ * Returns `ambiguous: true` (and a best-effort packageName) when a flavored
25
+ * build cannot be statically resolved, so the caller can downgrade rather than
26
+ * emit a blocking error.
27
+ */
28
+ export declare function resolveEffectiveApplicationId(projectDir: string, flavor?: string): EffectiveApplicationId;
29
+ declare const SDK_DIMENSIONS: {
30
+ readonly minSdk: "minSdkVersion";
31
+ readonly targetSdk: "targetSdkVersion";
32
+ readonly compileSdk: "compileSdkVersion";
33
+ };
34
+ export type SdkDimension = keyof typeof SDK_DIMENSIONS;
35
+ /**
36
+ * Remove block and line (`//`) comments from a Gradle source. Required because
37
+ * gradleApplicationId / resolveSdk must not match commented declarations.
38
+ * Conservative: line stripping cuts from `//` to end-of-line, so a `//` inside
39
+ * a string literal trims that line's tail - acceptable for the presence/value
40
+ * scans these helpers perform.
41
+ */
42
+ export declare function stripGradleComments(source: string): string;
43
+ /**
44
+ * Parse android/variables.gradle `ext { name = <int> }` into a key->number map.
45
+ * The canonical Capacitor template puts SDK literals here and app/build.gradle
46
+ * only references rootProject.ext.*. Non-integer values are skipped.
47
+ */
48
+ export declare function variablesGradle(projectDir: string): Record<string, number>;
49
+ /**
50
+ * Resolve an SDK dimension: literal in comment-stripped app/build.gradle first,
51
+ * then android/variables.gradle, then the manifest <uses-sdk>. Returns null
52
+ * (skip the dimension) when unresolved so SDK checks agree on one source of
53
+ * truth.
54
+ *
55
+ * The gradle literal scan excises the `productFlavors` block first. A flavor
56
+ * routinely pins a per-flavor SDK literal (e.g. a legacy/wear flavor with a
57
+ * lower minSdk) that the active build does not necessarily apply; reading the
58
+ * first literal anywhere in the file would let that flavor value drive a false
59
+ * blocking SDK error (minSdkCapacitor / targetSdkPlay). Excising productFlavors
60
+ * keeps the android-level `compileSdkVersion` and the defaultConfig
61
+ * `minSdkVersion`/`targetSdkVersion` (the values the build actually applies)
62
+ * while ignoring per-flavor overrides.
63
+ */
64
+ export declare function resolveSdk(projectDir: string, dim: SdkDimension): number | null;
65
+ export {};
@@ -0,0 +1,61 @@
1
+ export { SCHEME_RE } from './checks/ios-plist';
2
+ export interface ManifestFile {
3
+ raw: string;
4
+ path: string;
5
+ }
6
+ export interface ScannedElement {
7
+ tag: string;
8
+ attrs: Record<string, string>;
9
+ /** byte offset of the element's opening `<` in the source */
10
+ start: number;
11
+ /** byte offset just past the element's closing `>` in the source */
12
+ end: number;
13
+ /** the raw open tag text, e.g. `<activity android:name=".X">` */
14
+ rawOpenTag: string;
15
+ }
16
+ /**
17
+ * Read android/app/src/main/AndroidManifest.xml. Returns null when absent.
18
+ * Memoized per projectDir (bounded cache) so repeat reads return the same
19
+ * object.
20
+ */
21
+ export declare function readAndroidManifest(projectDir: string): ManifestFile | null;
22
+ /**
23
+ * Remove `<!-- ... -->` comment blocks so typo/duplicate/exported scans don't
24
+ * trip on commented-out elements. Replaces each comment with an equal-length
25
+ * run of spaces so byte offsets stay stable for callers that slice the source.
26
+ */
27
+ export declare function stripXmlComments(raw: string): string;
28
+ /**
29
+ * The one parse primitive consumed by every manifest check. Matches each
30
+ * element open tag (self-closing or not) and parses its `name="value"`
31
+ * attributes into a map. Closing tags (`</application>`) never match.
32
+ */
33
+ export declare function scanElements(raw: string): ScannedElement[];
34
+ export interface NamespaceFlags {
35
+ android: boolean;
36
+ tools: boolean;
37
+ }
38
+ /** Detect the android + tools xmlns declarations anywhere in the source. */
39
+ export declare function hasNamespaceXmlns(raw: string): NamespaceFlags;
40
+ export interface ApplicationBlock {
41
+ openTag: string;
42
+ body: string;
43
+ start: number;
44
+ end: number;
45
+ }
46
+ /**
47
+ * Slice the `<application ...>` ... `</application>` block (the XML analogue of
48
+ * extractBraceBlock). Returns null when there is no application element.
49
+ */
50
+ export declare function applicationBlock(raw: string): ApplicationBlock | null;
51
+ /**
52
+ * The 32 Android Lint valid manifest tags. A tag NOT in this set (and not
53
+ * namespaced/custom) within edit distance 1..3 of one of these is a typo.
54
+ */
55
+ export declare const MANIFEST_VALID_TAGS: Set<string>;
56
+ /**
57
+ * Bounded Levenshtein distance capped at `max` (Android Lint uses 3). Returns a
58
+ * value > max as soon as the distance is provably over the cap so distant
59
+ * strings never pay the full O(a*b) compute. No npm string-distance package.
60
+ */
61
+ export declare function editDistance(a: string, b: string, max: number): number;
@@ -0,0 +1,15 @@
1
+ import type { PrescanOutcome } from './types';
2
+ export interface WarningGateOptions {
3
+ silent?: boolean;
4
+ /** test seam; defaults to canPromptInteractively() at call time */
5
+ interactive?: boolean;
6
+ /** test seam; defaults to @clack/prompts confirm */
7
+ confirmImpl?: (opts: {
8
+ message: string;
9
+ }) => Promise<boolean | symbol>;
10
+ }
11
+ /**
12
+ * Resolve an 'ask' outcome: interactive → user decides; non-interactive → proceed (per spec).
13
+ * Returns the final go/no-go.
14
+ */
15
+ export declare function resolveWarningGate(outcome: PrescanOutcome, opts?: WarningGateOptions): Promise<'proceed' | 'block'>;
@@ -0,0 +1,2 @@
1
+ import type { PrescanCheck } from './types';
2
+ export declare const ALL_CHECKS: PrescanCheck[];
@@ -0,0 +1,6 @@
1
+ import type { PrescanReport } from './types';
2
+ export declare function renderTerminalReport(report: PrescanReport, opts?: {
3
+ verbose?: boolean;
4
+ color?: boolean;
5
+ }): string;
6
+ export declare function renderJsonReport(report: PrescanReport): string;
@@ -0,0 +1,50 @@
1
+ import type { SupabaseClient } from '@supabase/supabase-js';
2
+ import type { Database } from '../../types/supabase.types';
3
+ import type { CapacitorConfig } from '../../schemas/config';
4
+ export type Severity = 'error' | 'warning' | 'info';
5
+ export type Platform = 'ios' | 'android';
6
+ export interface Finding {
7
+ id: string;
8
+ severity: Severity;
9
+ /**
10
+ * detail/fix/title are printed to the terminal and serialized into --json reports
11
+ * (routinely captured in CI logs): they must NEVER contain credential material
12
+ * (passwords, key/cert contents, raw credential field values).
13
+ */
14
+ title: string;
15
+ detail?: string;
16
+ fix?: string;
17
+ docsUrl?: string;
18
+ }
19
+ export interface ScanContext {
20
+ appId: string;
21
+ platform: Platform;
22
+ projectDir: string;
23
+ config?: CapacitorConfig;
24
+ /** merged credentials, env-var style keys (BUILD_CERTIFICATE_BASE64, ANDROID_KEYSTORE_FILE, ...) */
25
+ credentials?: Record<string, string>;
26
+ distributionMode?: 'app_store' | 'ad_hoc';
27
+ androidFlavor?: string;
28
+ apikey?: string;
29
+ supabase?: SupabaseClient<Database>;
30
+ }
31
+ export interface PrescanCheck {
32
+ id: string;
33
+ platforms: Platform[];
34
+ /** requires ctx.supabase; skipped (with notice) when absent */
35
+ remote?: boolean;
36
+ appliesTo?: (ctx: ScanContext) => boolean;
37
+ run: (ctx: ScanContext) => Promise<Finding[]>;
38
+ }
39
+ export interface PrescanReport {
40
+ findings: Finding[];
41
+ counts: Record<Severity, number>;
42
+ skippedRemote: number;
43
+ durationMs: number;
44
+ checksRun: number;
45
+ }
46
+ export type PrescanOutcome = 'proceed' | 'ask' | 'block';
47
+ export interface OutcomeOptions {
48
+ failOnWarnings?: boolean;
49
+ ignoreFatal?: boolean;
50
+ }
@@ -0,0 +1,3 @@
1
+ import type { ScanContext } from './types';
2
+ export declare function willUploadToPlay(ctx: ScanContext): boolean;
3
+ export declare function willUploadToAppStore(ctx: ScanContext): boolean;
@@ -71,6 +71,9 @@ export declare const buildRequestOptionsSchema: z.ZodObject<{
71
71
  "auto-prompt": "auto-prompt";
72
72
  "caller-handled": "caller-handled";
73
73
  }>>;
74
+ prescan: z.ZodOptional<z.ZodBoolean>;
75
+ prescanIgnoreFatal: z.ZodOptional<z.ZodBoolean>;
76
+ failOnWarnings: z.ZodOptional<z.ZodBoolean>;
74
77
  builderJourneyId: z.ZodOptional<z.ZodString>;
75
78
  }, z.core.$strip>;
76
79
  export type BuildRequestOptions = z.infer<typeof buildRequestOptionsSchema>;
@@ -278,6 +278,8 @@ export declare const requestBuildOptionsSchema: z.ZodObject<{
278
278
  apikey: z.ZodOptional<z.ZodString>;
279
279
  supaHost: z.ZodOptional<z.ZodString>;
280
280
  supaAnon: z.ZodOptional<z.ZodString>;
281
+ prescan: z.ZodOptional<z.ZodBoolean>;
282
+ prescanIgnoreFatal: z.ZodOptional<z.ZodBoolean>;
281
283
  }, z.core.$strip>;
282
284
  export type RequestBuildOptions = z.infer<typeof requestBuildOptionsSchema>;
283
285
  export declare const currentBundleOptionsSchema: z.ZodObject<{