|
4 | 4 | * Licensed under the BSD 3-Clause license. |
5 | 5 | * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause |
6 | 6 | */ |
| 7 | +import * as os from 'os'; |
| 8 | +import * as path from 'path'; |
| 9 | +import { flags, FlagsConfig } from '@salesforce/command'; |
| 10 | +import { Lifecycle, Messages } from '@salesforce/core'; |
| 11 | +import { SourceDeployResult } from '@salesforce/source-deploy-retrieve'; |
| 12 | +import { Duration } from '@salesforce/kit'; |
| 13 | +import { asString, asArray } from '@salesforce/ts-types'; |
| 14 | +import * as chalk from 'chalk'; |
| 15 | +import { SourceCommand } from '../../../sourceCommand'; |
| 16 | + |
| 17 | +Messages.importMessagesDirectory(__dirname); |
| 18 | +const messages = Messages.loadMessages('@salesforce/plugin-source', 'deploy'); |
| 19 | + |
| 20 | +export class deploy extends SourceCommand { |
| 21 | + public static readonly description = messages.getMessage('description'); |
| 22 | + public static readonly examples = messages.getMessage('examples').split(os.EOL); |
| 23 | + public static readonly requiresProject = true; |
| 24 | + public static readonly requiresUsername = true; |
| 25 | + public static readonly flagsConfig: FlagsConfig = { |
| 26 | + checkonly: flags.boolean({ |
| 27 | + char: 'c', |
| 28 | + description: messages.getMessage('flags.checkonly'), |
| 29 | + default: false, |
| 30 | + }), |
| 31 | + wait: flags.minutes({ |
| 32 | + char: 'w', |
| 33 | + default: Duration.minutes(SourceCommand.DEFAULT_SRC_WAIT_MINUTES), |
| 34 | + min: Duration.minutes(SourceCommand.MINIMUM_SRC_WAIT_MINUTES), |
| 35 | + description: messages.getMessage('flags.wait'), |
| 36 | + }), |
| 37 | + testlevel: flags.enum({ |
| 38 | + char: 'l', |
| 39 | + description: messages.getMessage('flags.testLevel'), |
| 40 | + options: ['NoTestRun', 'RunSpecifiedTests', 'RunLocalTests', 'RunAllTestsInOrg'], |
| 41 | + default: 'NoTestRun', |
| 42 | + }), |
| 43 | + runtests: flags.array({ |
| 44 | + char: 'r', |
| 45 | + description: messages.getMessage('flags.runTests'), |
| 46 | + default: [], |
| 47 | + }), |
| 48 | + ignoreerrors: flags.boolean({ |
| 49 | + char: 'o', |
| 50 | + description: messages.getMessage('flags.ignoreErrors'), |
| 51 | + default: false, |
| 52 | + }), |
| 53 | + ignorewarnings: flags.boolean({ |
| 54 | + char: 'g', |
| 55 | + description: messages.getMessage('flags.ignoreWarnings'), |
| 56 | + default: false, |
| 57 | + }), |
| 58 | + validateddeployrequestid: flags.id({ |
| 59 | + char: 'q', |
| 60 | + description: messages.getMessage('flags.validateDeployRequestId'), |
| 61 | + exclusive: [ |
| 62 | + 'manifest', |
| 63 | + 'metadata', |
| 64 | + 'sourcepath', |
| 65 | + 'checkonly', |
| 66 | + 'testlevel', |
| 67 | + 'runtests', |
| 68 | + 'ignoreerrors', |
| 69 | + 'ignorewarnings', |
| 70 | + ], |
| 71 | + }), |
| 72 | + verbose: flags.builtin({ |
| 73 | + description: messages.getMessage('flags.verbose'), |
| 74 | + }), |
| 75 | + metadata: flags.array({ |
| 76 | + char: 'm', |
| 77 | + description: messages.getMessage('flags.metadata'), |
| 78 | + exclusive: ['manifest', 'sourcepath'], |
| 79 | + }), |
| 80 | + sourcepath: flags.array({ |
| 81 | + char: 'p', |
| 82 | + description: messages.getMessage('flags.sourcePath'), |
| 83 | + exclusive: ['manifest', 'metadata'], |
| 84 | + }), |
| 85 | + manifest: flags.filepath({ |
| 86 | + char: 'x', |
| 87 | + description: messages.getMessage('flags.manifest'), |
| 88 | + exclusive: ['metadata', 'sourcepath'], |
| 89 | + }), |
| 90 | + }; |
| 91 | + protected readonly lifecycleEventNames = ['predeploy', 'postdeploy']; |
| 92 | + |
| 93 | + public async run(): Promise<SourceDeployResult> { |
| 94 | + if (this.flags.validatedeployrequestid) { |
| 95 | + // TODO: return this.doDeployRecentValidation(); |
| 96 | + } |
| 97 | + const hookEmitter = Lifecycle.getInstance(); |
| 98 | + |
| 99 | + const cs = await this.createComponentSet({ |
| 100 | + sourcepath: asArray<string>(this.flags.sourcepath), |
| 101 | + manifest: asString(this.flags.manifest), |
| 102 | + metadata: asArray<string>(this.flags.metadata), |
| 103 | + }); |
| 104 | + |
| 105 | + await hookEmitter.emit('predeploy', { packageXmlPath: cs.getPackageXml() }); |
| 106 | + |
| 107 | + const results = await cs.deploy(this.org.getUsername(), { |
| 108 | + wait: (this.flags.wait as Duration).milliseconds, |
| 109 | + apiOptions: { |
| 110 | + // TODO: build out more api options |
| 111 | + checkOnly: this.flags.checkonly as boolean, |
| 112 | + ignoreWarnings: this.flags.ignorewarnings as boolean, |
| 113 | + runTests: this.flags.runtests as string[], |
| 114 | + }, |
| 115 | + }); |
| 116 | + |
| 117 | + await hookEmitter.emit('postdeploy', results); |
| 118 | + |
| 119 | + this.print(results); |
| 120 | + |
| 121 | + return results; |
| 122 | + } |
| 123 | + |
| 124 | + private printComponentFailures(result: SourceDeployResult): void { |
| 125 | + if (result.status === 'Failed' && result.components) { |
| 126 | + // sort by filename then fullname |
| 127 | + const failures = result.components.sort((i, j) => { |
| 128 | + if (i.component.type.directoryName === j.component.type.directoryName) { |
| 129 | + // if the have the same directoryName then sort by fullName |
| 130 | + return i.component.fullName < j.component.fullName ? 1 : -1; |
| 131 | + } |
| 132 | + return i.component.type.directoryName < j.component.type.directoryName ? 1 : -1; |
| 133 | + }); |
| 134 | + this.ux.log(''); |
| 135 | + this.ux.styledHeader(chalk.red(`Component Failures [${failures.length}]`)); |
| 136 | + this.ux.table(failures, { |
| 137 | + // TODO: these accessors are temporary until library JSON fixes |
| 138 | + columns: [ |
| 139 | + { key: 'component.type.name', label: 'Type' }, |
| 140 | + { key: 'diagnostics[0].filePath', label: 'File' }, |
| 141 | + { key: 'component.name', label: 'Name' }, |
| 142 | + { key: 'diagnostics[0].message', label: 'Problem' }, |
| 143 | + ], |
| 144 | + }); |
| 145 | + this.ux.log(''); |
| 146 | + } |
| 147 | + } |
| 148 | + |
| 149 | + private printComponentSuccess(result: SourceDeployResult): void { |
| 150 | + if (result.success && result.components) { |
| 151 | + if (result.components.length > 0) { |
| 152 | + // sort by type then filename then fullname |
| 153 | + const files = result.components.sort((i, j) => { |
| 154 | + if (i.component.type.name === j.component.type.name) { |
| 155 | + // same metadata type, according to above comment sort on filename |
| 156 | + if (i.component.type.directoryName === j.component.type.directoryName) { |
| 157 | + // same filename's according to comment sort by fullName |
| 158 | + return i.component.fullName < j.component.fullName ? 1 : -1; |
| 159 | + } |
| 160 | + return i.component.type.directoryName < j.component.type.directoryName ? 1 : -1; |
| 161 | + } |
| 162 | + return i.component.type.name < j.component.type.name ? 1 : -1; |
| 163 | + }); |
| 164 | + // get relative path for table output |
| 165 | + files.forEach((file) => { |
| 166 | + if (file.component.content) { |
| 167 | + file.component.content = path.relative(process.cwd(), file.component.content); |
| 168 | + } |
| 169 | + }); |
| 170 | + this.ux.log(''); |
| 171 | + this.ux.styledHeader(chalk.blue('Deployed Source')); |
| 172 | + this.ux.table(files, { |
| 173 | + // TODO: these accessors are temporary until library JSON fixes |
| 174 | + columns: [ |
| 175 | + { key: 'component.name', label: 'FULL NAME' }, |
| 176 | + { key: 'component.type.name', label: 'TYPE' }, |
| 177 | + { key: 'component.content', label: 'PROJECT PATH' }, |
| 178 | + ], |
| 179 | + }); |
| 180 | + } |
| 181 | + } |
| 182 | + } |
| 183 | + |
| 184 | + private print(result: SourceDeployResult): SourceDeployResult { |
| 185 | + this.printComponentSuccess(result); |
| 186 | + this.printComponentFailures(result); |
| 187 | + // TODO: this.printTestResults(result); <- this has WI @W-8903671@ |
| 188 | + if (result.success && this.flags.checkonly) { |
| 189 | + this.log(messages.getMessage('checkOnlySuccess')); |
| 190 | + } |
| 191 | + |
| 192 | + return result; |
| 193 | + } |
| 194 | +} |
0 commit comments