Skip to content

Commit 78850f5

Browse files
committed
fix: cache everything as a manifest
1 parent 2f4f11c commit 78850f5

File tree

6 files changed

+77
-15
lines changed

6 files changed

+77
-15
lines changed

messages/deploy.metadata.cancel.md

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,10 @@ Job ID of the deploy operation you want to cancel.
2626

2727
These commands return a job ID if they time out or you specified the --async flag:
2828

29-
- sf project deploy start
30-
- sf project deploy validate
31-
- sf project deploy quick
32-
- sf project deploy cancel
29+
- <%= config.bin %> project deploy start
30+
- <%= config.bin %> project deploy validate
31+
- <%= config.bin %> project deploy quick
32+
- <%= config.bin %> project deploy cancel
3333

3434
The job ID is valid for 10 days from when you started the deploy operation.
3535

@@ -47,16 +47,20 @@ Number of minutes to wait for the command to complete and display results.
4747

4848
# flags.wait.description
4949

50-
If the command continues to run after the wait period, the CLI returns control of the terminal window to you. To resume watching the cancellation, run "sf project deploy resume". To check the status of the cancellation, run "sf project deploy report".
50+
If the command continues to run after the wait period, the CLI returns control of the terminal window to you. To resume watching the cancellation, run "sf project deploy resume". To check the status of the cancellation, run "<%= config.bin %> project deploy report".
5151

5252
# flags.async.summary
5353

5454
Run the command asynchronously.
5555

5656
# flags.async.description
5757

58-
The command immediately returns the control of the terminal to you. This way, you can continue to use the CLI. To resume watching the cancellation, run "sf project deploy resume". To check the status of the cancellation, run "sf project deploy report".
58+
The command immediately returns the control of the terminal to you. This way, you can continue to use the CLI. To resume watching the cancellation, run "sf project deploy resume". To check the status of the cancellation, run "<%= config.bin %> project deploy report".
5959

6060
# error.CannotCancelDeploy
6161

6262
Can't cancel deploy because it's already completed.
63+
64+
# error.CannotCancelDeployPre
65+
66+
Can't cancel deploy with Job Id %s because it's already completed (status is %s)

src/commands/project/deploy/cancel.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import { Messages } from '@salesforce/core';
99
import { Duration } from '@salesforce/kit';
1010
import { SfCommand, Flags } from '@salesforce/sf-plugins-core';
11+
import { RequestStatus } from '@salesforce/source-deploy-retrieve';
1112
import { cancelDeploy, cancelDeployAsync } from '../../../utils/deploy';
1213
import { DeployCache } from '../../../utils/deployCache';
1314
import { AsyncDeployCancelResultFormatter, DeployCancelResultFormatter } from '../../../utils/output';
@@ -62,6 +63,15 @@ export default class DeployMetadataCancel extends SfCommand<DeployResultJson> {
6263
const jobId = cache.resolveLatest(flags['use-most-recent'], flags['job-id']);
6364

6465
const deployOpts = cache.get(jobId);
66+
// we may already know the job finished
67+
if (
68+
deployOpts.status &&
69+
[RequestStatus.Canceled, RequestStatus.Failed, RequestStatus.Succeeded, RequestStatus.SucceededPartial].includes(
70+
deployOpts.status
71+
)
72+
) {
73+
messages.createError('error.CannotCancelDeployPre', [jobId, deployOpts.status]);
74+
}
6575

6676
if (flags.async) {
6777
const asyncResult = await cancelDeployAsync({ 'target-org': deployOpts['target-org'] }, jobId);

src/commands/project/deploy/quick.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,6 @@ export default class DeployMetadataQuick extends SfCommand<DeployResultJson> {
9191

9292
await org.getConnection(flags['api-version']).deployRecentValidation({ id: jobId, rest: api === 'REST' });
9393
const componentSet = await buildComponentSet({ ...deployOpts, wait: flags.wait });
94-
9594
this.log(getVersionMessage('Deploying', componentSet, api));
9695
this.log(`Deploy ID: ${bold(jobId)}`);
9796

src/utils/deploy.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import { getPackageDirs, getSourceApiVersion } from './project';
2222
import { API, PathInfo, TestLevel } from './types';
2323
import { DEPLOY_STATUS_CODES } from './errorCodes';
2424
import { DeployCache } from './deployCache';
25+
import { writeManifest } from './manifestCache';
26+
2527
Messages.importMessagesDirectory(__dirname);
2628
export const cacheMessages = Messages.load('@salesforce/plugin-deploy-retrieve', 'cache', [
2729
'error.NoRecentJobId',
@@ -55,7 +57,10 @@ export type DeployOptions = {
5557
status?: RequestStatus;
5658
};
5759

58-
export type CachedOptions = Omit<DeployOptions, 'wait'> & { wait: number };
60+
/** Manifest is expected. You cannot pass metadata and source-dir array--use those to get a manifest */
61+
export type CachedOptions = Omit<DeployOptions, 'wait' | 'metadata' | 'source-dir'> & {
62+
wait: number;
63+
} & Partial<Pick<DeployOptions, 'manifest'>>;
5964

6065
export function validateTests(testLevel: TestLevel, tests: Nullable<string[]>): boolean {
6166
if (testLevel === TestLevel.RunSpecifiedTests && (tests ?? []).length === 0) return false;
@@ -155,10 +160,11 @@ export async function executeDeploy(
155160
usernameOrConnection,
156161
apiOptions,
157162
});
158-
await DeployCache.set(deploy.id, { ...opts, wait: opts.wait?.minutes ?? 33 });
159163
}
160164

161-
await DeployCache.set(deploy.id, { ...opts, wait: opts.wait?.minutes ?? 33 });
165+
// does not apply to mdapi deploys
166+
const manifestPath = componentSet ? await writeManifest(deploy.id, componentSet) : undefined;
167+
await DeployCache.set(deploy.id, { ...opts, manifest: manifestPath });
162168

163169
return { deploy, componentSet };
164170
}
@@ -170,7 +176,7 @@ export async function cancelDeploy(opts: Partial<DeployOptions>, id: string): Pr
170176
const deploy = new MetadataApiDeploy({ usernameOrConnection, id });
171177

172178
const componentSet = await buildComponentSet({ ...opts });
173-
await DeployCache.set(deploy.id, { ...opts, wait: opts.wait?.minutes ?? 33 });
179+
await DeployCache.set(deploy.id, { ...opts });
174180

175181
await deploy.cancel();
176182
return poll(org, deploy.id, opts.wait ?? Duration.minutes(33), componentSet);

src/utils/deployCache.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
import { Global, TTLConfig } from '@salesforce/core';
88
import { Duration } from '@salesforce/kit';
99
import { JsonMap } from '@salesforce/ts-types';
10-
import { CachedOptions, cacheMessages } from './deploy';
10+
import { DeployOptions, CachedOptions, cacheMessages } from './deploy';
11+
import { maybeDestroyManifest } from './manifestCache';
1112

1213
export class DeployCache extends TTLConfig<TTLConfig.Options, CachedOptions> {
1314
public static getFileName(): string {
@@ -24,16 +25,24 @@ export class DeployCache extends TTLConfig<TTLConfig.Options, CachedOptions> {
2425
};
2526
}
2627

27-
public static async set(key: string, value: Partial<CachedOptions>): Promise<void> {
28+
/**
29+
*
30+
* @param key jobId
31+
* @param value a DeployOptions object (wait is a duration, can use non-manifest options)
32+
* @param manifestFilePath the path to the manifest file generated by the deploy
33+
*/
34+
public static async set(key: string, value: Partial<DeployOptions>, manifestFilePath?: string): Promise<void> {
2835
const cache = await DeployCache.create();
29-
cache.set(key, value);
36+
const { manifest, 'metadata-dir': mdDir, wait, 'source-dir': sourceDir, ...cleanValue } = value;
37+
const modifiedValue = { ...cleanValue, manifest: manifest ?? manifestFilePath, wait: wait?.minutes ?? 33 };
38+
cache.set(key, modifiedValue);
3039
await cache.write();
3140
}
3241

3342
public static async unset(key: string): Promise<void> {
3443
const cache = await DeployCache.create();
3544
cache.unset(key);
36-
await cache.write();
45+
await Promise.all([cache.write(), maybeDestroyManifest(key)]);
3746
}
3847

3948
public static async update(key: string, obj: JsonMap): Promise<void> {

src/utils/manifestCache.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright (c) 2020, salesforce.com, inc.
3+
* All rights reserved.
4+
* Licensed under the BSD 3-Clause license.
5+
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
6+
*/
7+
import * as path from 'path';
8+
import { homedir } from 'os';
9+
import * as fs from 'fs';
10+
import { ComponentSet } from '@salesforce/source-deploy-retrieve';
11+
import { Global } from '@salesforce/core';
12+
13+
const MANIFEST_CACHE_DIR = 'manifestCache';
14+
15+
/** Give it a jobId, ComponentSet it will write the manifest file
16+
* returns the file path it wrote to */
17+
export const writeManifest = async (jobId: string, componentSet: ComponentSet): Promise<string> => {
18+
const xml = await componentSet.getPackageXml();
19+
const filePath = getManifestFilePath(jobId);
20+
await fs.promises.mkdir(path.dirname(filePath), { recursive: true });
21+
await fs.promises.writeFile(filePath, xml);
22+
return filePath;
23+
};
24+
25+
export const maybeDestroyManifest = async (jobId: string): Promise<void> => {
26+
try {
27+
return await fs.promises.rm(getManifestFilePath(jobId));
28+
} catch (e) {
29+
// that's ok in a maybe
30+
}
31+
};
32+
33+
const getManifestFilePath = (jobId: string): string =>
34+
path.join(homedir(), Global.SF_STATE_FOLDER, MANIFEST_CACHE_DIR, `${jobId}.xml`);

0 commit comments

Comments
 (0)