@salesforce/plugin-release-management
Version:
A plugin for preparing and publishing npm packages
276 lines • 13 kB
JavaScript
;
/*
* Copyright (c) 2021, 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 fs = require("fs/promises");
const path = require("path");
const fg = require("fast-glob");
const shelljs_1 = require("shelljs");
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 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.tarballs.verify', [
'description',
'examples',
'cli',
'windowsUsernameBuffer',
]);
const PASSED = chalk_1.green.bold('PASSED');
const FAILED = chalk_1.red.bold('FAILED');
/**
* Checks if a file path exists
*
* @param filePath the file path to check the existence of
*/
async function fileExists(filePath) {
try {
await fs.access(filePath);
return true;
}
catch (err) {
return false;
}
}
/**
* The functionality of this command is taken entirely from https://github.com/salesforcecli/sfdx-cli/blob/v7.109.0/scripts/verify-tarballs
*/
class Verify extends command_1.SfdxCommand {
constructor() {
super(...arguments);
this.step = 1;
this.totalSteps = 1;
}
async run() {
const cli = (0, ts_types_1.ensure)(this.flags.cli);
this.baseDir = path.join('tmp', cli);
const cliRunLists = {
[types_1.CLI.SFDX]: [
this.ensureNoWebDriverIO.bind(this),
this.ensureNoHerokuCliUtilNyc.bind(this),
this.ensureWindowsPathLengths.bind(this),
this.ensureApexNode.bind(this),
this.ensurePluginGenerateTestTemplates.bind(this),
this.ensureTemplatesCommands.bind(this),
this.ensureNoDistTestsOrMaps.bind(this),
this.ensureNoUnexpectedFiles.bind(this),
this.ensureSfIsIncluded.bind(this),
],
[types_1.CLI.SF]: [
this.ensureNoDistTestsOrMaps.bind(this),
this.ensureNoUnexpectedFiles.bind(this),
this.ensureWindowsPathLengths.bind(this),
this.ensureMdMessagesExist.bind(this),
],
};
this.totalSteps = cliRunLists[cli].length;
for (const test of cliRunLists[cli]) {
await test();
}
}
async execute(msg, validate) {
this.ux.startSpinner(`[${this.step}/${this.totalSteps}] ${msg}`);
if (!(await validate())) {
this.ux.stopSpinner(FAILED);
return false;
}
this.step += 1;
this.ux.stopSpinner(PASSED);
return true;
}
async ensureNoWebDriverIO() {
const webDriverIo = path.join(this.baseDir, 'node_modules', 'webdriverio', 'test');
const validate = async () => {
return !(await fileExists(webDriverIo));
};
const passed = await this.execute('Ensure webdriverio does not exist', validate);
if (!passed) {
throw new core_1.SfError(`${webDriverIo} is present. Was the clean not aggressive enough?`);
}
}
async ensureNoHerokuCliUtilNyc() {
const herokuCliUtil = path.join(this.baseDir, 'node_modules', '@salesforce', 'plugin-templates', 'node_modules', 'salesforce-alm', 'node_modules', 'heroku-cli-util', '.nyc_output');
const validate = async () => {
return !(await fileExists(herokuCliUtil));
};
const passed = await this.execute('Ensure heroku-cli-util/.nyc_output does not exist', validate);
if (!passed) {
throw new core_1.SfError(`${herokuCliUtil} is present. Was the clean not aggressive enough?`);
}
}
/**
* Ensure that the path lengths in the build tree are as windows safe as possible.
*
* The check fails if the path lengths DO NOT allow for a username longer than the --windows-username-buffer
*
* Warnings will be emitted for any path that does not allow for a username longer than 48 characters
*/
async ensureWindowsPathLengths() {
const validate = async () => {
const maxWindowsPath = 259;
const cli = (0, ts_types_1.ensure)(this.flags.cli);
const supportedUsernameLength = (0, ts_types_1.ensureNumber)(this.flags['windows-username-buffer']);
const fakeSupportedUsername = 'u'.repeat(supportedUsernameLength);
const supportedBaseWindowsPath = `C:\\Users\\${fakeSupportedUsername}\\AppData\\Local\\${cli}\\tmp\\${cli}-cli-v1.xxx.yyy-abcdef-windows-x64\\`;
const maxUsernameLength = 64;
const fakeMaxUsername = 'u'.repeat(maxUsernameLength);
const maxBaseWindowsPath = `C:\\Users\\${fakeMaxUsername}\\AppData\\Local\\${cli}\\tmp\\${cli}-cli-v1.xxx.yyy-abcdef-windows-x64\\`;
const supportedWindowsPathLength = maxWindowsPath - supportedBaseWindowsPath.length;
const maxWindowsPathLength = maxWindowsPath - maxBaseWindowsPath.length;
this.log('Windows Path Length Test:');
this.log(` - max windows path length: ${maxWindowsPath}`);
this.log(' ---- Upper Limit ----');
this.log(` - ${cli} max username length: ${maxUsernameLength}`);
this.log(` - ${cli} max base path length: ${maxBaseWindowsPath.length}`);
this.log(` - ${cli} max allowable path length: ${maxWindowsPathLength}`);
this.log(' ---- Supported Limit ----');
this.log(` - ${cli} supported username length: ${supportedUsernameLength}`);
this.log(` - ${cli} supported base path length: ${supportedBaseWindowsPath.length}`);
this.log(` - ${cli} supported allowable path length: ${supportedWindowsPathLength}`);
const paths = (await fg(`${this.baseDir}/node_modules/**/*`)).map((p) => p.replace(`${this.baseDir}${path.sep}`, ''));
const warnPaths = paths
.filter((p) => p.length >= maxWindowsPathLength && p.length < supportedWindowsPathLength)
.sort();
const errorPaths = paths.filter((p) => p.length >= supportedWindowsPathLength).sort();
if (warnPaths.length) {
this.log(`${chalk_1.yellow.bold('WARNING:')} Some paths could result in errors for Windows users with usernames that are ${maxUsernameLength} characters!`);
warnPaths.forEach((p) => this.log(`${p.length} - ${p}`));
}
if (errorPaths.length) {
this.log(`${chalk_1.red.bold('ERROR:')} Unacceptably long paths detected in base build!`);
errorPaths.forEach((p) => this.log(`${p.length} - ${p}`));
return false;
}
return true;
};
const passed = await this.execute('Ensure windows path lengths', validate);
if (!passed) {
throw new core_1.SfError('Unacceptably long paths detected in base build!');
}
}
async ensureApexNode() {
const apexNodePath = path.join(this.baseDir, 'node_modules', '@salesforce', 'apex-node', 'lib', 'src', 'tests');
const validate = async () => fileExists(apexNodePath);
const passed = await this.execute('Ensure apex-node exists', validate);
if (!passed) {
throw new core_1.SfError(`${apexNodePath} is missing!. Was the clean too aggressive?`);
}
}
async ensurePluginGenerateTestTemplates() {
const pluginGeneratorTestPath = path.join(this.baseDir, 'node_modules', '@salesforce', 'plugin-generator', 'templates', 'sfdxPlugin', 'test');
const validate = async () => fileExists(pluginGeneratorTestPath);
const passed = await this.execute('Ensure plugin generator test template exists', validate);
if (!passed) {
throw new core_1.SfError(`${pluginGeneratorTestPath} is missing!. Was the clean too aggressive?`);
}
}
async ensureTemplatesCommands() {
const templatesPath = path.join(this.baseDir, 'node_modules', '@salesforce', 'plugin-templates');
const validate = async () => fileExists(templatesPath);
const passed = await this.execute('Ensure templates commands exist', validate);
if (!passed) {
throw new core_1.SfError(`${templatesPath} is missing!. Was the doc clean too aggressive?`);
}
}
async ensureNoDistTestsOrMaps() {
const validate = async () => {
const files = await fg([`${this.baseDir}/dist/*.test.js`, `${this.baseDir}/dist/*.js.map`]);
if (files.length) {
this.log(chalk_1.red.bold('Found the following in dist:'));
for (const file of files)
this.log(file);
return false;
}
return true;
};
const passed = await this.execute('Ensure no tests or maps in dist', validate);
if (!passed) {
throw new core_1.SfError(`Found .test.js and/or .js.map files in ${path.join(this.baseDir, 'dist')}`);
}
}
async ensureNoUnexpectedFiles() {
const validate = async () => {
const expectedFileGlobs = [
`${this.baseDir}/package.json`,
`${this.baseDir}/LICENSE.txt`,
`${this.baseDir}/README.md`,
`${this.baseDir}/CHANGELOG.md`,
`${this.baseDir}/yarn.lock`,
`${this.baseDir}/oclif.manifest.json`,
`${this.baseDir}/bin/*`,
`${this.baseDir}/dist/**/*.js`,
`${this.baseDir}/dist/builtins/package.json`,
`${this.baseDir}/scripts/*`,
`${this.baseDir}/sf/**/*`,
];
const expectedFiles = await fg(expectedFileGlobs);
const allFiles = await fg([`${this.baseDir}/**/*`, `!${this.baseDir}/node_modules/**/*`]);
const unexpectedFiles = allFiles.filter((f) => !expectedFiles.includes(f));
if (unexpectedFiles.length) {
this.log(chalk_1.red.bold('Found unexpected files in base build dir:'));
for (const file of unexpectedFiles)
this.log(file);
return false;
}
return true;
};
const passed = await this.execute('Ensure no unexpected files', validate);
if (!passed) {
throw new core_1.SfError('Unexpected file found in base build dir!');
}
}
async ensureMdMessagesExist() {
const validate = async () => {
const fileData = await fs.readFile(path.join(this.baseDir, 'package.json'), 'utf8');
const packageJson = (0, kit_1.parseJson)(fileData, path.join(this.baseDir, 'package.json'), false);
const plugins = (0, ts_types_1.get)(packageJson, 'oclif.plugins', []);
const globs = plugins.map((p) => `${this.baseDir}/node_modules/${p}/messages/*.md`);
const files = await fg(globs);
return Boolean(files.length);
};
const passed = await this.execute('Ensure .md messages exist', validate);
if (!passed) {
throw new core_1.SfError('Found no .md message files. Was the clean too aggresive?');
}
}
async ensureSfIsIncluded() {
const validate = async () => {
const sfBin = path.join(this.baseDir, 'bin', 'sf');
const sfBinExists = await fileExists(sfBin);
const sfCmd = path.join(this.baseDir, 'bin', 'sf.cmd');
const sfCmdExists = await fileExists(sfCmd);
const version = (0, shelljs_1.exec)(`${sfBin} --version`, { silent: false });
const help = (0, shelljs_1.exec)(`${sfBin} --help`, { silent: false });
return sfBinExists && sfCmdExists && version.code === 0 && help.code === 0;
};
const passed = await this.execute('Ensure sf is included\n', validate);
if (!passed) {
throw new core_1.SfError('sf was not included! Did include-sf.js succeed?');
}
}
}
exports.default = Verify;
Verify.description = messages.getMessage('description');
Verify.examples = messages.getMessage('examples').split(os.EOL);
Verify.flagsConfig = {
cli: command_1.flags.enum({
description: messages.getMessage('cli'),
options: Object.values(types_1.CLI),
default: 'sfdx',
char: 'c',
}),
['windows-username-buffer']: command_1.flags.number({
description: messages.getMessage('windowsUsernameBuffer'),
default: 41,
char: 'w',
}),
};
//# sourceMappingURL=verify.js.map