├── coverage ├── favicon.png ├── lcov-report │ ├── favicon.png │ ├── sort-arrow-sprite.png │ ├── prettify.css │ ├── block-navigation.js │ ├── bin │ │ └── index.html │ ├── lib │ │ ├── report │ │ │ ├── long.js.html │ │ │ └── whitelist.js.html │ │ └── help.js.html │ ├── index.html │ ├── base.css │ └── sorter.js ├── sort-arrow-sprite.png ├── prettify.css ├── block-navigation.js ├── bin │ └── index.html ├── lib │ ├── report │ │ ├── long.js.html │ │ └── whitelist.js.html │ ├── help.js.html │ └── index.html ├── index.html ├── base.css └── sorter.js ├── .gitignore ├── tap-snapshots └── test │ ├── help.js.snap │ ├── details.js.snap │ ├── install.js.snap │ ├── report.js.snap │ ├── whitelist.js.snap │ ├── install.js.md │ ├── install.js.test.cjs │ ├── proxied.js.test.cjs │ ├── help.js.test.cjs │ ├── help.js.md │ ├── whitelist.js.md │ ├── whitelist.js.test.cjs │ ├── details.js.test.cjs │ └── details.js.md ├── .travis.yml ├── lib ├── help.js ├── report │ ├── long.js │ ├── whitelist.js │ ├── score.js │ ├── summary.js │ ├── short.js │ ├── github-action.js │ └── module.js ├── config.js ├── ncm-style.js ├── client-request.js └── util.js ├── test ├── fixtures │ ├── poisoned-project │ │ ├── package.json │ │ └── package-lock.json │ └── mock-project │ │ └── package.json ├── lib │ ├── install-mock-bin.js │ └── http-proxy-bin.js ├── help.js ├── details.js ├── github-actions.js ├── install.js ├── whitelist.js └── proxied.js ├── .c8rc.json ├── ava.config.js ├── LICENSE ├── tools └── check-deps.sh ├── commands ├── signout.js ├── help.js ├── orgs.js ├── config.js ├── signin.js ├── details.js └── install.js ├── contributing.md ├── bin └── ncm-cli.js ├── package.json └── changelog.md /coverage/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nodesource/ncm-cli/HEAD/coverage/favicon.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | test/fixtures/*/node_modules 3 | .DS_Store 4 | .vscode/ 5 | .nyc_output 6 | .tmp 7 | -------------------------------------------------------------------------------- /coverage/lcov-report/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nodesource/ncm-cli/HEAD/coverage/lcov-report/favicon.png -------------------------------------------------------------------------------- /coverage/sort-arrow-sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nodesource/ncm-cli/HEAD/coverage/sort-arrow-sprite.png -------------------------------------------------------------------------------- /tap-snapshots/test/help.js.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nodesource/ncm-cli/HEAD/tap-snapshots/test/help.js.snap -------------------------------------------------------------------------------- /tap-snapshots/test/details.js.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nodesource/ncm-cli/HEAD/tap-snapshots/test/details.js.snap -------------------------------------------------------------------------------- /tap-snapshots/test/install.js.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nodesource/ncm-cli/HEAD/tap-snapshots/test/install.js.snap -------------------------------------------------------------------------------- /tap-snapshots/test/report.js.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nodesource/ncm-cli/HEAD/tap-snapshots/test/report.js.snap -------------------------------------------------------------------------------- /tap-snapshots/test/whitelist.js.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nodesource/ncm-cli/HEAD/tap-snapshots/test/whitelist.js.snap -------------------------------------------------------------------------------- /coverage/lcov-report/sort-arrow-sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nodesource/ncm-cli/HEAD/coverage/lcov-report/sort-arrow-sprite.png -------------------------------------------------------------------------------- /tap-snapshots/test/install.js.md: -------------------------------------------------------------------------------- 1 | # Snapshot report for `test/install.js` 2 | 3 | The actual snapshot is saved in `install.js.snap`. 4 | 5 | Generated by [AVA](https://avajs.dev). 6 | 7 | ## install output matches snapshot 8 | 9 | > Snapshot 1 10 | 11 | `✓ has been set to:␊ 12 | test/lib/install-mock-bin.js␊ 13 | ␊ 14 | ` 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - "8" 5 | - "10" 6 | - "12" 7 | matrix: 8 | include: 9 | - node_js: "13" 10 | env: "NVM_NODEJS_ORG_MIRROR=https://nodejs.org/download/nightly" 11 | allow_failures: 12 | # Allow the nightly installs to fail 13 | - env: "NVM_NODEJS_ORG_MIRROR=https://nodejs.org/download/nightly" 14 | script: 15 | - npm run test 16 | -------------------------------------------------------------------------------- /lib/help.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { 4 | header, 5 | rawBox 6 | } = require('./ncm-style') 7 | const L = console.log 8 | 9 | module.exports = { 10 | helpHeader 11 | } 12 | 13 | function helpHeader (cmd, usage, usageRaw, description) { 14 | L() 15 | L(header(`NCM CLI Help: ncm ${cmd}`)) 16 | description ? L(description) : L() 17 | L('Usage:') 18 | L(rawBox(usage, usageRaw.length || usage.length)) 19 | L() 20 | } 21 | -------------------------------------------------------------------------------- /test/fixtures/poisoned-project/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "poisoned-project", 3 | "version": "0.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Jeremiah Senkpiel ", 10 | "license": "MIT", 11 | "dependencies": { 12 | "left-pad": "^1.3.0", 13 | "is-path-in-cwd": "^2.1.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.c8rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "all": true, 3 | "include": [ 4 | "bin/**/*.js", 5 | "lib/**/*.js", 6 | "index.js" 7 | ], 8 | "exclude": [ 9 | "test/**", 10 | "coverage/**", 11 | "node_modules/**", 12 | "**/*.d.ts", 13 | "tap-snapshots/**", 14 | "tools/**" 15 | ], 16 | "reporter": [ 17 | "text", 18 | "html", 19 | "lcov" 20 | ], 21 | "check-coverage": false, 22 | "per-file": true, 23 | "cache": false 24 | } 25 | -------------------------------------------------------------------------------- /lib/report/long.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = longReport 4 | 5 | const summary = require('./summary') 6 | const { moduleList } = require('./util') 7 | 8 | function longReport (report, whitelist, dir) { 9 | summary(report, dir) 10 | 11 | if (whitelist.length > 0) { 12 | moduleList(whitelist, 'Whitelisted Modules') 13 | 14 | moduleList(report, 'Non-whitelisted Modules') 15 | } else { 16 | moduleList(report, 'Modules') 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /test/fixtures/mock-project/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mock-project", 3 | "version": "0.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Jeremiah Senkpiel ", 10 | "license": "MIT", 11 | "dependencies": { 12 | "chalk": "^2.4.2", 13 | "debug": "2.2.0", 14 | "handlebars": "4.0.5", 15 | "brace-expansion": "1.1.2", 16 | "left-pad": "^1.3.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tap-snapshots/test/install.js.test.cjs: -------------------------------------------------------------------------------- 1 | /* IMPORTANT 2 | * This snapshot file is auto-generated, but designed for humans. 3 | * It should be checked into source control and tracked carefully. 4 | * Re-generate by setting TAP_SNAPSHOT=1 and running tests. 5 | * Make sure to inspect the output below. Do not ignore changes! 6 | */ 7 | 'use strict' 8 | exports[`test/install.js TAP install output matches snapshot > config-set-output 1`] = ` 9 | ✓ has been set to: 10 | test/lib/install-mock-bin.js 11 | 12 | 13 | ` 14 | -------------------------------------------------------------------------------- /ava.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // AVA settings 3 | files: ['test/*.js'], // Only include the main test files, not helper files in subdirectories 4 | concurrency: 5, // Similar to tap's -J flag 5 | environmentVariables: { 6 | FORCE_COLOR: '3', 7 | NODE_ENV: 'testing' 8 | }, 9 | verbose: true, 10 | timeout: '2m', // Generous timeout for tests 11 | snapshotDir: 'tap-snapshots', // Use existing snapshot directory for compatibility 12 | // Exclude helper files from test/lib directory 13 | ignoredByWatcher: [ 14 | 'test/lib/**/*.js' 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 NodeSource 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /test/lib/install-mock-bin.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict' 4 | 5 | process.argv.slice(2).forEach(arg => { 6 | if (arg.includes('@')) { 7 | console.log('[NCM::SECURITY] Scanning npm dependency substitution vulnerabilities...') 8 | console.log(`[NCM::SECURITY] Verifying the package '${arg.split('@')[0]}'`) 9 | } 10 | }) 11 | 12 | // Filter out color argument for test output 13 | const args = process.argv.slice(2).filter(arg => !arg.startsWith('--color=')) 14 | // Format args as a string array for consistent test output 15 | const formattedArgs = args.map(arg => `'${arg}'`) 16 | console.log('SUBCOMMAND ARGS: [', formattedArgs.join(', '), ']') 17 | -------------------------------------------------------------------------------- /tools/check-deps.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "Running 'dependency-check . --missing'" 4 | echo "Anything listed here should be added to package.json" 5 | echo "" 6 | node_modules/.bin/dependency-check . --missing 7 | 8 | missingStatus=$? 9 | echo "" 10 | 11 | echo "Running 'dependency-check . --unused'" 12 | echo "Anything listed here should be removed from package.json" 13 | echo "or added to the ignore-module options in tools/check-deps.sh" 14 | echo "" 15 | node_modules/.bin/dependency-check . --unused \ 16 | --no-dev 17 | 18 | unusedStatus=$? 19 | echo "" 20 | 21 | if [ $missingStatus -ne 0 ]; then exit 1; fi 22 | if [ $unusedStatus -ne 0 ]; then exit 1; fi 23 | -------------------------------------------------------------------------------- /coverage/prettify.css: -------------------------------------------------------------------------------- 1 | .pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} 2 | -------------------------------------------------------------------------------- /coverage/lcov-report/prettify.css: -------------------------------------------------------------------------------- 1 | .pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} 2 | -------------------------------------------------------------------------------- /lib/report/whitelist.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { moduleList } = require('./util') 4 | const { 5 | COLORS, 6 | tooltip 7 | } = require('../ncm-style') 8 | const L = console.log 9 | 10 | const chalk = require('chalk') 11 | 12 | module.exports = whitelistReport 13 | 14 | function whitelistReport (report) { 15 | L() 16 | L(chalk`${report.length} {${COLORS.light1} modules total}`) 17 | 18 | if (report.length === 0) { 19 | L() 20 | L(tooltip('Run `ncm whitelist --add @` to add a package to the whitelist.')) 21 | L() 22 | } else { 23 | /* todo: what to do if unpublished */ 24 | moduleList(report, 'Whitelisted Modules') 25 | L(tooltip('Run `ncm whitelist --remove @` to remove a package from the whitelist.')) 26 | L() 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /test/help.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { exec } = require('child_process') 4 | const path = require('path') 5 | const test = require('ava').default 6 | 7 | const NCM_BIN = path.join(__dirname, '..', 'bin', 'ncm-cli.js') 8 | 9 | // Simple test that doesn't need the test runner 10 | test('help output matches help snapshot', async t => { 11 | const { promisify } = require('util') 12 | const execPromise = promisify(exec) 13 | 14 | const { stdout, stderr } = await execPromise(`node ${NCM_BIN} help --color=16m`, { 15 | env: Object.assign({ FORCE_COLOR: 3 }, process.env) 16 | }) 17 | 18 | t.is(stderr, '') 19 | t.snapshot(stdout) // Removed id parameter as AVA 5.x doesn't support it 20 | t.regex(stdout, /NodeSource Certified Modules CLI Help/) 21 | t.regex(stdout, /Usage:/) 22 | }) 23 | -------------------------------------------------------------------------------- /commands/signout.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { setTokens } = require('../lib/config') 4 | const { helpHeader } = require('../lib/help') 5 | const { COLORS } = require('../lib/ncm-style') 6 | const chalk = require('chalk') 7 | const L = console.log 8 | 9 | module.exports = signout 10 | module.exports.optionsList = optionsList 11 | 12 | async function signout (argv) { 13 | if (argv.help) { 14 | printHelp() 15 | return 16 | } 17 | 18 | setTokens({ session: ' ', refreshToken: ' ' }) 19 | } 20 | 21 | function printHelp () { 22 | helpHeader( 23 | 'signout', 24 | chalk`{${COLORS.light1} ncm} {${COLORS.yellow} signout}`, 25 | 'ncm signout', 26 | ` 27 | Sign out. 28 | Has no impact on CI token usage. 29 | ` 30 | ) 31 | 32 | L(optionsList()) 33 | L() 34 | } 35 | 36 | function optionsList () { 37 | return chalk` 38 | {${COLORS.light1} ncm} {${COLORS.yellow} signout} 39 | `.trim() 40 | } 41 | -------------------------------------------------------------------------------- /test/lib/http-proxy-bin.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const http = require('http') 4 | const httpProxy = require('http-proxy') 5 | 6 | const target = process.argv[2] 7 | const PORT = process.argv[3] || 80 8 | 9 | // 10 | // Create a proxy server with custom application logic 11 | // 12 | const proxy = httpProxy.createProxyServer() 13 | 14 | // 15 | // Create your custom server and just call `proxy.web()` to proxy 16 | // a web request to the target passed in the options 17 | // also you can use `proxy.ws()` to proxy a websockets request 18 | // 19 | const server = http.createServer(function (req, res) { 20 | // You can define here your custom logic to handle the request 21 | // and then proxy the request. 22 | console.log('Request!') 23 | proxy.web(req, res, { target }) 24 | }) 25 | 26 | server.listen(PORT, _ => { 27 | const { port } = server.address() 28 | console.log(`listening on port ${port}`) 29 | 30 | if (typeof process.send === 'function') { 31 | process.send(port) 32 | } 33 | }) 34 | -------------------------------------------------------------------------------- /test/details.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // Only import what we need, avoid unused imports 4 | const { NCMTestRunner } = require('./lib/test-runner.js') 5 | 6 | // Use the new createTest helper function to create tests with the runner 7 | NCMTestRunner.createTest('details output matches snapshot', (runner, t) => { 8 | return new Promise(/** @type {(resolve: any) => void} */ (resolve) => { 9 | runner.exec('details npm @ 6.8.0', (err, stdout, stderr) => { 10 | t.is(err?.code, 1) 11 | t.is(stderr, '') 12 | t.snapshot(stdout) 13 | t.regex(stdout, /npm @ 6.8.0/) 14 | t.regex(stdout, /No Security Vulnerabilities/) 15 | t.regex(stdout, /Noncompliant license: Artistic-2.0/) 16 | 17 | resolve() 18 | }) 19 | }) 20 | }) 21 | 22 | NCMTestRunner.createTest('details dir output matches snapshot', (runner, t) => { 23 | return new Promise(/** @type {(resolve: any) => void} */ (resolve) => { 24 | runner.exec('details chalk@2.4.2 --dir ./test/fixtures/mock-project', (err, stdout, stderr) => { 25 | t.is(err?.code, 1) 26 | t.is(stderr, '') 27 | t.snapshot(stdout) 28 | t.regex(stdout, /chalk @ 2.4.2/) 29 | 30 | resolve() 31 | }) 32 | }) 33 | }) 34 | -------------------------------------------------------------------------------- /contributing.md: -------------------------------------------------------------------------------- 1 | 2 | ## Developer's Certificate of Origin 1.1 3 | 4 | By making a contribution to this project, I certify that: 5 | 6 | * (a) The contribution was created in whole or in part by me and I 7 | have the right to submit it under the open source license 8 | indicated in the file; or 9 | 10 | * (b) The contribution is based upon previous work that, to the best 11 | of my knowledge, is covered under an appropriate open source 12 | license and I have the right under that license to submit that 13 | work with modifications, whether created in whole or in part 14 | by me, under the same open source license (unless I am 15 | permitted to submit under a different license), as indicated 16 | in the file; or 17 | 18 | * (c) The contribution was provided directly to me by some other 19 | person who certified (a), (b) or (c) and I have not modified 20 | it. 21 | 22 | * (d) I understand and agree that this project and the contribution 23 | are public and that a record of the contribution (including all 24 | personal information I submit with it, including my sign-off) is 25 | maintained indefinitely and may be redistributed consistent with 26 | this project or the open source license(s) involved. 27 | -------------------------------------------------------------------------------- /test/fixtures/poisoned-project/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "poisoned-project", 3 | "version": "0.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "is-path-in-cwd": { 8 | "version": "2.1.0", 9 | "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz", 10 | "integrity": "sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ==", 11 | "requires": { 12 | "is-path-inside": "^2.1.0" 13 | } 14 | }, 15 | "is-path-inside": { 16 | "version": "2.1.0", 17 | "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-2.1.0.tgz", 18 | "integrity": "sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg==", 19 | "requires": { 20 | "path-is-inside": "^1.0.2" 21 | } 22 | }, 23 | "left-pad": { 24 | "version": "1.3.0", 25 | "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz", 26 | "integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==" 27 | }, 28 | "path-is-inside": { 29 | "version": "1.0.2", 30 | "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", 31 | "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=" 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /test/github-actions.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // Import AVA's default export 4 | const test = require('ava').default 5 | const sinon = require('sinon') 6 | 7 | const core = require('@actions/core') 8 | const updateCheck = require('../lib/report/github-action') 9 | 10 | test('Github Action Annotation', async (t) => { 11 | sinon.stub(core, 'getInput').returns('test-run') 12 | 13 | const octokit = { 14 | checks: { 15 | update: () => { 16 | console.log({ ok: 200 }) 17 | } 18 | } 19 | } 20 | 21 | const listChecks = { 22 | data: { 23 | total_count: 1, 24 | check_runs: [ 25 | { 26 | name: 'test-run' 27 | } 28 | ] 29 | } 30 | } 31 | 32 | const annotations = [ 33 | { 34 | path: 'package.json', 35 | start_line: 10, 36 | end_line: 10, 37 | annotation_level: 'failure', 38 | message: 'DDOS vulnerability discovered by MrRobot666', 39 | title: '[supersecurepkg@0.0.1] Certification Warning' 40 | }, 41 | { 42 | path: 'package.json', 43 | start_line: 12, 44 | end_line: 12, 45 | annotation_level: 'warning', 46 | message: 'This package version is Whitelisted.', 47 | title: '[mysuperpkg@6.6.6] Certification Warning' 48 | } 49 | ] 50 | 51 | const contextData = { 52 | owner: 'nodesource', 53 | repo: 'ncm-cli' 54 | } 55 | 56 | // AVA handles assertions differently than tap 57 | await t.notThrowsAsync(async () => { 58 | updateCheck(octokit, listChecks, annotations, contextData) 59 | }) 60 | }) 61 | -------------------------------------------------------------------------------- /test/install.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // Import only what we need, avoid unused imports 4 | const { NCMTestRunner } = require('./lib/test-runner.js') 5 | const path = require('path') 6 | 7 | const XDG_CONFIG_HOME = path.join(__dirname, '.tmp') 8 | 9 | NCMTestRunner.createTest('install output matches snapshot', async (runner, t) => { 10 | // First part - config set 11 | const MOCK_INSTALL_BIN = path.join('test', 'lib', 'install-mock-bin.js') 12 | 13 | const { stdout: stdout1, stderr: stderr1 } = await runner.execP( 14 | `config set installBin ${MOCK_INSTALL_BIN}`, 15 | { XDG_CONFIG_HOME } 16 | ) 17 | 18 | t.is(stderr1, '') 19 | t.snapshot(stdout1) 20 | 21 | const out1 = stdout1.toString() 22 | t.regex(out1, / has been set to:\n.+install-mock-bin\.js/) 23 | 24 | { 25 | const { stdout, stderr } = await runner.execP( 26 | 'install npm @ 6.8.0 --force', 27 | { XDG_CONFIG_HOME } 28 | ) 29 | 30 | t.is(stderr, '') 31 | t.regex(stdout, /[NCM::SECURITY]/) 32 | 33 | const out = stdout.toString() 34 | // Split output into lines and find the SUBCOMMAND ARGS line 35 | const lines = out.split('\n') 36 | const subcommandLine = lines.find(line => line.includes('SUBCOMMAND ARGS:')) 37 | t.truthy(subcommandLine, 'Should find SUBCOMMAND ARGS line') 38 | // Remove ANSI color codes for comparison 39 | const cleanLine = subcommandLine?.replace(/\[\d+m/g, '') 40 | t.regex(cleanLine, /SUBCOMMAND ARGS: \[ 'install', 'npm@6.8.0', '--force' \]/, 'Should have correct command arguments') 41 | } 42 | }) 43 | -------------------------------------------------------------------------------- /commands/help.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { 4 | COLORS, 5 | header, 6 | tooltip, 7 | rawBox 8 | } = require('../lib/ncm-style') 9 | const chalk = require('chalk') 10 | const L = console.log 11 | 12 | module.exports = help 13 | module.exports.optionsList = optionsList 14 | 15 | const commandNames = [ 16 | 'report', 17 | 'details', 18 | 'install', 19 | 'whitelist', 20 | 'signin', 21 | 'signout', 22 | 'orgs', 23 | 'config' 24 | ] 25 | 26 | const commands = commandNames.map(name => { 27 | return require(`./${name}`) 28 | }) 29 | 30 | async function help (_, arg1) { 31 | // Support for "ncm help " 32 | const cmdIdx = commandNames.indexOf(arg1) 33 | if (cmdIdx > -1) { 34 | commands[cmdIdx]({ help: true }) 35 | return 36 | } 37 | 38 | L() 39 | L(header('NodeSource Certified Modules CLI Help')) 40 | L() 41 | 42 | L('Usage:') 43 | L(chalk` 44 | ${rawBox( 45 | chalk`{${COLORS.light1} ncm} {${COLORS.yellow} } {${COLORS.teal} [options]}`, 46 | 'ncm [options]'.length 47 | )} 48 | 49 | {${COLORS.teal} -h, --help} {white Display help for any command OR this message} 50 | {${COLORS.teal} -v, --version} {white Print ncm CLI version} 51 | `.trim()) 52 | 53 | L() 54 | 55 | for (const command of commands) { 56 | L(command.optionsList()) 57 | L() 58 | } 59 | 60 | L(tooltip('For more information on a specific command, run "ncm --help".')) 61 | L() 62 | } 63 | 64 | function optionsList () { 65 | return chalk` 66 | {${COLORS.light1} ncm} {${COLORS.yellow} help} {italic (this message)} 67 | `.trim() 68 | } 69 | -------------------------------------------------------------------------------- /test/whitelist.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { NCMTestRunner } = require('./lib/test-runner.js') 4 | 5 | NCMTestRunner.createTest('whitelist updates properly', async (runner, t) => { 6 | { 7 | const { stdout, stderr } = await runner.execP( 8 | 'whitelist --add ansi-styles@3.2.1', 9 | { env: Object.assign({ FORCE_COLOR: 3 }, process.env) } 10 | ) 11 | t.is(stderr, '') 12 | t.snapshot(stdout, 'add-output') 13 | 14 | const out = stdout.toString() 15 | t.regex(out, /Package\(s\) added successfully./) 16 | } 17 | { 18 | const { stdout, stderr } = await runner.execP( 19 | 'whitelist --list', 20 | { env: Object.assign({ FORCE_COLOR: 3 }, process.env) } 21 | ) 22 | t.is(stderr, '') 23 | t.snapshot(stdout, 'list-added-output') 24 | 25 | const out = stdout.toString() 26 | t.regex(out, /debug @ 2.2.0/) 27 | t.regex(out, /ansi-styles @ 3.2.1/) 28 | } 29 | { 30 | const { stdout, stderr } = await runner.execP( 31 | 'whitelist --remove ansi-styles@3.2.1', 32 | { env: Object.assign({ FORCE_COLOR: 3 }, process.env) } 33 | ) 34 | t.is(stderr, '') 35 | t.snapshot(stdout, 'remove-output') 36 | 37 | const out = stdout.toString() 38 | t.regex(out, /Package\(s\) removed successfully/) 39 | } 40 | { 41 | const { stdout, stderr } = await runner.execP( 42 | 'whitelist --list', 43 | { env: Object.assign({ FORCE_COLOR: 3 }, process.env) } 44 | ) 45 | t.is(stderr, '') 46 | t.snapshot(stdout, 'list-removed-output') 47 | 48 | const out = stdout.toString() 49 | t.regex(out, /debug @ 2.2.0/) 50 | t.notRegex(out, /ansi-styles @ 3.2.1/) 51 | } 52 | }) 53 | -------------------------------------------------------------------------------- /bin/ncm-cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict' 4 | 5 | function handleError (err) { 6 | console.error(err) 7 | process.exit(1) 8 | } 9 | 10 | process.on('unhandledRejection', handleError) 11 | 12 | const parseArgs = require('minimist') 13 | const pkg = require('../package.json') 14 | 15 | const commands = { 16 | config: require('../commands/config'), 17 | help: require('../commands/help'), 18 | i: require('../commands/install'), 19 | install: require('../commands/install'), 20 | orgs: require('../commands/orgs'), 21 | whitelist: require('../commands/whitelist'), 22 | signin: require('../commands/signin'), 23 | signout: require('../commands/signout'), 24 | report: require('../commands/report'), 25 | details: require('../commands/details') 26 | } 27 | 28 | async function main () { 29 | const argv = parseArgs(process.argv.slice(2), { 30 | alias: { 31 | dir: 'd', 32 | github: 'g', 33 | google: 'G', 34 | compliance: 'c', 35 | security: 's', 36 | help: 'h', 37 | long: 'l', 38 | org: 'O', 39 | report: 'r', 40 | version: 'v', 41 | json: 'j' 42 | } 43 | }) 44 | 45 | if (argv.dir && typeof argv.dir !== 'string') { 46 | handleError('ERR_INVALID_ARG_TYPE: --dir or -d must to be a string') 47 | } 48 | 49 | let [command = 'help', ...subargs] = argv._ 50 | if (!Object.keys(commands).includes(command)) { 51 | command = 'help' 52 | } 53 | 54 | if (argv.version) { 55 | console.log(pkg.version) 56 | return 57 | } 58 | 59 | await commands[command](argv, ...subargs) 60 | } 61 | 62 | // invoke main 63 | if (require.main === module) { 64 | main().catch(err => { 65 | console.error(err) 66 | process.exitCode = 1 67 | }) 68 | } 69 | -------------------------------------------------------------------------------- /test/proxied.js: -------------------------------------------------------------------------------- 1 | // Import the correct test runner class 2 | const { NCMTestRunner } = require('./lib/test-runner.js') 3 | 4 | // No longer need the once polyfill 5 | 6 | const { spawn } = require('child_process') 7 | const path = require('path') 8 | 9 | NCMTestRunner.createTest('api requests respect ENV proxy settings', (runner, t) => { 10 | return new Promise(/** @type {(resolve: any) => void} */ (resolve) => { 11 | const proc = spawn( 12 | process.execPath, 13 | [ 14 | path.join(__dirname, 'lib', 'http-proxy-bin.js'), 15 | `http://localhost:${runner.port}`, // The mock server address 16 | 0 // Autodecide the proxy port 17 | ], 18 | { 19 | // Make an ipc 'message' channel separate from stdio 20 | stdio: ['pipe', 'pipe', 'pipe', 'ipc'] 21 | } 22 | ) 23 | 24 | // Wait for the proxy to start and tell us which port it chose 25 | proc.on('message', async (procPort) => { 26 | // The proxy sends the port number directly 27 | 28 | try { 29 | // Run an API command through the proxy 30 | await runner.execP('report --dir=.', { 31 | http_proxy: `http://localhost:${procPort}` 32 | }) 33 | } catch (err) { 34 | const { stdout, stderr } = err 35 | 36 | proc.kill() 37 | 38 | t.is(err.code, 1) 39 | // Now we know the actual error message, so update our assertions to match it 40 | t.regex(stderr, /Failed to fetch user info/, 'Should show user info fetch failure in stderr') 41 | t.regex(stderr, /Have you run `ncm signin`\?/, 'Should prompt for signin in stderr') 42 | t.regex(stdout, /Report/, 'Should include Report text in stdout') 43 | 44 | resolve() 45 | } 46 | }) 47 | }) 48 | }) 49 | -------------------------------------------------------------------------------- /lib/report/score.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const scores = { 4 | 'readme-exists': -20, 5 | 'readme-size': -10, 6 | 'disk-usage-expanded-size': -15, 7 | 'disk-usage-file-count': -10, 8 | 'disk-usage-dir-count': -10, 9 | 'has-scm-info': -10, 10 | 'scm-tagged-versions': -10, 11 | 'has-install-scripts': -25, 12 | 'has-gyp-file': -20, 13 | 'uses-eval': -20, 14 | 'uses-dynamic-require': -20, 15 | 'missing-strict-mode': -20, 16 | 'has-unsafe-regexps': -50, 17 | 'uses-deprecated-node-apis': -40, 18 | 'has-lost-callback-errs': -30, 19 | 'has-abandoned-promises': -10, 20 | 'has-callback-in-async-function': -25, 21 | 'has-async-without-await': -25, 22 | license: -100, 23 | deprecated: -100 24 | } 25 | 26 | function calcSev (sev) { 27 | switch (sev) { 28 | case 'NONE': 29 | return 0 30 | case 'LOW': 31 | return -10 32 | case 'MEDIUM': 33 | return -25 34 | case 'HIGH': 35 | return -50 36 | case 'CRITICAL': 37 | return -85 38 | default: 39 | return 0 40 | } 41 | } 42 | 43 | function score (pkgScores = [], maxSeverity) { 44 | if (maxSeverity === 4) return 0 45 | let totalScore = 100 46 | let sevScore = 0 47 | pkgScores.forEach(({ pass, group, severity, name }) => { 48 | switch (group) { 49 | case 'security': 50 | sevScore = calcSev(severity) 51 | if (sevScore !== 0) sevScore += -100 52 | break 53 | case 'risk': 54 | sevScore += calcSev(severity) * 2 55 | break 56 | case 'compliance': 57 | sevScore += calcSev(severity) 58 | break 59 | } 60 | 61 | // Quality scores do not affect a package’s risk level 62 | // https://docs.nodesource.com/ncmv2/docs#quality-severity 63 | if (!pass && group !== 'quality') { 64 | sevScore += scores[name] || 0 65 | } 66 | }) 67 | totalScore += sevScore 68 | return Math.max(totalScore, 0) 69 | } 70 | 71 | module.exports = score 72 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ncm-cli", 3 | "version": "1.5.0", 4 | "license": "Apache-2.0", 5 | "description": "Command-line tool for NodeSource Certified Modules 2.0", 6 | "author": "NodeSource (https://nodesource.com)", 7 | "contributors": [ 8 | "Michael Sterpka (https://github.com/mster)", 9 | "Jeremiah Senkpiel (https://github.com/Fishrock123)" 10 | ], 11 | "preferGlobal": true, 12 | "keywords": [ 13 | "ncm", 14 | "ncm-cli", 15 | "nodesource", 16 | "certified", 17 | "module", 18 | "modules", 19 | "risk", 20 | "report", 21 | "score" 22 | ], 23 | "repository": "nodesource/ncm-cli", 24 | "scripts": { 25 | "check-deps": "tools/check-deps.sh", 26 | "start": "bin/ncm-cli.js", 27 | "lint": "standard --verbose", 28 | "lint-fix": "standard --fix --verbose", 29 | "pretest": "npm run -s lint", 30 | "test": "npm run -s test-only", 31 | "test-only": "c8 ava 'test/**/*.js'", 32 | "posttest": "rimraf test/.tmp", 33 | "update-test-snapshots": "ava --update-snapshots 'test/**/*.js'" 34 | }, 35 | "bin": { 36 | "ncm": "bin/ncm-cli.js", 37 | "ncm-cli": "bin/ncm-cli.js" 38 | }, 39 | "dependencies": { 40 | "@actions/core": "^1.10.0", 41 | "@actions/github": "^6.0.0", 42 | "@octokit/rest": "^21.1.1", 43 | "chalk": "^4.1.2", 44 | "client-request": "^2.3.0", 45 | "configstore": "^5.0.1", 46 | "cross-spawn": "^7.0.6", 47 | "debug": "^4.4.0", 48 | "dependency-tree": "^11.1.1", 49 | "events.once": "^2.0.2", 50 | "minimist": "^1.2.8", 51 | "p-defer": "^3.0.0", 52 | "proxy-agent": "^6.5.0", 53 | "proxy-from-env": "^1.1.0", 54 | "rc": "^1.2.8", 55 | "rimraf": "^6.0.1", 56 | "semver": "^7.7.1", 57 | "universal-module-tree": "^4.0.0", 58 | "update-notifier": "^7.3.1" 59 | }, 60 | "devDependencies": { 61 | "ava": "^5.3.1", 62 | "c8": "^10.1.3", 63 | "dependency-check": "^4.1.0", 64 | "express": "^4.18.3", 65 | "graphql": "^16.10.0", 66 | "graphql-http": "^1.22.0", 67 | "http-proxy": "^1.18.1", 68 | "sinon": "^19.0.2", 69 | "standard": "^17.1.0" 70 | }, 71 | "standard": { 72 | "ignore": [ 73 | "tap-snapshots" 74 | ] 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /lib/report/summary.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = summary 4 | 5 | const { 6 | COLORS, 7 | tooltip 8 | } = require('../ncm-style') 9 | const { 10 | SEVERITY_RMAP 11 | } = require('./util') 12 | const L = console.log 13 | const chalk = require('chalk') 14 | 15 | function summary (report, dir, filterOptions) { 16 | filterOptions = filterOptions || {} 17 | 18 | L() 19 | L(chalk`${report.length} {${COLORS.light1} packages checked}`) 20 | L() 21 | 22 | const riskCount = [0, 0, 0, 0, 0] 23 | let insecureModules = 0 24 | let complianceCount = 0 25 | let securityCount = 0 26 | 27 | for (const pkg of report) { 28 | let insecure = false 29 | let pkgMaxSeverity = 0 30 | for (const score of pkg.scores) { 31 | if (score.group === 'quality') continue 32 | if (score.group === 'compliance' && !score.pass) complianceCount++ 33 | if (score.group === 'security' && !score.pass) { 34 | securityCount++ 35 | insecure = true 36 | } 37 | const scoreIndex = SEVERITY_RMAP.indexOf(score.severity) 38 | pkgMaxSeverity = scoreIndex > pkgMaxSeverity ? scoreIndex : pkgMaxSeverity 39 | } 40 | if (pkg.auditScore != null) pkgMaxSeverity = pkg.auditScore 41 | riskCount[pkgMaxSeverity]++ 42 | if (insecure) insecureModules++ 43 | } 44 | 45 | L(chalk` {${COLORS.red} ! ${riskCount[4]}} critical risk`) 46 | L(chalk` {${COLORS.orange} ${riskCount[3]}} high risk`) 47 | L(chalk` {${COLORS.yellow} ${riskCount[2]}} medium risk`) 48 | L(chalk` {${COLORS.light1} ${riskCount[1]}} low risk`) 49 | 50 | L() 51 | if (securityCount) { 52 | L(chalk` {${COLORS.red} !} ${securityCount} security vulnerabilities found across ${insecureModules} modules`) 53 | if (!filterOptions.filterSecurity) { 54 | L(' ' + tooltip('Run `ncm report --filter=security` for a list')) 55 | } 56 | } else { 57 | L(chalk` {${COLORS.green} ✓} No security vulnerabilities found`) 58 | } 59 | L() 60 | if (complianceCount) { 61 | L(chalk` {${COLORS.red} !} ${complianceCount} noncompliant modules found`) 62 | if (!filterOptions.filterCompliance) { 63 | L(' ' + tooltip('Run `ncm report --filter=compliance` for a list')) 64 | } 65 | } else { 66 | L(chalk` {${COLORS.green} ✓} All modules compliant`) 67 | } 68 | L() 69 | return riskCount 70 | } 71 | -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | 1.4.0 / 2021-03-24 2 | ================== 3 | 4 | * Ensured the prevention of npm substitution attacks during `install`. 5 | * Added Github actions integration. 6 | * Fixed handling of metadata during `report` 7 | * Dependency updates. 8 | 9 | 1.3.4 / 2019-07-30 10 | ================== 11 | 12 | * Added automatic retry for http request timeouts. 13 | * Fixed handling of local dependency checking errors during `details`. 14 | * Fixed and improved line-wrapping for `details` output. 15 | 16 | 1.3.3 / 2019-06-27 17 | ================== 18 | 19 | * Better Windows support for `install`. 20 | * Fixed handling of npm errors during `install`. 21 | * Fixed organization selection for orgs which are expired trials. 22 | 23 | 1.3.2 / 2019-05-26 24 | ================== 25 | 26 | * Better handling of malformed ncm api data. 27 | 28 | 1.3.1 / 2019-05-16 29 | ================== 30 | 31 | * Ensured `analyze()` error messages are always printed in `report`. 32 | * Support for `DEBUG=ncm` error debugging. 33 | 34 | 1.3.0 / 2019-05-09 35 | ================== 36 | 37 | * Added support for `http{s}_proxy` env vars for all network requests. 38 | * Fixed version display in `details` when checking the `latest` version. 39 | 40 | 1.2.0 / 2019-04-24 41 | ================== 42 | 43 | * Added `install` command, which runs `details`, and then runs `npm install` on a confirmation. 44 | * Added descriptive text to ` --help` output. 45 | * Added `help ` as an alias to ` --help`. 46 | * Fixed `NCM_TOKEN` functionality when making whitelist api requests. 47 | * Fixed `details` parsing `@scope/module@version` input. 48 | * Reworked Readme documentation. 49 | 50 | 1.1.0 / 2019-03-26 51 | ================== 52 | 53 | * Added dependency path visualization to `details` 54 | * Documented `-d`/`--dir` option 55 | 56 | 1.0.3 / 2019-03-22 57 | ================== 58 | 59 | * Fixed risk meters in `whitelist --list` 60 | * Made sign-in password prompt hide input correctly 61 | 62 | 1.0.2 / 2019-03-14 63 | ================== 64 | 65 | * deps: universal-module-tree @ ^3.0.2 66 | - Fixed issues for some projects 67 | 68 | 1.0.1 / 2019-03-12 69 | ================== 70 | 71 | * Added missing package metadata 72 | * Fixed email sign-in 73 | * Fixed adding scoped packages to a whitelist 74 | * Fixed some multi-line wrapping 75 | 76 | 1.0.0 / 2019-03-12 77 | ================== 78 | 79 | * Initial release! 80 | -------------------------------------------------------------------------------- /lib/config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Configstore = require('configstore') 4 | const pkg = require('../package.json') 5 | const conf = new Configstore(pkg.name) 6 | 7 | const api = process.env.NCM_API || 'https://api.nodesource.com' 8 | const ncmApi = process.env.NCM_API || 'https://api.ncm.nodesource.com' 9 | const keys = { 10 | installCmd: 'install', 11 | installBin: 'npm', 12 | token: ' ', 13 | refreshToken: ' ', 14 | org: 'default', 15 | orgId: '1', 16 | policy: ' ', 17 | policyId: ' ', 18 | cachedPublicPackages: [] 19 | } 20 | 21 | module.exports = { 22 | setValue, 23 | getValue, 24 | delValue, 25 | popValue, 26 | resetState, 27 | setTokens, 28 | getTokens, 29 | api, 30 | ncmApi, 31 | keyNames: Object.keys(keys) 32 | } 33 | 34 | const validateState = () => { 35 | for (const key in keys) { 36 | if (!conf.get(key)) conf.set(key, keys[key]) 37 | } 38 | } 39 | 40 | function setValue (key, value) { 41 | validateState() 42 | 43 | if (!keys[key]) { 44 | throw new Error(`Attempted to set invalid config key "${key}"`) 45 | } 46 | conf.set(key, value) 47 | } 48 | 49 | function getValue (key) { 50 | validateState() 51 | 52 | if (!keys[key]) { 53 | throw new Error(`Attempted to get invalid config key "${key}"`) 54 | } 55 | 56 | return conf.get(key) 57 | } 58 | 59 | function delValue (key) { 60 | validateState() 61 | 62 | if (!keys[key]) { 63 | throw new Error(`Attempted to delete invalid config key "${key}"`) 64 | } 65 | 66 | conf.set(key, keys[key]) 67 | } 68 | 69 | // get and delete 70 | function popValue (key) { 71 | validateState() 72 | 73 | if (!keys[key]) { 74 | throw new Error(`Attempted to pop invalid config key "${key}"`) 75 | } 76 | 77 | const val = conf.get(key) 78 | conf.set(key, keys[key]) 79 | return val 80 | } 81 | 82 | function resetState () { 83 | validateState() 84 | 85 | for (const key in keys) { 86 | conf.set(key, keys[key]) 87 | } 88 | } 89 | 90 | function setTokens ({ session, refreshToken }) { 91 | validateState() 92 | 93 | conf.set('token', session) 94 | conf.set('refreshToken', refreshToken) 95 | } 96 | 97 | function getTokens () { 98 | validateState() 99 | 100 | return { 101 | token: process.env.NCM_TOKEN || conf.get('token'), 102 | refreshToken: conf.get('refreshToken') 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /coverage/block-navigation.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | var jumpToCode = (function init() { 3 | // Classes of code we would like to highlight in the file view 4 | var missingCoverageClasses = ['.cbranch-no', '.cstat-no', '.fstat-no']; 5 | 6 | // Elements to highlight in the file listing view 7 | var fileListingElements = ['td.pct.low']; 8 | 9 | // We don't want to select elements that are direct descendants of another match 10 | var notSelector = ':not(' + missingCoverageClasses.join('):not(') + ') > '; // becomes `:not(a):not(b) > ` 11 | 12 | // Selecter that finds elements on the page to which we can jump 13 | var selector = 14 | fileListingElements.join(', ') + 15 | ', ' + 16 | notSelector + 17 | missingCoverageClasses.join(', ' + notSelector); // becomes `:not(a):not(b) > a, :not(a):not(b) > b` 18 | 19 | // The NodeList of matching elements 20 | var missingCoverageElements = document.querySelectorAll(selector); 21 | 22 | var currentIndex; 23 | 24 | function toggleClass(index) { 25 | missingCoverageElements 26 | .item(currentIndex) 27 | .classList.remove('highlighted'); 28 | missingCoverageElements.item(index).classList.add('highlighted'); 29 | } 30 | 31 | function makeCurrent(index) { 32 | toggleClass(index); 33 | currentIndex = index; 34 | missingCoverageElements.item(index).scrollIntoView({ 35 | behavior: 'smooth', 36 | block: 'center', 37 | inline: 'center' 38 | }); 39 | } 40 | 41 | function goToPrevious() { 42 | var nextIndex = 0; 43 | if (typeof currentIndex !== 'number' || currentIndex === 0) { 44 | nextIndex = missingCoverageElements.length - 1; 45 | } else if (missingCoverageElements.length > 1) { 46 | nextIndex = currentIndex - 1; 47 | } 48 | 49 | makeCurrent(nextIndex); 50 | } 51 | 52 | function goToNext() { 53 | var nextIndex = 0; 54 | 55 | if ( 56 | typeof currentIndex === 'number' && 57 | currentIndex < missingCoverageElements.length - 1 58 | ) { 59 | nextIndex = currentIndex + 1; 60 | } 61 | 62 | makeCurrent(nextIndex); 63 | } 64 | 65 | return function jump(event) { 66 | if ( 67 | document.getElementById('fileSearch') === document.activeElement && 68 | document.activeElement != null 69 | ) { 70 | // if we're currently focused on the search input, we don't want to navigate 71 | return; 72 | } 73 | 74 | switch (event.which) { 75 | case 78: // n 76 | case 74: // j 77 | goToNext(); 78 | break; 79 | case 66: // b 80 | case 75: // k 81 | case 80: // p 82 | goToPrevious(); 83 | break; 84 | } 85 | }; 86 | })(); 87 | window.addEventListener('keydown', jumpToCode); 88 | -------------------------------------------------------------------------------- /coverage/lcov-report/block-navigation.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | var jumpToCode = (function init() { 3 | // Classes of code we would like to highlight in the file view 4 | var missingCoverageClasses = ['.cbranch-no', '.cstat-no', '.fstat-no']; 5 | 6 | // Elements to highlight in the file listing view 7 | var fileListingElements = ['td.pct.low']; 8 | 9 | // We don't want to select elements that are direct descendants of another match 10 | var notSelector = ':not(' + missingCoverageClasses.join('):not(') + ') > '; // becomes `:not(a):not(b) > ` 11 | 12 | // Selecter that finds elements on the page to which we can jump 13 | var selector = 14 | fileListingElements.join(', ') + 15 | ', ' + 16 | notSelector + 17 | missingCoverageClasses.join(', ' + notSelector); // becomes `:not(a):not(b) > a, :not(a):not(b) > b` 18 | 19 | // The NodeList of matching elements 20 | var missingCoverageElements = document.querySelectorAll(selector); 21 | 22 | var currentIndex; 23 | 24 | function toggleClass(index) { 25 | missingCoverageElements 26 | .item(currentIndex) 27 | .classList.remove('highlighted'); 28 | missingCoverageElements.item(index).classList.add('highlighted'); 29 | } 30 | 31 | function makeCurrent(index) { 32 | toggleClass(index); 33 | currentIndex = index; 34 | missingCoverageElements.item(index).scrollIntoView({ 35 | behavior: 'smooth', 36 | block: 'center', 37 | inline: 'center' 38 | }); 39 | } 40 | 41 | function goToPrevious() { 42 | var nextIndex = 0; 43 | if (typeof currentIndex !== 'number' || currentIndex === 0) { 44 | nextIndex = missingCoverageElements.length - 1; 45 | } else if (missingCoverageElements.length > 1) { 46 | nextIndex = currentIndex - 1; 47 | } 48 | 49 | makeCurrent(nextIndex); 50 | } 51 | 52 | function goToNext() { 53 | var nextIndex = 0; 54 | 55 | if ( 56 | typeof currentIndex === 'number' && 57 | currentIndex < missingCoverageElements.length - 1 58 | ) { 59 | nextIndex = currentIndex + 1; 60 | } 61 | 62 | makeCurrent(nextIndex); 63 | } 64 | 65 | return function jump(event) { 66 | if ( 67 | document.getElementById('fileSearch') === document.activeElement && 68 | document.activeElement != null 69 | ) { 70 | // if we're currently focused on the search input, we don't want to navigate 71 | return; 72 | } 73 | 74 | switch (event.which) { 75 | case 78: // n 76 | case 74: // j 77 | goToNext(); 78 | break; 79 | case 66: // b 80 | case 75: // k 81 | case 80: // p 82 | goToPrevious(); 83 | break; 84 | } 85 | }; 86 | })(); 87 | window.addEventListener('keydown', jumpToCode); 88 | -------------------------------------------------------------------------------- /lib/ncm-style.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const util = require('util') 4 | const chalk = require('chalk') 5 | const debug = require('debug')('ncm') 6 | 7 | const COLORS = { 8 | light1: 'hex(\'#89a19d\')', 9 | base: 'hex(\'#4c5859\')', 10 | blue: 'hex(\'#4cb5ff\')', 11 | teal: 'hex(\'#66ccbb\')', 12 | green: 'hex(\'#5ac878\')', 13 | yellow: 'hex(\'#ffb726\')', 14 | orange: 'hex(\'#ff8b40\')', 15 | red: 'hex(\'#ff6040\')' 16 | } 17 | 18 | module.exports = { 19 | COLORS, 20 | 21 | // formatted printers 22 | header, 23 | line, 24 | failure, 25 | success, 26 | action, 27 | prompt, 28 | tooltip, 29 | divider, 30 | box, 31 | boxbox, 32 | rawBox, 33 | formatError 34 | } 35 | 36 | // Formatted printers 37 | function header (text) { 38 | const { length } = text 39 | return chalk` 40 | {${COLORS.light1} ╔══${'═'.repeat(length)}╗} 41 | {${COLORS.light1} ║} {white ${text}} {${COLORS.light1} ║} 42 | {${COLORS.light1} ╚══${'═'.repeat(length)}╝} 43 | `.trim() 44 | } 45 | 46 | function line (symbol, text, color = COLORS.light1) { 47 | return chalk`{${color} ${symbol}} ${text}` 48 | } 49 | 50 | function failure (text) { 51 | return line('X', text, COLORS.red) 52 | } 53 | 54 | function success (text) { 55 | return line('✓', text, COLORS.green) 56 | } 57 | 58 | function action (text) { 59 | return line('|➔', text, COLORS.yellow) 60 | } 61 | 62 | function prompt (text) { 63 | return line('?', text, COLORS.red) 64 | } 65 | 66 | function tooltip (text) { 67 | return chalk`{${COLORS.teal} |➔ ${text}}` 68 | } 69 | 70 | function divider (length, char = '-') { 71 | return chalk`{${COLORS.light1} ${char.repeat(length)}}` 72 | } 73 | 74 | function box (symbol, text, color = COLORS.light1) { 75 | const length = symbol.length + text.length 76 | return chalk` 77 | {${color} ┌──${'─'.repeat(length)}─┐} 78 | {${color} │ ${symbol}} {white ${text}} {${color} │} 79 | {${color} └──${'─'.repeat(length)}─┘} 80 | `.trim() 81 | } 82 | 83 | function boxbox (symbol, text, color = COLORS.light1, symbolLength) { 84 | return chalk` 85 | {${color} ┌─${'─'.repeat(symbolLength || symbol.length)}─┬─${'─'.repeat(text.length)}─┐} 86 | {${color} │ ${symbol} │} {white ${text}} {${color} │} 87 | {${color} └─${'─'.repeat(symbolLength || symbol.length)}─┴─${'─'.repeat(text.length)}─┘} 88 | `.trim() 89 | } 90 | 91 | function rawBox (text, length, color = COLORS.light1) { 92 | return chalk` 93 | {${color} ┌─${'─'.repeat(length)}─┐} 94 | {${color} │} ${text} {${color} │} 95 | {${color} └─${'─'.repeat(length)}─┘} 96 | `.trim() 97 | } 98 | 99 | function formatError (message, err) { 100 | let errMsg 101 | if (err) { 102 | if (err.code) { 103 | errMsg = util.format(message, err.code, err) 104 | } else { 105 | errMsg = util.format(message, err) 106 | } 107 | } 108 | 109 | if (errMsg) { 110 | if (process.env.NCM_DEV === 'true') message = errMsg 111 | debug(errMsg) 112 | } 113 | 114 | return line('‼︎', chalk`{${COLORS.red} ${message}}`, COLORS.red) 115 | } 116 | -------------------------------------------------------------------------------- /lib/client-request.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // client-request/promise with additions, such as retry & proxy support. 4 | 5 | const clientRequestP = require('client-request/promise') 6 | const util = require('util') 7 | 8 | // http(s) proxy support 9 | const { getProxyForUrl } = require('proxy-from-env') 10 | const ProxyAgent = require('proxy-agent') 11 | 12 | const REDIRECT_CODES = [ // List of acceptible 300-series redirect codes. 13 | 301, 302, 303, 307, 308 14 | ] 15 | const RETRY_CODES = [ // List of codes which are acceptible to retry on. 16 | 408, 500, 502, 503, 504 17 | ] 18 | const DEFAULT_RETRY_LIMIT = 3 19 | 20 | module.exports = ClientRequestExtended 21 | 22 | async function ClientRequestExtended (options) { 23 | options = Object.assign({}, options) 24 | let retries = options.retries || DEFAULT_RETRY_LIMIT 25 | 26 | const proxyUri = getProxyForUrl(options.uri) 27 | if (proxyUri) { 28 | options.agent = new ProxyAgent(proxyUri) 29 | } 30 | 31 | let responseRef, bodyRef, timeoutRef 32 | do { 33 | const { response, body, timeout } = await DoRequest(options) 34 | responseRef = response 35 | bodyRef = body 36 | timeoutRef = timeout 37 | 38 | if (timeout) { 39 | // The do-while() will still decrease the retry count. 40 | continue 41 | } 42 | 43 | // Only attempt to follow redirects if we allow retries. 44 | // Restrict redirect chains via the retry limit. 45 | if (REDIRECT_CODES.includes(response.statusCode)) { 46 | options.uri = response.location 47 | } 48 | } while (retries-- > 0 && (timeoutRef || RETRY_CODES.includes(responseRef.statusCode))) 49 | 50 | // If we still timed out, we want to bail out. 51 | if (timeoutRef) { 52 | // Timeout always gets set to the upstream timeout error if there was one. 53 | throw timeoutRef 54 | } 55 | 56 | // Any non- 200-series code 57 | if (!options.opted && (responseRef.statusCode < 200 || responseRef.statusCode >= 300)) { 58 | responseRef.destroy() 59 | 60 | let error 61 | if (bodyRef && !options.stream) { 62 | bodyRef = util.inspect(bodyRef) 63 | if (bodyRef.length > 30) { 64 | bodyRef = `${bodyRef.substr(0, 30)}...` 65 | } 66 | error = new Error(`${responseRef.statusCode} ${options.method} ${options.uri} - '${bodyRef}'`) 67 | } else { 68 | error = new Error(`${responseRef.statusCode} ${options.method} ${options.uri}`) 69 | } 70 | 71 | error.statusCode = responseRef.statusCode 72 | throw error 73 | } 74 | 75 | return { response: responseRef, body: bodyRef } 76 | } 77 | 78 | async function DoRequest (options) { 79 | let result 80 | try { 81 | result = await clientRequestP(options) 82 | } catch (err) { 83 | result = err 84 | if (!result.response) { 85 | // Less than ideal 86 | // See https://github.com/brycebaril/client-request/issues/14 87 | if (typeof err.message === 'string' && 88 | err.message.toLowerCase().includes('timeout')) { 89 | return { timeout: err } 90 | } else { 91 | throw err 92 | } 93 | } 94 | } 95 | 96 | return result 97 | } 98 | -------------------------------------------------------------------------------- /tap-snapshots/test/proxied.js.test.cjs: -------------------------------------------------------------------------------- 1 | /* IMPORTANT 2 | * This snapshot file is auto-generated, but designed for humans. 3 | * It should be checked into source control and tracked carefully. 4 | * Re-generate by setting TAP_SNAPSHOT=1 and running tests. 5 | * Make sure to inspect the output below. Do not ignore changes! 6 | */ 7 | 'use strict' 8 | exports[`test/proxied.js TAP api requests respect ENV proxy settings > details-output 1`] = ` 9 | 10 | ╔═════════════╗ 11 | ║ npm @ 6.8.0 ║ 12 | ╚═════════════╝ 13 | 14 | ┌──────┬─────────────┐ 15 | │ |||| │ Medium Risk │ 16 | └──────┴─────────────┘ 17 | 18 | Security Risk: 19 | ✓ 0 security vulnerabilities found 20 | C 0 critical severity 21 | H 0 high severity 22 | M 0 medium severity 23 | L 0 low severity 24 | 25 | ┌───┬─────────────────────────────┐ 26 | │ ✓ │ No Security Vulnerabilities │ 27 | └───┴─────────────────────────────┘ 28 | 29 | License Risk: 30 | ┌───┬────────────────────────────────────┐ 31 | │ X │ Noncompliant license: Artistic-2.0 │ 32 | └───┴────────────────────────────────────┘ 33 | 34 | Module Risk: 35 | ┌───┬────────────────┐ 36 | │ ✓ │ No Module Risk │ 37 | └───┴────────────────┘ 38 | 39 | Code Quality (does not affect risk score): 40 | ┌───┬────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ 41 | │ ! │ This package version's size on disk is 32.0 MB. │ 42 | ├───┼────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ 43 | │ ! │ This package has 4,028 file(s). │ 44 | ├───┼────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ 45 | │ ! │ This package has 900 dir(s). │ 46 | └───┴────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ 47 | 48 | 49 | ` 50 | -------------------------------------------------------------------------------- /tap-snapshots/test/help.js.test.cjs: -------------------------------------------------------------------------------- 1 | /* IMPORTANT 2 | * This snapshot file is auto-generated, but designed for humans. 3 | * It should be checked into source control and tracked carefully. 4 | * Re-generate by setting TAP_SNAPSHOT=1 and running tests. 5 | * Make sure to inspect the output below. Do not ignore changes! 6 | */ 7 | 'use strict' 8 | exports[`test/help.js TAP help output matches help snapshot > help-output 1`] = ` 9 | 10 | ╔═══════════════════════════════════════╗ 11 | ║ NodeSource Certified Modules CLI Help ║ 12 | ╚═══════════════════════════════════════╝ 13 | 14 | Usage: 15 | ┌─────────────────────────┐ 16 | │ ncm  [options] │ 17 | └─────────────────────────┘ 18 | 19 | -h, --help Display help for any command OR this message 20 | -v, --version Print ncm CLI version 21 | 22 | ncm report 23 | ncm report  24 | -d, --dir Another way to specify  25 | -l, --long Full module list output 26 | -c --compliance Compliance failures only output 27 | -s --security Security failures only output 28 | 29 | ncm details  30 | -d, --dir Directory to check for dependency path 31 | 32 | ncm install  [npm options] 33 | ncm i  [npm options] 34 | -d, --dir Directory to check for dependency path 35 | 36 | ncm whitelist