55 * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
66 */
77
8- import { AuthRemover } from '@salesforce/core' ;
8+ import { AuthRemover , Global } from '@salesforce/core' ;
99import { ExtensionProviderService , sfProjectPreconditionChecker } from '@salesforce/effect-ext-utils' ;
1010import { Command , SfCommandBuilder } from '@salesforce/salesforcedx-utils' ;
1111import {
@@ -19,8 +19,9 @@ import {
1919 workspaceUtils
2020} from '@salesforce/salesforcedx-utils-vscode' ;
2121import * as Effect from 'effect/Effect' ;
22+ import * as path from 'node:path' ;
2223import * as vscode from 'vscode' ;
23- import { OUTPUT_CHANNEL } from '../channels' ;
24+ import { channelService , OUTPUT_CHANNEL } from '../channels' ;
2425import { getOrgRuntime } from '../extensionProvider' ;
2526import { nls } from '../messages' ;
2627import { PromptConfirmGatherer } from '../parameterGatherers/promptConfirmGatherer' ;
@@ -55,6 +56,15 @@ const unsetTargetDevHubIfMatch = Effect.fn('OrgDelete.unsetTargetDevHubIfMatch')
5556 }
5657) ;
5758
59+ /** Checks if the auth file exists in .sfdx or .sf; sf org delete may already remove it (idempotent). */
60+ const authFileExists = Effect . fn ( 'OrgDelete.authFileExists' ) ( function * ( username : string ) {
61+ const api = yield * ( yield * ExtensionProviderService ) . getServicesApi ;
62+ const authFileName = `${ username } .json` ;
63+ const inSfdx = yield * api . services . FsService . fileOrFolderExists ( path . join ( Global . SFDX_DIR , authFileName ) ) ;
64+ if ( inSfdx ) return true ;
65+ return yield * api . services . FsService . fileOrFolderExists ( path . join ( Global . SF_DIR , authFileName ) ) ;
66+ } ) ;
67+
5868/** Runs sf org:delete:scratch or org:delete:sandbox for a single org and resolves when done. */
5969const runDeleteCli = ( username : string , orgType : 'scratch' | 'sandbox' ) : Promise < boolean > => {
6070 const deleteArg = orgType === 'sandbox' ? 'org:delete:sandbox' : 'org:delete:scratch' ;
@@ -73,6 +83,8 @@ const runDeleteCli = (username: string, orgType: 'scratch' | 'sandbox'): Promise
7383 env : { SF_JSON_TO_STDOUT : 'true' }
7484 } ) . execute ( cancellationTokenSource . token ) ;
7585
86+ channelService . streamCommandOutput ( execution ) ;
87+
7688 execution . processExitSubject . subscribe ( data => {
7789 // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
7890 const exitCode = Array . isArray ( data ) ? data [ 0 ] : data ;
@@ -96,26 +108,39 @@ class OrgDeleteExecutor extends LibraryCommandletExecutor<{ orgs: OrgToDelete[]
96108 const aliases = await getOrgRuntime ( ) . runPromise ( getAliasesForUsername ( username ) ) ;
97109 const success = await runDeleteCli ( username , orgType ) ;
98110 if ( success ) {
99- await authRemover . removeAuth ( username ) ;
111+ // sf org delete scratch may already remove the auth file; only call removeAuth if file exists (idempotent)
112+ const authFileExistsResult = await getOrgRuntime ( ) . runPromise ( authFileExists ( username ) ) ;
113+ if ( authFileExistsResult ) {
114+ await authRemover . removeAuth ( username ) ;
115+ }
100116 await getOrgRuntime ( ) . runPromise ( removeOrgAliases ( username ) ) ;
101117 await getOrgRuntime ( ) . runPromise ( unsetTargetOrgIfMatch ( username , aliases ) ) ;
102118 await getOrgRuntime ( ) . runPromise ( unsetTargetDevHubIfMatch ( username , aliases ) ) ;
103119 } else {
104120 allSucceeded = false ;
121+ channelService . appendLine (
122+ nls . localize ( 'org_delete_failed_for_org' , username , orgType === 'scratch' ? 'scratch org' : 'sandbox' )
123+ ) ;
105124 }
106125 }
107126 await updateConfigAndStateAggregators ( ) ;
108127 return allSucceeded ;
109128 } catch ( e ) {
110129 const err = e instanceof Error ? e : new Error ( String ( e ) ) ;
111130 telemetryService . sendException ( 'org_delete_selected' , `Error: name = ${ err . name } message = ${ err . message } ` ) ;
131+ channelService . appendLine ( err . message ) ;
132+ channelService . showChannelOutput ( ) ;
112133 return false ;
113134 }
114135 }
115136}
116137
117138/** Executor for deleting the default org (no picker). */
118139class OrgDeleteDefaultExecutor extends SfCommandletExecutor < { } > {
140+ constructor ( ) {
141+ super ( OUTPUT_CHANNEL ) ;
142+ }
143+
119144 public build ( _data : { } ) : Command {
120145 return new SfCommandBuilder ( )
121146 . withDescription ( nls . localize ( 'org_delete_default_text' ) )
0 commit comments