diff --git a/LICENSE.txt b/LICENSE.txt index d1c9164c78..f2cee7bb61 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,4 +1,4 @@ -Copyright (c) 2022, Salesforce.com, Inc. +Copyright (c) 2023, Salesforce.com, Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/src/org/org.ts b/src/org/org.ts index 5668c607db..6b19873532 100644 --- a/src/org/org.ts +++ b/src/org/org.ts @@ -388,7 +388,7 @@ export class Org extends AsyncOptionalCreatable { } /** - * Cleans up all org related artifacts including users, sandbox config(if a sandbox and auth file. + * Cleans up all org related artifacts including users, sandbox config (if a sandbox), source tracking files, and auth file. * * @param throwWhenRemoveFails Determines if the call should throw an error or fail silently. */ @@ -406,6 +406,7 @@ export class Org extends AsyncOptionalCreatable { // // So, just in case no users are added to this org we will try the remove again. await this.removeAuth(); + await this.removeSourceTrackingFiles(); } /** @@ -1455,6 +1456,25 @@ export class Org extends AsyncOptionalCreatable { pollInterval = pollInterval.seconds > wait.seconds ? wait : pollInterval; return [wait, pollInterval]; } + + /** + * removes source tracking files hosted in the project/.sf/orgs// + * + * @private + */ + private async removeSourceTrackingFiles(): Promise { + try { + const rootFolder = await Config.resolveRootFolder(false); + await fs.promises.rm(pathJoin(rootFolder, Global.SF_STATE_FOLDER, 'orgs', this.getOrgId()), { + recursive: true, + force: true, + }); + } catch (e) { + const err = SfError.wrap(e as string | Error); + // consume the error in case something went wrong + this.logger.debug(`error deleting source tracking information for ${this.getOrgId()} error: ${err.message}`); + } + } } export namespace Org { diff --git a/test/unit/org/orgTest.ts b/test/unit/org/orgTest.ts index 1b33de00d0..7ae7ed0f18 100644 --- a/test/unit/org/orgTest.ts +++ b/test/unit/org/orgTest.ts @@ -260,6 +260,54 @@ describe('Org Tests', () => { expect(removeSpy.calledOnce).to.be.true; }); + it('should delete the source tracking files', async () => { + const dev = await createOrgViaAuthInfo(testData.username); + const orgTestData = new MockTestOrgData(); + const org = await createOrgViaAuthInfo(orgTestData.username, orgTestData); + const fsSpy = $$.SANDBOX.stub(fs.promises, 'rm'); + + const devHubQuery = stubMethod($$.SANDBOX, Connection.prototype, 'singleRecordQuery').resolves({ + Id: orgTestData.orgId, + }); + const devHubDelete = stubMethod($$.SANDBOX, Org.prototype, 'destroyScratchOrg').resolves(); + + await org.deleteFrom(dev); + + expect(devHubQuery.calledOnce).to.be.true; + expect(devHubQuery.firstCall.args[0]).to.equal( + `SELECT Id FROM ActiveScratchOrg WHERE SignupUsername='${orgTestData.username}'` + ); + + expect(devHubDelete.calledOnce).to.be.true; + expect(devHubDelete.firstCall.args[1]).to.equal(orgTestData.orgId); + expect(fsSpy.callCount).to.equal(1); + expect(fsSpy.firstCall.args[0]).to.include(pathJoin('orgs', org.getOrgId())); + }); + + it('should not throw when attempting to delete the source tracking files', async () => { + const dev = await createOrgViaAuthInfo(testData.username); + const orgTestData = new MockTestOrgData(); + const org = await createOrgViaAuthInfo(orgTestData.username, orgTestData); + const fsSpy = $$.SANDBOX.stub(fs.promises, 'rm').throws('error'); + + const devHubQuery = stubMethod($$.SANDBOX, Connection.prototype, 'singleRecordQuery').resolves({ + Id: orgTestData.orgId, + }); + const devHubDelete = stubMethod($$.SANDBOX, Org.prototype, 'destroyScratchOrg').resolves(); + + await org.deleteFrom(dev); + + expect(devHubQuery.calledOnce).to.be.true; + expect(devHubQuery.firstCall.args[0]).to.equal( + `SELECT Id FROM ActiveScratchOrg WHERE SignupUsername='${orgTestData.username}'` + ); + + expect(devHubDelete.calledOnce).to.be.true; + expect(devHubDelete.firstCall.args[1]).to.equal(orgTestData.orgId); + expect(fsSpy.callCount).to.equal(1); + expect(fsSpy.firstCall.args[0]).to.include(pathJoin('orgs', org.getOrgId())); + }); + it('should handle INVALID_TYPE or INSUFFICIENT_ACCESS_OR_READONLY errors', async () => { const dev = await createOrgViaAuthInfo();