@@ -51,6 +51,7 @@ let globalChannelName = defaultChannel
5151let globalPlatform : 'ios' | 'android' = 'ios'
5252let globalDelta = false
5353let globalCurrentVersion : string | undefined
54+ let globalAppId : string | undefined
5455
5556function readTmpObj ( ) {
5657 tmpObject ??= readdirSync ( tmp . tmpdir )
@@ -391,10 +392,16 @@ async function ensureWorkspaceReadyForInit(initialAppId?: string): Promise<strin
391392 }
392393}
393394
395+ let globalOrgId : string | undefined
396+ let globalOrgName : string | undefined
397+
394398function markStepDone ( step : number , pathToPackageJson ?: string , channelName ?: string ) {
395399 try {
396400 writeFileSync ( getTmpObjectPath ( ) , JSON . stringify ( {
397401 step_done : step ,
402+ orgId : globalOrgId ,
403+ orgName : globalOrgName ,
404+ appId : globalAppId ,
398405 pathToPackageJson : pathToPackageJson ?? globalPathToPackageJson ,
399406 channelName : channelName ?? globalChannelName ,
400407 platform : globalPlatform ,
@@ -414,14 +421,30 @@ function markStepDone(step: number, pathToPackageJson?: string, channelName?: st
414421 }
415422}
416423
417- async function readStepsDone ( orgId : string , apikey : string ) : Promise < number | undefined > {
424+ interface ResumeResult {
425+ stepDone : number
426+ orgId : string
427+ orgName : string
428+ appId ?: string
429+ }
430+
431+ async function tryResumeOnboarding ( apikey : string ) : Promise < ResumeResult | undefined > {
418432 try {
419433 const rawData = readFileSync ( getTmpObjectPath ( ) , 'utf-8' )
420434 if ( ! rawData || rawData . length === 0 )
421435 return undefined
422436
423- const { step_done, pathToPackageJson, channelName, platform, delta, currentVersion } = JSON . parse ( rawData )
437+ const { step_done, orgId, orgName, appId : savedAppId , pathToPackageJson, channelName, platform, delta, currentVersion } = JSON . parse ( rawData )
438+ if ( ! orgId || ! step_done ) {
439+ pLog . warn ( '⚠️ Found previous onboarding progress, but it was saved in an older format.' )
440+ pLog . info ( ' Starting fresh. Your previous progress cannot be resumed.' )
441+ return undefined
442+ }
443+
424444 pLog . info ( formatInitResumeMessage ( step_done , initOnboardingSteps . length ) )
445+ if ( orgName ) {
446+ pLog . info ( ` Organization: ${ orgName } ` )
447+ }
425448 const resumeChoice = await pSelect ( {
426449 message : 'Would you like to continue from where you left off?' ,
427450 options : [
@@ -446,9 +469,14 @@ async function readStepsDone(orgId: string, apikey: string): Promise<number | un
446469 if ( typeof currentVersion === 'string' && currentVersion . length > 0 ) {
447470 globalCurrentVersion = currentVersion
448471 }
449- return step_done
472+ if ( savedAppId ) {
473+ globalAppId = savedAppId
474+ }
475+ return { stepDone : step_done , orgId, orgName, appId : savedAppId }
450476 }
451477
478+ // User chose to start over — delete the saved progress
479+ cleanupStepsDone ( )
452480 return undefined
453481 }
454482 catch ( err ) {
@@ -1201,33 +1229,21 @@ async function addChannelStep(orgId: string, apikey: string, appId: string) {
12011229 }
12021230
12031231 globalChannelName = channelName
1204- const doChannel = await pConfirm ( { message : `Create channel ${ channelName } for ${ appId } in Capgo?` } )
1205- await cancelCommand ( doChannel , orgId , apikey )
1206- if ( doChannel ) {
1207- const s = pSpinner ( )
1208- // create production channel public
1209- s . start ( `Running: ${ pm . runner } @capgo/cli@latest channel add ${ channelName } ${ appId } --default` )
1210- try {
1211- const addChannelRes = await addChannelInternal ( channelName , appId , {
1212- default : true ,
1213- apikey,
1214- } , true )
1215- if ( ! addChannelRes )
1216- s . stop ( `Channel already added ✅` )
1217- else
1218- s . stop ( `Channel add Done ✅` )
1219- }
1220- catch ( error ) {
1221- s . stop ( `Channel creation failed ❌` )
1222- throw error
1223- }
1232+ const s = pSpinner ( )
1233+ s . start ( `Running: ${ pm . runner } @capgo/cli@latest channel add ${ channelName } ${ appId } --default` )
1234+ try {
1235+ const addChannelRes = await addChannelInternal ( channelName , appId , {
1236+ default : true ,
1237+ apikey,
1238+ } , true )
1239+ if ( ! addChannelRes )
1240+ s . stop ( `Channel already added ✅` )
1241+ else
1242+ s . stop ( `Channel add done ✅` )
12241243 }
1225- else {
1226- pLog . info ( `If you change your mind, run it for yourself with: "${ pm . runner } @capgo/cli@latest channel add ${ channelName } ${ appId } --default"` )
1227- pLog . info ( `Alternatively, you can:` )
1228- pLog . info ( ` • Set the channel in your capacitor.config.ts file` )
1229- pLog . info ( ` • Use the JavaScript setChannel() method to dynamically set the channel` )
1230- pLog . info ( ` • Configure channels later from the Capgo web console` )
1244+ catch ( error ) {
1245+ s . stop ( `Channel creation failed ❌` )
1246+ throw error
12311247 }
12321248 await markStep ( orgId , apikey , 'add-channel' , appId )
12331249 return channelName
@@ -2318,13 +2334,65 @@ export async function initApp(apikeyCommand: string, appId: string, options: Sup
23182334 const supabase = await createSupabaseClient ( options . apikey , options . supaHost , options . supaAnon )
23192335 await verifyUser ( supabase , options . apikey , [ 'upload' , 'all' , 'read' , 'write' ] )
23202336
2321- const organization = await selectOrganizationForInit ( supabase , [ 'admin' , 'super_admin' ] )
2337+ // Try to resume from saved state before asking for org selection
2338+ const resumed = await tryResumeOnboarding ( options . apikey )
2339+ let stepToSkip = resumed ?. stepDone ?? 0
2340+
2341+ let organization : Organization
2342+ if ( resumed ) {
2343+ // Fetch orgs to validate the saved one still exists and is accessible
2344+ const { error : orgError , data : allOrganizations } = await supabase . rpc ( 'get_orgs_v7' )
2345+ if ( orgError || ! allOrganizations ) {
2346+ pLog . error ( `Cannot verify organization access: ${ orgError ? JSON . stringify ( orgError ) : 'no data returned' } ` )
2347+ pLog . warn ( 'Falling back to organization selection.' )
2348+ organization = await selectOrganizationForInit ( supabase , [ 'admin' , 'super_admin' ] )
2349+ stepToSkip = 0
2350+ }
2351+ else {
2352+ const savedOrg = allOrganizations . find ( org => org . gid === resumed . orgId )
2353+ const normalizeRole = ( role : string | null | undefined ) => role ?. replace ( / ^ o r g _ / , '' ) ?? ''
2354+ const hasRequiredRole = savedOrg && [ 'admin' , 'super_admin' ] . includes ( normalizeRole ( savedOrg . role ) )
2355+ const blocked2fa = savedOrg ?. enforcing_2fa && ! savedOrg [ '2fa_has_access' ]
2356+
2357+ if ( ! savedOrg ) {
2358+ pLog . warn ( `Previously used organization "${ resumed . orgName } " is no longer available. Please select a new one.` )
2359+ organization = await selectOrganizationForInit ( supabase , [ 'admin' , 'super_admin' ] )
2360+ stepToSkip = 0
2361+ }
2362+ else if ( ! hasRequiredRole ) {
2363+ pLog . warn ( `You no longer have admin access to "${ savedOrg . name } ". Please select a different organization.` )
2364+ organization = await selectOrganizationForInit ( supabase , [ 'admin' , 'super_admin' ] )
2365+ stepToSkip = 0
2366+ }
2367+ else if ( blocked2fa ) {
2368+ pLog . warn ( `Organization "${ savedOrg . name } " now requires 2FA. Enable it at https://web.capgo.app/settings/account` )
2369+ pLog . warn ( 'Please select a different organization or enable 2FA and try again.' )
2370+ organization = await selectOrganizationForInit ( supabase , [ 'admin' , 'super_admin' ] )
2371+ stepToSkip = 0
2372+ }
2373+ else {
2374+ organization = savedOrg
2375+ pLog . info ( `Using organization "${ savedOrg . name } "` )
2376+ }
2377+ }
2378+ }
2379+ else {
2380+ organization = await selectOrganizationForInit ( supabase , [ 'admin' , 'super_admin' ] )
2381+ }
2382+
23222383 const orgId = organization . gid
2384+ globalOrgId = orgId
2385+ globalOrgName = organization . name
2386+
2387+ if ( resumed ?. appId ) {
2388+ appId = resumed . appId
2389+ globalAppId = appId
2390+ }
2391+
23232392 const pendingOnboardingSelection = await maybeReusePendingOnboardingApp ( organization , options . apikey , appId , supabase )
23242393 appId = pendingOnboardingSelection . appId ?? appId
23252394 await ensureCapacitorProjectReady ( orgId , options . apikey , appId , pendingOnboardingSelection . pendingApp )
23262395
2327- let stepToSkip = await readStepsDone ( orgId , options . apikey ) ?? 0
23282396 if ( pendingOnboardingSelection . reusedPendingApp ) {
23292397 stepToSkip = Math . max ( stepToSkip , 1 )
23302398 }
@@ -2351,6 +2419,7 @@ export async function initApp(apikeyCommand: string, appId: string, options: Sup
23512419 renderCurrentStep ( 1 )
23522420 await checkPrerequisitesStep ( orgId , options . apikey )
23532421 appId = await addAppStep ( organization , options . apikey , appId , options )
2422+ globalAppId = appId
23542423 markStepDone ( 1 )
23552424 }
23562425
0 commit comments