UNPKG

@salesforce/plugin-release-management

Version:
272 lines 11 kB
"use strict"; /* * Copyright (c) 2020, salesforce.com, inc. * All rights reserved. * Licensed under the BSD 3-Clause license. * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ Object.defineProperty(exports, "__esModule", { value: true }); const os = require("os"); const command_1 = require("@salesforce/command"); const core_1 = require("@salesforce/core"); const ts_types_1 = require("@salesforce/ts-types"); const chalk_1 = require("chalk"); const types_1 = require("../../types"); const amazonS3_1 = require("../../amazonS3"); const dependencies_1 = require("../../dependencies"); const pluginCommand_1 = require("../../pluginCommand"); core_1.Messages.importMessagesDirectory(__dirname); const messages = core_1.Messages.loadMessages('@salesforce/plugin-release-management', 'channel.promote'); const TARGETS = ['linux-x64', 'linux-arm', 'win32-x64', 'win32-x86', 'darwin-x64']; class Promote extends command_1.SfdxCommand { async run() { this.validateFlags(); // preparing parameters for call to oclif promote commands const cli = this.flags.cli; const target = (0, ts_types_1.ensureString)(this.flags.target); const maxAge = (0, ts_types_1.ensureNumber)(this.flags.maxage); const indexes = this.flags.indexes ? '--indexes' : ''; const xz = this.flags.xz ? '--xz' : '--no-xz'; const targets = this.flags.targets ? ['--targets', ...(0, ts_types_1.ensureArray)(this.flags.targets)] : []; const { sha, version } = await this.determineShaAndVersion(cli); const platforms = (0, ts_types_1.asArray)(this.flags.platform, []).map((p) => `--${p}`); if (!this.flags.dryrun) { const oclifPlugin = await pluginCommand_1.PluginCommand.create({ commandBin: 'oclif', npmName: 'oclif', cliRoot: this.config.root, }); const results = oclifPlugin.runPluginCmd({ command: 'promote', parameters: [ '--version', version, '--sha', sha, '--channel', target, '--max-age', `${maxAge}`, ...platforms, ...targets, indexes, xz, ], }); this.ux.log(results.stdout); } else { if (!this.flags.json) { this.log(messages.getMessage('DryRunMessage', [cli, version, sha, target, (0, ts_types_1.ensureArray)(this.flags.platform).join(', ')].map((s) => (0, chalk_1.bold)(s)))); } } return { dryRun: !!this.flags.dryrun, cli, target, sha, version, platforms: (0, ts_types_1.ensureArray)(this.flags.platform), }; } /** * Based on which flag was provided, locate the sha and version in S3 that will be used in the promote * * when candidate channel flag present, find sha a version via the channel for candidate * when version flag present, find the sha from version subfolders with the most recent modified date * when sha flag is present, find the version that owns the subfolder named as sha value * * @param cli * @private */ async determineShaAndVersion(cli) { if (this.flags.candidate) { const manifest = await this.findManifestForCandidate(cli, this.flags.candidate); return { sha: manifest.sha, version: manifest.version }; } else if (this.flags.version) { const sha = await this.findShaForVersion(cli, (0, ts_types_1.ensureString)(this.flags.version)); return { sha, version: (0, ts_types_1.ensureString)(this.flags.version) }; } else { const sha = (0, ts_types_1.ensureString)(this.flags.sha); const version = await this.findVersionForSha(cli, sha); return { sha, version }; } throw new core_1.SfError(messages.getMessage('CouldNotDetermineShaAndVersion')); } /** * validate flag combinations * * @private */ validateFlags() { // requires one of the following flags if (!this.flags.version && !this.flags.sha && !this.flags.candidate) { throw new core_1.SfError(messages.getMessage('MissingSourceOfPromote')); } // cannot promote when channel names are the same if (this.flags.candidate && this.flags.candidate === this.flags.target) { throw new core_1.SfError(messages.getMessage('CannotPromoteToSameChannel')); } // make sure necessary runtime dependencies are present const deps = (0, dependencies_1.verifyDependencies)(this.flags, (dep) => dep.name.startsWith('AWS'), (args) => !args.dryrun); if (deps.failures > 0) { const errType = 'MissingDependencies'; const missing = deps.results.filter((d) => d.passed === false).map((d) => d.message); throw new core_1.SfError(messages.getMessage(errType), errType, missing); } } /** * find a manifest file in the channel * * @param cli * @param channel * @private */ async findManifestForCandidate(cli, channel) { const amazonS3 = new amazonS3_1.AmazonS3({ cli, channel }); return await amazonS3.getManifestFromChannel(channel); } /** * find the sha that was uploaded most recently for the named version * * @param cli * @param version * @private */ async findShaForVersion(cli, version) { const amazonS3 = new amazonS3_1.AmazonS3({ cli }); const versions = await amazonS3.listCommonPrefixes('versions'); const foundVersion = versions.find((v) => v.Prefix.endsWith(`${version}/`))?.Prefix; if (foundVersion) { this.logger.debug(`Looking for version ${version} for cli ${cli}. Found ${foundVersion}`); const versionShas = await amazonS3.listCommonPrefixes(foundVersion); this.logger.debug(`Looking for version ${version} for cli ${cli} shas. Found ${versionShas.length} entries`); const manifestForMostRecentSha = (await Promise.all(versionShas.map(async (versionSha) => { const versionShaContents = (await amazonS3.listKeyContents(versionSha.Prefix)); return versionShaContents.map((content) => { return { ...content, ...{ LastModifiedDate: new Date(content.LastModified) } }; }); }))).flat() .filter((content) => content.Key.includes('manifest')) .sort((left, right) => right.LastModifiedDate.getMilliseconds() - left.LastModifiedDate.getMilliseconds()) .find((content) => content); if (manifestForMostRecentSha) { const manifest = await amazonS3.getObject({ Key: manifestForMostRecentSha.Key, ResponseContentType: 'application/json', }); this.logger.debug(`Loaded manifest ${manifestForMostRecentSha.Key} contents: ${manifest.toString()}`); const json = JSON.parse(manifest.Body.toString()); return json.sha; } } const error = new core_1.SfError(messages.getMessage('CouldNotLocateShaForVersion', [version])); this.logger.debug(error); throw error; } /** * find the version that owns the named sha * * @param cli * @param sha * @private */ async findVersionForSha(cli, sha) { const amazonS3 = new amazonS3_1.AmazonS3({ cli }); const foundVersion = (await Promise.all((await amazonS3.listCommonPrefixes('versions')).map(async (version) => { return await amazonS3.listCommonPrefixes(version.Prefix); }))) .flat() .find((s) => s.Prefix.replace(/\/$/, '').endsWith(sha)); if (foundVersion) { // Prefix looks like this "media/salesforce-cli/sf/versions/0.0.10/1d4b10d/", // when reversed after split version number should occupy entry 1 of the array return foundVersion.Prefix.replace(/\/$/, '').split('/').reverse()[1]; } const error = new core_1.SfError(messages.getMessage('CouldNotLocateVersionForSha', [sha])); this.logger.debug(error); throw error; } } exports.default = Promote; Promote.description = messages.getMessage('description'); Promote.examples = messages.getMessage('examples').split(os.EOL); Promote.flagsConfig = { dryrun: command_1.flags.boolean({ char: 'd', default: false, description: messages.getMessage('dryrun'), }), target: command_1.flags.string({ char: 't', default: types_1.Channel.STABLE, description: messages.getMessage('target'), // options: Object.values(Channel), required: true, }), candidate: command_1.flags.string({ char: 'C', description: messages.getMessage('candidate'), // options: Object.values(Channel), exclusive: ['sha'], }), platform: command_1.flags.array({ char: 'p', description: messages.getMessage('platform'), options: ['win', 'macos', 'deb'], default: [], multiple: true, }), cli: command_1.flags.enum({ char: 'c', description: messages.getMessage('cli'), required: true, options: Object.values(types_1.CLI), }), sha: command_1.flags.string({ char: 's', description: messages.getMessage('sha'), exclusive: ['candidate'], parse: (input) => { return Promise.resolve(input.slice(0, 7)); }, validate: (input) => { if (input.length < 7) { return false; } return true; }, }), maxage: command_1.flags.number({ char: 'm', description: messages.getMessage('maxage'), default: 300, }), indexes: command_1.flags.boolean({ char: 'i', description: messages.getMessage('indexes'), default: true, allowNo: true, }), xz: command_1.flags.boolean({ char: 'x', description: messages.getMessage('xz'), default: true, allowNo: true, }), targets: command_1.flags.array({ char: 'T', description: messages.getMessage('targets'), options: TARGETS, }), version: command_1.flags.string({ char: 'T', description: messages.getMessage('version'), exclusive: ['sha', 'candidate'], parse: (input) => Promise.resolve(input.trim()), validate: (input) => /^([0-9]+\.){2}[0-9]+$/.test(input), }), }; //# sourceMappingURL=promote.js.map