@salesforce/plugin-release-management
Version:
A plugin for preparing and publishing npm packages
272 lines • 11 kB
JavaScript
;
/*
* 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