@salesforce/plugin-release-management
Version:
A plugin for preparing and publishing npm packages
143 lines • 6.2 kB
JavaScript
/*
* Copyright (c) 2023, 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
*/
/* eslint-disable no-await-in-loop */
import fs from 'node:fs';
import path from 'node:path';
import os from 'node:os';
import { promisify } from 'node:util';
import { exec as execSync } from 'node:child_process';
import { Ux } from '@salesforce/sf-plugins-core';
import chalk from 'chalk';
import { SfError } from '@salesforce/core';
import { parseJson } from '@salesforce/kit';
import stripAnsi from 'strip-ansi';
const exec = promisify(execSync);
async function exists(filePath) {
try {
await fs.promises.access(filePath);
return true;
}
catch {
return false;
}
}
export async function testJITInstall(options) {
const { jsonEnabled, jitPlugin } = options;
let { executable } = options;
const ux = new Ux({ jsonEnabled });
if (executable.endsWith('run') && !(await exists(executable))) {
// This is a workaround for the ESM branch of sf.
// If the executable is bin/run but it doesn't exist, that likely means
// that bin/run.js is the executable.
executable += '.js';
}
const tmpDir = path.join(os.tmpdir(), 'sf-jit-test');
// Clear tmp dir before test to ensure that we're starting from a clean slate
await fs.promises.rm(tmpDir, { recursive: true, force: true });
await fs.promises.mkdir(tmpDir, { recursive: true });
ux.styledHeader('Testing JIT installation');
const fileData = await fs.promises.readFile('package.json', 'utf8');
const packageJson = parseJson(fileData);
const jitPlugins = jitPlugin ?? Object.keys(packageJson.oclif?.jitPlugins ?? {});
if (jitPlugins.length === 0)
return;
let manifestData;
try {
manifestData = options.manifestPath
? await fs.promises.readFile(options.manifestPath, 'utf8')
: await fs.promises.readFile('oclif.manifest.json', 'utf8');
}
catch {
ux.log('No oclif.manifest.json found. Generating one now.');
await exec('yarn oclif manifest');
manifestData = await fs.promises.readFile('oclif.manifest.json', 'utf8');
}
const manifest = parseJson(manifestData);
const commands = Object.values(manifest.commands);
const help = async (command) => {
try {
await exec(`${executable} ${command} --help`);
return true;
}
catch (e) {
return false;
}
};
const verifyInstall = async (plugin, dataDir) => {
const userPjsonRaw = await fs.promises.readFile(path.join(dataDir, 'package.json'), 'utf-8');
const userPjson = parseJson(userPjsonRaw);
return Boolean(userPjson.dependencies?.[plugin]);
};
const passedInstalls = [];
const failedInstalls = [];
await Promise.all(jitPlugins.map(async (plugin) => {
ux.log(`Testing JIT install for ${plugin}`);
const dataDir = path.join(tmpDir, plugin, 'data');
const cacheDir = path.join(tmpDir, plugin, 'cache');
const configDir = path.join(tmpDir, plugin, 'config');
await fs.promises.mkdir(dataDir, { recursive: true });
await fs.promises.mkdir(cacheDir, { recursive: true });
await fs.promises.mkdir(configDir, { recursive: true });
let resultStdout = '';
let resultStderr = '';
try {
const firstCommand = commands.find((c) => c.pluginName === plugin);
if (!firstCommand) {
throw new SfError(`Unable to find command for ${plugin}`);
}
// Test that --help works on JIT commands
const helpResult = await help(firstCommand.id);
ux.log(`${executable} ${firstCommand.id} --help ${helpResult ? chalk.green('PASSED') : chalk.red('FAILED')}`);
ux.log(`${executable} ${firstCommand.id}`);
// Test that executing the command will trigger JIT install
// This will likely always fail because we're not providing all the required flags or it depends on some other setup.
// However, this is okay because all we need to verify is that running the command will trigger the JIT install
const { stdout, stderr } = await exec(`${executable} ${firstCommand.id}`, {
env: {
...process.env,
SF_DATA_DIR: dataDir,
SF_CACHE_DIR: cacheDir,
SF_CONFIG_DIR: configDir,
},
});
resultStdout = stripAnsi(stdout);
resultStderr = stripAnsi(stderr);
}
catch (e) {
const err = e;
// @ts-expect-error ExecException type doesn't have a stdout or stderr property
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
resultStdout = stripAnsi(err.stdout);
// @ts-expect-error ExecException type doesn't have a stdout or stderr property
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
resultStderr = stripAnsi(err.stderr);
}
finally {
const result = await verifyInstall(plugin, dataDir);
if (result) {
ux.log(`✅ ${chalk.green(`Verified installation of ${plugin}\n`)}`);
passedInstalls.push(plugin);
}
else {
ux.log(`❌ ${chalk.red(`Failed installation of ${plugin}\n`)}`);
failedInstalls.push(plugin);
ux.log(`stdout:\n${resultStdout}`);
ux.log(`stderr:\n${resultStderr}`);
}
}
}));
ux.styledHeader('JIT Installation Results');
ux.log(`Passed (${passedInstalls.length})`);
passedInstalls.forEach((msg) => ux.log(`• ${msg}`));
if (failedInstalls.length) {
ux.log();
ux.log(`Failed (${failedInstalls.length})`);
failedInstalls.forEach((msg) => ux.log(`• ${msg}`));
throw new SfError('Failed JIT installation');
}
}
//# sourceMappingURL=jit.js.map