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
2 changes: 1 addition & 1 deletion cli/src/app/debug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,7 @@ export async function debugApp(appId: string, options: AppDebugOptions) {
}
if (!appId) {
log.error('Missing argument, you need to provide a appId, or be in a capacitor project')
throw new Error('Missing appId')
throw new CliUserError('Missing appId')
}

const orgId = await getOrganizationId(options.apikey!, appId, { supaHost: options.supaHost, supaAnon: options.supaAnon })
Expand Down
2 changes: 1 addition & 1 deletion cli/src/app/delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export async function deleteAppInternal(
if (!appId) {
if (!silent)
log.error('Missing argument, you need to provide a appId, or be in a capacitor project')
throw new Error('Missing appId')
throw new CliUserError('Missing appId')
}

const supabase = await createSupabaseClient(options.apikey, options.supaHost, options.supaAnon)
Expand Down
3 changes: 2 additions & 1 deletion cli/src/app/set.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { intro, log, outro } from '@clack/prompts'
import { checkAppExistsAndHasPermissionOrgErr, getAppIconStoragePath, resolveAppSetIconPath } from '../api/app'
import { assertChannelExists, disableDownloadChannels as disableAllDownloadChannels, setDefaultDownloadChannel } from './default-channels'
import { normalizeStoreUrl } from './store-url'
import { CliUserError } from '../shared/cli-user-error'
import {
createSupabaseClient,
findSavedKey,
Expand Down Expand Up @@ -39,7 +40,7 @@ export async function setAppInternal(appId: string, options: Options, silent = f
if (!appId) {
if (!silent)
log.error('Missing argument, you need to provide a appId, or be in a capacitor project')
throw new Error('Missing appId')
throw new CliUserError('Missing appId')
}

const supabase = await createSupabaseClient(options.apikey, options.supaHost, options.supaAnon)
Expand Down
3 changes: 2 additions & 1 deletion cli/src/build/needed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { difference, parse } from '@std/semver'
import { trackEvent } from '../analytics/track'
import { check2FAComplianceForApp, checkAppExistsAndHasPermissionOrgErr } from '../api/app'
import { formatTable } from '../terminal-table'
import { CliUserError } from '../shared/cli-user-error'
import {
checkCompatibilityCloud,
createSupabaseClient,
Expand Down Expand Up @@ -263,7 +264,7 @@ export async function getBuildNeeded(
if (!resolvedAppId) {
if (configError instanceof Error)
throw configError
throw new Error('Missing appId')
throw new CliUserError('Missing appId')
}

if (!enrichedOptions.apikey)
Expand Down
3 changes: 2 additions & 1 deletion cli/src/build/prescan/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { SupabaseClient } from '@supabase/supabase-js'
import type { Database } from '../../types/supabase.types'
import type { Platform, ScanContext } from './types'
import { getConfig } from '../../utils'
import { CliUserError } from '../../shared/cli-user-error'
import { mergeCredentials } from '../credentials'
import { withCwd } from '../cwd'

Expand All @@ -28,7 +29,7 @@ export async function buildScanContext(args: BuildScanContextArgs): Promise<Scan
try { config = (await withCwd(args.projectDir, () => getConfig(true))).config }
catch { config = undefined } // no capacitor project — checks degrade individually
const appId = args.appId ?? config?.appId
if (!appId) throw new Error('Missing appId: pass it explicitly or run inside a Capacitor project')
if (!appId) throw new CliUserError('Missing appId: pass it explicitly or run inside a Capacitor project')
const credentials = args.credentials
?? (await mergeCredentials(appId, args.platform) as Record<string, string> | undefined)
return {
Expand Down
2 changes: 1 addition & 1 deletion cli/src/bundle/cleanup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export async function cleanupBundleInternal(appId: string, options: BundleCleanu
if (!appId) {
if (!silent)
log.error('Missing argument, you need to provide a appid, or be in a capacitor project')
throw new Error('Missing appId')
throw new CliUserError('Missing appId')
}

const supabase = await createSupabaseClient(options.apikey, options.supaHost, options.supaAnon)
Expand Down
3 changes: 2 additions & 1 deletion cli/src/bundle/compatibility.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { intro, log } from '@clack/prompts'
import { trackEvent } from '../analytics/track'
import { check2FAComplianceForApp, checkAppExistsAndHasPermissionOrgErr } from '../api/app'
import { formatTable } from '../terminal-table'
import { CliUserError } from '../shared/cli-user-error'
import {
checkCompatibilityCloud,
createSupabaseClient,
Expand Down Expand Up @@ -54,7 +55,7 @@ export async function checkCompatibilityInternal(
if (!resolvedAppId) {
if (!silent)
log.error('Missing argument, you need to provide an appId, or be in a capacitor project')
throw new Error('Missing appId')
throw new CliUserError('Missing appId')
}

const supabase = await createSupabaseClient(
Expand Down
3 changes: 2 additions & 1 deletion cli/src/bundle/delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { BundleDeleteOptions } from '../schemas/bundle'
import { intro, log, outro } from '@clack/prompts'
import { check2FAComplianceForApp, checkAppExistsAndHasPermissionOrgErr } from '../api/app'
import { deleteSpecificVersion } from '../api/versions'
import { CliUserError } from '../shared/cli-user-error'
import { createSupabaseClient, findSavedKey, getAppId, getConfig, getOrganizationId, resolveUserIdFromApiKey, sendEvent } from '../utils'

export async function deleteBundleInternal(bundleId: string, appId: string, options: BundleDeleteOptions, silent = false) {
Expand All @@ -21,7 +22,7 @@ export async function deleteBundleInternal(bundleId: string, appId: string, opti
if (!appId) {
if (!silent)
log.error('Missing argument, you need to provide a appId, or be in a capacitor project')
throw new Error('Missing appId')
throw new CliUserError('Missing appId')
}

if (!bundleId) {
Expand Down
14 changes: 12 additions & 2 deletions cli/src/bundle/encrypt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,17 @@ export async function encryptZipInternal(
await ensurePublicKeyFromPrivateKey(privateKey, { silent: silent || json, json })
}
else if (!userSuppliedPrivateKey) {
await ensurePublicKeyInConfig({ interactive, silent: silent || json, json })
try {
await ensurePublicKeyInConfig({ interactive, silent: silent || json, json })
}
catch (error) {
if (!interactive && error instanceof Error
&& /missing public key|missing_public_key/i.test(error.message)
&& !(error instanceof CliUserError)) {
throw new CliUserError(error.message)
}
throw error
Comment thread
cursor[bot] marked this conversation as resolved.
}
}
extConfig = await getConfigForWrite()
}
Expand All @@ -85,7 +95,7 @@ export async function encryptZipInternal(
else
log.warning('Warning: Missing Public Key in config')
}
throw new Error('Missing public key in config')
throw new CliUserError('Missing public key in config')
}

if (!privateKey) {
Expand Down
3 changes: 2 additions & 1 deletion cli/src/bundle/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { trackEvent } from '../analytics/track'
import { check2FAComplianceForApp, checkAppExistsAndHasPermissionOrgErr } from '../api/app'
import { checkAlerts } from '../api/update'
import { displayBundles, getActiveAppVersions } from '../api/versions'
import { CliUserError } from '../shared/cli-user-error'
import { createSupabaseClient, findSavedKey, getAppId, getConfig, resolveUserIdFromApiKey } from '../utils'

export async function listBundle(appId: string, options: OptionsBase, silent = false) {
Expand All @@ -24,7 +25,7 @@ export async function listBundle(appId: string, options: OptionsBase, silent = f
if (!appId) {
if (!silent)
log.error('Missing argument, you need to provide a appid, or be in a capacitor project')
throw new Error('Missing appId')
throw new CliUserError('Missing appId')
}

const supabase = await createSupabaseClient(options.apikey, options.supaHost, options.supaAnon)
Expand Down
3 changes: 2 additions & 1 deletion cli/src/bundle/unlink.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { intro, log, outro } from '@clack/prompts'
import { check2FAComplianceForApp, checkAppExistsAndHasPermissionOrgErr } from '../api/app'
import { checkVersionNotUsedInChannel } from '../api/channels'
import { getVersionData } from '../api/versions'
import { CliUserError } from '../shared/cli-user-error'
import {
checkPlanValid,
createSupabaseClient,
Expand Down Expand Up @@ -54,7 +55,7 @@ export async function unlinkDeviceInternal(
if (!resolvedAppId) {
if (!silent)
log.error('Missing argument, you need to provide an appId, or be in a capacitor project')
throw new Error('Missing appId')
throw new CliUserError('Missing appId')
}

if (!bundle) {
Expand Down
44 changes: 33 additions & 11 deletions cli/src/bundle/zip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
resolveUpdaterPackageJsonPath,
} from '../recovery/bundle-zip'
import { ensureNotifyAppReadyInBuildFolder, buildCiNotifyAppReadyMessage } from '../recovery/notify-app-ready'
import { CliUserError } from '../shared/cli-user-error'
import { parsePackageJsonOptionPaths, resolveAppIdWithRecovery } from '../recovery/app-id'
import { checkIndexPosition, searchInDirectory } from './check'

Expand All @@ -44,6 +45,13 @@ function emitJsonError(error: unknown) {
console.error(formatError(error))
}

function emitCliUserJsonError(error: CliUserError) {
if (error.context?.message)
emitJsonError({ error: error.message, message: error.context.message })
else
emitJsonError({ error: error.message })
}

export async function zipBundleInternal(appId: string, options: BundleZipOptions, silent = false): Promise<ZipResult> {
const { json } = options
let { bundle, path } = options
Expand Down Expand Up @@ -72,7 +80,7 @@ export async function zipBundleInternal(appId: string, options: BundleZipOptions
intro(`Zipping ${resolvedAppId ?? 'app'}@${bundle}`)

// Expected setup failures use plain Error (not CliUserError) so PostHog still captures
// real user aborts after declined recovery. notifyAppReady stays a bare Error too.
// real user aborts after declined recovery.
if (bundle && !regexSemver.test(bundle)) {
if (interactive) {
const recoveredBundle = await recoverInvalidSemverBundle(bundle, resolveLocalSemverFallback(uuid))
Expand Down Expand Up @@ -116,13 +124,22 @@ export async function zipBundleInternal(appId: string, options: BundleZipOptions

if (shouldCheckNotifyAppReady) {
if (!searchInDirectory(path, 'notifyAppReady')) {
const recovery = await ensureNotifyAppReadyInBuildFolder({
webDir: path,
interactive,
json,
})
if (recovery !== 'skipped' && !searchInDirectory(path, 'notifyAppReady')) {
throw new Error(buildCiNotifyAppReadyMessage(path))
if (interactive) {
const recovery = await ensureNotifyAppReadyInBuildFolder({
webDir: path,
interactive,
json,
})
if (recovery !== 'skipped' && !searchInDirectory(path, 'notifyAppReady')) {
throw new CliUserError('notifyAppReady() is missing in build folder')
}
}
else {
throw json
? new CliUserError('notifyAppReady_not_in_source_code', {
message: buildCiNotifyAppReadyMessage(path),
})
: new CliUserError(buildCiNotifyAppReadyMessage(path))
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

Expand Down Expand Up @@ -227,10 +244,15 @@ export async function zipBundleInternal(appId: string, options: BundleZipOptions
}
catch (error) {
if (!silent) {
if (json)
emitJsonError(error)
else
if (json) {
if (error instanceof CliUserError)
emitCliUserJsonError(error)
else
emitJsonError(error)
}
else {
log.error(formatError(error))
}
}
throw error instanceof Error ? error : new Error(String(error))
}
Expand Down
3 changes: 2 additions & 1 deletion cli/src/channel/add.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { trackEvent } from '../analytics/track'
import { check2FAComplianceForApp, checkAppExistsAndHasPermissionOrgErr } from '../api/app'
import { createChannel, findChannel } from '../api/channels'
import { isChannelAlreadyExistsError } from '../init/channel-conflict'
import { CliUserError } from '../shared/cli-user-error'
import {
createSupabaseClient,
findSavedKey,
Expand Down Expand Up @@ -81,7 +82,7 @@ export async function addChannelInternal(channelId: string, appId: string, optio
if (!appId) {
if (!silent)
log.error('Missing argument, you need to provide a appId, or be in a capacitor project')
throw new Error('Missing appId')
throw new CliUserError('Missing appId')
}

const supabase = await createSupabaseClient(options.apikey, options.supaHost, options.supaAnon, silent)
Expand Down
3 changes: 2 additions & 1 deletion cli/src/channel/delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { intro, log, outro } from '@clack/prompts'
import { check2FAComplianceForApp, checkAppExistsAndHasPermissionOrgErr } from '../api/app'
import { delChannel, findChannel, findVersionsLinkedToChannel, isVersionLinkedToOtherChannel } from '../api/channels'
import { deleteAppVersion } from '../api/versions'
import { CliUserError } from '../shared/cli-user-error'
import { createSupabaseClient, findSavedKey, formatError, getAppId, getConfig, getOrganizationId, hasCliPermission, invokeCapgoCliApi, sendEvent } from '../utils'

export async function deleteChannelInternal(channelId: string, appId: string, options: ChannelDeleteOptions, silent = false) {
Expand All @@ -22,7 +23,7 @@ export async function deleteChannelInternal(channelId: string, appId: string, op
if (!appId) {
if (!silent)
log.error('Missing argument, you need to provide a appId, or be in a capacitor project')
throw new Error('Missing appId')
throw new CliUserError('Missing appId')
}

const supabase = await createSupabaseClient(options.apikey, options.supaHost, options.supaAnon)
Expand Down
3 changes: 2 additions & 1 deletion cli/src/channel/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { OptionsBase } from '../schemas/base'
import { intro, log, outro } from '@clack/prompts'
import { check2FAComplianceForApp, checkAppExistsAndHasPermissionOrgErr } from '../api/app'
import { displayChannels, getActiveChannels } from '../api/channels'
import { CliUserError } from '../shared/cli-user-error'
import { createSupabaseClient, findSavedKey, getAppId, getConfig, getOrganizationId, sendEvent } from '../utils'

export async function listChannelsInternal(appId: string, options: OptionsBase, silent = false) {
Expand All @@ -21,7 +22,7 @@ export async function listChannelsInternal(appId: string, options: OptionsBase,
if (!appId) {
if (!silent)
log.error('Missing argument, you need to provide a appId, or be in a capacitor project')
throw new Error('Missing appId')
throw new CliUserError('Missing appId')
}

const supabase = await createSupabaseClient(options.apikey, options.supaHost, options.supaAnon)
Expand Down
3 changes: 2 additions & 1 deletion cli/src/channel/set.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { getActiveAppVersions, getVersionData } from '../api/versions'
import { sendUpdateNotificationsForChannels } from '../notifications/send-update'
import { printPreviewQrForResolvedTarget, resolveChannelPreviewTarget } from '../preview/qr'
import { formatTable } from '../terminal-table'
import { CliUserError } from '../shared/cli-user-error'
import { channelUpdatePackageCliError, checkCompatibilityNativePackages, createSupabaseClient, findSavedKey, getAppId, getBundleVersion, getCompatibilityDetails, getConfig, getOrganizationId, invokeCapgoCliApi, isCompatible, resolveUserIdFromApiKey, sendEvent } from '../utils'

/**
Expand Down Expand Up @@ -71,7 +72,7 @@ export async function setChannelInternal(channel: string, appId: string, options
if (!appId) {
if (!silent)
log.error('Missing argument, you need to provide a appId, or be in a capacitor project')
throw new Error('Missing appId')
throw new CliUserError('Missing appId')
}

if (!channel) {
Expand Down
3 changes: 2 additions & 1 deletion cli/src/preview/qr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { chmod, mkdir } from 'node:fs/promises'
import { dirname, resolve } from 'node:path'
import QRCode from 'qrcode'
import { buildPreviewWebUrl, type PreviewWebEnv } from './web-url'
import { CliUserError } from '../shared/cli-user-error'
import { check2FAComplianceForApp, checkAppExistsAndHasPermissionOrgErr } from '../api/app'
import { createSupabaseClient, findSavedKey, formatError, getAppId, getCapgoCliHttpStatus, getConfig, invokeCapgoCliApi, readCapgoCliApiErrorPayload } from '../utils'

Expand Down Expand Up @@ -330,7 +331,7 @@ export async function getPreviewQr(appId: string, target: string | undefined, op

if (!appId) {
log.error('Missing argument, you need to provide a appId, or be in a capacitor project')
throw new Error('Missing appId')
throw new CliUserError('Missing appId')
}

const supabase = await createSupabaseClient(options.apikey, options.supaHost, options.supaAnon)
Expand Down
6 changes: 4 additions & 2 deletions cli/test/test-cli-recovery.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { join } from 'node:path'
import process from 'node:process'
import vm from 'node:vm'
import { shouldCapturePosthogException } from '../src/posthog.ts'
import { CliUserError } from '../src/shared/cli-user-error.ts'
import { saveKeyInternal } from '../src/key.ts'
import { setConfigWriteTarget } from '../src/config/index.ts'
import {
Expand Down Expand Up @@ -322,7 +323,7 @@ await test('zipBundleInternal rejects declared updater missing from node_modules
}
})

await test('zipBundleInternal silent notifyAppReady failure stays PostHog-capturable', async () => {
await test('zipBundleInternal silent notifyAppReady failure uses CliUserError', async () => {
const root = makeTempDir('zip-silent')
const webDir = join(root, 'www')
mkdirSync(webDir)
Expand All @@ -332,8 +333,9 @@ await test('zipBundleInternal silent notifyAppReady failure stays PostHog-captur
await assert.rejects(
() => zipBundleInternal('com.example.app', { path: webDir, bundle: '1.0.0' }, true),
(error) => {
assert.equal(error instanceof CliUserError, true)
assert.match(error.message, /notifyAppReady\(\) is missing/)
assert.equal(shouldCapturePosthogException(error), true)
assert.equal(shouldCapturePosthogException(error), false)
return true
},
)
Expand Down
Loading
Loading