Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
"test:onboarding-telemetry": "bun test/test-onboarding-telemetry.mjs",
"test:v2-event-migration": "bun test/test-v2-event-migration.mjs",
"test:analytics": "bun test/test-analytics.mjs",
"test:authenticated-command-invocation": "bun test/test-authenticated-command-invocation.mjs",
"test:analytics-error-category": "bun test/test-analytics-error-category.mjs",
"test:analytics-org-resolver": "bun test/test-analytics-org-resolver.mjs",
"test:supabase-perf": "bun test/test-supabase-perf.mjs",
Expand Down Expand Up @@ -163,7 +164,7 @@
"test:ios-marketing-version": "bun test/test-ios-marketing-version.mjs",
"test:platform-flow-contract": "bun test/test-platform-flow-contract.mjs",
"test:tail-engine-shared": "bun test/test-tail-engine-shared.mjs",
"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:auto-bump-version && bun run test:auto-bump-ai-diff && bun run test:version-edge-cases && bun run test:regex && bun run test:upload && bun run test:fail-on-incompatible && bun run test:native-dependencies && bun run test:package-json-guard && 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:app-set-options && 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-monorepo-targeting && bun run test:init-app-conflict && bun run test:wait-log && bun run test:init-guardrails && bun run test:init-replay && bun run test:init-telemetry && 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-upload-prompt && 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:ios-marketing-version && bun run test:platform-flow-contract && bun run test:tail-engine-shared && bun run test:prescan && bun run test:android-reporting-api && bun run test:android-app-verification && bun run test:android-rename && bun run test:appflow-auth && bun run test:appflow-api-map && bun run test:appflow-validate && bun run test:appflow-flow && bun run test:appflow-gapfill && bun run test:appflow-engine && bun run test:appflow-tail && bun run test:appflow-fetch && bun run test:appflow-sa-decode && bun run test:app-permission-helper && bun run test:organization-set-api-host && bun run test:trial-warning && bun run test:plan-validation",
"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:auto-bump-version && bun run test:auto-bump-ai-diff && bun run test:version-edge-cases && bun run test:regex && bun run test:upload && bun run test:fail-on-incompatible && bun run test:native-dependencies && bun run test:package-json-guard && 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:authenticated-command-invocation && 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:app-set-options && 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-monorepo-targeting && bun run test:init-app-conflict && bun run test:wait-log && bun run test:init-guardrails && bun run test:init-replay && bun run test:init-telemetry && 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-upload-prompt && 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:ios-marketing-version && bun run test:platform-flow-contract && bun run test:tail-engine-shared && bun run test:prescan && bun run test:android-reporting-api && bun run test:android-app-verification && bun run test:android-rename && bun run test:appflow-auth && bun run test:appflow-api-map && bun run test:appflow-validate && bun run test:appflow-flow && bun run test:appflow-gapfill && bun run test:appflow-engine && bun run test:appflow-tail && bun run test:appflow-fetch && bun run test:appflow-sa-decode && bun run test:app-permission-helper && bun run test:organization-set-api-host && bun run test:trial-warning && bun run test:plan-validation",
"test:build-platform-selection": "bun test/test-build-platform-selection.mjs",
"test:ai-log-capture": "bun test/test-ai-log-capture.mjs",
"test:ai-analyze-flow": "bun test/test-ai-analyze-flow.mjs",
Expand Down
33 changes: 30 additions & 3 deletions cli/src/analytics/track.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,13 @@ export interface CommandContext {
positional_arg_count: number
}

interface DeferredCommandInvocation {
commandPath: string
ctx: CommandContext
}

let deferredCommandInvocation: DeferredCommandInvocation | undefined

interface CommanderLike {
args: readonly string[]
opts: () => Record<string, unknown>
Expand All @@ -179,10 +186,9 @@ export function extractCommandContext(command: CommanderLike): CommandContext {
}
}

export function trackCommandInvoked(commandPath: string, ctx: CommandContext): void {
commandStartedAt = Date.now()
currentCommandPath = commandPath
function emitCommandInvoked(commandPath: string, ctx: CommandContext, apikey?: string): void {
void trackEvent({
apikey,
channel: CLI_USAGE_CHANNEL,
event: 'CLI Command Invoked',
icon: '⚡',
Expand All @@ -195,6 +201,27 @@ export function trackCommandInvoked(commandPath: string, ctx: CommandContext): v
})
}

export function trackCommandInvoked(commandPath: string, ctx: CommandContext): void {
commandStartedAt = Date.now()
currentCommandPath = commandPath
emitCommandInvoked(commandPath, ctx)
}

export function deferCommandInvocation(commandPath: string, ctx: CommandContext): void {
commandStartedAt = Date.now()
currentCommandPath = commandPath
deferredCommandInvocation = { commandPath, ctx }
}

export function flushDeferredCommandInvocation(apikey: string): void {
const invocation = deferredCommandInvocation
if (!invocation)
return

deferredCommandInvocation = undefined
emitCommandInvoked(invocation.commandPath, invocation.ctx, apikey)
}

export function trackCommandSucceeded(commandPath: string): void {
void trackEvent({
channel: CLI_USAGE_CHANNEL,
Expand Down
25 changes: 25 additions & 0 deletions cli/src/auth/command-input.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
function normalized(value?: string): string | undefined {
return value?.trim() || undefined
}

export function resolveLoginCommandApiKey(positional?: string, option?: string): string | undefined {
return normalized(option) ?? normalized(positional)
}

export function resolveInitCommandInput(positionalKey?: string, positionalAppId?: string, optionKey?: string) {
const flagKey = normalized(optionKey)
const legacyKey = normalized(positionalKey)
if (flagKey) {
return {
apikey: flagKey,
appId: normalized(positionalAppId) ?? legacyKey,
explicitApiKey: true,
}
}

return {
apikey: legacyKey,
appId: normalized(positionalAppId),
explicitApiKey: Boolean(legacyKey),
}
}
10 changes: 8 additions & 2 deletions cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { categorizeCliError } from './analytics/error-category'
import { applyCommandAnalyticsOptOut, applyRawCommandAnalyticsOptOut } from './analytics/opt-out'
import { enableSupabaseInstrumentation } from './analytics/supabase-perf'
import { setCurrentCliCommand } from './analytics/cli-headers'
import { extractCommandContext, flushAnalytics, trackCommandFailed, trackCommandInvoked, trackCommandSucceeded } from './analytics/track'
import { deferCommandInvocation, extractCommandContext, flushAnalytics, trackCommandFailed, trackCommandInvoked, trackCommandSucceeded } from './analytics/track'
import { addApp } from './app/add'
import { debugApp } from './app/debug'
import { deleteApp } from './app/delete'
Expand Down Expand Up @@ -92,7 +92,11 @@ program.hook('preAction', (_thisCommand, actionCommand) => {
currentCommandPath = getCommandPath(actionCommand)
setCurrentCliCommand(currentCommandPath)
applyCommandAnalyticsOptOut(currentCommandPath, actionCommand.opts())
trackCommandInvoked(currentCommandPath, extractCommandContext(actionCommand))
const commandContext = extractCommandContext(actionCommand)
if (currentCommandPath === 'login' || currentCommandPath === 'init')
deferCommandInvocation(currentCommandPath, commandContext)
else
trackCommandInvoked(currentCommandPath, commandContext)
})

program.hook('postAction', (_thisCommand, actionCommand) => {
Expand All @@ -116,6 +120,7 @@ During the iOS run-on-device step, choose a physical iPhone/iPad or simulator. I

Example: npx @capgo/cli@latest init YOUR_API_KEY com.example.app`)
.action(initApp)
.option('-a, --apikey <apikey>', optionDescriptions.apikey)
.option('-n, --name <name>', `App name for display in Capgo Cloud`)
.option('-i, --icon <icon>', `App icon path for display in Capgo Cloud`)
.option('--supa-host <supaHost>', optionDescriptions.supaHost)
Expand Down Expand Up @@ -181,6 +186,7 @@ Use --apikey=******** in any command to override it.

Example: npx @capgo/cli@latest login YOUR_API_KEY`)
.action(login)
.option('-a, --apikey <apikey>', optionDescriptions.apikey)
.option('--local', `Only save in local folder, git ignored for security.`)
.option('--supa-host <supaHost>', optionDescriptions.supaHost)
.option('--supa-anon <supaAnon>', optionDescriptions.supaAnon)
Expand Down
11 changes: 8 additions & 3 deletions cli/src/init/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ import open from 'open'
import tmp from 'tmp'
import { checkAppIdsExist, completePendingOnboardingApp, findAppInOrganization, listPendingOnboardingApps } from '../api/app'
import { checkVersionStatus } from '../api/update'
import { flushDeferredCommandInvocation } from '../analytics/track'
import { addAppInternal } from '../app/add'
import { markSnag, waitLog } from '../app/debug'
import { deleteAppInternal } from '../app/delete'
import { resolveInitCommandInput } from '../auth/command-input'
import { getInfoInternal } from '../app/info'
import { WAIT_LOG_CONTINUE_HINT } from '../app/wait-log-query'
import { canUseFilePicker, openPackageJsonPicker } from '../build/onboarding/file-picker'
Expand Down Expand Up @@ -5289,7 +5291,9 @@ export async function initApp(apikeyCommand: string, appId: string, options: Sup
setConfigWriteTarget(initialTargets.capacitorConfigPath)
globalSupaHost = options.supaHost // honor --supa-host for the support-logs upload
const pm = getPMAndCommand()
options.apikey = apikeyCommand
const commandInput = resolveInitCommandInput(apikeyCommand, appId, options.apikey)
options.apikey = commandInput.apikey ?? ''
Comment thread
WcaleNieWolny marked this conversation as resolved.
Resolved
appId = commandInput.appId ?? ''
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
startInitReplay({
analyticsEnabled,
apikey: options.apikey?.trim() || findSavedKeySilent(),
Expand All @@ -5305,14 +5309,14 @@ export async function initApp(apikeyCommand: string, appId: string, options: Sup

if (!options.apikey) {
try {
options.apikey ??= findSavedKey(true)
options.apikey = findSavedKey(true)
}
catch {
}
}

const log = pSpinner()
if (!doLoginExists() || apikeyCommand) {
if (!doLoginExists() || commandInput.explicitApiKey) {
log.start(`Running: ${pm.runner} @capgo/cli@latest login ***`)
try {
await loginInternal(options.apikey, options, true)
Expand All @@ -5326,6 +5330,7 @@ export async function initApp(apikeyCommand: string, appId: string, options: Sup

const supabase = await createSupabaseClient(options.apikey, options.supaHost, options.supaAnon)
await resolveUserIdFromApiKey(supabase, options.apikey)
flushDeferredCommandInvocation(options.apikey)
activeInitTelemetry?.setAuth('', options.apikey)

let resumed = await tryResumeOnboarding(options.apikey, initialTargets, initialCwd, supabase, {
Expand Down
10 changes: 8 additions & 2 deletions cli/src/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ import { existsSync } from 'node:fs'
import { homedir } from 'node:os'
import { intro, isCancel, log, outro, password } from '@clack/prompts'
import { checkAlerts } from './api/update'
import { flushDeferredCommandInvocation } from './analytics/track'
import { resolveLoginCommandApiKey } from './auth/command-input'
import { validateAndSaveKey } from './auth/session'
import { CliUserError } from './shared/cli-user-error'

interface Options {
apikey?: string
local: boolean
supaHost?: string
supaAnon?: string
Expand All @@ -16,7 +19,7 @@ export function doLoginExists() {
return existsSync(`${userHomeDir}/.capgo`) || existsSync('.capgo')
}

export async function loginInternal(apikey: string, options: Options, silent = false) {
export async function loginInternal(apikey: string | undefined, options: Options, silent = false): Promise<string> {
if (!silent)
intro(`Login to Capgo`)

Expand Down Expand Up @@ -61,8 +64,11 @@ export async function loginInternal(apikey: string, options: Options, silent = f
log.success(`login saved into .capgo file in ${local ? 'local' : 'home'} directory`)
outro('Done ✅')
}

return apikey
}

export async function login(apikey: string, options: Options) {
await loginInternal(apikey, options, false)
const validatedApiKey = await loginInternal(resolveLoginCommandApiKey(apikey, options.apikey), options, false)
flushDeferredCommandInvocation(validatedApiKey)
}
Loading
Loading