UNPKG

@salesforce/plugin-release-management

Version:
334 lines 14.1 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 }); 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