snyk-sbt-plugin
Version:
Snyk CLI SBT plugin
231 lines • 9.72 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildArgs = exports.inspect = void 0;
const path = require("path");
const fs = require("fs");
const semver = require("semver");
const debugModule = require("debug");
// To enable debugging output, run the CLI as `DEBUG=snyk-sbt-plugin snyk ...`
const debug = debugModule('snyk-sbt-plugin');
const constants_1 = require("./constants");
const subProcess = require("./sub-process");
const parser = require("./parse-sbt");
const plugin_search_1 = require("./plugin-search");
const version_1 = require("./version");
const tmp = require("tmp");
tmp.setGracefulCleanup();
const packageFormatVersion = 'mvn:0.0.1';
async function inspect(root, targetFile, options) {
if (!options) {
options = { dev: false };
}
const isCoursierPresent = await (0, plugin_search_1.isPluginInstalled)(root, targetFile, constants_1.sbtCoursierPluginName);
const isSbtDependencyGraphPresent = await (0, plugin_search_1.isPluginInstalled)(root, targetFile, constants_1.sbtDependencyGraphPluginName);
const isNewSbtDependencyGraphPresent = await (0, plugin_search_1.isPluginInstalled)(root, targetFile, constants_1.sbtDependencyGraphPluginNameNew);
debug(`isCoursierPresent: ${isCoursierPresent}, isSbtDependencyGraphPresent: ${isSbtDependencyGraphPresent},
isNewSbtDependencyGraphPresent: ${isNewSbtDependencyGraphPresent}`);
// If coursier is present, use it because it gives different results to sbt dependencyTree
let result;
if (isCoursierPresent) {
try {
options.useCoursier = true;
result = await legacyInspect(root, targetFile, options);
return result;
}
catch (err) {
debug('Coursier failed with error: ', err);
}
}
// TODO:Legacy path creates a large output that can cause RangeError in bigger projects
// We would prefer to use plugin inspect by default but currently it requires sbt-dep-graph plugin
if (isSbtDependencyGraphPresent) {
try {
debug('Applying plugin inspect');
result = await pluginInspect(root, targetFile, options);
}
catch (err) {
debug('pluginInspect failed with error: ', err);
}
if (result) {
result.package.packageFormatVersion = packageFormatVersion;
return result;
}
}
options.useCoursier = false;
try {
result = await legacyInspect(root, targetFile, options);
return result;
}
catch (err) {
const hintMsg = buildHintMessage(options);
err.message = err.message + '\n' + hintMsg;
throw new Error(err);
}
}
exports.inspect = inspect;
async function legacyInspect(root, targetFile, options) {
const targetFilePath = path.dirname(path.resolve(root, targetFile));
if (!fs.existsSync(targetFilePath)) {
debug(`build.sbt not found at location: ${targetFilePath}. This may result in no dependencies`);
}
const useCoursier = options.useCoursier;
const sbtArgs = buildArgs(options.args, useCoursier);
debug(`running command: sbt ${sbtArgs.join(' ')}`);
const result = {
sbtOutput: await subProcess.execute('sbt', sbtArgs, {
cwd: targetFilePath,
}),
coursier: useCoursier,
};
const packageName = path.basename(root);
const packageVersion = '0.0.0';
const depTree = parser.parse(result.sbtOutput, packageName, packageVersion, result.coursier);
depTree.packageFormatVersion = packageFormatVersion;
return {
plugin: {
name: 'bundled:sbt',
runtime: 'unknown',
},
package: depTree,
};
}
async function injectSbtScript(sbtPluginPath, targetFolderPath) {
try {
// We could be running from a bundled CLI generated by `pkg`.
// The Node filesystem in that case is not real: https://github.com/zeit/pkg#snapshot-filesystem
// Copying the injectable script into a temp file.
let projectFolderPath = path.resolve(targetFolderPath, 'project/');
debug(`injectSbtScript: injecting snyk sbt plugin "${sbtPluginPath}" in "${projectFolderPath}"`);
if (!fs.existsSync(projectFolderPath)) {
debug(`injectSbtScript: "${projectFolderPath}" does not exist`);
projectFolderPath = path.resolve(targetFolderPath, '..', 'project/');
debug(`injectSbtScript: will try "${projectFolderPath}"`);
}
const tmpSbtPlugin = tmp.fileSync({
postfix: '-SnykSbtPlugin.scala',
dir: projectFolderPath,
});
fs.createReadStream(sbtPluginPath).pipe(fs.createWriteStream(tmpSbtPlugin.name));
debug(`successfully injected plugin at "${tmpSbtPlugin.name}"`);
return { path: tmpSbtPlugin.name, remove: tmpSbtPlugin.removeCallback };
}
catch (error) {
error.message =
error.message +
'\n\n' +
'Failed to create a temporary file to host Snyk script for SBT build analysis.';
throw error;
}
}
function generateSbtPluginPath(sbtVersion) {
let pluginName = 'SnykSbtPlugin-1.2x.scala';
if (semver.lt(sbtVersion, '0.1.0')) {
throw new Error('Snyk does not support sbt with version less than 0.1.0');
}
if (semver.gte(sbtVersion, '0.1.0') && semver.lt(sbtVersion, '1.1.0')) {
pluginName = 'SnykSbtPlugin-0.1x.scala';
}
if (/index.[tj]s$/.test(__filename)) {
debug('Applying ', pluginName);
return path.join(__dirname, `../scala/${pluginName}`);
}
else {
throw new Error(`Cannot locate ${pluginName} script`);
}
}
async function pluginInspect(root, targetFile, options) {
let injectedScript;
try {
const targetFolderPath = path.dirname(path.resolve(root, targetFile));
const sbtArgs = buildArgs(options.args, false, true);
const sbtVersion = await (0, version_1.getSbtVersion)(root, targetFile);
const sbtPluginPath = generateSbtPluginPath(sbtVersion);
const packageName = path.basename(root);
const packageVersion = '1.0.0';
injectedScript = await injectSbtScript(sbtPluginPath, targetFolderPath);
debug('injectedScript.path: ' + injectedScript.path);
debug('args passed to plugin inspect: ', sbtArgs.join(' '));
const stdout = await subProcess.execute('sbt', sbtArgs, {
cwd: targetFolderPath,
});
return {
plugin: {
name: 'snyk:sbt',
runtime: 'unknown',
meta: {
versionBuildInfo: {
metaBuildVersion: {
sbtVersion,
},
},
},
},
package: parser.parseSbtPluginResults(stdout, packageName, packageVersion),
};
}
catch (error) {
debug('Failed to produce dependency tree with custom snyk plugin due to error: ' +
error.message);
return null;
}
finally {
// in case of subProcess.execute failing, perform cleanup here, as putting it after `getInjectScriptPath` might
// not be executed because of `sbt` failing
if (injectedScript && injectedScript.remove) {
try {
injectedScript.remove();
debug(`Removed the snyk sbt plugin at '${injectedScript.path}'`);
}
catch (error) {
// NOTE(alexmu): we don't want to kill the whole run because we can still fall back to the legacy
// method, but at least tell the user to clean up after the CLI :(.
// tslint:disable-next-line:no-console
console.warn(`Failed to remove the snyk sbt plugin file at '${injectedScript.path}'`);
}
}
}
}
function buildHintMessage(options) {
const dgArgs = '`sbt ' + buildArgs(options.args, false).join(' ') + '`';
const csArgs = '`sbt ' + buildArgs(options.args, true).join(' ') + '`';
return ('\n\n' +
'Please make sure that the `sbt-dependency-graph` plugin ' +
'(https://github.com/jrudolph/sbt-dependency-graph) is installed ' +
'globally or on the current project, and that ' +
dgArgs +
' executes successfully on this project.\n\n' +
'Alternatively you can use `sbt-coursier` for dependency resolution ' +
'(https://get-coursier.io/docs/sbt-coursier), in which case ensure ' +
'that the plugin is installed on the current project and that ' +
csArgs +
' executes successfully on this project.\n\n' +
'For this project we guessed that you are using ' +
(options.useCoursier ? 'sbt-coursier' : 'sbt-dependency-graph') +
'.\n\n' +
'If the problem persists, collect the output of ' +
dgArgs +
' or ' +
csArgs +
' and contact support@snyk.io\n');
}
function buildArgs(sbtArgs, isCoursierProject, isOutputGraph) {
// force plain output so we don't have to parse colour codes
let args = ['-Dsbt.log.noformat=true'];
if (sbtArgs) {
args = args.concat(sbtArgs);
}
if (isOutputGraph) {
args.push('snykRenderTree'); // sbt-dependency-graph
}
else if (isCoursierProject) {
args.push('coursierDependencyTree'); // coursier
}
else {
// enhance sbt default output width from 40 chars to the max
args.push('set asciiGraphWidth := 999999999');
args.push('dependencyTree'); // sbt native
}
return args;
}
exports.buildArgs = buildArgs;
//# sourceMappingURL=index.js.map
;