@contrast/contrast 1.0.2 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/.prettierignore +1 -0
  2. package/README.md +6 -4
  3. package/dist/audit/languageAnalysisEngine/langugageAnalysisFactory.js +25 -0
  4. package/dist/commands/audit/saveFile.js +11 -0
  5. package/dist/commands/auth/auth.js +19 -1
  6. package/dist/commands/config/config.js +19 -8
  7. package/dist/commands/scan/processScan.js +4 -22
  8. package/dist/common/HTTPClient.js +11 -0
  9. package/dist/common/errorHandling.js +9 -23
  10. package/dist/common/{findLatestCLIVersion.js → versionChecker.js} +10 -3
  11. package/dist/constants/constants.js +4 -2
  12. package/dist/constants/locales.js +17 -7
  13. package/dist/constants.js +34 -2
  14. package/dist/index.js +48 -30
  15. package/dist/sbom/generateSbom.js +20 -0
  16. package/dist/scan/help.js +4 -2
  17. package/dist/scan/populateProjectIdAndProjectName.js +1 -0
  18. package/dist/scan/saveResults.js +9 -10
  19. package/dist/scan/scan.js +27 -2
  20. package/dist/scan/scanConfig.js +20 -1
  21. package/dist/scan/scanController.js +7 -2
  22. package/dist/scan/scanResults.js +5 -0
  23. package/dist/utils/requestUtils.js +1 -1
  24. package/dist/utils/saveFile.js +19 -0
  25. package/package.json +1 -1
  26. package/src/audit/languageAnalysisEngine/langugageAnalysisFactory.js +32 -0
  27. package/src/commands/audit/processAudit.ts +0 -1
  28. package/src/commands/audit/saveFile.ts +6 -0
  29. package/src/commands/auth/auth.js +25 -1
  30. package/src/commands/config/config.js +22 -8
  31. package/src/commands/scan/processScan.js +4 -23
  32. package/src/common/HTTPClient.js +13 -0
  33. package/src/common/errorHandling.ts +11 -24
  34. package/src/common/versionChecker.ts +39 -0
  35. package/src/constants/constants.js +5 -4
  36. package/src/constants/locales.js +23 -8
  37. package/src/constants.js +37 -2
  38. package/src/index.ts +63 -36
  39. package/src/sbom/generateSbom.ts +17 -0
  40. package/src/scan/help.js +4 -2
  41. package/src/scan/populateProjectIdAndProjectName.js +1 -0
  42. package/src/scan/saveResults.js +8 -9
  43. package/src/scan/scan.js +30 -2
  44. package/src/scan/scanConfig.js +26 -1
  45. package/src/scan/scanController.js +9 -2
  46. package/src/scan/scanResults.js +10 -0
  47. package/src/utils/getConfig.ts +2 -0
  48. package/src/utils/requestUtils.js +1 -1
  49. package/src/utils/saveFile.js +19 -0
  50. package/src/common/findLatestCLIVersion.ts +0 -27
@@ -1,15 +1,14 @@
1
1
  "use strict";
2
2
  const fs = require('fs');
3
- const writeResultsToFile = (responseBody, name = 'results.sarif') => {
4
- fs.writeFile(name, JSON.stringify(responseBody, null, 2), err => {
5
- if (err) {
6
- console.log('Error writing Scan Results to file');
7
- }
8
- else {
9
- console.log(`Scan Results saved to ${name}`);
10
- }
11
- });
3
+ const writeResultsToFile = async (responseBody, name = 'results.sarif') => {
4
+ try {
5
+ fs.writeFileSync(name, JSON.stringify(responseBody, null, 2));
6
+ console.log(`Scan Results saved to ${name}`);
7
+ }
8
+ catch (err) {
9
+ console.log('Error writing Scan Results to file');
10
+ }
12
11
  };
13
12
  module.exports = {
14
- writeResultsToFile
13
+ writeResultsToFile: writeResultsToFile
15
14
  };
package/dist/scan/scan.js CHANGED
@@ -50,6 +50,7 @@ const sendScan = async (config) => {
50
50
  oraWrapper.failSpinner(startUploadSpinner, i18n.__('uploadingScanFail'));
51
51
  if (res.statusCode === 403) {
52
52
  console.log(i18n.__('permissionsError'));
53
+ process.exit(1);
53
54
  }
54
55
  console.log(i18n.__('genericServiceError', res.statusCode));
55
56
  process.exit(1);
@@ -79,6 +80,15 @@ const formatScanOutput = (overview, results) => {
79
80
  console.log(`\t ${count}. ${lineInfo}`);
80
81
  count++;
81
82
  });
83
+ if (entry?.cwe && entry?.cwe.length > 0) {
84
+ formatLinks('cwe', entry.cwe);
85
+ }
86
+ if (entry?.reference && entry?.reference.length > 0) {
87
+ formatLinks('reference', entry.reference);
88
+ }
89
+ if (entry?.owasp && entry?.owasp.length > 0) {
90
+ formatLinks('owasp', entry.owasp);
91
+ }
82
92
  console.log(chalk.bold('How to fix:'));
83
93
  console.log(entry.recommendation);
84
94
  console.log();
@@ -88,10 +98,18 @@ const formatScanOutput = (overview, results) => {
88
98
  overview.medium +
89
99
  overview.low +
90
100
  overview.note;
91
- console.log(chalk.bold(`Found ${totalVulnerabilities} vulnerabilities`));
101
+ let vulMessage = totalVulnerabilities === 1 ? `vulnerability` : `vulnerabilities`;
102
+ console.log(chalk.bold(`Found ${totalVulnerabilities} ${vulMessage}`));
92
103
  console.log(i18n.__('foundDetailedVulnerabilities', overview.critical, overview.high, overview.medium, overview.low, overview.note));
93
104
  }
94
105
  };
106
+ const formatLinks = (objName, entry) => {
107
+ console.log(chalk.bold(objName + ':'));
108
+ entry.forEach(link => {
109
+ console.log(link);
110
+ });
111
+ console.log();
112
+ };
95
113
  const getGroups = content => {
96
114
  const groupTypeSet = new Set(content.map(({ ruleId }) => ruleId));
97
115
  let groupTypeResults = [];
@@ -99,12 +117,18 @@ const getGroups = content => {
99
117
  let groupResultsObj = {
100
118
  ruleId: groupName,
101
119
  lineInfoSet: new Set(),
120
+ cwe: '',
121
+ owasp: '',
122
+ reference: '',
102
123
  recommendation: '',
103
124
  severity: ''
104
125
  };
105
126
  content.forEach(resultEntry => {
106
127
  if (resultEntry.ruleId === groupName) {
107
128
  groupResultsObj.severity = resultEntry.severity;
129
+ groupResultsObj.cwe = resultEntry.cwe;
130
+ groupResultsObj.owasp = resultEntry.owasp;
131
+ groupResultsObj.reference = resultEntry.reference;
108
132
  groupResultsObj.recommendation = resultEntry.recommendation
109
133
  ? stripMustacheTags(resultEntry.recommendation)
110
134
  : 'No Recommendations Data Found';
@@ -132,5 +156,6 @@ module.exports = {
132
156
  allowedFileTypes: allowedFileTypes,
133
157
  isFileAllowed: isFileAllowed,
134
158
  stripMustacheTags: stripMustacheTags,
135
- formatScanOutput: formatScanOutput
159
+ formatScanOutput: formatScanOutput,
160
+ formatLinks: formatLinks
136
161
  };
@@ -3,9 +3,24 @@ const paramHandler = require('../utils/paramsUtil/paramHandler');
3
3
  const constants = require('../../src/constants.js');
4
4
  const parsedCLIOptions = require('../../src/utils/parsedCLIOptions');
5
5
  const path = require('path');
6
+ const { supportedLanguages } = require('../audit/languageAnalysisEngine/constants');
7
+ const i18n = require('i18n');
8
+ const { scanUsageGuide } = require('./help');
6
9
  const getScanConfig = argv => {
7
10
  let scanParams = parsedCLIOptions.getCommandLineArgsCustom(argv, constants.commandLineDefinitions.scanOptionDefinitions);
11
+ if (scanParams.help) {
12
+ printHelpMessage();
13
+ process.exit(0);
14
+ }
8
15
  const paramsAuth = paramHandler.getAuth(scanParams);
16
+ if (scanParams.language) {
17
+ scanParams.language = scanParams.language.toUpperCase();
18
+ if (!Object.values(supportedLanguages).includes(scanParams.language)) {
19
+ console.log(`Did not recognise --language ${scanParams.language}`);
20
+ console.log(i18n.__('constantsHowToRunDev3'));
21
+ process.exit(0);
22
+ }
23
+ }
9
24
  if (!scanParams.name && scanParams.file) {
10
25
  scanParams.name = getFileName(scanParams.file);
11
26
  }
@@ -14,7 +29,11 @@ const getScanConfig = argv => {
14
29
  const getFileName = file => {
15
30
  return file.split(path.sep).pop();
16
31
  };
32
+ const printHelpMessage = () => {
33
+ console.log(scanUsageGuide);
34
+ };
17
35
  module.exports = {
18
36
  getScanConfig,
19
- getFileName
37
+ getFileName,
38
+ printHelpMessage
20
39
  };
@@ -6,6 +6,7 @@ const scan = require('./scan');
6
6
  const scanResults = require('./scanResults');
7
7
  const autoDetection = require('./autoDetection');
8
8
  const fileFunctions = require('./fileUtils');
9
+ const { performance } = require('perf_hooks');
9
10
  const getTimeout = config => {
10
11
  if (config.timeout) {
11
12
  return config.timeout;
@@ -21,7 +22,7 @@ const fileAndLanguageLogic = async (configToUse) => {
21
22
  if (configToUse.file) {
22
23
  if (!fileFunctions.fileExists(configToUse.file)) {
23
24
  console.log(i18n.__('fileNotExist'));
24
- process.exit(0);
25
+ process.exit(1);
25
26
  }
26
27
  return configToUse;
27
28
  }
@@ -32,17 +33,21 @@ const fileAndLanguageLogic = async (configToUse) => {
32
33
  }
33
34
  };
34
35
  const startScan = async (configToUse) => {
36
+ const startTime = performance.now();
35
37
  await fileAndLanguageLogic(configToUse);
36
38
  if (!configToUse.projectId) {
37
39
  configToUse.projectId = await populateProjectIdAndProjectName.populateProjectId(configToUse);
38
40
  }
39
41
  const codeArtifactId = await scan.sendScan(configToUse);
40
42
  if (!configToUse.ff) {
41
- const startScanSpinner = returnOra('Contrast Scan started');
43
+ const startScanSpinner = returnOra('🚀 Contrast Scan started');
42
44
  startSpinner(startScanSpinner);
43
45
  const scanDetail = await scanResults.returnScanResults(configToUse, codeArtifactId, getTimeout(configToUse), startScanSpinner);
44
46
  const scanResultsInstances = await scanResults.returnScanResultsInstances(configToUse, scanDetail.id);
47
+ const endTime = performance.now();
48
+ const scanDurationMs = endTime - startTime;
45
49
  succeedSpinner(startScanSpinner, 'Contrast Scan complete');
50
+ console.log(`----- Scan completed in ${(scanDurationMs / 1000).toFixed(2)}s -----`);
46
51
  const projectOverview = await scanResults.returnScanProjectById(configToUse);
47
52
  return { projectOverview, scanDetail, scanResultsInstances };
48
53
  }
@@ -3,6 +3,7 @@ const commonApi = require('../utils/commonApi');
3
3
  const requestUtils = require('../../src/utils/requestUtils');
4
4
  const oraFunctions = require('../utils/oraWrapper');
5
5
  const _ = require('lodash');
6
+ const i18n = require('i18n');
6
7
  const getScanId = async (config, codeArtifactId, client) => {
7
8
  return client
8
9
  .getScanId(config, codeArtifactId)
@@ -41,6 +42,10 @@ const returnScanResults = async (config, codeArtifactId, timeout, startScanSpinn
41
42
  complete = true;
42
43
  oraFunctions.failSpinner(startScanSpinner, 'Contrast Scan Failed.');
43
44
  console.log(result.body.errorMessage);
45
+ if (result.body.errorMessage ===
46
+ 'Unable to determine language for code artifact') {
47
+ console.log('Try scanning again using --language param. ', i18n.__('scanOptionsLanguageSummary'));
48
+ }
44
49
  process.exit(1);
45
50
  }
46
51
  }
@@ -6,7 +6,7 @@ function sendRequest({ options, method = 'put' }) {
6
6
  return request[`${method}Async`](options.url, options);
7
7
  }
8
8
  const millisToSeconds = millis => {
9
- return ((millis % 60000) / 1000).toFixed(0);
9
+ return (millis / 1000).toFixed(0);
10
10
  };
11
11
  const sleep = ms => {
12
12
  return new Promise(resolve => setTimeout(resolve, ms));
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ const { SARIF_FILE } = require('../constants/constants');
3
+ const commonApi = require('./commonApi');
4
+ const saveResults = require('../scan/saveResults');
5
+ const i18n = require('i18n');
6
+ const saveScanFile = async (config, scanResults) => {
7
+ if (config.save === null || config.save.toUpperCase() === SARIF_FILE) {
8
+ const scanId = scanResults.scanDetail.id;
9
+ const client = commonApi.getHttpClient(config);
10
+ const rawResults = await client.getSpecificScanResultSarif(config, scanId);
11
+ await saveResults.writeResultsToFile(rawResults?.body);
12
+ }
13
+ else {
14
+ console.log(i18n.__('scanNoFiletypeSpecifiedForSave'));
15
+ }
16
+ };
17
+ module.exports = {
18
+ saveScanFile: saveScanFile
19
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrast/contrast",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "Contrast Security's command line tool",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -13,6 +13,10 @@ const { vulnerabilityReport } = require('./report/reportingFeature')
13
13
  const { vulnReportWithoutDevDep } = require('./report/newReportingFeature')
14
14
  const { checkDevDeps } = require('./report/checkIgnoreDevDep')
15
15
  const { newSendSnapShot } = require('../languageAnalysisEngine/sendSnapshot')
16
+ const fs = require('fs')
17
+ const chalk = require('chalk')
18
+ const saveFile = require('../../commands/audit/saveFile').default
19
+ const generateSbom = require('../../sbom/generateSbom').default
16
20
 
17
21
  module.exports = exports = (err, analysis) => {
18
22
  const { identifiedLanguageInfo } = analysis.languageAnalysis
@@ -59,6 +63,10 @@ module.exports = exports = (err, analysis) => {
59
63
  await vulnerabilityReport(analysis, catalogueAppId, config)
60
64
  }
61
65
  }
66
+
67
+ //should be moved to processAudit.ts once promises implemented
68
+ await auditSave(config)
69
+
62
70
  console.log(
63
71
  '\n ***************CONTRAST OSS ANALYSIS COMPLETE************** \n'
64
72
  )
@@ -92,3 +100,27 @@ module.exports = exports = (err, analysis) => {
92
100
  goAE(identifiedLanguageInfo, analysis.config, langCallback)
93
101
  }
94
102
  }
103
+
104
+ async function auditSave(config) {
105
+ //should be moved to processAudit.ts once promises implemented
106
+ if (config.save) {
107
+ if (config.save.toLowerCase() === 'sbom') {
108
+ saveFile(config, await generateSbom(config))
109
+
110
+ const filename = `${config.applicationId}-sbom-cyclonedx.json`
111
+ if (fs.existsSync(filename)) {
112
+ console.log(i18n.__('auditSBOMSaveSuccess') + ` - ${filename}`)
113
+ } else {
114
+ console.log(
115
+ chalk.yellow.bold(
116
+ `\n Unable to save ${filename} Software Bill of Materials (SBOM)`
117
+ )
118
+ )
119
+ }
120
+ } else {
121
+ console.log(i18n.__('auditBadFiletypeSpecifiedForSave'))
122
+ }
123
+ } else {
124
+ console.log(i18n.__('auditNoFiletypeSpecifiedForSave'))
125
+ }
126
+ }
@@ -11,7 +11,6 @@ export const processAudit = async (argv: parameterInput) => {
11
11
  }
12
12
  const config = getAuditConfig(argv)
13
13
  const auditResults = await startAudit(config)
14
- //report here
15
14
  }
16
15
 
17
16
  const printHelpMessage = () => {
@@ -0,0 +1,6 @@
1
+ import fs from 'fs'
2
+
3
+ export default function saveFile(config: any, rawResults: any) {
4
+ const fileName = `${config.applicationId}-sbom-cyclonedx.json`
5
+ fs.writeFileSync(fileName, JSON.stringify(rawResults))
6
+ }
@@ -11,8 +11,21 @@ const {
11
11
  succeedSpinner
12
12
  } = require('../../utils/oraWrapper')
13
13
  const { TIMEOUT, AUTH_UI_URL } = require('../../constants/constants')
14
+ const parsedCLIOptions = require('../../utils/parsedCLIOptions')
15
+ const constants = require('../../constants')
16
+ const commandLineUsage = require('command-line-usage')
17
+
18
+ const processAuth = async (argv, config) => {
19
+ let authParams = parsedCLIOptions.getCommandLineArgsCustom(
20
+ argv,
21
+ constants.commandLineDefinitions.authOptionDefinitions
22
+ )
23
+
24
+ if (authParams.help) {
25
+ console.log(authUsageGuide)
26
+ process.exit(0)
27
+ }
14
28
 
15
- const processAuth = async config => {
16
29
  const token = uuidv4()
17
30
  const url = `${AUTH_UI_URL}/?token=${token}`
18
31
 
@@ -68,6 +81,17 @@ const pollAuthResult = async (token, client) => {
68
81
  })
69
82
  }
70
83
 
84
+ const authUsageGuide = commandLineUsage([
85
+ {
86
+ header: i18n.__('authHeader'),
87
+ content: [i18n.__('constantsAuthHeaderContents')]
88
+ },
89
+ {
90
+ header: i18n.__('constantsAuthUsageHeader'),
91
+ content: [i18n.__('constantsAuthUsageContents')]
92
+ }
93
+ ])
94
+
71
95
  module.exports = {
72
96
  processAuth: processAuth
73
97
  }
@@ -1,15 +1,19 @@
1
- const commandLineArgs = require('command-line-args')
1
+ const parsedCLIOptions = require('../../utils/parsedCLIOptions')
2
+ const constants = require('../../constants')
3
+ const commandLineUsage = require('command-line-usage')
4
+ const i18n = require('i18n')
2
5
 
3
6
  const processConfig = (argv, config) => {
4
- const options = [{ name: 'clear', alias: 'c', type: Boolean }]
5
-
6
7
  try {
7
- const configOptions = commandLineArgs(options, {
8
+ let configParams = parsedCLIOptions.getCommandLineArgsCustom(
8
9
  argv,
9
- caseInsensitive: true,
10
- camelCase: true
11
- })
12
- if (configOptions.clear) {
10
+ constants.commandLineDefinitions.configOptionDefinitions
11
+ )
12
+ if (configParams.help) {
13
+ console.log(configUsageGuide)
14
+ process.exit(0)
15
+ }
16
+ if (configParams.clear) {
13
17
  config.clear()
14
18
  } else {
15
19
  console.log(JSON.parse(JSON.stringify(config.store)))
@@ -20,6 +24,16 @@ const processConfig = (argv, config) => {
20
24
  }
21
25
  }
22
26
 
27
+ const configUsageGuide = commandLineUsage([
28
+ {
29
+ header: i18n.__('configHeader')
30
+ },
31
+ {
32
+ content: [i18n.__('constantsConfigUsageContents')],
33
+ optionList: constants.commandLineDefinitions.configOptionDefinitions
34
+ }
35
+ ])
36
+
23
37
  module.exports = {
24
38
  processConfig: processConfig
25
39
  }
@@ -2,16 +2,9 @@ const { startScan } = require('../../scan/scanController')
2
2
  const { formatScanOutput } = require('../../scan/scan')
3
3
  const { scanUsageGuide } = require('../../scan/help')
4
4
  const scanConfig = require('../../scan/scanConfig')
5
- const saveResults = require('../../scan/saveResults')
6
- const commonApi = require('../../utils/commonApi')
7
- const i18n = require('i18n')
5
+ const { saveScanFile } = require('../../utils/saveFile')
8
6
 
9
7
  const processScan = async argvMain => {
10
- if (argvMain.indexOf('--help') !== -1) {
11
- printHelpMessage()
12
- process.exit(1)
13
- }
14
-
15
8
  let config = scanConfig.getScanConfig(argvMain)
16
9
 
17
10
  let scanResults = await startScan(config)
@@ -22,23 +15,11 @@ const processScan = async argvMain => {
22
15
  )
23
16
  }
24
17
 
25
- if (config.save) {
26
- if (config.save.toLowerCase() === 'sarif') {
27
- const scanId = scanResults.scanDetail.id
28
- const client = commonApi.getHttpClient(config)
29
- const rawResults = await client.getSpecificScanResultSarif(config, scanId)
30
- saveResults.writeResultsToFile(rawResults?.body)
31
- } else {
32
- console.log(i18n.__('scanNoFiletypeSpecifiedForSave'))
33
- }
18
+ if (config.save !== undefined) {
19
+ await saveScanFile(config, scanResults)
34
20
  }
35
21
  }
36
22
 
37
- const printHelpMessage = () => {
38
- console.log(scanUsageGuide)
39
- }
40
-
41
23
  module.exports = {
42
- processScan,
43
- printHelpMessage
24
+ processScan
44
25
  }
@@ -130,6 +130,9 @@ HTTPClient.prototype.createProjectId = function createProjectId(config) {
130
130
  name: config.name,
131
131
  archived: 'false'
132
132
  }
133
+ if (config.language) {
134
+ options.body.language = config.language
135
+ }
133
136
  options.url = createHarmonyProjectsUrl(config)
134
137
  return requestUtils.sendRequest({ method: 'post', options })
135
138
  }
@@ -317,6 +320,12 @@ HTTPClient.prototype.checkLibrary = function checkLibrary(data) {
317
320
  return requestUtils.sendRequest({ method: 'post', options })
318
321
  }
319
322
 
323
+ HTTPClient.prototype.getSbom = function getSbom(config) {
324
+ const options = _.cloneDeep(this.requestOptions)
325
+ options.url = createSbomCycloneDXUrl(config)
326
+ return requestUtils.sendRequest({ method: 'get', options })
327
+ }
328
+
320
329
  // scan
321
330
  const createGetScanIdURL = config => {
322
331
  return `${config.host}/Contrast/api/sast/v1/organizations/${config.organizationId}/projects/${config.projectId}/scans/`
@@ -386,6 +395,10 @@ const createGetDependencyTree = (protocol, orgUuid, appId, reportId) => {
386
395
  return `${protocol}/Contrast/api/ng/sca/organizations/${orgUuid}/applications/${appId}/reports/${reportId}`
387
396
  }
388
397
 
398
+ function createSbomCycloneDXUrl(config) {
399
+ return `${config.host}/Contrast/api/ng/${config.organizationId}/applications/${config.applicationId}/libraries/sbom/cyclonedx`
400
+ }
401
+
389
402
  module.exports = HTTPClient
390
403
  module.exports.pollForAuthUrl = pollForAuthUrl
391
404
  module.exports.getServerlessHost = getServerlessHost
@@ -73,6 +73,7 @@ const badRequestError = (catalogue: boolean) => {
73
73
 
74
74
  const forbiddenError = () => {
75
75
  generalError('forbiddenRequestErrorHeader', 'forbiddenRequestErrorMessage')
76
+ process.exit(1)
76
77
  }
77
78
 
78
79
  const proxyError = () => {
@@ -120,7 +121,7 @@ const generalError = (header: string, message?: string) => {
120
121
  console.log(finalMessage)
121
122
  }
122
123
 
123
- const approximateCommandOnError = (unknownOptions: string[]) => {
124
+ const findCommandOnError = (unknownOptions: string[]) => {
124
125
  const commandKeywords = {
125
126
  auth: 'auth',
126
127
  audit: 'audit',
@@ -128,34 +129,20 @@ const approximateCommandOnError = (unknownOptions: string[]) => {
128
129
  lambda: 'lambda',
129
130
  config: 'config'
130
131
  }
131
- const sortedUnknownOptions = sortBy(unknownOptions, param =>
132
- param === 'auth' ||
133
- param === 'audit' ||
134
- param === 'scan' ||
135
- param === 'lambda' ||
136
- param === 'config'
137
- ? 0
138
- : 1
139
- )
140
132
 
141
- const foundCommands = sortedUnknownOptions.filter(
133
+ const containsCommandKeyword = unknownOptions.some(
142
134
  // @ts-ignore
143
135
  command => commandKeywords[command]
144
136
  )
145
137
 
146
- const parsedUnknownOptions = sortedUnknownOptions
147
- .toString()
148
- .replace(/,/g, ' ')
149
-
150
- const approximateParams = parsedUnknownOptions
151
- .replace(new RegExp(foundCommands.join('|'), 'g'), '')
152
- .trim()
153
-
154
- const approximateCommand = `${foundCommands[0]} ${approximateParams}`
138
+ if (containsCommandKeyword) {
139
+ const foundCommands = unknownOptions.filter(
140
+ // @ts-ignore
141
+ command => commandKeywords[command]
142
+ )
155
143
 
156
- return {
157
- approximateCommand,
158
- approximateCommandKeyword: foundCommands[0]
144
+ //return the first command found
145
+ return foundCommands[0]
159
146
  }
160
147
  }
161
148
 
@@ -171,5 +158,5 @@ export {
171
158
  getErrorMessage,
172
159
  handleResponseErrors,
173
160
  libraryAnalysisError,
174
- approximateCommandOnError
161
+ findCommandOnError
175
162
  }
@@ -0,0 +1,39 @@
1
+ import latestVersion from 'latest-version'
2
+ import { APP_VERSION } from '../constants/constants'
3
+ import boxen from 'boxen'
4
+ import chalk from 'chalk'
5
+ import semver from 'semver'
6
+
7
+ export async function findLatestCLIVersion() {
8
+ const latestCLIVersion = await latestVersion('@contrast/contrast')
9
+
10
+ if (semver.lt(APP_VERSION, latestCLIVersion)) {
11
+ const updateAvailableMessage = `Update available ${chalk.yellow(
12
+ APP_VERSION
13
+ )} → ${chalk.green(latestCLIVersion)}`
14
+
15
+ const npmUpdateAvailableCommand = `Run ${chalk.cyan(
16
+ 'npm i @contrast/contrast -g'
17
+ )} to update via npm`
18
+
19
+ const homebrewUpdateAvailableCommand = `Run ${chalk.cyan(
20
+ 'brew install contrastsecurity/tap/contrast'
21
+ )} to update via brew`
22
+
23
+ console.log(
24
+ boxen(
25
+ `${updateAvailableMessage}\n${npmUpdateAvailableCommand}\n\n${homebrewUpdateAvailableCommand}`,
26
+ {
27
+ titleAlignment: 'center',
28
+ margin: 1,
29
+ padding: 1,
30
+ align: 'center'
31
+ }
32
+ )
33
+ )
34
+ }
35
+ }
36
+
37
+ export async function isCorrectNodeVersion(currentVersion: string) {
38
+ return semver.satisfies(currentVersion, '>=16.13.2 <17')
39
+ }
@@ -8,17 +8,17 @@ const GO = 'GO'
8
8
  // we set the langauge as Node instead of PHP since we're using the Node engine in TS
9
9
  const PHP = 'PHP'
10
10
  const JAVASCRIPT = 'JAVASCRIPT'
11
-
12
11
  const LOW = 'LOW'
13
12
  const MEDIUM = 'MEDIUM'
14
13
  const HIGH = 'HIGH'
15
14
  const CRITICAL = 'CRITICAL'
16
-
17
15
  const APP_NAME = 'contrast'
18
- const APP_VERSION = '1.0.2'
16
+ const APP_VERSION = '1.0.3'
19
17
  const TIMEOUT = 120000
18
+
20
19
  const AUTH_UI_URL = 'https://cli-auth.contrastsecurity.com'
21
20
  const AUTH_CALLBACK_URL = 'https://cli-auth-api.contrastsecurity.com'
21
+ const SARIF_FILE = 'SARIF'
22
22
 
23
23
  module.exports = {
24
24
  supportedLanguages: { NODE, DOTNET, JAVA, RUBY, PYTHON, GO, PHP, JAVASCRIPT },
@@ -30,5 +30,6 @@ module.exports = {
30
30
  APP_NAME,
31
31
  TIMEOUT,
32
32
  AUTH_UI_URL,
33
- AUTH_CALLBACK_URL
33
+ AUTH_CALLBACK_URL,
34
+ SARIF_FILE
34
35
  }