Skip to content

Commit

Permalink
fix: handle a corrupt stash.json file (#291)
Browse files Browse the repository at this point in the history
* fix: handle a corrupt stash.json file

* fix: display both error messages and stacks
  • Loading branch information
shetzel authored Nov 11, 2021
1 parent fa87e12 commit ac15a24
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 4 deletions.
1 change: 1 addition & 0 deletions messages/deploy.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
"MissingRequiredParam": "Missing one of the following parameters: %s",
"checkOnlySuccess": "Successfully validated the deployment. %s components deployed and %s tests run.\nUse the --verbose parameter to see detailed output.",
"MissingDeployId": "No deploy ID was provided or found in deploy history",
"InvalidStashFile": "Invalid deployment stash file encountered. File has been renamed to: %s. Please specify a deploy ID using the command parameter.",
"deployCanceled": "The deployment has been canceled by %s",
"deployFailed": "Deploy failed.",
"asyncDeployQueued": "Deploy has been queued.",
Expand Down
22 changes: 20 additions & 2 deletions src/deployCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,18 @@
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/

import * as fs from 'fs';
import { ComponentSet, DeployResult, MetadataApiDeployStatus } from '@salesforce/source-deploy-retrieve';
import { ConfigAggregator, ConfigFile, PollingClient, SfdxError, StatusResult } from '@salesforce/core';
import { AnyJson, asString, getBoolean } from '@salesforce/ts-types';
import { Duration, once } from '@salesforce/kit';
import { SourceCommand } from './sourceCommand';

interface StashFile {
isGlobal: boolean;
filename: string;
}

export abstract class DeployCommand extends SourceCommand {
protected static readonly STASH_KEY = 'SOURCE_DEPLOY';

Expand Down Expand Up @@ -45,17 +51,29 @@ export abstract class DeployCommand extends SourceCommand {
}

protected resolveDeployId(id: string): string {
let stash: ConfigFile<StashFile>;
if (id) {
return id;
} else {
try {
const stash = this.getStash();
stash = this.getStash();
stash.readSync(true);
const deployId = asString((stash.get(DeployCommand.STASH_KEY) as { jobid: string }).jobid);
this.logger.debug(`Using deploy ID: ${deployId} from ${stash.getPath()}`);
return deployId;
} catch (err: unknown) {
const error = err as Error & { code: string };
if (error.name === 'JsonParseError') {
const stashFilePath = stash.getPath();
const corruptFilePath = `${stashFilePath}_corrupted_${Date.now()}`;
fs.renameSync(stashFilePath, corruptFilePath);
const invalidStashErr = SfdxError.create('@salesforce/plugin-source', 'deploy', 'InvalidStashFile', [
corruptFilePath,
]);
invalidStashErr.message = `${invalidStashErr.message}\n${error.message}`;
invalidStashErr.stack = `${invalidStashErr.stack}\nDue to:\n${error.stack}`;
throw invalidStashErr;
}
if (error.code === 'ENOENT') {
throw SfdxError.create('@salesforce/plugin-source', 'deploy', 'MissingDeployId');
}
Expand Down Expand Up @@ -106,7 +124,7 @@ export abstract class DeployCommand extends SourceCommand {
return pollingClient.subscribe() as unknown as Promise<DeployResult>;
}

private getStash(): ConfigFile<{ isGlobal: true; filename: 'stash.json' }> {
private getStash(): ConfigFile<StashFile> {
return new ConfigFile({ isGlobal: true, filename: 'stash.json' });
}
}
Expand Down
24 changes: 22 additions & 2 deletions test/commands/source/report.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
*/

import { join } from 'path';
import * as fs from 'fs';
import * as sinon from 'sinon';
import { expect } from 'chai';
import { assert, expect } from 'chai';
import { fromStub, spyMethod, stubInterface, stubMethod } from '@salesforce/ts-sinon';
import { ConfigFile, Org, SfdxProject } from '@salesforce/core';
import { IConfig } from '@oclif/config';
Expand Down Expand Up @@ -37,6 +38,7 @@ describe('force:source:report', () => {
let checkDeployStatusStub: sinon.SinonStub;
let uxLogStub: sinon.SinonStub;
let pollStatusStub: sinon.SinonStub;
let readSyncStub: sinon.SinonStub;

class TestReport extends Report {
public async runIt() {
Expand Down Expand Up @@ -80,13 +82,16 @@ describe('force:source:report', () => {
cmd.setOrg(orgStub);
});
uxLogStub = stubMethod(sandbox, UX.prototype, 'log');
stubMethod(sandbox, ConfigFile.prototype, 'readSync');
stubMethod(sandbox, ConfigFile.prototype, 'get').returns({ jobid: stashedDeployId });
checkDeployStatusStub = sandbox.stub().resolves(expectedResults);

return cmd.runIt();
};

beforeEach(() => {
readSyncStub = stubMethod(sandbox, ConfigFile.prototype, 'readSync');
});

afterEach(() => {
sandbox.restore();
});
Expand All @@ -107,6 +112,21 @@ describe('force:source:report', () => {
expect(progressBarStub.calledOnce).to.equal(true);
});

it('should rename corrupt stash.json and throw an error', async () => {
const jsonParseError = new Error();
jsonParseError.name = 'JsonParseError';
readSyncStub.throws(jsonParseError);
const renameSyncStub = stubMethod(sandbox, fs, 'renameSync');
try {
await runReportCmd(['--json']);
assert(false, 'Expected report command to throw a JsonParseError');
} catch (error: unknown) {
const err = error as Error;
expect(err.name).to.equal('InvalidStashFile');
expect(renameSyncStub.calledOnce).to.be.true;
}
});

it('should use the jobid flag', async () => {
const getStashSpy = spyMethod(sandbox, Report.prototype, 'getStash');
const result = await runReportCmd(['--json', '--jobid', expectedResults.id]);
Expand Down

0 comments on commit ac15a24

Please sign in to comment.