UNPKG

@salesforce/plugin-release-management

Version:
261 lines 12.3 kB
/* * 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 */ import fs from 'node:fs/promises'; import path from 'node:path'; import fg from 'fast-glob'; import shelljs from 'shelljs'; import { Flags, SfCommand } from '@salesforce/sf-plugins-core'; import { Messages, SfError } from '@salesforce/core'; import { ensure, ensureNumber, get } from '@salesforce/ts-types'; import chalk from 'chalk'; import { parseJson } from '@salesforce/kit'; import { CLI } from '../../../types.js'; Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); const messages = Messages.loadMessages('@salesforce/plugin-release-management', 'cli.tarballs.verify'); const PASSED = chalk.green.bold('PASSED'); const FAILED = chalk.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 */ export default class Verify extends SfCommand { static summary = messages.getMessage('description'); static description = messages.getMessage('description'); static examples = messages.getMessages('examples'); static flags = { cli: Flags.custom({ options: Object.values(CLI), })({ summary: messages.getMessage('cli'), default: CLI.SFDX, char: 'c', }), ['windows-username-buffer']: Flags.integer({ summary: messages.getMessage('flags.windows-username-buffer.summary'), default: 41, char: 'w', }), }; baseDir; step = 1; totalSteps = 1; flags; async run() { const { flags } = await this.parse(Verify); this.flags = flags; const cli = ensure(this.flags.cli); this.baseDir = path.join('tmp', cli); const cliRunLists = { [CLI.SFDX]: [ this.ensureNoWebDriverIO.bind(this), this.ensureNoHerokuCliUtilNyc.bind(this), this.ensureWindowsPathLengths.bind(this), this.ensureApexNode.bind(this), this.ensureTemplatesCommands.bind(this), this.ensureNoDistTestsOrMaps.bind(this), this.ensureNoUnexpectedFiles.bind(this), this.ensureSfIsIncluded.bind(this), ], [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]) { // eslint-disable-next-line no-await-in-loop await test(); } } async execute(msg, validate) { this.spinner.start(`[${this.step}/${this.totalSteps}] ${msg}`); if (!(await validate())) { this.spinner.stop(FAILED); return false; } this.step += 1; this.spinner.stop(PASSED); return true; } async ensureNoWebDriverIO() { const webDriverIo = path.join(this.baseDir, 'node_modules', 'webdriverio', 'test'); const validate = async () => !(await fileExists(webDriverIo)); const passed = await this.execute('Ensure webdriverio does not exist', validate); if (!passed) { throw new 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 () => !(await fileExists(herokuCliUtil)); const passed = await this.execute('Ensure heroku-cli-util/.nyc_output does not exist', validate); if (!passed) { throw new 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 = ensure(this.flags.cli); const supportedUsernameLength = 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.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.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 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 SfError(`${apexNodePath} 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 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.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 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}/npm-shrinkwrap.json`, `${this.baseDir}/oclif.manifest.json`, `${this.baseDir}/oclif.lock`, `${this.baseDir}/bin/*`, `${this.baseDir}/dist/**/*.js`, `${this.baseDir}/dist/builtins/package.json`, `${this.baseDir}/scripts/*`, `${this.baseDir}/sf/**/*`, `${this.baseDir}/theme.json`, ]; 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.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 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 = parseJson(fileData, path.join(this.baseDir, 'package.json'), false); const plugins = 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 SfError('Found no .md message files. Was the clean too aggressive?'); } } 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 = shelljs.exec(`${sfBin} --version`, { silent: false }); const help = shelljs.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 SfError('sf was not included! Did include-sf.js succeed?'); } } } //# sourceMappingURL=verify.js.map