From 89057b6305a23fe51d37fb3d5737d0c8b50158e3 Mon Sep 17 00:00:00 2001 From: Steve Hetzel Date: Tue, 19 Sep 2023 16:58:15 -0600 Subject: [PATCH 1/4] fix: adds target-org and wait support to deploy report command --- command-snapshot.json | 4 +- messages/deploy.metadata.quick.md | 2 +- src/commands/project/deploy/report.ts | 88 +++++++++++++++++---- test/commands/deploy/metadata/report.nut.ts | 47 ++++++----- 4 files changed, 105 insertions(+), 36 deletions(-) diff --git a/command-snapshot.json b/command-snapshot.json index becd4b6f..bb043608 100644 --- a/command-snapshot.json +++ b/command-snapshot.json @@ -101,9 +101,9 @@ { "command": "project:deploy:report", "plugin": "@salesforce/plugin-deploy-retrieve", - "flags": ["coverage-formatters", "job-id", "json", "junit", "results-dir", "use-most-recent"], + "flags": ["coverage-formatters", "job-id", "json", "junit", "results-dir", "target-org", "use-most-recent", "wait"], "alias": ["deploy:metadata:report"], - "flagChars": ["i", "r"], + "flagChars": ["i", "o", "r", "w"], "flagAliases": [] }, { diff --git a/messages/deploy.metadata.quick.md b/messages/deploy.metadata.quick.md index 23f7ca49..8767e89c 100644 --- a/messages/deploy.metadata.quick.md +++ b/messages/deploy.metadata.quick.md @@ -78,7 +78,7 @@ Overrides your default org. # error.CannotQuickDeploy -Job ID can't be used for quick deployment. Possible reasons include the deployment hasn't been validated or the validation expired because you ran it more than 10 days ago. +Job ID can't be used for quick deployment. Possible reasons include the deployment hasn't been validated, has already been deployed, or the validation expired because you ran it more than 10 days ago. # error.QuickDeployFailure diff --git a/src/commands/project/deploy/report.ts b/src/commands/project/deploy/report.ts index 008150d6..13e94079 100644 --- a/src/commands/project/deploy/report.ts +++ b/src/commands/project/deploy/report.ts @@ -5,11 +5,11 @@ * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -import { Messages, Org } from '@salesforce/core'; -import { Duration } from '@salesforce/kit'; +import { Messages, Org, SfProject } from '@salesforce/core'; import { SfCommand, Flags } from '@salesforce/sf-plugins-core'; -import { DeployResult, MetadataApiDeployStatus } from '@salesforce/source-deploy-retrieve'; -import { buildComponentSet } from '../../../utils/deploy'; +import { ComponentSet, DeployResult, MetadataApiDeploy } from '@salesforce/source-deploy-retrieve'; +import { buildComponentSet, DeployOptions } from '../../../utils/deploy'; +import { DeployProgress } from '../../../utils/progressBar'; import { DeployCache } from '../../../utils/deployCache'; import { DeployReportResultFormatter } from '../../../formatters/deployReportResultFormatter'; import { DeployResultJson } from '../../../utils/types'; @@ -17,6 +17,7 @@ import { coverageFormattersFlag } from '../../../utils/flags'; Messages.importMessagesDirectory(__dirname); const messages = Messages.loadMessages('@salesforce/plugin-deploy-retrieve', 'deploy.metadata.report'); +const deployMessages = Messages.loadMessages('@salesforce/plugin-deploy-retrieve', 'deploy.metadata'); const testFlags = 'Test'; export default class DeployMetadataReport extends SfCommand { @@ -27,6 +28,11 @@ export default class DeployMetadataReport extends SfCommand { public static readonly deprecateAliases = true; public static readonly flags = { + 'target-org': Flags.optionalOrg({ + char: 'o', + description: deployMessages.getMessage('flags.target-org.description'), + summary: deployMessages.getMessage('flags.target-org.summary'), + }), 'job-id': Flags.salesforceId({ char: 'i', startsWith: '0Af', @@ -51,23 +57,77 @@ export default class DeployMetadataReport extends SfCommand { summary: messages.getMessage('flags.results-dir.summary'), helpGroup: testFlags, }), + // we want to allow undefined for a simple check deploy status + // eslint-disable-next-line sf-plugin/flag-min-max-default + wait: Flags.duration({ + char: 'w', + summary: deployMessages.getMessage('flags.wait.summary'), + description: deployMessages.getMessage('flags.wait.description'), + unit: 'minutes', + helpValue: '', + min: 1, + }), }; public async run(): Promise { const [{ flags }, cache] = await Promise.all([this.parse(DeployMetadataReport), DeployCache.create()]); - const jobId = cache.resolveLatest(flags['use-most-recent'], flags['job-id']); + const jobId = cache.resolveLatest(flags['use-most-recent'], flags['job-id'], false); + + const deployOpts = cache.get(jobId) ?? ({} as DeployOptions & { isMdapi: boolean }); + const waitDuration = flags['wait']; + const org = flags['target-org'] ?? (await Org.create({ aliasOrUsername: deployOpts['target-org'] })); - const deployOpts = cache.get(jobId); - const org = await Org.create({ aliasOrUsername: deployOpts['target-org'] }); - const [deployStatus, componentSet] = await Promise.all([ - // we'll use whatever the org supports since we can't specify the org + // if we're using mdapi we won't have a component set + let componentSet = new ComponentSet(); + if (!deployOpts.isMdapi) { + if (!cache.get(jobId)) { + // If the cache file isn't there, use the project package directories for the CompSet + try { + this.project = await SfProject.resolve(); + const sourcepath = this.project.getUniquePackageDirectories().map((pDir) => pDir.fullPath); + componentSet = await buildComponentSet({ 'source-dir': sourcepath, wait: waitDuration }); + } catch (err) { + // ignore the error. this was just to get improved command output. + } + } else { + componentSet = await buildComponentSet({ ...deployOpts, wait: waitDuration }); + } + } + const mdapiDeploy = new MetadataApiDeploy({ + // setting an API version here won't matter since we're just checking deploy status // eslint-disable-next-line sf-plugin/get-connection-with-version - org.getConnection().metadata.checkDeployStatus(jobId, true), - // if we're using mdapi, we won't have a component set - deployOpts.isMdapi ? undefined : buildComponentSet({ ...deployOpts, wait: Duration.minutes(deployOpts.wait) }), - ]); + usernameOrConnection: org.getConnection(), + id: jobId, + components: componentSet, + apiOptions: { + rest: deployOpts.api === 'REST', + }, + }); + + const getDeployResult = async (): Promise => { + const deployStatus = await mdapiDeploy.checkStatus(); + return new DeployResult(deployStatus, componentSet); + }; - const result = new DeployResult(deployStatus as MetadataApiDeployStatus, componentSet); + let result: DeployResult; + if (waitDuration) { + // poll for deploy results + try { + new DeployProgress(mdapiDeploy, this.jsonEnabled()).start(); + result = await mdapiDeploy.pollStatus(500, waitDuration.seconds); + } catch (error) { + if (error instanceof Error && error.message.includes('The client has timed out')) { + this.debug('[project deploy report] polling timed out. Requesting status...'); + } else { + throw error; + } + } finally { + result = await getDeployResult(); + } + } else { + // check the deploy status + result = await getDeployResult(); + } const formatter = new DeployReportResultFormatter(result, { ...deployOpts, diff --git a/test/commands/deploy/metadata/report.nut.ts b/test/commands/deploy/metadata/report.nut.ts index 49bb382f..0072c006 100644 --- a/test/commands/deploy/metadata/report.nut.ts +++ b/test/commands/deploy/metadata/report.nut.ts @@ -12,13 +12,16 @@ import { assert, isObject } from '@salesforce/ts-types'; import { expect } from 'chai'; import { DeployResultJson } from '../../../../src/utils/types'; -describe('deploy metadata report NUTs with source-dir', () => { +describe('[project deploy report] NUTs with source-dir', () => { let testkit: SourceTestkit; + const orgAlias = 'reportTestOrg2'; + before(async () => { testkit = await SourceTestkit.create({ repository: 'https://github.com/salesforcecli/sample-project-multiple-packages.git', nut: __filename, + scratchOrgs: [{ duration: 1, alias: orgAlias, config: path.join('config', 'project-scratch-def.json') }], }); }); @@ -28,13 +31,13 @@ describe('deploy metadata report NUTs with source-dir', () => { describe('--use-most-recent', () => { it('should report most recently started deployment', async () => { - await testkit.execute('deploy:metadata', { + await testkit.execute('project deploy start', { args: '--source-dir force-app --async', json: true, exitCode: 0, }); - const deploy = await testkit.execute('deploy:metadata:report', { + const deploy = await testkit.execute('project deploy report', { args: '--use-most-recent', json: true, exitCode: 0, @@ -42,49 +45,55 @@ describe('deploy metadata report NUTs with source-dir', () => { assert(isObject(deploy)); await testkit.expect.filesToBeDeployedViaResult(['force-app/**/*'], ['force-app/test/**/*'], deploy.result.files); }); + }); - it.skip('should report most recently started deployment without specifying the flag', async () => { - await testkit.execute('deploy:metadata', { - args: '--source-dir force-app --async', + describe('--job-id', () => { + it('should report the provided job id', async () => { + const first = await testkit.execute('project deploy start', { + args: '--source-dir force-app --async --ignore-conflicts', json: true, exitCode: 0, }); - - const deploy = await testkit.execute('deploy:metadata:report', { + const deploy = await testkit.execute('project deploy report', { + args: `--job-id ${first?.result.id}`, json: true, exitCode: 0, }); assert(isObject(deploy)); await testkit.expect.filesToBeDeployedViaResult(['force-app/**/*'], ['force-app/test/**/*'], deploy.result.files); }); - }); - describe('--job-id', () => { - it('should report the provided job id', async () => { - const first = await testkit.execute('deploy:metadata', { - args: '--source-dir force-app --async --ignore-conflicts', + it('should report from specified target-org and job-id without deploy cache', async () => { + const first = await testkit.execute('project deploy start', { + args: `--source-dir force-app --async --target-org ${orgAlias}`, json: true, exitCode: 0, }); - const deploy = await testkit.execute('deploy:metadata:report', { - args: `--job-id ${first?.result.id}`, + + // delete the cache file so we can verify that reporting just with job-id and org works + const deployCacheFilePath = path.resolve(testkit.projectDir, path.join('..', '.sf', 'deploy-cache.json')); + fs.unlinkSync(deployCacheFilePath); + assert(!fs.existsSync(deployCacheFilePath)); + + const deploy = await testkit.execute('project deploy report', { + args: `--job-id ${first?.result.id} --target-org ${orgAlias} --wait 9`, json: true, exitCode: 0, }); assert(isObject(deploy)); - await testkit.expect.filesToBeDeployedViaResult(['force-app/**/*'], ['force-app/test/**/*'], deploy.result.files); + await testkit.expect.filesToBeDeployed(['force-app/**/*'], ['force-app/test/**/*']); }); }); describe('test flags', () => { it('should override the --output-dir', async () => { - const first = await testkit.execute('deploy:metadata', { + const first = await testkit.execute('project deploy start', { args: '--source-dir force-app --async --ignore-conflicts --test-level RunAllTestsInOrg --coverage-formatters html --junit --results-dir test-output', json: true, exitCode: 0, }); - const deploy = await testkit.execute('deploy:metadata:report', { - args: `--job-id ${first?.result.id} --coverage-formatters html --coverage-formatters text --junit --results-dir test-output-override`, + const deploy = await testkit.execute('project deploy report', { + args: `--job-id ${first?.result.id} --coverage-formatters html --coverage-formatters text --junit --results-dir test-output-override --wait 9`, json: true, exitCode: 0, }); From dd488a330b8eb0815ce6ab6f295f0c02bf8a76e4 Mon Sep 17 00:00:00 2001 From: Steve Hetzel Date: Tue, 26 Sep 2023 12:59:05 -0600 Subject: [PATCH 2/4] fix: better error reporting and more tests --- messages/deploy.metadata.md | 11 ++++- src/commands/project/deploy/report.ts | 15 +++++-- src/formatters/deployReportResultFormatter.ts | 10 ++++- src/formatters/deployResultFormatter.ts | 3 +- .../deploy/metadata/report-mdapi.nut.ts | 43 +++++++++++++------ test/commands/deploy/metadata/report.nut.ts | 26 +++++------ 6 files changed, 73 insertions(+), 35 deletions(-) diff --git a/messages/deploy.metadata.md b/messages/deploy.metadata.md index 8a15113f..40506839 100644 --- a/messages/deploy.metadata.md +++ b/messages/deploy.metadata.md @@ -78,7 +78,7 @@ Overrides your default org. # flags.metadata.summary -Metadata component names to deploy. Wildcards ( * ) supported as long as you use quotes, such as 'ApexClass:MyClass*' +Metadata component names to deploy. Wildcards ( _ ) supported as long as you use quotes, such as 'ApexClass:MyClass_' # flags.test-level.summary @@ -219,6 +219,15 @@ No local changes to deploy. - To see conflicts and ignored files, run "%s project deploy preview" with any of the manifest, directory, or metadata flags. +# error.InvalidDeployId + +Invalid deploy ID: %s for org: %s + +# error.InvalidDeployId.actions + +- Ensure the deploy ID is correct. +- Ensure the target-org username or alias is correct. + # flags.junit.summary Output JUnit test results. diff --git a/src/commands/project/deploy/report.ts b/src/commands/project/deploy/report.ts index 13e94079..043a5fda 100644 --- a/src/commands/project/deploy/report.ts +++ b/src/commands/project/deploy/report.ts @@ -8,7 +8,7 @@ import { Messages, Org, SfProject } from '@salesforce/core'; import { SfCommand, Flags } from '@salesforce/sf-plugins-core'; import { ComponentSet, DeployResult, MetadataApiDeploy } from '@salesforce/source-deploy-retrieve'; -import { buildComponentSet, DeployOptions } from '../../../utils/deploy'; +import { buildComponentSet } from '../../../utils/deploy'; import { DeployProgress } from '../../../utils/progressBar'; import { DeployCache } from '../../../utils/deployCache'; import { DeployReportResultFormatter } from '../../../formatters/deployReportResultFormatter'; @@ -73,7 +73,7 @@ export default class DeployMetadataReport extends SfCommand { const [{ flags }, cache] = await Promise.all([this.parse(DeployMetadataReport), DeployCache.create()]); const jobId = cache.resolveLatest(flags['use-most-recent'], flags['job-id'], false); - const deployOpts = cache.get(jobId) ?? ({} as DeployOptions & { isMdapi: boolean }); + const deployOpts = cache.get(jobId) ?? {}; const waitDuration = flags['wait']; const org = flags['target-org'] ?? (await Org.create({ aliasOrUsername: deployOpts['target-org'] })); @@ -105,8 +105,15 @@ export default class DeployMetadataReport extends SfCommand { }); const getDeployResult = async (): Promise => { - const deployStatus = await mdapiDeploy.checkStatus(); - return new DeployResult(deployStatus, componentSet); + try { + const deployStatus = await mdapiDeploy.checkStatus(); + return new DeployResult(deployStatus, componentSet); + } catch (error) { + if (error instanceof Error && error.name === 'sf:INVALID_CROSS_REFERENCE_KEY') { + throw deployMessages.createError('error.InvalidDeployId', [jobId, org.getUsername()]); + } + throw error; + } }; let result: DeployResult; diff --git a/src/formatters/deployReportResultFormatter.ts b/src/formatters/deployReportResultFormatter.ts index 421db1d0..2d338923 100644 --- a/src/formatters/deployReportResultFormatter.ts +++ b/src/formatters/deployReportResultFormatter.ts @@ -32,9 +32,15 @@ export class DeployReportResultFormatter extends DeployResultFormatter { ux.table(response, { key: {}, value: {} }, { title: tableHeader('Deploy Info'), 'no-truncate': true }); const opts = Object.entries(this.flags).reduce>((result, [key, value]) => { - if (key === 'timestamp') return result; - if (key === 'target-org') + if (key === 'timestamp') { + return result; + } + if (key === 'target-org') { return result.concat({ key: 'target-org', value: this.flags['target-org']?.getUsername() }); + } + if (key === 'wait') { + return result.concat({ key: 'wait', value: `${this.flags['wait']?.quantity} minutes` }); + } return result.concat({ key, value }); }, []); ux.log(); diff --git a/src/formatters/deployResultFormatter.ts b/src/formatters/deployResultFormatter.ts index 7abe6a3a..4393544f 100644 --- a/src/formatters/deployResultFormatter.ts +++ b/src/formatters/deployResultFormatter.ts @@ -10,7 +10,7 @@ import * as fs from 'fs'; import { ux } from '@oclif/core'; import { DeployResult, FileResponse, FileResponseFailure, RequestStatus } from '@salesforce/source-deploy-retrieve'; import { Org, SfError, Lifecycle } from '@salesforce/core'; -import { ensureArray } from '@salesforce/kit'; +import { Duration, ensureArray } from '@salesforce/kit'; import { CodeCoverageResult, CoverageReporter, @@ -45,6 +45,7 @@ export class DeployResultFormatter extends TestResultsFormatter implements Forma junit: boolean; 'results-dir': string; 'target-org': Org; + wait: Duration; }> ) { super(result, flags); diff --git a/test/commands/deploy/metadata/report-mdapi.nut.ts b/test/commands/deploy/metadata/report-mdapi.nut.ts index e47af141..8c0cbfff 100644 --- a/test/commands/deploy/metadata/report-mdapi.nut.ts +++ b/test/commands/deploy/metadata/report-mdapi.nut.ts @@ -5,20 +5,26 @@ * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ +import { unlinkSync, existsSync } from 'node:fs'; +import { join, resolve } from 'node:path'; import { SourceTestkit } from '@salesforce/source-testkit'; import { assert, expect } from 'chai'; +import { RequestStatus } from '@salesforce/source-deploy-retrieve'; import { DeployResultJson } from '../../../../src/utils/types'; -describe('deploy metadata report NUTs with source-dir', () => { +describe('[project deploy report] NUTs with metadata-dir', () => { let testkit: SourceTestkit; + const mdSourceDir = 'mdapiOut'; + const orgAlias = 'reportMdTestOrg2'; before(async () => { testkit = await SourceTestkit.create({ repository: 'https://github.com/salesforcecli/sample-project-multiple-packages.git', nut: __filename, + scratchOrgs: [{ duration: 1, alias: orgAlias, config: join('config', 'project-scratch-def.json') }], }); await testkit.convert({ - args: '--source-dir force-app --output-dir mdapiOut', + args: `--source-dir force-app --output-dir ${mdSourceDir}`, json: true, exitCode: 0, }); @@ -31,7 +37,7 @@ describe('deploy metadata report NUTs with source-dir', () => { describe('--use-most-recent', () => { it('should report most recently started deployment', async () => { await testkit.execute('project deploy start', { - args: '--metadata-dir mdapiOut --async', + args: `--metadata-dir ${mdSourceDir} --async`, json: true, exitCode: 0, }); @@ -42,40 +48,49 @@ describe('deploy metadata report NUTs with source-dir', () => { exitCode: 0, }); assert(deploy?.result); - expect(deploy.result.success).to.equal(true); + expect([RequestStatus.Pending, RequestStatus.Succeeded, RequestStatus.InProgress]).includes(deploy.result.status); }); + }); - it.skip('should report most recently started deployment without specifying the flag', async () => { - await testkit.execute('project deploy start', { - args: '--metadata-dir mdapiOut --async', + describe('--job-id', () => { + it('should report the provided job id', async () => { + const first = await testkit.execute('project deploy start', { + args: `--metadata-dir ${mdSourceDir} --async`, json: true, exitCode: 0, }); - const deploy = await testkit.execute('project deploy report', { + args: `--job-id ${first?.result.id}`, json: true, exitCode: 0, }); assert(deploy?.result); - expect(deploy.result.success).to.equal(true); + expect([RequestStatus.Pending, RequestStatus.Succeeded, RequestStatus.InProgress]).includes(deploy.result.status); + expect(deploy.result.id).to.equal(first?.result.id); }); - }); - describe('--job-id', () => { - it('should report the provided job id', async () => { + it('should report from specified target-org and job-id without deploy cache', async () => { const first = await testkit.execute('project deploy start', { - args: '--metadata-dir mdapiOut --async', + args: `--metadata-dir ${mdSourceDir} --async --target-org ${orgAlias}`, json: true, exitCode: 0, }); + + // delete the cache file so we can verify that reporting just with job-id and org works + const deployCacheFilePath = resolve(testkit.projectDir, join('..', '.sf', 'deploy-cache.json')); + unlinkSync(deployCacheFilePath); + assert(!existsSync(deployCacheFilePath)); + const deploy = await testkit.execute('project deploy report', { - args: `--job-id ${first?.result.id}`, + args: `--job-id ${first?.result.id} --target-org ${orgAlias} --wait 9`, json: true, exitCode: 0, }); assert(deploy?.result); expect(deploy.result.success).to.equal(true); + expect(deploy.result.status).to.equal(RequestStatus.Succeeded); expect(deploy.result.id).to.equal(first?.result.id); + await testkit.expect.filesToBeDeployed(['force-app/**/*'], ['force-app/test/**/*']); }); }); }); diff --git a/test/commands/deploy/metadata/report.nut.ts b/test/commands/deploy/metadata/report.nut.ts index 0072c006..f21f11a0 100644 --- a/test/commands/deploy/metadata/report.nut.ts +++ b/test/commands/deploy/metadata/report.nut.ts @@ -5,8 +5,8 @@ * 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 * as path from 'path'; +import { unlinkSync, existsSync } from 'node:fs'; +import { join, resolve } from 'node:path'; import { SourceTestkit } from '@salesforce/source-testkit'; import { assert, isObject } from '@salesforce/ts-types'; import { expect } from 'chai'; @@ -21,7 +21,7 @@ describe('[project deploy report] NUTs with source-dir', () => { testkit = await SourceTestkit.create({ repository: 'https://github.com/salesforcecli/sample-project-multiple-packages.git', nut: __filename, - scratchOrgs: [{ duration: 1, alias: orgAlias, config: path.join('config', 'project-scratch-def.json') }], + scratchOrgs: [{ duration: 1, alias: orgAlias, config: join('config', 'project-scratch-def.json') }], }); }); @@ -71,9 +71,9 @@ describe('[project deploy report] NUTs with source-dir', () => { }); // delete the cache file so we can verify that reporting just with job-id and org works - const deployCacheFilePath = path.resolve(testkit.projectDir, path.join('..', '.sf', 'deploy-cache.json')); - fs.unlinkSync(deployCacheFilePath); - assert(!fs.existsSync(deployCacheFilePath)); + const deployCacheFilePath = resolve(testkit.projectDir, join('..', '.sf', 'deploy-cache.json')); + unlinkSync(deployCacheFilePath); + assert(!existsSync(deployCacheFilePath)); const deploy = await testkit.execute('project deploy report', { args: `--job-id ${first?.result.id} --target-org ${orgAlias} --wait 9`, @@ -97,13 +97,13 @@ describe('[project deploy report] NUTs with source-dir', () => { json: true, exitCode: 0, }); - expect(fs.existsSync(path.join(testkit.projectDir, 'test-output-override'))).to.be.true; - expect(fs.existsSync(path.join(testkit.projectDir, 'test-output-override', 'coverage'))).to.be.true; - expect(fs.existsSync(path.join(testkit.projectDir, 'test-output-override', 'coverage', 'html'))).to.be.true; - expect(fs.existsSync(path.join(testkit.projectDir, 'test-output-override', 'coverage', 'text.txt'))).to.be.true; - expect(fs.existsSync(path.join(testkit.projectDir, 'test-output-override', 'junit'))).to.be.true; - expect(fs.existsSync(path.join(testkit.projectDir, 'test-output-override', 'junit', 'junit.xml'))).to.be.true; - expect(fs.existsSync(path.join(testkit.projectDir, 'test-output'))).to.be.false; + expect(existsSync(join(testkit.projectDir, 'test-output-override'))).to.be.true; + expect(existsSync(join(testkit.projectDir, 'test-output-override', 'coverage'))).to.be.true; + expect(existsSync(join(testkit.projectDir, 'test-output-override', 'coverage', 'html'))).to.be.true; + expect(existsSync(join(testkit.projectDir, 'test-output-override', 'coverage', 'text.txt'))).to.be.true; + expect(existsSync(join(testkit.projectDir, 'test-output-override', 'junit'))).to.be.true; + expect(existsSync(join(testkit.projectDir, 'test-output-override', 'junit', 'junit.xml'))).to.be.true; + expect(existsSync(join(testkit.projectDir, 'test-output'))).to.be.false; assert(isObject(deploy)); await testkit.expect.filesToBeDeployedViaResult(['force-app/**/*'], ['force-app/test/**/*'], deploy.result.files); }); From 7eed027391bdefb461ba4b36082d9ce1d24a4b10 Mon Sep 17 00:00:00 2001 From: Steve Hetzel Date: Tue, 26 Sep 2023 17:05:21 -0600 Subject: [PATCH 3/4] Update deploy.metadata.md --- messages/deploy.metadata.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/deploy.metadata.md b/messages/deploy.metadata.md index 40506839..435514a9 100644 --- a/messages/deploy.metadata.md +++ b/messages/deploy.metadata.md @@ -78,7 +78,7 @@ Overrides your default org. # flags.metadata.summary -Metadata component names to deploy. Wildcards ( _ ) supported as long as you use quotes, such as 'ApexClass:MyClass_' +Metadata component names to deploy. Wildcards ( * ) supported as long as you use quotes, such as 'ApexClass:MyClass*' # flags.test-level.summary From e9ca2fedc93ffe2da86b67507b6554e35ef05d3c Mon Sep 17 00:00:00 2001 From: Steve Hetzel Date: Tue, 26 Sep 2023 17:56:28 -0600 Subject: [PATCH 4/4] fix: fixed handling of wait in the formatter --- src/commands/project/deploy/report.ts | 10 +++++----- src/formatters/deployReportResultFormatter.ts | 6 ++++-- src/formatters/deployResultFormatter.ts | 2 +- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/commands/project/deploy/report.ts b/src/commands/project/deploy/report.ts index 043a5fda..85b23a3a 100644 --- a/src/commands/project/deploy/report.ts +++ b/src/commands/project/deploy/report.ts @@ -74,7 +74,7 @@ export default class DeployMetadataReport extends SfCommand { const jobId = cache.resolveLatest(flags['use-most-recent'], flags['job-id'], false); const deployOpts = cache.get(jobId) ?? {}; - const waitDuration = flags['wait']; + const wait = flags['wait']; const org = flags['target-org'] ?? (await Org.create({ aliasOrUsername: deployOpts['target-org'] })); // if we're using mdapi we won't have a component set @@ -85,12 +85,12 @@ export default class DeployMetadataReport extends SfCommand { try { this.project = await SfProject.resolve(); const sourcepath = this.project.getUniquePackageDirectories().map((pDir) => pDir.fullPath); - componentSet = await buildComponentSet({ 'source-dir': sourcepath, wait: waitDuration }); + componentSet = await buildComponentSet({ 'source-dir': sourcepath, wait }); } catch (err) { // ignore the error. this was just to get improved command output. } } else { - componentSet = await buildComponentSet({ ...deployOpts, wait: waitDuration }); + componentSet = await buildComponentSet({ ...deployOpts, wait }); } } const mdapiDeploy = new MetadataApiDeploy({ @@ -117,11 +117,11 @@ export default class DeployMetadataReport extends SfCommand { }; let result: DeployResult; - if (waitDuration) { + if (wait) { // poll for deploy results try { new DeployProgress(mdapiDeploy, this.jsonEnabled()).start(); - result = await mdapiDeploy.pollStatus(500, waitDuration.seconds); + result = await mdapiDeploy.pollStatus(500, wait.seconds); } catch (error) { if (error instanceof Error && error.message.includes('The client has timed out')) { this.debug('[project deploy report] polling timed out. Requesting status...'); diff --git a/src/formatters/deployReportResultFormatter.ts b/src/formatters/deployReportResultFormatter.ts index 2d338923..0654c0ac 100644 --- a/src/formatters/deployReportResultFormatter.ts +++ b/src/formatters/deployReportResultFormatter.ts @@ -7,6 +7,7 @@ import { ux } from '@oclif/core'; import { RequestStatus } from '@salesforce/source-deploy-retrieve'; import { StandardColors } from '@salesforce/sf-plugins-core'; +import { Duration } from '@salesforce/kit'; import { tableHeader } from '../utils/output'; import { DeployResultFormatter } from './deployResultFormatter'; @@ -38,8 +39,9 @@ export class DeployReportResultFormatter extends DeployResultFormatter { if (key === 'target-org') { return result.concat({ key: 'target-org', value: this.flags['target-org']?.getUsername() }); } - if (key === 'wait') { - return result.concat({ key: 'wait', value: `${this.flags['wait']?.quantity} minutes` }); + if (key === 'wait' && this.flags['wait']) { + const wait = this.flags['wait'] instanceof Duration ? this.flags['wait'].quantity : this.flags['wait']; + return result.concat({ key: 'wait', value: `${wait} minutes` }); } return result.concat({ key, value }); }, []); diff --git a/src/formatters/deployResultFormatter.ts b/src/formatters/deployResultFormatter.ts index 4393544f..89e5b6c5 100644 --- a/src/formatters/deployResultFormatter.ts +++ b/src/formatters/deployResultFormatter.ts @@ -45,7 +45,7 @@ export class DeployResultFormatter extends TestResultsFormatter implements Forma junit: boolean; 'results-dir': string; 'target-org': Org; - wait: Duration; + wait: Duration | number; }> ) { super(result, flags);