|
1 | 1 | 'use strict';
|
2 |
| -const childProcess = require('child_process'); |
3 |
| -const path = require('path'); |
4 | 2 | const { promises: fs } = require('fs');
|
5 |
| -const os = require('os'); |
6 |
| -const { glob } = require('glob'); |
7 |
| -const { promisify } = require('util'); |
8 |
| -const execFile = promisify(childProcess.execFile); |
| 3 | +const path = require('path'); |
| 4 | +const fetch = require('make-fetch-happen'); |
| 5 | + |
| 6 | +const MAKE_FETCH_HAPPEN_OPTIONS = { |
| 7 | + timeout: 10000, |
| 8 | + retry: { |
| 9 | + retries: 3, |
| 10 | + factor: 1, |
| 11 | + minTimeout: 1000, |
| 12 | + maxTimeout: 3000, |
| 13 | + randomize: true, |
| 14 | + }, |
| 15 | +}; |
| 16 | + |
| 17 | +async function snykTest(dependency) { |
| 18 | + const { name, version } = dependency; |
| 19 | + |
| 20 | + process.stdout.write(`Testing ${name}@${version} ... `); |
| 21 | + |
| 22 | + const response = await fetch( |
| 23 | + `https://api.snyk.io/v1/test/npm/${encodeURIComponent(name)}/${version}`, |
| 24 | + { |
| 25 | + ...MAKE_FETCH_HAPPEN_OPTIONS, |
| 26 | + headers: { |
| 27 | + Authorization: `token ${process.env.SNYK_TOKEN}`, |
| 28 | + }, |
| 29 | + } |
| 30 | + ); |
9 | 31 |
|
10 |
| -async function fileExists(filePath) { |
11 |
| - try { |
12 |
| - await fs.access(filePath); |
13 |
| - return true; |
14 |
| - } catch { |
15 |
| - return false; |
| 32 | + if (!response.ok) { |
| 33 | + throw new Error(`HTTP error! status: ${response.status}`); |
16 | 34 | }
|
17 |
| -} |
18 | 35 |
|
19 |
| -async function snykTest(cwd) { |
20 |
| - const tmpPath = path.join(os.tmpdir(), 'tempfile-' + Date.now()); |
| 36 | + const vulnerabilities = (await response.json()).issues?.vulnerabilities ?? []; |
21 | 37 |
|
22 |
| - let execErr; |
23 |
| - |
24 |
| - try { |
25 |
| - if (!(await fileExists(path.join(cwd, `package.json`)))) { |
26 |
| - return; |
27 |
| - } |
28 |
| - |
29 |
| - console.info(`testing ${cwd} ...`); |
30 |
| - await fs.mkdir(path.join(cwd, `node_modules`), { recursive: true }); |
31 |
| - |
32 |
| - try { |
33 |
| - await execFile( |
34 |
| - 'npx', |
35 |
| - [ |
36 |
| - 'snyk@latest', |
37 |
| - 'test', |
38 |
| - '--all-projects', |
39 |
| - '--severity-threshold=low', |
40 |
| - '--dev', |
41 |
| - `--json-file-output=${tmpPath}`, |
42 |
| - ], |
43 |
| - { |
44 |
| - cwd, |
45 |
| - maxBuffer: 50 /* MB */ * 1024 * 1024, // default is 1 MB |
46 |
| - } |
47 |
| - ); |
48 |
| - } catch (err) { |
49 |
| - execErr = err; |
50 |
| - } |
| 38 | + process.stdout.write(`Done\n`); |
51 | 39 |
|
52 |
| - const res = JSON.parse(await fs.readFile(tmpPath)); |
53 |
| - console.info(`testing ${cwd} done.`); |
54 |
| - return res; |
55 |
| - } catch (err) { |
56 |
| - console.error(`Snyk failed to create a json report for ${cwd}:`, execErr); |
57 |
| - throw new Error(`Testing ${cwd} failed.`); |
58 |
| - } finally { |
59 |
| - try { |
60 |
| - await fs.rm(tmpPath); |
61 |
| - } catch (error) { |
62 |
| - // |
63 |
| - } |
64 |
| - } |
| 40 | + return vulnerabilities.map((v) => { |
| 41 | + // for some reason the api doesn't add these properties unlike `snyk test` |
| 42 | + return { ...v, name: v.package, fixedIn: v.upgradePath ?? [] }; |
| 43 | + }); |
65 | 44 | }
|
66 | 45 |
|
67 | 46 | async function main() {
|
68 |
| - const rootPath = path.resolve(__dirname, '..'); |
| 47 | + if (!process.env.SNYK_TOKEN) { |
| 48 | + throw new Error('process.env.SNYK_TOKEN is missing.'); |
| 49 | + } |
69 | 50 |
|
70 |
| - const { workspaces } = JSON.parse( |
71 |
| - await fs.readFile(path.join(rootPath, 'package.json')) |
72 |
| - ); |
| 51 | + const rootPath = path.resolve(__dirname, '..'); |
73 | 52 |
|
74 |
| - const packages = (await Promise.all(workspaces.map((w) => glob(w)))).flat(); |
| 53 | + const dependenciesFile = path.join(rootPath, '.sbom', 'dependencies.json'); |
| 54 | + const dependencies = JSON.parse(await fs.readFile(dependenciesFile, 'utf-8')); |
75 | 55 |
|
76 | 56 | const results = [];
|
77 | 57 |
|
78 |
| - results.push(await snykTest(rootPath)); |
79 |
| - |
80 |
| - for (const location of packages) { |
81 |
| - const result = await snykTest(location); |
82 |
| - if (result) { |
83 |
| - results.push(result); |
| 58 | + for (const dependency of dependencies) { |
| 59 | + const vulnerabilities = await snykTest(dependency); |
| 60 | + if (vulnerabilities && vulnerabilities.length) { |
| 61 | + results.push({ vulnerabilities }); |
84 | 62 | }
|
85 | 63 | }
|
86 | 64 |
|
87 |
| - await fs.mkdir(path.join(rootPath, `.sbom`), { recursive: true }); |
88 |
| - |
89 | 65 | await fs.writeFile(
|
90 | 66 | path.join(rootPath, `.sbom/snyk-test-result.json`),
|
91 |
| - JSON.stringify(results.flat(), null, 2) |
92 |
| - ); |
93 |
| - |
94 |
| - await execFile( |
95 |
| - 'npx', |
96 |
| - [ |
97 |
| - 'snyk-to-html', |
98 |
| - '-i', |
99 |
| - path.join(rootPath, '.sbom/snyk-test-result.json'), |
100 |
| - '-o', |
101 |
| - path.join(rootPath, `.sbom/snyk-test-result.html`), |
102 |
| - ], |
103 |
| - { cwd: rootPath } |
| 67 | + JSON.stringify(results, null, 2) |
104 | 68 | );
|
105 | 69 | }
|
106 | 70 |
|
|
0 commit comments