@salesforce/plugin-release-management
Version:
A plugin for preparing and publishing npm packages
334 lines • 14.1 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 });
exports.Location = exports.Channel = void 0;
const os = require("os");
const path = require("path");
const util = require("util");
const fs = require("fs/promises");
const fg = require("fast-glob");
const shelljs_1 = require("shelljs");
const command_1 = require("@salesforce/command");
const core_1 = require("@salesforce/core");
const chalk_1 = require("chalk");
const ts_types_1 = require("@salesforce/ts-types");
const kit_1 = require("@salesforce/kit");
const types_1 = require("../../../types");
core_1.Messages.importMessagesDirectory(__dirname);
const messages = core_1.Messages.load('@salesforce/plugin-release-management', 'cli.versions.inspect', [
'description',
'examples',
'deps',
'salesforce',
'channels',
'locations',
'cli',
]);
const LEGACY_PATH = 'https://developer.salesforce.com/media/salesforce-cli/sfdx-cli/channels/stable';
const LEGACY_TOP_LEVEL_PATH = 'https://developer.salesforce.com/media/salesforce-cli';
const SALESFORCE_DEP_GLOBS = ['@salesforce/**/*', 'salesforce-alm', 'salesforcedx'];
var Channel;
(function (Channel) {
Channel["LEGACY"] = "legacy";
Channel["STABLE"] = "stable";
Channel["STABLE_RC"] = "stable-rc";
Channel["LATEST"] = "latest";
Channel["LATEST_RC"] = "latest-rc";
})(Channel = exports.Channel || (exports.Channel = {}));
var Location;
(function (Location) {
Location["ARCHIVE"] = "archive";
Location["NPM"] = "npm";
})(Location = exports.Location || (exports.Location = {}));
const ARCHIVES = {
[Channel.STABLE]: [
'%s/%s-darwin-x64.tar.gz',
'%s/%s-darwin-x64.tar.xz',
'%s/%s-linux-arm.tar.gz',
'%s/%s-linux-arm.tar.xz',
'%s/%s-linux-x64.tar.gz',
'%s/%s-linux-x64.tar.xz',
'%s/%s-win32-x64.tar.gz',
'%s/%s-win32-x64.tar.xz',
'%s/%s-win32-x86.tar.gz',
'%s/%s-win32-x86.tar.xz',
],
[Channel.STABLE_RC]: [
'%s/%s-darwin-x64.tar.gz',
'%s/%s-darwin-x64.tar.xz',
'%s/%s-linux-arm.tar.gz',
'%s/%s-linux-arm.tar.xz',
'%s/%s-linux-x64.tar.gz',
'%s/%s-linux-x64.tar.xz',
'%s/%s-win32-x64.tar.gz',
'%s/%s-win32-x64.tar.xz',
'%s/%s-win32-x86.tar.gz',
'%s/%s-win32-x86.tar.xz',
],
[Channel.LEGACY]: [
`${LEGACY_PATH}/%s-darwin-x64.tar.gz`,
`${LEGACY_PATH}/%s-darwin-x64.tar.xz`,
`${LEGACY_PATH}/%s-linux-arm.tar.gz`,
`${LEGACY_PATH}/%s-linux-arm.tar.xz`,
`${LEGACY_PATH}/%s-linux-x64.tar.gz`,
`${LEGACY_PATH}/%s-linux-x64.tar.xz`,
`${LEGACY_PATH}/%s-windows-x64.tar.gz`,
`${LEGACY_PATH}/%s-windows-x64.tar.xz`,
`${LEGACY_PATH}/%s-windows-x86.tar.gz`,
`${LEGACY_PATH}/%s-windows-x86.tar.xz`,
`${LEGACY_TOP_LEVEL_PATH}/%s-linux-amd64.tar.gz`,
`${LEGACY_TOP_LEVEL_PATH}/%s-linux-amd64.tar.xz`,
],
};
const CHANNEL_MAPPING = {
[Location.NPM]: {
[Channel.STABLE_RC]: Channel.LATEST_RC,
[Channel.STABLE]: Channel.LATEST,
[Channel.LATEST_RC]: Channel.LATEST_RC,
[Channel.LATEST]: Channel.LATEST,
[Channel.LEGACY]: Channel.LEGACY,
},
[Location.ARCHIVE]: {
[Channel.LATEST_RC]: Channel.STABLE_RC,
[Channel.LATEST]: Channel.STABLE,
[Channel.STABLE_RC]: Channel.STABLE_RC,
[Channel.STABLE]: Channel.STABLE,
[Channel.LEGACY]: Channel.LEGACY,
},
};
const CLI_META = {
[types_1.CLI.SFDX]: {
npm: 'https://www.npmjs.com/package/sfdx-cli',
repoName: 'sfdx-cli',
packageName: 'sfdx-cli',
},
[types_1.CLI.SF]: {
npm: 'https://www.npmjs.com/package/@salesforce/cli',
repoName: 'cli',
packageName: '@salesforce/cli',
},
};
class Inspect extends command_1.SfdxCommand {
constructor() {
super(...arguments);
this.workingDir = path.join(os.tmpdir(), 'cli_inspection');
}
async run() {
const locations = (0, kit_1.ensureArray)(this.flags.locations);
const channels = (0, kit_1.ensureArray)(this.flags.channels);
if (this.flags.cli === types_1.CLI.SF && channels.includes(Channel.LEGACY)) {
throw new core_1.SfError('the sf CLI does not have a legacy channel');
}
this.ux.log(`Working Directory: ${this.workingDir}`);
// ensure that we are starting with a clean directory
try {
await fs.rm(this.workingDir, { recursive: true, force: true });
}
catch {
// error means that folder doesn't exist which is okay
}
await fs.mkdir(this.workingDir, { recursive: true });
this.initArchives();
const results = [];
if (locations.includes(Location.ARCHIVE)) {
results.push(...(await this.inspectArchives(channels)));
}
if (locations.includes(Location.NPM)) {
results.push(...(await this.inspectNpm(channels)));
}
this.logResults(results, locations, channels);
return results;
}
initArchives() {
const cli = (0, ts_types_1.ensure)(this.flags.cli);
const stablePath = `https://developer.salesforce.com/media/salesforce-cli/${cli}/channels/stable`;
const stableRcPath = `https://developer.salesforce.com/media/salesforce-cli/${cli}/channels/stable-rc`;
this.archives = {};
for (const [channel, paths] of Object.entries(ARCHIVES)) {
if (channel === Channel.LEGACY && cli === types_1.CLI.SFDX) {
this.archives[channel] = paths.map((p) => {
if (p.includes('amd64')) {
return util.format(p, this.flags.cli);
}
else {
return util.format(p, CLI_META[this.flags.cli].packageName);
}
});
}
else if (channel === Channel.STABLE) {
this.archives[channel] = paths.map((p) => util.format(p, stablePath, this.flags.cli));
}
else if (channel === Channel.STABLE_RC) {
this.archives[channel] = paths.map((p) => util.format(p, stableRcPath, this.flags.cli));
}
}
}
async inspectArchives(channels) {
const tarDir = await this.mkdir(this.workingDir, 'tar');
const pathsByChannel = channels.reduce((res, current) => {
const channel = CHANNEL_MAPPING[Location.ARCHIVE][current];
return Object.assign(res, { [channel]: this.archives[channel] });
}, {});
const results = [];
for (const channel of Object.keys(pathsByChannel)) {
this.ux.log(`---- ${Location.ARCHIVE} ${channel} ----`);
for (const archivePath of pathsByChannel[channel]) {
this.ux.startSpinner(`Downloading: ${(0, chalk_1.cyan)(archivePath)}`);
const curlResult = (0, shelljs_1.exec)(`curl ${archivePath} -Os`, { cwd: tarDir });
this.ux.stopSpinner();
if (curlResult.code !== 0) {
this.ux.log((0, chalk_1.red)('Download failed. That is a big deal. Investigate immediately.'));
continue;
}
const filename = path.basename(archivePath);
const unpackedDir = await this.mkdir(this.workingDir, 'unpacked', filename);
this.ux.startSpinner(`Unpacking: ${(0, chalk_1.cyan)(unpackedDir)}`);
const tarResult = (0, shelljs_1.exec)(`tar -xf ${filename} -C ${unpackedDir} --strip-components 1`, { cwd: tarDir });
this.ux.stopSpinner();
if (tarResult.code !== 0) {
this.ux.log((0, chalk_1.red)('Failed to unpack. Skipping...'));
continue;
}
const pkgJson = await this.readPackageJson(unpackedDir);
results.push({
dependencies: await this.getDependencies(unpackedDir),
origin: archivePath,
channel,
location: Location.ARCHIVE,
version: pkgJson.version,
});
}
}
return results;
}
async inspectNpm(channels) {
const cliMeta = CLI_META[this.flags.cli];
const npmDir = await this.mkdir(this.workingDir, 'npm');
const results = [];
const tags = channels.map((c) => CHANNEL_MAPPING[Location.NPM][c]).filter((c) => c !== Channel.LEGACY);
for (const tag of tags) {
this.ux.log(`---- ${Location.NPM} ${tag} ----`);
const installDir = await this.mkdir(npmDir, tag);
const name = `${cliMeta.packageName}@${tag}`;
this.ux.startSpinner(`Installing: ${(0, chalk_1.cyan)(name)}`);
(0, shelljs_1.exec)(`npm install ${name}`, { cwd: installDir, silent: true });
this.ux.stopSpinner();
const pkgJson = await this.readPackageJson(path.join(installDir, 'node_modules', cliMeta.repoName));
results.push({
dependencies: await this.getDependencies(installDir),
origin: `${cliMeta.npm}/v/${pkgJson.version}`,
channel: tag,
location: Location.NPM,
version: pkgJson.version,
});
}
return results;
}
async getDependencies(directory) {
const depGlobs = [];
if (this.flags.dependencies) {
const globPatterns = this.flags.dependencies.map((d) => `${directory}/node_modules/${d}`);
depGlobs.push(...globPatterns);
}
if (this.flags.salesforce) {
const globPatterns = SALESFORCE_DEP_GLOBS.map((d) => `${directory}/node_modules/${d}`);
depGlobs.push(...globPatterns);
}
const dependencyPaths = await fg(depGlobs, { onlyDirectories: true, deep: 1 });
const dependencies = [];
for (const dep of dependencyPaths) {
const pkg = await this.readPackageJson(dep);
dependencies.push({
name: pkg.name,
version: pkg.version,
});
}
return dependencies;
}
async readPackageJson(pkgDir) {
const fileData = await fs.readFile(path.join(pkgDir, 'package.json'), 'utf8');
return (0, kit_1.parseJson)(fileData, path.join(pkgDir, 'package.json'), false);
}
async mkdir(...parts) {
const dir = path.resolve(path.join(...parts));
await fs.mkdir(dir, { recursive: true });
return dir;
}
logResults(results, locations, channels) {
let allMatch;
let npmAndArchivesMatch;
this.ux.log();
results.forEach((result) => {
this.ux.log((0, chalk_1.bold)(`${result.origin}: ${(0, chalk_1.green)(result.version)}`));
result.dependencies.forEach((dep) => {
this.ux.log(` ${dep.name}: ${dep.version}`);
});
});
this.ux.log();
if (locations.includes(Location.ARCHIVE)) {
const archivesMatch = new Set(results.filter((r) => r.location === Location.ARCHIVE).map((r) => r.version)).size === 1;
this.ux.log(`${'All archives match?'} ${archivesMatch ? (0, chalk_1.green)(archivesMatch) : (0, chalk_1.yellow)(archivesMatch)}`);
channels.forEach((channel) => {
allMatch = new Set(results.filter((r) => r.channel === channel).map((r) => r.version)).size === 1;
this.ux.log(`${`All ${Location.ARCHIVE}@${channel} versions match?`} ${allMatch ? (0, chalk_1.green)(allMatch) : (0, chalk_1.red)(allMatch)}`);
});
}
if (locations.includes(Location.NPM) && locations.includes(Location.ARCHIVE)) {
channels
.filter((c) => c !== Channel.LEGACY)
.forEach((channel) => {
const npmChannel = CHANNEL_MAPPING[Location.NPM][channel];
const archiveChannel = CHANNEL_MAPPING[Location.ARCHIVE][channel];
npmAndArchivesMatch =
new Set(results.filter((r) => r.channel === npmChannel || r.channel === archiveChannel).map((r) => r.version)).size === 1;
const match = npmAndArchivesMatch ? (0, chalk_1.green)(true) : (0, chalk_1.red)(false);
this.ux.log(`${Location.NPM}@${npmChannel} and all ${Location.ARCHIVE}@${archiveChannel} versions match? ${match}`);
});
}
// npmAndArchivesMatch can be undefined
if ((npmAndArchivesMatch !== undefined && !npmAndArchivesMatch) || !allMatch) {
throw new core_1.SfError('Version Mismatch');
}
}
}
exports.default = Inspect;
Inspect.description = messages.getMessage('description');
Inspect.examples = messages.getMessage('examples').split(os.EOL);
Inspect.flagsConfig = {
dependencies: command_1.flags.string({
description: messages.getMessage('deps'),
char: 'd',
multiple: true,
}),
salesforce: command_1.flags.boolean({
description: messages.getMessage('salesforce'),
char: 's',
default: false,
}),
channels: command_1.flags.string({
description: messages.getMessage('channels'),
char: 'c',
options: Object.values(Channel),
required: true,
multiple: true,
}),
locations: command_1.flags.string({
description: messages.getMessage('locations'),
char: 'l',
options: Object.values(Location),
required: true,
multiple: true,
}),
cli: command_1.flags.enum({
description: messages.getMessage('cli'),
options: Object.values(types_1.CLI),
default: types_1.CLI.SFDX,
required: true,
}),
};
//# sourceMappingURL=inspect.js.map