@capgo/cli 8.3.1 → 8.4.1
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.
- package/dist/index.js +608 -608
- package/dist/package.json +26 -2
- package/dist/src/build/mobileprovision-parser.d.ts +9 -0
- package/dist/src/build/onboarding/android/flow.d.ts +505 -0
- package/dist/src/build/onboarding/android/keystore.d.ts +18 -0
- package/dist/src/build/onboarding/android/oauth-google.d.ts +31 -0
- package/dist/src/build/onboarding/android/oauth-scopes.d.ts +4 -0
- package/dist/src/build/onboarding/android/types.d.ts +42 -1
- package/dist/src/build/onboarding/apple-api.d.ts +5 -1
- package/dist/src/build/onboarding/env-export.d.ts +12 -1
- package/dist/src/build/onboarding/flow/android-flow.d.ts +3 -0
- package/dist/src/build/onboarding/flow/contract.d.ts +24 -0
- package/dist/src/build/onboarding/flow/ios-flow.d.ts +3 -0
- package/dist/src/build/onboarding/ios/flow.d.ts +650 -0
- package/dist/src/build/onboarding/ios/progress.d.ts +2 -0
- package/dist/src/build/onboarding/mcp/app-id-validation.d.ts +8 -0
- package/dist/src/build/onboarding/mcp/contract.d.ts +51 -0
- package/dist/src/build/onboarding/mcp/engine.d.ts +131 -0
- package/dist/src/build/onboarding/mcp/explanations.d.ts +4 -0
- package/dist/src/build/onboarding/mcp/oauth-session.d.ts +29 -0
- package/dist/src/build/onboarding/mcp/onboarding-tools.d.ts +17 -0
- package/dist/src/build/onboarding/mcp/step-input.d.ts +45 -0
- package/dist/src/build/onboarding/mcp/terminal-launch.d.ts +22 -0
- package/dist/src/build/onboarding/tail/flow.d.ts +283 -0
- package/dist/src/build/onboarding/tail-types.d.ts +29 -0
- package/dist/src/build/onboarding/types.d.ts +82 -1
- package/dist/src/build/onboarding/ui/p8-error.d.ts +13 -0
- package/dist/src/build/output-record.d.ts +17 -0
- package/dist/src/schemas/onboarding.d.ts +41 -0
- package/dist/src/sdk.js +226 -226
- package/package.json +26 -2
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export type StepKind = 'auto' | 'human_gate' | 'choice' | 'done' | 'error' | 'info';
|
|
2
|
+
export type OnboardingPhase = 'preflight' | 'app' | 'credentials' | 'build' | 'done';
|
|
3
|
+
export type Platform = 'ios' | 'android';
|
|
4
|
+
export interface ChoiceOption {
|
|
5
|
+
value: string;
|
|
6
|
+
label?: string;
|
|
7
|
+
note?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface CollectField {
|
|
10
|
+
field: string;
|
|
11
|
+
desc: string;
|
|
12
|
+
}
|
|
13
|
+
export interface NextAction {
|
|
14
|
+
/** The exact tool to call next. */
|
|
15
|
+
tool: string;
|
|
16
|
+
/** Argument hint for the model. */
|
|
17
|
+
with?: Record<string, unknown>;
|
|
18
|
+
/** A literal, copy-pasteable example call the model can pattern-match. */
|
|
19
|
+
call?: string;
|
|
20
|
+
/** Plain-English directive. */
|
|
21
|
+
instruction: string;
|
|
22
|
+
}
|
|
23
|
+
export interface NextStepResult {
|
|
24
|
+
onboarding: 'capgo-builder';
|
|
25
|
+
phase: OnboardingPhase;
|
|
26
|
+
/** Granular step name (reuses OnboardingStep vocabulary where applicable). */
|
|
27
|
+
state: string;
|
|
28
|
+
platform?: Platform;
|
|
29
|
+
/** 0–100. */
|
|
30
|
+
progress: number;
|
|
31
|
+
kind: StepKind;
|
|
32
|
+
summary: string;
|
|
33
|
+
/** Human-facing journey overview; informational, not an execution list. */
|
|
34
|
+
roadmap?: string[];
|
|
35
|
+
context?: Record<string, unknown>;
|
|
36
|
+
/** Present when kind === 'choice'. */
|
|
37
|
+
options?: ChoiceOption[];
|
|
38
|
+
/** Present when kind === 'human_gate'. */
|
|
39
|
+
human?: {
|
|
40
|
+
instruction: string;
|
|
41
|
+
resourceUri?: string;
|
|
42
|
+
};
|
|
43
|
+
/** Present when kind === 'human_gate': what to bring back. */
|
|
44
|
+
collect?: CollectField[];
|
|
45
|
+
next?: NextAction;
|
|
46
|
+
/** Rules of engagement; included on the first result of a session. */
|
|
47
|
+
rules?: string[];
|
|
48
|
+
}
|
|
49
|
+
export declare const ONBOARDING_RULES: string[];
|
|
50
|
+
/** Render a result into MCP text content: imperative directive first, structured data last. */
|
|
51
|
+
export declare function renderResult(result: NextStepResult): string;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import type { AndroidOnboardingProgress } from '../android/types.js';
|
|
2
|
+
import type { AndroidEffectDeps } from '../android/flow.js';
|
|
3
|
+
import type { OnboardingProgress } from '../types.js';
|
|
4
|
+
import type { NextStepResult, Platform } from './contract.js';
|
|
5
|
+
import type { BuildOutputRecord } from '../../output-record.js';
|
|
6
|
+
import { androidViewForStep } from '../android/flow.js';
|
|
7
|
+
import { beginOAuthSession, clearOAuthSession, pollOAuthSession } from './oauth-session.js';
|
|
8
|
+
/** Facts gathered during preflight; the pure deciders branch only on these. */
|
|
9
|
+
export interface PreflightFacts {
|
|
10
|
+
capacitorProject: boolean;
|
|
11
|
+
appId?: string;
|
|
12
|
+
platformsDetected: Platform[];
|
|
13
|
+
authenticated: boolean;
|
|
14
|
+
appRegistered: boolean;
|
|
15
|
+
androidProgress: AndroidOnboardingProgress | null;
|
|
16
|
+
iosProgress: OnboardingProgress | null;
|
|
17
|
+
}
|
|
18
|
+
/** User input carried into the flow via next_step. */
|
|
19
|
+
interface OnboardingInput {
|
|
20
|
+
platform?: string;
|
|
21
|
+
serviceAccountJsonPath?: string;
|
|
22
|
+
runBuild?: boolean;
|
|
23
|
+
checkBuild?: boolean;
|
|
24
|
+
keyId?: string;
|
|
25
|
+
issuerId?: string;
|
|
26
|
+
p8Path?: string;
|
|
27
|
+
serviceAccountMethod?: 'generate' | 'existing';
|
|
28
|
+
playDeveloperId?: string;
|
|
29
|
+
gcpProjectId?: string;
|
|
30
|
+
gcpProjectName?: string;
|
|
31
|
+
androidPackage?: string;
|
|
32
|
+
saMethodChoice?: 'retry' | 'save-anyway' | 'oauth';
|
|
33
|
+
credentialsExistChoice?: 'backup' | 'cancel';
|
|
34
|
+
keystoreMethod?: 'existing' | 'generate';
|
|
35
|
+
keystorePath?: string;
|
|
36
|
+
keystoreStorePassword?: string;
|
|
37
|
+
keystoreAlias?: string;
|
|
38
|
+
keystoreKeyPassword?: string;
|
|
39
|
+
keystoreNewAlias?: string;
|
|
40
|
+
keystorePasswordMethod?: 'random' | 'manual';
|
|
41
|
+
keystoreCommonName?: string;
|
|
42
|
+
}
|
|
43
|
+
/** Decide the first/again step for a fresh or resumed session. */
|
|
44
|
+
export declare function decideStart(facts: PreflightFacts, progress: OnboardingProgress | null, deps: EngineDeps): Promise<NextStepResult>;
|
|
45
|
+
export declare function decideIos(facts: PreflightFacts): NextStepResult;
|
|
46
|
+
export declare function mapAndroidView(view: ReturnType<typeof androidViewForStep>, facts: PreflightFacts, opts?: {
|
|
47
|
+
keystorePath?: string;
|
|
48
|
+
keystorePassword?: string;
|
|
49
|
+
}): NextStepResult;
|
|
50
|
+
export declare function decideAndroid(facts: PreflightFacts, deps: EngineDeps, opts?: {
|
|
51
|
+
signInProceed?: boolean;
|
|
52
|
+
}): Promise<NextStepResult>;
|
|
53
|
+
export declare function decideAdvance(facts: PreflightFacts, progress: OnboardingProgress | null, input: OnboardingInput | undefined, deps: EngineDeps): Promise<NextStepResult>;
|
|
54
|
+
export interface EngineDeps {
|
|
55
|
+
cwd: string;
|
|
56
|
+
hasSavedKey: () => boolean;
|
|
57
|
+
getAppId: () => Promise<string | undefined>;
|
|
58
|
+
detectPlatforms: () => Promise<Platform[]>;
|
|
59
|
+
isAppRegistered: (appId: string) => Promise<boolean>;
|
|
60
|
+
loadProgress: (appId: string) => Promise<OnboardingProgress | null>;
|
|
61
|
+
registerApp: (appId: string) => Promise<{
|
|
62
|
+
ok: true;
|
|
63
|
+
} | {
|
|
64
|
+
ok: false;
|
|
65
|
+
alreadyExists: boolean;
|
|
66
|
+
error: string;
|
|
67
|
+
}>;
|
|
68
|
+
loadAndroidProgress: (appId: string) => Promise<AndroidOnboardingProgress | null>;
|
|
69
|
+
finalizeAndroidCredentials: (appId: string) => Promise<{
|
|
70
|
+
ok: true;
|
|
71
|
+
} | {
|
|
72
|
+
ok: false;
|
|
73
|
+
error: string;
|
|
74
|
+
}>;
|
|
75
|
+
readBuildRecord: (path: string) => Promise<BuildOutputRecord | null>;
|
|
76
|
+
buildRecordPath: (appId: string, platform: Platform) => string;
|
|
77
|
+
setIosApiKey: (appId: string, keyId: string, issuerId: string, p8Path: string) => Promise<void>;
|
|
78
|
+
finalizeIosCredentials: (appId: string) => Promise<{
|
|
79
|
+
ok: true;
|
|
80
|
+
} | {
|
|
81
|
+
ok: false;
|
|
82
|
+
error: string;
|
|
83
|
+
}>;
|
|
84
|
+
androidEffectDeps: AndroidEffectDeps;
|
|
85
|
+
/** Returns true when the current host can launch Terminal.app (macOS). Injectable for tests. */
|
|
86
|
+
canLaunchTerminal: () => boolean;
|
|
87
|
+
/** Launch `command` in a new macOS Terminal.app window. Injectable for tests. */
|
|
88
|
+
launchBuildInTerminal: (command: string) => Promise<{
|
|
89
|
+
ok: true;
|
|
90
|
+
} | {
|
|
91
|
+
ok: false;
|
|
92
|
+
error: string;
|
|
93
|
+
}>;
|
|
94
|
+
/**
|
|
95
|
+
* Optional injectable OAuth session registry for testing. When provided, the
|
|
96
|
+
* engine uses these instead of the module-level functions in oauth-session.ts.
|
|
97
|
+
* Production builds omit this and rely on the module-level registry.
|
|
98
|
+
*/
|
|
99
|
+
oauthSession?: {
|
|
100
|
+
begin: typeof beginOAuthSession;
|
|
101
|
+
poll: typeof pollOAuthSession;
|
|
102
|
+
clear: typeof clearOAuthSession;
|
|
103
|
+
};
|
|
104
|
+
/**
|
|
105
|
+
* Write the generated/loaded Android keystore (.p12) to a file on disk so the
|
|
106
|
+
* user has a durable copy after onboarding. Called once when the keystore phase
|
|
107
|
+
* completes. Returns the absolute path of the written file.
|
|
108
|
+
*
|
|
109
|
+
* Optional — when omitted the keystore is kept in progress only (no file written).
|
|
110
|
+
* Omitting does not break the flow; the keystore data is always in _keystoreBase64.
|
|
111
|
+
*/
|
|
112
|
+
writeKeystoreFile?: (appId: string, base64: string, alias: string) => Promise<string>;
|
|
113
|
+
}
|
|
114
|
+
export declare function gatherFacts(deps: EngineDeps): Promise<PreflightFacts>;
|
|
115
|
+
export declare function runStart(deps: EngineDeps): Promise<NextStepResult>;
|
|
116
|
+
export declare function runAdvance(deps: EngineDeps, input?: OnboardingInput): Promise<NextStepResult>;
|
|
117
|
+
/**
|
|
118
|
+
* Read-only: determine the onboarding state the user is currently on, WITHOUT
|
|
119
|
+
* running any side effect. Mirrors the branch selection of decideStart/decideAndroid
|
|
120
|
+
* (preflight → platform → android resume step) but never calls effects.
|
|
121
|
+
*/
|
|
122
|
+
export declare function resolveCurrentState(facts: PreflightFacts): string;
|
|
123
|
+
/**
|
|
124
|
+
* Read-only "explain the current step" entry point backing the
|
|
125
|
+
* capgo_builder_onboarding_explain tool. Gathers facts (read-only) and returns a
|
|
126
|
+
* plain-language explanation string. Never advances the flow or runs effects.
|
|
127
|
+
*/
|
|
128
|
+
export declare function explainOnboarding(deps: EngineDeps, input?: {
|
|
129
|
+
state?: string;
|
|
130
|
+
}): Promise<string>;
|
|
131
|
+
export {};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** state → multi-line plain-language explanation (WHAT · WHY · OPTIONS · WHAT TO DO). */
|
|
2
|
+
export declare const EXPLANATIONS: Record<string, string>;
|
|
3
|
+
/** Return the explanation for a state, or a generic fallback for unknown states. */
|
|
4
|
+
export declare function explainForState(state: string | undefined | null): string;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { GoogleOAuthTokens } from '../android/oauth-google.js';
|
|
2
|
+
import type { PendingOAuthSession } from '../android/oauth-google.js';
|
|
3
|
+
export type { PendingOAuthSession };
|
|
4
|
+
/**
|
|
5
|
+
* Start a new OAuth session for `appId`, replacing any existing one.
|
|
6
|
+
*
|
|
7
|
+
* `start` is a factory that returns a `PendingOAuthSession` (e.g.
|
|
8
|
+
* `() => startOAuthFlow(config, options)`). Passing a factory rather than the
|
|
9
|
+
* session directly lets the caller defer construction until after the prior
|
|
10
|
+
* session has been cleaned up.
|
|
11
|
+
*/
|
|
12
|
+
export declare function beginOAuthSession(appId: string, start: () => Promise<PendingOAuthSession>): Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* Poll the current status for `appId`.
|
|
15
|
+
*
|
|
16
|
+
* Returns `{ status: 'absent' }` when no session has been started or the
|
|
17
|
+
* entry has been cleared. Otherwise returns the entry's current
|
|
18
|
+
* `status`/`tokens`/`error`.
|
|
19
|
+
*/
|
|
20
|
+
export declare function pollOAuthSession(appId: string): {
|
|
21
|
+
status: 'pending' | 'done' | 'error' | 'absent';
|
|
22
|
+
tokens?: GoogleOAuthTokens;
|
|
23
|
+
error?: Error;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Close and remove the session entry for `appId`. Safe to call on an absent
|
|
27
|
+
* appId or after the result has already settled.
|
|
28
|
+
*/
|
|
29
|
+
export declare function clearOAuthSession(appId: string): void;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { CapgoSDK } from '../../../sdk.js';
|
|
2
|
+
import type { EngineDeps } from './engine.js';
|
|
3
|
+
/** Minimal shape of the MCP server's tool registrar (matches McpServer.tool). */
|
|
4
|
+
interface McpLike {
|
|
5
|
+
tool: (name: string, description: string, schema: Record<string, unknown>, handler: (args: any) => Promise<{
|
|
6
|
+
content: Array<{
|
|
7
|
+
type: 'text';
|
|
8
|
+
text: string;
|
|
9
|
+
}>;
|
|
10
|
+
}>) => unknown;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Register the 2-tool onboarding spine onto an MCP server.
|
|
14
|
+
* `depsOverride` is for tests; production passes only `server` + `sdk`.
|
|
15
|
+
*/
|
|
16
|
+
export declare function registerOnboardingTools(server: McpLike, sdk: CapgoSDK, depsOverride?: EngineDeps): void;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { AndroidOnboardingStep } from '../android/types.js';
|
|
2
|
+
/** state → the set of input fields that legitimately answer it (in order). */
|
|
3
|
+
export declare const STEP_ALLOWED_FIELDS: Partial<Record<AndroidOnboardingStep, string[]>>;
|
|
4
|
+
/** The set of all android input keys we govern (for the extras check). */
|
|
5
|
+
export declare const ANDROID_INPUT_KEYS: string[];
|
|
6
|
+
/**
|
|
7
|
+
* Validate an incoming next_step input against the step it answers.
|
|
8
|
+
*
|
|
9
|
+
* Returns { ok:true } when the input carries EXACTLY ONE of the step's allowed
|
|
10
|
+
* fields and no other governed android key. Otherwise { ok:false } with the
|
|
11
|
+
* allowed fields + the offending extra keys for a corrective message.
|
|
12
|
+
*
|
|
13
|
+
* Steps with no allowed-field entry (auto/sign-in/no-field) are not governed and
|
|
14
|
+
* always pass — the strict gate only constrains interactive input steps.
|
|
15
|
+
*
|
|
16
|
+
* @param currentStep the resume step the user is currently on
|
|
17
|
+
* @param input the next_step input object
|
|
18
|
+
*/
|
|
19
|
+
export declare function validateStepInput(currentStep: AndroidOnboardingStep, input: Record<string, unknown>): {
|
|
20
|
+
ok: boolean;
|
|
21
|
+
allowedFields?: string[];
|
|
22
|
+
extras: string[];
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Content validation for the keystore store-password steps, mirroring the ink
|
|
26
|
+
* TUI onSubmit guards in app.tsx so the stateless MCP path enforces the SAME
|
|
27
|
+
* rule before a value is persisted (and before it can reach keystore-generating):
|
|
28
|
+
*
|
|
29
|
+
* - keystore-new-store-password → reject < 6 chars (app.tsx:2575,
|
|
30
|
+
* 'Password must be at least 6 characters')
|
|
31
|
+
* - keystore-existing-store-password → reject empty (app.tsx:2455,
|
|
32
|
+
* 'Store password cannot be empty')
|
|
33
|
+
*
|
|
34
|
+
* Returns { ok:true } when the value passes (or the step is not a store-password
|
|
35
|
+
* step). On failure returns { ok:false, message } with the exact main wording so
|
|
36
|
+
* the gate can re-render the current step with a corrective summary and persist
|
|
37
|
+
* nothing.
|
|
38
|
+
*
|
|
39
|
+
* @param currentStep the resume step the user is currently on
|
|
40
|
+
* @param storePassword the supplied keystoreStorePassword (or undefined/null)
|
|
41
|
+
*/
|
|
42
|
+
export declare function validateStorePassword(currentStep: AndroidOnboardingStep, storePassword: string | undefined | null): {
|
|
43
|
+
ok: boolean;
|
|
44
|
+
message?: string;
|
|
45
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build the arguments array for invoking osascript to open a Terminal window
|
|
3
|
+
* running `command`. Escapes backslashes first, then double-quotes, so that
|
|
4
|
+
* neither character can break out of the AppleScript string literal.
|
|
5
|
+
*/
|
|
6
|
+
export declare function buildOsascriptArgs(command: string): string[];
|
|
7
|
+
/**
|
|
8
|
+
* Returns true when the current platform is macOS (darwin).
|
|
9
|
+
* Accepts an explicit platform string for testability.
|
|
10
|
+
*/
|
|
11
|
+
export declare function canLaunchTerminal(platform?: string): boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Launch `command` in a new macOS Terminal.app window via osascript.
|
|
14
|
+
* The optional `exec` parameter is injectable so tests never spawn a real
|
|
15
|
+
* process — pass a fake to avoid side effects.
|
|
16
|
+
*/
|
|
17
|
+
export declare function launchBuildInTerminal(command: string, exec?: (cmd: string, args: string[]) => Promise<unknown>): Promise<{
|
|
18
|
+
ok: true;
|
|
19
|
+
} | {
|
|
20
|
+
ok: false;
|
|
21
|
+
error: string;
|
|
22
|
+
}>;
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import type { BuildCredentials } from '../../../schemas/build.js';
|
|
2
|
+
import type { BuildLogger, BuildRequestOptions, BuildRequestResult } from '../../request.js';
|
|
3
|
+
import type { AsyncCommandRunner, CiSecretDiscovery, CiSecretEntry, CiSecretSetupAdvice, CiSecretTarget, CommandRunner } from '../ci-secrets.js';
|
|
4
|
+
import type { EnvExportOpts, EnvExportResult } from '../env-export.js';
|
|
5
|
+
import type { BuildScriptChoice, GeneratedWorkflow, PackageManager, WorkflowGeneratorOpts } from '../workflow-generator.js';
|
|
6
|
+
import type { WorkflowWriteOptions, WorkflowWriteResult } from '../workflow-writer.js';
|
|
7
|
+
export type TailStep = 'saving-credentials' | 'detecting-ci-secrets' | 'ci-secrets-setup' | 'ci-secrets-target-select' | 'ask-ci-secrets' | 'checking-ci-secrets' | 'confirm-ci-secret-overwrite' | 'uploading-ci-secrets' | 'ci-secrets-failed' | 'ask-github-actions-setup' | 'confirm-secrets-push' | 'ask-export-env' | 'exporting-env' | 'confirm-env-export-overwrite' | 'overwrite-and-export-env' | 'pick-package-manager' | 'pick-build-script' | 'pick-build-script-custom' | 'preview-workflow-file' | 'view-workflow-diff' | 'writing-workflow-file' | 'ask-build' | 'requesting-build' | 'build-complete';
|
|
8
|
+
export type TailStepKind = 'auto' | 'input' | 'choice' | 'done' | 'error';
|
|
9
|
+
export interface TailStepOption {
|
|
10
|
+
value: string;
|
|
11
|
+
label?: string;
|
|
12
|
+
note?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface TailStepView {
|
|
15
|
+
step: string;
|
|
16
|
+
kind: TailStepKind;
|
|
17
|
+
title?: string;
|
|
18
|
+
prompt?: string;
|
|
19
|
+
collect?: string[];
|
|
20
|
+
options?: TailStepOption[];
|
|
21
|
+
message?: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Runtime context for the tail views — the OPTIONAL transient data a driver
|
|
25
|
+
* surfaces from a prior effect. Mirrors the tail subset of `AndroidStepCtx` so
|
|
26
|
+
* the android engine can pass its ctx straight through.
|
|
27
|
+
*/
|
|
28
|
+
export interface TailStepCtx {
|
|
29
|
+
ciSecretEntries?: CiSecretEntry[];
|
|
30
|
+
ciSecretTargets?: CiSecretTarget[];
|
|
31
|
+
ciSecretSetupAdvice?: CiSecretSetupAdvice[];
|
|
32
|
+
ciSecretRepoLabel?: string | null;
|
|
33
|
+
detectedPackageManager?: string;
|
|
34
|
+
availableScripts?: Record<string, string>;
|
|
35
|
+
recommendedScript?: string | null;
|
|
36
|
+
defaultEnvExportPath?: string;
|
|
37
|
+
}
|
|
38
|
+
export interface TailEffectProgress {
|
|
39
|
+
appId: string;
|
|
40
|
+
setupMode?: 'undecided' | 'with-workflow' | 'secrets-only' | 'declined';
|
|
41
|
+
ciSecretTarget?: CiSecretTarget | null;
|
|
42
|
+
selectedPackageManager?: PackageManager | null;
|
|
43
|
+
buildScriptChoice?: BuildScriptChoice | null;
|
|
44
|
+
envExportTargetPath?: string;
|
|
45
|
+
/**
|
|
46
|
+
* Android-only marker that gates the random-password backup hint at
|
|
47
|
+
* saving-credentials. DRIVER REQUIREMENT: the driver MUST set this on `progress`
|
|
48
|
+
* when its keystore step auto-generated the store password (the bespoke android
|
|
49
|
+
* tail only held this in React `randomPasswordGenerated` state, never persisted);
|
|
50
|
+
* the engine reads it from `progress` and has no other source. Never set on iOS.
|
|
51
|
+
*/
|
|
52
|
+
keystorePasswordGenerated?: boolean;
|
|
53
|
+
}
|
|
54
|
+
export interface TailEffectDeps<P extends TailEffectProgress = TailEffectProgress> {
|
|
55
|
+
/** Tags the saved-cred store, env-export filename and build/workflow platform. */
|
|
56
|
+
platform: 'ios' | 'android';
|
|
57
|
+
/**
|
|
58
|
+
* Build the platform credential SHAPE written at `saving-credentials` (e.g.
|
|
59
|
+
* ANDROID_KEYSTORE_FILE… on android). Throws on missing inputs — same guards
|
|
60
|
+
* the android engine used inline.
|
|
61
|
+
*/
|
|
62
|
+
buildSavedCredentials: (progress: P) => Record<string, string> | Promise<Record<string, string>>;
|
|
63
|
+
/**
|
|
64
|
+
* Lossy fallback used when the driver did not thread the saved-credential map
|
|
65
|
+
* through `carried` (crash-recovery resume). Returns {} when not rebuildable.
|
|
66
|
+
*/
|
|
67
|
+
rebuildTailCredentials: (progress: P) => Record<string, string>;
|
|
68
|
+
/**
|
|
69
|
+
* The platform's resume resolver — used by the `saving-credentials` self-heal
|
|
70
|
+
* guard to detect a progress that should resume elsewhere.
|
|
71
|
+
*/
|
|
72
|
+
resumeStep: (progress: P) => string;
|
|
73
|
+
updateSavedCredentials: (appId: string, platform: 'ios' | 'android', credentials: Record<string, string>) => Promise<void>;
|
|
74
|
+
loadProgress: (appId: string) => Promise<P | null>;
|
|
75
|
+
/**
|
|
76
|
+
* Persist progress. NOTE: the POST-SAVE tail never calls this — saving-credentials
|
|
77
|
+
* deletes progress.json and every later tail step runs purely from transient/
|
|
78
|
+
* carried (the bespoke android tail is in-memory-only), so persisting would
|
|
79
|
+
* re-create the deleted file. Kept on the surface so drivers can still supply it
|
|
80
|
+
* (and for symmetry with the pre-save engine), but unused by runTailEffect.
|
|
81
|
+
*/
|
|
82
|
+
saveProgress: (appId: string, progress: P) => Promise<void>;
|
|
83
|
+
deleteProgress: (appId: string) => Promise<void>;
|
|
84
|
+
createCiSecretEntries?: (credentials: Partial<BuildCredentials>, apiKey?: string) => CiSecretEntry[];
|
|
85
|
+
detectCiSecretTargets?: (runner?: CommandRunner) => CiSecretDiscovery;
|
|
86
|
+
getCiSecretRepoLabelAsync?: (target: CiSecretTarget, runner?: AsyncCommandRunner) => Promise<string | null>;
|
|
87
|
+
listExistingCiSecretKeysAsync?: (target: CiSecretTarget, keys: string[], runner?: AsyncCommandRunner) => Promise<string[]>;
|
|
88
|
+
uploadCiSecretsAsync?: (target: CiSecretTarget, entries: CiSecretEntry[], existingKeys?: string[], runner?: AsyncCommandRunner, onProgress?: (current: number, total: number, keyName: string) => void) => Promise<void>;
|
|
89
|
+
exportCredentialsToEnv?: (opts: EnvExportOpts) => EnvExportResult;
|
|
90
|
+
defaultExportPath?: (appId: string, platform: 'ios' | 'android') => string;
|
|
91
|
+
generateWorkflow?: (opts: WorkflowGeneratorOpts) => GeneratedWorkflow;
|
|
92
|
+
writeWorkflowFile?: (opts: WorkflowGeneratorOpts, writeOptions?: WorkflowWriteOptions) => WorkflowWriteResult;
|
|
93
|
+
requestBuildInternal?: (appId: string, options: BuildRequestOptions, silent?: boolean, logger?: BuildLogger) => Promise<BuildRequestResult>;
|
|
94
|
+
/**
|
|
95
|
+
* The streaming BuildLogger the TUI threads into requestBuildInternal (the 4th
|
|
96
|
+
* arg). On android it streams every line into `setBuildOutput`; the engine just
|
|
97
|
+
* forwards it. When absent, requestBuildInternal is called without a logger.
|
|
98
|
+
*/
|
|
99
|
+
logger?: BuildLogger;
|
|
100
|
+
/**
|
|
101
|
+
* The build VIEWER sink — DISTINCT from `onLog` (the side-log). The bespoke
|
|
102
|
+
* android tail (app.tsx ~L1654-1740) writes the build header / blank+queued /
|
|
103
|
+
* ⚠ failure / no-key UX / catch lines via `setBuildOutput` (a dedicated build
|
|
104
|
+
* output pane), NOT via the side-log `addLog`. The shared engine forwards
|
|
105
|
+
* those build-viewer lines here so the driver can route them to the right
|
|
106
|
+
* sink. OPTIONAL — absent on iOS (and legacy callers), where the lines are
|
|
107
|
+
* simply dropped and routing is unaffected.
|
|
108
|
+
*/
|
|
109
|
+
onBuildOutput?: (line: string) => void;
|
|
110
|
+
/**
|
|
111
|
+
* Resolves the Capgo API key the build request should use, mirroring the
|
|
112
|
+
* android tail's CLI-flag-over-saved precedence (`apikey ?? findSavedKeySilent()`).
|
|
113
|
+
* Returns undefined when no key is resolvable — in which case requesting-build
|
|
114
|
+
* skips the build attempt and finishes at build-complete (the android no-key UX).
|
|
115
|
+
* When this dep is ABSENT the engine falls back to the legacy empty-string apikey
|
|
116
|
+
* so existing callers/tests that never resolved a key keep working.
|
|
117
|
+
*/
|
|
118
|
+
resolveApikey?: () => string | undefined;
|
|
119
|
+
/**
|
|
120
|
+
* Per-key upload progress, forwarded as the 5th arg of uploadCiSecretsAsync.
|
|
121
|
+
* The android tail feeds this into `setCiSecretUploadProgress`. No-op when absent.
|
|
122
|
+
*/
|
|
123
|
+
onCiSecretUploadProgress?: (current: number, total: number, keyName: string) => void;
|
|
124
|
+
/**
|
|
125
|
+
* The 2-phase checking-ci-secrets status text ('Resolving GitHub repository…'
|
|
126
|
+
* then 'Checking existing env vars in <repo>…'). The android tail feeds this into
|
|
127
|
+
* `setCiSecretCheckPhase`. This is the ONLY sink for the check phases — they are
|
|
128
|
+
* intentionally NOT surfaced via `onStatus`, which the driver routes to the
|
|
129
|
+
* oauth/gcp panes (sending the check phases there would corrupt those). No-op
|
|
130
|
+
* when absent.
|
|
131
|
+
*/
|
|
132
|
+
onCiSecretCheckPhase?: (phase: string) => void;
|
|
133
|
+
/**
|
|
134
|
+
* The ci-secrets-failed reason (repo-null / catch in checking-ci-secrets). The
|
|
135
|
+
* android tail feeds this into `setCiSecretError`, which the CiSecretsFailedStep
|
|
136
|
+
* renders. Also surfaced via `transient.ciSecretError`. OPTIONAL — no-op when
|
|
137
|
+
* absent (the failed-step view falls back to its generic message).
|
|
138
|
+
*/
|
|
139
|
+
onCiSecretError?: (message: string) => void;
|
|
140
|
+
/** Reads the project's package.json scripts map. */
|
|
141
|
+
getPackageScripts?: () => Record<string, string>;
|
|
142
|
+
/** Detects the web-framework project type (best-effort; may resolve null). */
|
|
143
|
+
findProjectType?: (options?: {
|
|
144
|
+
quiet?: boolean;
|
|
145
|
+
}) => Promise<string | null>;
|
|
146
|
+
/** Maps a detected project type to its recommended build script name. */
|
|
147
|
+
findBuildCommandForProjectType?: (projectType: string) => Promise<string | null>;
|
|
148
|
+
/**
|
|
149
|
+
* Workflow-file telemetry hook (e.g. 'workflow-file-written'). The android tail
|
|
150
|
+
* calls `trackWorkflowEvent`. No-op when absent.
|
|
151
|
+
*/
|
|
152
|
+
trackWorkflowEvent?: (event: string, options?: {
|
|
153
|
+
decision?: string;
|
|
154
|
+
}) => void;
|
|
155
|
+
/**
|
|
156
|
+
* DRIVER-HELD transient tail state, threaded back into each effect. The TUI
|
|
157
|
+
* resolves these ONCE (at `saving-credentials`) and keeps them in React state;
|
|
158
|
+
* a headless driver mirrors that by capturing `TailEffectResult.transient` and
|
|
159
|
+
* passing it back here on the NEXT effect. NEVER persisted to progress.json.
|
|
160
|
+
*/
|
|
161
|
+
carried?: {
|
|
162
|
+
savedCredentials?: Record<string, string>;
|
|
163
|
+
ciSecretEntries?: CiSecretEntry[];
|
|
164
|
+
ciSecretExistingKeys?: string[];
|
|
165
|
+
/**
|
|
166
|
+
* Whether the workflow file did NOT exist when previewed (app.tsx's
|
|
167
|
+
* `previewIsNew`, resolved at `preview-workflow-file` via existsSync — driver
|
|
168
|
+
* state, never persisted). `writing-workflow-file` logs '✔ Wrote' vs
|
|
169
|
+
* '✔ Overwrote' from it. Absent/undefined defaults to NEW ('Wrote'), matching
|
|
170
|
+
* the bespoke React `useState(true)` default.
|
|
171
|
+
*/
|
|
172
|
+
workflowIsNew?: boolean;
|
|
173
|
+
};
|
|
174
|
+
onStatus?: (message: string) => void;
|
|
175
|
+
onLog?: (message: string, color?: string) => void;
|
|
176
|
+
/** Internal-only diagnostic line → the support internal log (main PR #2406). Optional; no-op when absent. */
|
|
177
|
+
onInternalLog?: (line: string) => void;
|
|
178
|
+
signal?: AbortSignal;
|
|
179
|
+
}
|
|
180
|
+
export interface TailEffectResult<P extends TailEffectProgress = TailEffectProgress> {
|
|
181
|
+
/** Updated progress after the effect ran (matches what was persisted). */
|
|
182
|
+
progress: P;
|
|
183
|
+
/** Explicit next step (a platform step id — string so each platform widens it). */
|
|
184
|
+
next?: string;
|
|
185
|
+
/** Transient runtime data that lives in the driver but is NOT persisted. */
|
|
186
|
+
transient?: TailTransient;
|
|
187
|
+
}
|
|
188
|
+
/** The tail subset of a platform's transient ctx. Every field is optional. */
|
|
189
|
+
export interface TailTransient {
|
|
190
|
+
ciSecretEntries?: CiSecretEntry[];
|
|
191
|
+
savedCredentials?: Record<string, string>;
|
|
192
|
+
ciSecretTargets?: CiSecretTarget[];
|
|
193
|
+
ciSecretSetupAdvice?: CiSecretSetupAdvice[];
|
|
194
|
+
ciSecretRepoLabel?: string | null;
|
|
195
|
+
ciSecretExistingKeys?: string[];
|
|
196
|
+
ciSecretUploadSummary?: string;
|
|
197
|
+
envExportPath?: string;
|
|
198
|
+
workflowFilePath?: string;
|
|
199
|
+
buildUrl?: string;
|
|
200
|
+
buildOutput?: string[];
|
|
201
|
+
aiJobId?: string;
|
|
202
|
+
/** Workflow-builder script preload (resolved at uploading-ci-secrets, with-workflow). */
|
|
203
|
+
availableScripts?: Record<string, string>;
|
|
204
|
+
recommendedScript?: string | null;
|
|
205
|
+
/** Set when env-export found nothing to write or threw — routed to build-complete, never thrown. */
|
|
206
|
+
envExportError?: string;
|
|
207
|
+
/** Set when requesting-build THREW — routed to build-complete, never thrown (app.tsx ~L1733). */
|
|
208
|
+
error?: string;
|
|
209
|
+
/** Set on the ci-secrets-failed routes (repo-null / catch) so the failed-step view can render the reason. */
|
|
210
|
+
ciSecretError?: string;
|
|
211
|
+
}
|
|
212
|
+
export type TailInput = {
|
|
213
|
+
step: 'ci-secrets-setup';
|
|
214
|
+
value: 'retry' | 'skip';
|
|
215
|
+
} | {
|
|
216
|
+
step: 'ci-secrets-target-select';
|
|
217
|
+
ciSecretTarget: CiSecretTarget | null;
|
|
218
|
+
} | {
|
|
219
|
+
step: 'ask-ci-secrets';
|
|
220
|
+
value: 'yes' | 'no';
|
|
221
|
+
} | {
|
|
222
|
+
step: 'confirm-ci-secret-overwrite';
|
|
223
|
+
value: 'replace' | 'skip';
|
|
224
|
+
} | {
|
|
225
|
+
step: 'ci-secrets-failed';
|
|
226
|
+
value: 'retry' | 'continue';
|
|
227
|
+
} | {
|
|
228
|
+
step: 'ask-github-actions-setup';
|
|
229
|
+
value: 'with-workflow' | 'secrets-only' | 'no';
|
|
230
|
+
} | {
|
|
231
|
+
step: 'confirm-secrets-push';
|
|
232
|
+
value: 'confirm' | 'cancel';
|
|
233
|
+
} | {
|
|
234
|
+
step: 'ask-export-env';
|
|
235
|
+
value: 'no';
|
|
236
|
+
} | {
|
|
237
|
+
step: 'ask-export-env';
|
|
238
|
+
value: 'yes';
|
|
239
|
+
envExportTargetPath: string;
|
|
240
|
+
} | {
|
|
241
|
+
step: 'confirm-env-export-overwrite';
|
|
242
|
+
value: 'replace' | 'skip';
|
|
243
|
+
} | {
|
|
244
|
+
step: 'pick-package-manager';
|
|
245
|
+
selectedPackageManager: PackageManager;
|
|
246
|
+
} | {
|
|
247
|
+
step: 'pick-build-script';
|
|
248
|
+
value: '__custom__';
|
|
249
|
+
} | {
|
|
250
|
+
step: 'pick-build-script';
|
|
251
|
+
buildScriptChoice: BuildScriptChoice;
|
|
252
|
+
} | {
|
|
253
|
+
step: 'pick-build-script-custom';
|
|
254
|
+
command: string;
|
|
255
|
+
} | {
|
|
256
|
+
step: 'preview-workflow-file';
|
|
257
|
+
value: 'write' | 'view' | 'cancel';
|
|
258
|
+
} | {
|
|
259
|
+
step: 'view-workflow-diff';
|
|
260
|
+
value: 'close';
|
|
261
|
+
} | {
|
|
262
|
+
step: 'ask-build';
|
|
263
|
+
value: 'yes' | 'no';
|
|
264
|
+
};
|
|
265
|
+
/**
|
|
266
|
+
* Pure: a UI-framework-neutral description of a tail step. Mirrors the matching
|
|
267
|
+
* <Select>/prompt the TUI renders. Moved verbatim from `androidViewForStep`'s
|
|
268
|
+
* tail cases — the android engine adapts the returned view to `AndroidStepView`.
|
|
269
|
+
*/
|
|
270
|
+
export declare function tailViewForStep(step: TailStep, progress: TailEffectProgress | null, ctx: TailStepCtx): TailStepView;
|
|
271
|
+
/**
|
|
272
|
+
* Pure state write for each tail choice/input step — returns a NEW progress
|
|
273
|
+
* object (spread — never mutate). Navigation-only / spinner-gate inputs return
|
|
274
|
+
* progress unchanged. Moved verbatim from `applyAndroidInput`'s tail reducers.
|
|
275
|
+
*/
|
|
276
|
+
export declare function applyTailInput<P extends TailEffectProgress>(step: TailStep, progress: P, input: TailInput): P;
|
|
277
|
+
/**
|
|
278
|
+
* Dispatches to the right tail effect handler. Moved verbatim from
|
|
279
|
+
* `runAndroidEffect`'s tail cases. Platform-specific calls are parameterised via
|
|
280
|
+
* `deps.platform` / `deps.buildSavedCredentials` / `deps.rebuildTailCredentials`
|
|
281
|
+
* / `deps.resumeStep`; everything else is unchanged.
|
|
282
|
+
*/
|
|
283
|
+
export declare function runTailEffect<P extends TailEffectProgress>(step: TailStep, progress: P, deps: TailEffectDeps<P>): Promise<TailEffectResult<P>>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { CiSecretTarget } from './ci-secrets.js';
|
|
2
|
+
import type { BuildScriptChoice, PackageManager } from './workflow-generator.js';
|
|
3
|
+
export interface TailProgress {
|
|
4
|
+
/**
|
|
5
|
+
* The 3-way GitHub Actions setup choice made at `ask-github-actions-setup`.
|
|
6
|
+
* Mirrors `const [setupMode] = useState<…>('undecided')` in both app.tsx.
|
|
7
|
+
*/
|
|
8
|
+
setupMode?: 'undecided' | 'with-workflow' | 'secrets-only' | 'declined';
|
|
9
|
+
/**
|
|
10
|
+
* The CI-secrets destination picked at `ci-secrets-target-select`.
|
|
11
|
+
* Mirrors `const [ciSecretTarget] = useState<CiSecretTarget | null>(null)`.
|
|
12
|
+
*/
|
|
13
|
+
ciSecretTarget?: CiSecretTarget | null;
|
|
14
|
+
/**
|
|
15
|
+
* The package manager chosen at `pick-package-manager`.
|
|
16
|
+
* Mirrors `const [selectedPackageManager] = useState<PackageManager | null>(null)`.
|
|
17
|
+
*/
|
|
18
|
+
selectedPackageManager?: PackageManager | null;
|
|
19
|
+
/**
|
|
20
|
+
* The build-script choice made at `pick-build-script`.
|
|
21
|
+
* Mirrors `const [buildScriptChoice] = useState<BuildScriptChoice | null>(null)`.
|
|
22
|
+
*/
|
|
23
|
+
buildScriptChoice?: BuildScriptChoice | null;
|
|
24
|
+
/**
|
|
25
|
+
* The user-supplied `.env` export path entered at `ask-export-env`.
|
|
26
|
+
* Mirrors `const [envExportTargetPath] = useState<string>('')`.
|
|
27
|
+
*/
|
|
28
|
+
envExportTargetPath?: string;
|
|
29
|
+
}
|