├── .c8rc.json ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── ava.config.js ├── bin └── ncm-cli.js ├── buildspec.yml ├── changelog.md ├── commands ├── config.js ├── details.js ├── help.js ├── install.js ├── orgs.js ├── report.js ├── signin.js ├── signout.js └── whitelist.js ├── contributing.md ├── coverage ├── base.css ├── bin │ ├── index.html │ └── ncm-cli.js.html ├── block-navigation.js ├── favicon.png ├── index.html ├── lcov-report │ ├── base.css │ ├── bin │ │ ├── index.html │ │ └── ncm-cli.js.html │ ├── block-navigation.js │ ├── favicon.png │ ├── index.html │ ├── lib │ │ ├── client-request.js.html │ │ ├── config.js.html │ │ ├── help.js.html │ │ ├── index.html │ │ ├── ncm-analyze-tree.js.html │ │ ├── ncm-style.js.html │ │ ├── report │ │ │ ├── github-action.js.html │ │ │ ├── index.html │ │ │ ├── long.js.html │ │ │ ├── module.js.html │ │ │ ├── score.js.html │ │ │ ├── short.js.html │ │ │ ├── summary.js.html │ │ │ ├── util.js.html │ │ │ └── whitelist.js.html │ │ └── util.js.html │ ├── prettify.css │ ├── prettify.js │ ├── sort-arrow-sprite.png │ └── sorter.js ├── lcov.info ├── lib │ ├── client-request.js.html │ ├── config.js.html │ ├── help.js.html │ ├── index.html │ ├── ncm-analyze-tree.js.html │ ├── ncm-style.js.html │ ├── report │ │ ├── github-action.js.html │ │ ├── index.html │ │ ├── long.js.html │ │ ├── module.js.html │ │ ├── score.js.html │ │ ├── short.js.html │ │ ├── summary.js.html │ │ ├── util.js.html │ │ └── whitelist.js.html │ └── util.js.html ├── prettify.css ├── prettify.js ├── sort-arrow-sprite.png ├── sorter.js └── tmp │ ├── coverage-83385-1741927295945-2.json │ ├── coverage-83385-1741927296170-3.json │ ├── coverage-83385-1741927296598-5.json │ ├── coverage-83385-1741927299002-1.json │ ├── coverage-83385-1741927307562-6.json │ ├── coverage-83385-1741927308394-7.json │ ├── coverage-83385-1741927310255-4.json │ ├── coverage-83385-1741927310339-0.json │ ├── coverage-83386-1741927294442-0.json │ ├── coverage-83388-1741927296034-0.json │ ├── coverage-83389-1741927298659-0.json │ ├── coverage-83391-1741927298452-0.json │ ├── coverage-83392-1741927298304-0.json │ ├── coverage-83393-1741927296548-0.json │ ├── coverage-83396-1741927301580-0.json │ ├── coverage-83397-1741927307035-0.json │ ├── coverage-83398-1741927306940-0.json │ ├── coverage-83399-1741927307378-0.json │ ├── coverage-83400-1741927306430-0.json │ ├── coverage-83401-1741927306985-0.json │ ├── coverage-83402-1741927307233-0.json │ ├── coverage-83403-1741927307294-0.json │ ├── coverage-83404-1741927306931-0.json │ ├── coverage-83405-1741927307307-0.json │ ├── coverage-83406-1741927307127-0.json │ ├── coverage-83407-1741927307084-0.json │ ├── coverage-83408-1741927307235-0.json │ ├── coverage-83409-1741927307233-0.json │ ├── coverage-83410-1741927307192-0.json │ ├── coverage-83411-1741927307177-0.json │ ├── coverage-83412-1741927307430-0.json │ ├── coverage-83413-1741927305719-0.json │ ├── coverage-83420-1741927310176-0.json │ ├── coverage-83439-1741927306790-0.json │ ├── coverage-83448-1741927306569-0.json │ ├── coverage-83467-1741927307261-0.json │ ├── coverage-83476-1741927306755-0.json │ ├── coverage-83490-1741927306842-0.json │ ├── coverage-83516-1741927307051-0.json │ ├── coverage-83519-1741927306794-0.json │ ├── coverage-83532-1741927306185-0.json │ ├── coverage-83546-1741927307241-0.json │ ├── coverage-83560-1741927306632-0.json │ ├── coverage-83578-1741927307180-0.json │ ├── coverage-83591-1741927307132-0.json │ ├── coverage-83602-1741927306575-0.json │ ├── coverage-83605-1741927305122-0.json │ ├── coverage-83619-1741927305542-0.json │ ├── coverage-83633-1741927306834-0.json │ ├── coverage-83647-1741927306709-0.json │ ├── coverage-83661-1741927306744-0.json │ ├── coverage-83675-1741927307684-0.json │ ├── coverage-83678-1741927308327-0.json │ └── coverage-83684-1741927310140-0.json ├── lib ├── client-request.js ├── config.js ├── help.js ├── ncm-analyze-tree.js ├── ncm-style.js ├── report │ ├── github-action.js │ ├── long.js │ ├── module.js │ ├── score.js │ ├── short.js │ ├── summary.js │ ├── util.js │ └── whitelist.js └── util.js ├── package-lock.json ├── package.json ├── tap-snapshots └── test │ ├── details.js.md │ ├── details.js.snap │ ├── details.js.test.cjs │ ├── help.js.md │ ├── help.js.snap │ ├── help.js.test.cjs │ ├── install.js.md │ ├── install.js.snap │ ├── install.js.test.cjs │ ├── proxied.js.test.cjs │ ├── report.js.md │ ├── report.js.snap │ ├── report.js.test.cjs │ ├── whitelist.js.md │ ├── whitelist.js.snap │ └── whitelist.js.test.cjs ├── test ├── details.js ├── fixtures │ ├── mock-project │ │ ├── package-lock.json │ │ └── package.json │ └── poisoned-project │ │ ├── package-lock.json │ │ └── package.json ├── github-actions.js ├── help.js ├── install.js ├── lib │ ├── http-proxy-bin.js │ ├── install-mock-bin.js │ ├── mock-packages.js │ └── test-runner.js ├── proxied.js ├── report.js └── whitelist.js └── tools └── check-deps.sh /.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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | test/fixtures/*/node_modules 3 | .DS_Store 4 | .vscode/ 5 | .nyc_output 6 | .tmp 7 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /buildspec.yml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | 3 | # for info on the build phases used here: 4 | # http://docs.aws.amazon.com/codebuild/latest/userguide/view-build-details.html#view-build-details-phases 5 | 6 | # for info on the buildspec.yml syntax: 7 | # http://docs.aws.amazon.com/codebuild/latest/userguide/build-spec-ref.html 8 | 9 | env: 10 | variables: 11 | REPO: "ncm-cli" 12 | 13 | phases: 14 | install: 15 | commands: 16 | - npm install 17 | - chmod +x tools/check-deps.sh 18 | 19 | build: 20 | commands: 21 | - npm run test 22 | - npm run check-deps 23 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /commands/config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { 4 | getValue, 5 | setValue, 6 | delValue, 7 | resetState, 8 | keyNames 9 | } = require('../lib/config') 10 | const { queryReadline } = require('../lib/util') 11 | const { helpHeader } = require('../lib/help') 12 | const { 13 | COLORS, 14 | failure, 15 | success, 16 | tooltip, 17 | box, 18 | formatError 19 | } = require('../lib/ncm-style') 20 | const chalk = require('chalk') 21 | const L = console.log 22 | const E = console.error 23 | 24 | module.exports = config 25 | module.exports.optionsList = optionsList 26 | 27 | async function config (argv, action, key, value) { 28 | if (argv.help) { 29 | printHelp() 30 | return 31 | } else if (!action) { 32 | printHelp() 33 | process.exitCode = 1 34 | return 35 | } 36 | 37 | switch (action) { 38 | case 'set': 39 | if (!key || !value) { 40 | printHelp() 41 | process.exitCode = 1 42 | return 43 | } 44 | try { 45 | setValue(key, value) 46 | } catch (err) { 47 | E() 48 | E(formatError(`Attempted to set invalid config key "${key}"`, err)) 49 | E() 50 | } 51 | L(success(`<${key}> has been set to:`)) 52 | L(value) 53 | L() 54 | break 55 | 56 | case 'get': { 57 | if (!key) { 58 | printHelp() 59 | process.exitCode = 1 60 | return 61 | } 62 | let val 63 | try { 64 | val = getValue(key) 65 | } catch (err) { 66 | E() 67 | E(formatError(`Attempted to get invalid config key "${key}"`, err)) 68 | E() 69 | } 70 | L(success(`<${key}> is:`)) 71 | L(val) 72 | L() 73 | break 74 | } 75 | 76 | case 'del': 77 | if (!key) { 78 | printHelp() 79 | process.exitCode = 1 80 | return 81 | } 82 | try { 83 | delValue(key) 84 | } catch (err) { 85 | E() 86 | E(formatError(`Attempted to delete invalid config key "${key}"`, err)) 87 | E() 88 | } 89 | L(success(`<${key}> was deleted`)) 90 | L() 91 | break 92 | 93 | case 'reset': { 94 | resetState() 95 | 96 | L(box('!', 'Are you sure you want to reset all config? (y/N)', COLORS.orange)) 97 | 98 | const answer = (await queryReadline(chalk`{${COLORS.light1} > }`)).trim() 99 | if (answer.toLowerCase() !== 'y') { 100 | L(failure('Did not reset any config.')) 101 | L() 102 | process.exitCode = 1 103 | return 104 | } 105 | L(success('All config was reset.')) 106 | L() 107 | break 108 | } 109 | 110 | case 'list': { 111 | const { unhide } = argv 112 | 113 | for (const key of keyNames) { 114 | let val = getValue(key) 115 | 116 | if (Array.isArray(val)) val = JSON.stringify(val) 117 | 118 | if (val.trim() === '') { 119 | val = chalk`{italic {${COLORS.light1} (empty)}}` 120 | } else if (/token/i.test(key) && !unhide) { 121 | val = chalk`{italic {${COLORS.light1} (${val.length} characters hidden)}}` 122 | } 123 | L(chalk`{${COLORS.teal} <${key}>} ${val}`) 124 | } 125 | if (!unhide) { 126 | L() 127 | L(tooltip('Run with --unhide to show hidden values.')) 128 | } 129 | L() 130 | break 131 | } 132 | 133 | default: 134 | printHelp() 135 | process.exitCode = 1 136 | break 137 | } 138 | } 139 | 140 | function printHelp () { 141 | helpHeader( 142 | 'config', 143 | chalk`ncm {${COLORS.yellow} config} {${COLORS.teal} }`, 144 | 'ncm config ', 145 | ` 146 | Adjust ncm-cli configuration and local storage. 147 | ` 148 | ) 149 | 150 | L(extendedOptionsList()) 151 | L() 152 | } 153 | 154 | function optionsList () { 155 | return chalk` 156 | {${COLORS.light1} ncm} {${COLORS.yellow} config } {${COLORS.teal} } 157 | `.trim() 158 | } 159 | 160 | function extendedOptionsList () { 161 | return chalk` 162 | {${COLORS.light1} ncm} {${COLORS.yellow} config list} 163 | {${COLORS.light1} ncm} {${COLORS.yellow} config reset} 164 | {${COLORS.light1} ncm} {${COLORS.yellow} config del} {${COLORS.teal} } 165 | {${COLORS.light1} ncm} {${COLORS.yellow} config get} {${COLORS.teal} } 166 | {${COLORS.light1} ncm} {${COLORS.yellow} config set} {${COLORS.teal} } 167 | `.trim() 168 | } 169 | -------------------------------------------------------------------------------- /commands/details.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const path = require('path') 4 | const debug = require('debug')('ncm:details') 5 | const { 6 | formatAPIURL, 7 | graphql, 8 | reversedSplit 9 | } = require('../lib/util') 10 | const moduleReport = require('../lib/report/module') 11 | const universalModuleTree = require('universal-module-tree') 12 | const { 13 | COLORS, 14 | header, 15 | failure, 16 | formatError 17 | } = require('../lib/ncm-style') 18 | const { helpHeader } = require('../lib/help') 19 | const semver = require('semver') 20 | const chalk = require('chalk') 21 | const L = console.log 22 | const E = console.error 23 | 24 | module.exports = details 25 | module.exports.optionsList = optionsList 26 | 27 | async function details (argv, arg1, arg2, arg3) { 28 | if (argv.help) { 29 | printHelp() 30 | return 31 | } 32 | let { dir } = argv 33 | if (!dir) dir = process.cwd() 34 | 35 | let name 36 | let version 37 | if (!arg1) { 38 | printHelp() 39 | process.exitCode = 1 40 | return 41 | } else if (arg1.lastIndexOf('@') > 0 && !arg2 && !arg3) { 42 | ;[name, version] = reversedSplit(arg1, /@(?!$)/) 43 | } else if (arg2 === '@' && arg3) { 44 | name = arg1 45 | version = arg3 46 | } else if (arg1) { 47 | name = arg1 48 | version = arg2 || 'latest' 49 | } else { 50 | printHelp() 51 | process.exitCode = 1 52 | return 53 | } 54 | 55 | let requirePaths = [] 56 | try { 57 | const tree = await universalModuleTree(dir) 58 | if (tree) { 59 | const list = universalModuleTree.flatten(tree) 60 | for (const pkg of list) { 61 | if (pkg.name === name && pkg.version === version) { 62 | requirePaths = pkg.paths 63 | break 64 | } 65 | } 66 | } 67 | } catch (err) { 68 | debug('Failed to analyze dependencies: %s', err.message) 69 | } 70 | 71 | if (!name || (version !== 'latest' && !semver.valid(version))) { 72 | E() 73 | E(failure(`Unrecognized module syntax: ${argv._.slice(1).join('')}`)) 74 | E() 75 | process.exitCode = 1 76 | return 77 | } 78 | 79 | const vars = { 80 | name, 81 | version 82 | } 83 | 84 | let hasFailures = false 85 | let data 86 | try { 87 | data = await graphql( 88 | formatAPIURL('/ncm2/api/v2/graphql'), 89 | `query($name: String!, $version: String!) { 90 | packageVersion(name: $name, version: $version) { 91 | name 92 | version 93 | published 94 | publishedAt 95 | description 96 | author 97 | maintainers 98 | keywords 99 | scores { 100 | group 101 | name 102 | pass 103 | severity 104 | title 105 | data 106 | } 107 | } 108 | } 109 | `, 110 | vars 111 | ) 112 | } catch (err) { 113 | E() 114 | E(formatError('Failed to query NCM API. Have you run `ncm signin`?', err)) 115 | E() 116 | process.exitCode = 1 117 | return 118 | } 119 | 120 | if (!data) { 121 | E() 122 | E(failure('Unable to fetch module details. Have you run `ncm signin`?')) 123 | E() 124 | process.exitCode = 1 125 | return 126 | } 127 | 128 | const report = Object.assign({ failures: [], requirePaths }, data.packageVersion) 129 | 130 | /* NCM-Cli Header */ 131 | // later than usual so we can pick up the actual version. 132 | L() 133 | if (requirePaths.length > 0) { 134 | L(header(`${name} @ ${report.version || version} (within ${path.basename(dir)})`)) 135 | } else { 136 | L(header(`${name} @ ${report.version || version}`)) 137 | } 138 | 139 | if (!report.published) { 140 | E() 141 | E(failure(`Module not found: ${argv._.slice(1).join('')}`)) 142 | E() 143 | process.exitCode = 1 144 | return 145 | } 146 | 147 | for (const score of report.scores) { 148 | if (score.group !== 'compliance' && 149 | score.group !== 'security' && 150 | score.group !== 'risk') { 151 | continue 152 | } 153 | 154 | if (score.pass === false) { 155 | report.failures.push(score) 156 | hasFailures = true 157 | } 158 | 159 | if (score.name === 'license') { 160 | report.license = score 161 | } 162 | } 163 | 164 | moduleReport(report) 165 | if (hasFailures) process.exitCode = 1 166 | } 167 | 168 | function printHelp () { 169 | helpHeader( 170 | 'details', 171 | chalk`{${COLORS.light1} ncm} {${COLORS.yellow} details} {${COLORS.teal} [options]}`, 172 | 'ncm details [options]', 173 | chalk` 174 | Returns a detailed report about a specific module version. 175 | 176 | Defaults to using the {${COLORS.teal} "latest"} version as published to npm if no {${COLORS.teal} "version"} is provided. 177 | ` 178 | ) 179 | 180 | L(optionsList()) 181 | L() 182 | } 183 | 184 | function optionsList () { 185 | return chalk` 186 | {${COLORS.light1} ncm} {${COLORS.yellow} details} {${COLORS.teal} } 187 | {${COLORS.teal} -d, --dir} {white Directory to check for dependency path} 188 | `.trim() 189 | } 190 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /commands/install.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // In order to work around windows shell issues... 4 | const spawn = require('cross-spawn') 5 | const { getValue, setValue } = require('../lib/config') 6 | const clientRequest = require('../lib/client-request') 7 | const { queryReadline, reversedSplit } = require('../lib/util') 8 | const details = require('./details') 9 | const { 10 | COLORS, 11 | line, 12 | failure 13 | } = require('../lib/ncm-style') 14 | const { helpHeader } = require('../lib/help') 15 | const path = require('path') 16 | const rc = require('rc') 17 | const chalk = require('chalk') 18 | 19 | const L = console.log 20 | const E = console.error 21 | 22 | module.exports = install 23 | module.exports.optionsList = optionsList 24 | 25 | const publicRegistryUrl = 'https://registry.npmjs.org/' 26 | const cachedPublicPackages = getValue('cachedPublicPackages') 27 | 28 | const registryUrl = scope => { 29 | const result = rc('npm', { registry: publicRegistryUrl }) 30 | const url = result[`${scope}:registry`] || result.config_registry || result.registry 31 | return url.slice(-1) === '/' ? url : `${url}/` 32 | } 33 | 34 | const scannerLog = () => 35 | L(chalk`[NCM::SECURITY] Scanning npm dependency substitution vulnerabilities...`) 36 | 37 | const concurrentTaskQueue = [] 38 | const verificationTask = async ({ name, privateRegistryUrl }) => { 39 | try { 40 | L(`[NCM::SECURITY] Verifying the package "${name}"`) 41 | 42 | // skip cached or scoped packages 43 | if (cachedPublicPackages.includes(name) || name.includes('/')) return 44 | 45 | const { body: { versions = {} } } = await clientRequest({ 46 | method: 'GET', 47 | uri: `${privateRegistryUrl}${name}`, 48 | json: true, 49 | opted: true 50 | }) 51 | let verLen = Object.keys(versions).length 52 | if (!verLen) return 53 | 54 | const { integrity: privateIntegrity } = versions[(Object.keys(versions)[verLen - 1])].dist 55 | if (!privateIntegrity) return 56 | 57 | const { body: { versions: pubVers = {} } } = await clientRequest({ 58 | method: 'GET', 59 | uri: `${publicRegistryUrl}${name}`, 60 | json: true, 61 | opted: true 62 | }) 63 | verLen = Object.keys(pubVers).length 64 | if (!verLen) return 65 | 66 | const { integrity: publicIntegrity } = pubVers[(Object.keys(pubVers)[verLen - 1])].dist 67 | 68 | if (!publicIntegrity) return 69 | if (privateIntegrity !== publicIntegrity) { 70 | E(chalk.red(`[NCM::SECURITY ISSUE DETECTED] "${name}" is vulnerable to npm substitution attacks. Use scopes for the internal packages to fix this`)) 71 | process.exit(1) 72 | } else { 73 | cachedPublicPackages.push(name) 74 | setValue('cachedPublicPackages', [...new Set(cachedPublicPackages)]) 75 | } 76 | } catch (err) { 77 | E(err) 78 | } 79 | } 80 | 81 | async function install (argv, arg1, arg2, arg3) { 82 | const childArgv = Array.from(process.argv.slice(3)) 83 | const regUrl = registryUrl() 84 | const privateRegistryUrl = regUrl.endsWith('/') ? regUrl : `${regUrl}/` 85 | let name 86 | let version 87 | 88 | if (!arg1) { 89 | try { 90 | scannerLog() 91 | 92 | let deps 93 | try { 94 | deps = require(path.join(process.cwd(), 'package.json')) 95 | } catch (_) {} // skipped by below if there's no package.json 96 | 97 | if (deps) { 98 | const pkgTree = [...new Set(Object.keys({ 99 | ...(deps.dependencies || {}), 100 | ...(deps.devDependencies || {}) 101 | }))] 102 | 103 | pkgTree.forEach(async name => 104 | concurrentTaskQueue.push(verificationTask({ name, privateRegistryUrl })) 105 | ) 106 | 107 | try { 108 | await Promise.all(concurrentTaskQueue) 109 | } catch (pErr) { 110 | E(pErr) 111 | } 112 | } 113 | } catch (err) { 114 | E(err) 115 | } 116 | 117 | L(chalk`[NCM::SECURITY] Passed. Now installing...`) 118 | L() 119 | 120 | const args = [getValue('installCmd'), '', ...childArgv] 121 | const bin = getValue('installBin') 122 | const cp = spawn(bin, args, { stdio: 'inherit' }) 123 | 124 | cp.on('error', error => { 125 | E() 126 | E(chalk`{COLORS.red ${error}}`) 127 | E() 128 | }) 129 | cp.on('exit', code => { 130 | process.exitCode = code 131 | }) 132 | return 133 | } else if (arg1.lastIndexOf('@') > 0 && !arg2 && !arg3) { 134 | childArgv.splice(childArgv.indexOf(arg1), 1) 135 | ;[name, version] = reversedSplit(arg1, /@(?!$)/) 136 | } else if (arg2 === '@' && arg3) { 137 | name = arg1 138 | version = arg3 139 | childArgv.splice(childArgv.indexOf(arg1), 1) 140 | childArgv.splice(childArgv.indexOf(arg2), 1) 141 | childArgv.splice(childArgv.indexOf(arg3), 1) 142 | } else if (arg1) { 143 | name = arg1 144 | version = arg2 || 'latest' 145 | childArgv.splice(childArgv.indexOf(arg1), 1) 146 | if (arg2) childArgv.splice(childArgv.indexOf(arg2), 1) 147 | } else { 148 | printHelp() 149 | process.exitCode = 1 150 | return 151 | } 152 | 153 | try { 154 | scannerLog() 155 | 156 | concurrentTaskQueue.push(verificationTask({ name, privateRegistryUrl })) 157 | try { 158 | await Promise.all(concurrentTaskQueue) 159 | } catch (pErr) { 160 | E(pErr) 161 | } 162 | } catch (err) { 163 | E(err) 164 | } 165 | 166 | await details(argv, arg1, arg2, arg3) 167 | 168 | const good = process.exitCode === 0 || process.exitCode === undefined 169 | 170 | const confirm = good ? `${COLORS.green} (Y/n)` : `${COLORS.red} (y/N)` 171 | 172 | L(line('|➔', chalk`Install this module? {${confirm}}`, COLORS.yellow)) 173 | L() 174 | 175 | let choice 176 | if (argv.force) { 177 | choice = 'y' 178 | } else if (process.stdin.isTTY) { 179 | choice = (await queryReadline(chalk`{${COLORS.light1} > }`)).trim().toLowerCase() 180 | } else { 181 | choice = good ? 'y' : 'n' 182 | } 183 | 184 | if ((good && choice === '') || choice === 'y') { 185 | const args = [getValue('installCmd'), `${name}@${version}`, ...childArgv] 186 | const bin = getValue('installBin') 187 | 188 | L() 189 | L(chalk`Running: {${COLORS.teal} ${bin} ${args.join(' ')}}`) 190 | L() 191 | 192 | const cp = spawn(bin, args, { stdio: 'inherit' }) 193 | cp.on('error', error => { 194 | E() 195 | E(chalk`{COLORS.red ${error}}`) 196 | E() 197 | }) 198 | cp.on('exit', code => { 199 | process.exitCode = code 200 | }) 201 | } else { 202 | L() 203 | L(failure(chalk`Did {bold not} install ${name}@${version}`)) 204 | L() 205 | } 206 | } 207 | 208 | function printHelp () { 209 | helpHeader( 210 | 'install', 211 | chalk`{${COLORS.light1} ncm} {${COLORS.yellow} install} {${COLORS.teal} [options] [npm options]}`, 212 | 'ncm install [options] [npm options]', 213 | chalk` 214 | Runs and displays {${COLORS.light1} "ncm details "} with an interactive confirmation prompt. 215 | If confirmed, attempts to run {${COLORS.light1} "npm install "} with any additional options provided. 216 | 217 | {italic The config keys {${COLORS.yellow} installBin} and {${COLORS.yellow} installCmd} can adjust this to work with other package installers if necessary.} 218 | ` 219 | ) 220 | 221 | L(optionsList()) 222 | L() 223 | } 224 | 225 | function optionsList () { 226 | return chalk` 227 | {${COLORS.light1} ncm} {${COLORS.yellow} install} {${COLORS.teal} [npm options]} 228 | {${COLORS.light1} ncm} {${COLORS.yellow} i} {${COLORS.teal} [npm options]} 229 | {${COLORS.teal} -d, --dir} {white Directory to check for dependency path} 230 | `.trim() 231 | } 232 | -------------------------------------------------------------------------------- /commands/orgs.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { 4 | apiRequest, 5 | formatAPIURL, 6 | queryReadline 7 | } = require('../lib/util') 8 | const { helpHeader } = require('../lib/help') 9 | const { setValue, getValue } = require('../lib/config') 10 | const { 11 | COLORS, 12 | header, 13 | line, 14 | box, 15 | formatError 16 | } = require('../lib/ncm-style') 17 | const chalk = require('chalk') 18 | const L = console.log 19 | const E = console.error 20 | 21 | module.exports = orgs 22 | module.exports.orgsCli = orgsCli 23 | module.exports.optionsList = optionsList 24 | 25 | async function orgs (argv, org) { 26 | if (argv.help) { 27 | printHelp() 28 | return 29 | } 30 | 31 | L() 32 | L(header('Select your NodeSource organization')) 33 | 34 | let details 35 | try { 36 | details = await apiRequest( 37 | 'GET', 38 | formatAPIURL('/accounts/user/details') 39 | ) 40 | } catch (err) { 41 | E() 42 | E(formatError('Failed to fetch user info. Have you run `ncm signin`?')) 43 | E() 44 | return 45 | } 46 | 47 | await orgsCli(details, org) 48 | } 49 | 50 | async function orgsCli (details, org) { 51 | const orgs = ['personal'] 52 | for (const orgId in details.orgs) { 53 | if (details.orgs[orgId].expired === true) return 54 | orgs.push(details.orgs[orgId].name) 55 | } 56 | 57 | const currentOrg = getValue('org') 58 | 59 | if (typeof org !== 'string') { 60 | if (orgs.length === 1) { 61 | org = orgs[0] 62 | } else { 63 | L(box('!', 'Multiple organizations', COLORS.yellow)) 64 | } 65 | } 66 | 67 | let hasOrg = orgs.includes(org) 68 | while (typeof org !== 'string' || !hasOrg) { 69 | L(line('|➔', 'Choose an organization to continue with:', COLORS.yellow)) 70 | L() 71 | orgs.forEach((orgName, index) => { 72 | if (orgName === currentOrg) { 73 | L(chalk`{bold {${COLORS.green} ${index}) ${orgName}}}`) 74 | } else { 75 | L(`${index}) ${orgName}`) 76 | } 77 | }) 78 | L() 79 | 80 | org = (await queryReadline(chalk`{${COLORS.light1} > }`)).trim() 81 | 82 | if (org === '') org = currentOrg 83 | 84 | // Index selection. 85 | if (orgs[org]) { 86 | org = orgs[org] 87 | } 88 | // Verifies that the user's choice is valid. 89 | hasOrg = orgs.includes(org) 90 | if (!hasOrg) { 91 | E() 92 | E(formatError('Unrecognized organization')) 93 | } 94 | } 95 | 96 | if (org === 'personal') { 97 | setValue('org', 'personal') 98 | setValue('orgId', '1') 99 | } else { 100 | let match 101 | for (const orgId in details.orgs) { 102 | if (orgId === org || details.orgs[orgId].name === org) { 103 | setValue('org', details.orgs[orgId].name) 104 | setValue('orgId', orgId) 105 | match = true 106 | break 107 | } 108 | } 109 | 110 | if (!match) { 111 | E() 112 | E(formatError('Org setting failed. Please contact support.')) 113 | E() 114 | process.exitCode = 1 115 | return 116 | } 117 | } 118 | 119 | L() 120 | L(line('✓', `You're using ncm with the ${org} settings.`, COLORS.green)) 121 | L() 122 | } 123 | 124 | function printHelp () { 125 | helpHeader( 126 | 'orgs', 127 | chalk`{${COLORS.light1} ncm} {${COLORS.yellow} orgs} {${COLORS.teal} [] [options]}`, 128 | 'ncm orgs [] [options]', 129 | chalk` 130 | Change your active NodeSource organization, which impacts the whitelist. 131 | Defaults to an interactive prompt. 132 | 133 | By passing an {${COLORS.teal} }, the interactive part may be skipped. 134 | 135 | Input is {italic case sensitive}. 136 | ` 137 | ) 138 | 139 | L(optionsList()) 140 | L() 141 | } 142 | 143 | function optionsList () { 144 | return chalk` 145 | {${COLORS.light1} ncm} {${COLORS.yellow} orgs} {italic (interactive)} 146 | {${COLORS.light1} ncm} {${COLORS.yellow} orgs} {${COLORS.teal} } 147 | `.trim() 148 | } 149 | -------------------------------------------------------------------------------- /commands/signin.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const clientRequest = require('../lib/client-request') 4 | const { 5 | apiRequest, 6 | formatAPIURL, 7 | queryReadline, 8 | queryReadlineHidden 9 | } = require('../lib/util') 10 | const { setTokens } = require('../lib/config') 11 | const { orgsCli } = require('./orgs') 12 | const { helpHeader } = require('../lib/help') 13 | const { 14 | COLORS, 15 | header, 16 | line, 17 | box, 18 | formatError 19 | } = require('../lib/ncm-style') 20 | const chalk = require('chalk') 21 | const L = console.log 22 | const E = console.error 23 | 24 | module.exports = signin 25 | module.exports.optionsList = optionsList 26 | 27 | async function signin (argv, email, password) { 28 | if (argv.help) { 29 | printHelp() 30 | return 31 | } 32 | 33 | const SSO = 34 | (argv.google ? 'google' : null) || 35 | (argv.github ? 'github' : null) || 36 | null 37 | 38 | let headerMessage = 'Sign into NodeSource' 39 | if (SSO) { 40 | headerMessage += ' with ' + SSO[0].toUpperCase() + SSO.slice(1) 41 | } 42 | 43 | L() 44 | L(header(headerMessage)) 45 | L() 46 | 47 | let authData 48 | 49 | // TODO: Token Auth 50 | 51 | if (SSO) { 52 | try { 53 | const { body: b1 } = await clientRequest({ 54 | method: 'GET', 55 | uri: formatAPIURL('/accounts/auth/social-signin-url', { source: SSO }), 56 | json: true 57 | }) 58 | const { url, nonce } = b1 59 | 60 | L(line('|➔', 'Open the following very ugly URL:', COLORS.yellow)) 61 | L() 62 | L(chalk`{${COLORS.blue} ${url}}`) 63 | 64 | const { body: b2 } = await clientRequest({ 65 | method: 'GET', 66 | uri: formatAPIURL('/accounts/auth/retrieve-session', { nonce }), 67 | json: true 68 | }) 69 | authData = b2 70 | } catch (err) { 71 | E() 72 | E(formatError('Failed SSO Authentication.', err)) 73 | E() 74 | process.exitCode = 1 75 | return 76 | } 77 | } else { 78 | while (!authData) { 79 | L(line('|➔', 'Enter your NodeSource credentials:', COLORS.yellow)) 80 | L() 81 | 82 | L(line('?', 'Email:', COLORS.red)) 83 | const email = (await queryReadline(chalk`{${COLORS.light1} > }`)).trim() 84 | L() 85 | L(line('?', 'Password:', COLORS.red)) 86 | const password = (await queryReadlineHidden(chalk`{${COLORS.light1} > }`)).trim() 87 | 88 | const usrInfo = JSON.stringify({ email, password }) 89 | 90 | try { 91 | const { body } = await clientRequest({ 92 | method: 'POST', 93 | uri: formatAPIURL('/accounts/auth/login'), 94 | json: true, 95 | body: usrInfo 96 | }) 97 | authData = body 98 | } catch (err) { 99 | E() 100 | E(formatError('Failed Authentication.', err)) 101 | E() 102 | } 103 | } 104 | } 105 | 106 | if (!authData.session || !authData.refreshToken) { 107 | E() 108 | E(formatError('Bad auth data.', authData)) 109 | E() 110 | process.exitCode = 1 111 | return 112 | } 113 | 114 | setTokens(authData) 115 | 116 | L() 117 | L(chalk`{${COLORS.light1} Authenticating...}`) 118 | 119 | let details 120 | try { 121 | details = await apiRequest( 122 | 'GET', 123 | formatAPIURL('/accounts/user/details') 124 | ) 125 | } catch (err) { 126 | E() 127 | E(formatError('Failed to fetch user info.', err)) 128 | E() 129 | process.exitCode = 1 130 | return 131 | } 132 | 133 | L(box('✓', 'Signed in successfully', COLORS.green)) 134 | 135 | await orgsCli(details) 136 | } 137 | 138 | function printHelp () { 139 | helpHeader( 140 | 'signin', 141 | chalk`{${COLORS.light1} ncm} {${COLORS.yellow} signin} {${COLORS.teal} [options]}`, 142 | 'ncm signin [options]', 143 | chalk` 144 | Sign-in interactively using your NodeSource account email and password. 145 | Alternatively, sign-in via single-sign-on by specifiying an option. 146 | 147 | For more information on organization selection, see {${COLORS.light1} "ncm orgs --help"}. 148 | See the readme for information on authentication via CI tokens. 149 | ` 150 | ) 151 | 152 | L(optionsList()) 153 | L() 154 | } 155 | 156 | function optionsList () { 157 | return chalk` 158 | {${COLORS.light1} ncm} {${COLORS.yellow} signin} {italic (interactive)} 159 | {${COLORS.teal} -g, --github} {white Sign in via GitHub account} 160 | {${COLORS.teal} -G, --google} {white Sign in via Google account} 161 | `.trim() 162 | } 163 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /coverage/base.css: -------------------------------------------------------------------------------- 1 | body, html { 2 | margin:0; padding: 0; 3 | height: 100%; 4 | } 5 | body { 6 | font-family: Helvetica Neue, Helvetica, Arial; 7 | font-size: 14px; 8 | color:#333; 9 | } 10 | .small { font-size: 12px; } 11 | *, *:after, *:before { 12 | -webkit-box-sizing:border-box; 13 | -moz-box-sizing:border-box; 14 | box-sizing:border-box; 15 | } 16 | h1 { font-size: 20px; margin: 0;} 17 | h2 { font-size: 14px; } 18 | pre { 19 | font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace; 20 | margin: 0; 21 | padding: 0; 22 | -moz-tab-size: 2; 23 | -o-tab-size: 2; 24 | tab-size: 2; 25 | } 26 | a { color:#0074D9; text-decoration:none; } 27 | a:hover { text-decoration:underline; } 28 | .strong { font-weight: bold; } 29 | .space-top1 { padding: 10px 0 0 0; } 30 | .pad2y { padding: 20px 0; } 31 | .pad1y { padding: 10px 0; } 32 | .pad2x { padding: 0 20px; } 33 | .pad2 { padding: 20px; } 34 | .pad1 { padding: 10px; } 35 | .space-left2 { padding-left:55px; } 36 | .space-right2 { padding-right:20px; } 37 | .center { text-align:center; } 38 | .clearfix { display:block; } 39 | .clearfix:after { 40 | content:''; 41 | display:block; 42 | height:0; 43 | clear:both; 44 | visibility:hidden; 45 | } 46 | .fl { float: left; } 47 | @media only screen and (max-width:640px) { 48 | .col3 { width:100%; max-width:100%; } 49 | .hide-mobile { display:none!important; } 50 | } 51 | 52 | .quiet { 53 | color: #7f7f7f; 54 | color: rgba(0,0,0,0.5); 55 | } 56 | .quiet a { opacity: 0.7; } 57 | 58 | .fraction { 59 | font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; 60 | font-size: 10px; 61 | color: #555; 62 | background: #E8E8E8; 63 | padding: 4px 5px; 64 | border-radius: 3px; 65 | vertical-align: middle; 66 | } 67 | 68 | div.path a:link, div.path a:visited { color: #333; } 69 | table.coverage { 70 | border-collapse: collapse; 71 | margin: 10px 0 0 0; 72 | padding: 0; 73 | } 74 | 75 | table.coverage td { 76 | margin: 0; 77 | padding: 0; 78 | vertical-align: top; 79 | } 80 | table.coverage td.line-count { 81 | text-align: right; 82 | padding: 0 5px 0 20px; 83 | } 84 | table.coverage td.line-coverage { 85 | text-align: right; 86 | padding-right: 10px; 87 | min-width:20px; 88 | } 89 | 90 | table.coverage td span.cline-any { 91 | display: inline-block; 92 | padding: 0 5px; 93 | width: 100%; 94 | } 95 | .missing-if-branch { 96 | display: inline-block; 97 | margin-right: 5px; 98 | border-radius: 3px; 99 | position: relative; 100 | padding: 0 4px; 101 | background: #333; 102 | color: yellow; 103 | } 104 | 105 | .skip-if-branch { 106 | display: none; 107 | margin-right: 10px; 108 | position: relative; 109 | padding: 0 4px; 110 | background: #ccc; 111 | color: white; 112 | } 113 | .missing-if-branch .typ, .skip-if-branch .typ { 114 | color: inherit !important; 115 | } 116 | .coverage-summary { 117 | border-collapse: collapse; 118 | width: 100%; 119 | } 120 | .coverage-summary tr { border-bottom: 1px solid #bbb; } 121 | .keyline-all { border: 1px solid #ddd; } 122 | .coverage-summary td, .coverage-summary th { padding: 10px; } 123 | .coverage-summary tbody { border: 1px solid #bbb; } 124 | .coverage-summary td { border-right: 1px solid #bbb; } 125 | .coverage-summary td:last-child { border-right: none; } 126 | .coverage-summary th { 127 | text-align: left; 128 | font-weight: normal; 129 | white-space: nowrap; 130 | } 131 | .coverage-summary th.file { border-right: none !important; } 132 | .coverage-summary th.pct { } 133 | .coverage-summary th.pic, 134 | .coverage-summary th.abs, 135 | .coverage-summary td.pct, 136 | .coverage-summary td.abs { text-align: right; } 137 | .coverage-summary td.file { white-space: nowrap; } 138 | .coverage-summary td.pic { min-width: 120px !important; } 139 | .coverage-summary tfoot td { } 140 | 141 | .coverage-summary .sorter { 142 | height: 10px; 143 | width: 7px; 144 | display: inline-block; 145 | margin-left: 0.5em; 146 | background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent; 147 | } 148 | .coverage-summary .sorted .sorter { 149 | background-position: 0 -20px; 150 | } 151 | .coverage-summary .sorted-desc .sorter { 152 | background-position: 0 -10px; 153 | } 154 | .status-line { height: 10px; } 155 | /* yellow */ 156 | .cbranch-no { background: yellow !important; color: #111; } 157 | /* dark red */ 158 | .red.solid, .status-line.low, .low .cover-fill { background:#C21F39 } 159 | .low .chart { border:1px solid #C21F39 } 160 | .highlighted, 161 | .highlighted .cstat-no, .highlighted .fstat-no, .highlighted .cbranch-no{ 162 | background: #C21F39 !important; 163 | } 164 | /* medium red */ 165 | .cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE } 166 | /* light red */ 167 | .low, .cline-no { background:#FCE1E5 } 168 | /* light green */ 169 | .high, .cline-yes { background:rgb(230,245,208) } 170 | /* medium green */ 171 | .cstat-yes { background:rgb(161,215,106) } 172 | /* dark green */ 173 | .status-line.high, .high .cover-fill { background:rgb(77,146,33) } 174 | .high .chart { border:1px solid rgb(77,146,33) } 175 | /* dark yellow (gold) */ 176 | .status-line.medium, .medium .cover-fill { background: #f9cd0b; } 177 | .medium .chart { border:1px solid #f9cd0b; } 178 | /* light yellow */ 179 | .medium { background: #fff4c2; } 180 | 181 | .cstat-skip { background: #ddd; color: #111; } 182 | .fstat-skip { background: #ddd; color: #111 !important; } 183 | .cbranch-skip { background: #ddd !important; color: #111; } 184 | 185 | span.cline-neutral { background: #eaeaea; } 186 | 187 | .coverage-summary td.empty { 188 | opacity: .5; 189 | padding-top: 4px; 190 | padding-bottom: 4px; 191 | line-height: 1; 192 | color: #888; 193 | } 194 | 195 | .cover-fill, .cover-empty { 196 | display:inline-block; 197 | height: 12px; 198 | } 199 | .chart { 200 | line-height: 0; 201 | } 202 | .cover-empty { 203 | background: white; 204 | } 205 | .cover-full { 206 | border-right: none !important; 207 | } 208 | pre.prettyprint { 209 | border: none !important; 210 | padding: 0 !important; 211 | margin: 0 !important; 212 | } 213 | .com { color: #999 !important; } 214 | .ignore-none { color: #999; font-weight: normal; } 215 | 216 | .wrapper { 217 | min-height: 100%; 218 | height: auto !important; 219 | height: 100%; 220 | margin: 0 auto -48px; 221 | } 222 | .footer, .push { 223 | height: 48px; 224 | } 225 | -------------------------------------------------------------------------------- /coverage/bin/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for bin 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files bin

23 |
24 | 25 |
26 | 80.88% 27 | Statements 28 | 55/68 29 |
30 | 31 | 32 |
33 | 50% 34 | Branches 35 | 3/6 36 |
37 | 38 | 39 |
40 | 50% 41 | Functions 42 | 1/2 43 |
44 | 45 | 46 |
47 | 80.88% 48 | Lines 49 | 55/68 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |
66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 |
FileStatementsBranchesFunctionsLines
ncm-cli.js 84 |
85 |
80.88%55/6850%3/650%1/280.88%55/68
98 |
99 |
100 |
101 | 106 | 107 | 112 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /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/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nodesource/ncm-cli/1542f412363e3dc50b5796887ce7444700f6bd53/coverage/favicon.png -------------------------------------------------------------------------------- /coverage/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for All files 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files

23 |
24 | 25 |
26 | 76.34% 27 | Statements 28 | 1246/1632 29 |
30 | 31 | 32 |
33 | 81.34% 34 | Branches 35 | 218/268 36 |
37 | 38 | 39 |
40 | 73.43% 41 | Functions 42 | 47/64 43 |
44 | 45 | 46 |
47 | 76.34% 48 | Lines 49 | 1246/1632 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |
66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 |
FileStatementsBranchesFunctionsLines
bin 84 |
85 |
80.88%55/6850%3/650%1/280.88%55/68
lib 99 |
100 |
70.37%437/62183.33%60/7270%28/4070.37%437/621
lib/report 114 |
115 |
79.95%754/94381.57%155/19081.81%18/2279.95%754/943
128 |
129 |
130 |
131 | 136 | 137 | 142 | 143 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /coverage/lcov-report/base.css: -------------------------------------------------------------------------------- 1 | body, html { 2 | margin:0; padding: 0; 3 | height: 100%; 4 | } 5 | body { 6 | font-family: Helvetica Neue, Helvetica, Arial; 7 | font-size: 14px; 8 | color:#333; 9 | } 10 | .small { font-size: 12px; } 11 | *, *:after, *:before { 12 | -webkit-box-sizing:border-box; 13 | -moz-box-sizing:border-box; 14 | box-sizing:border-box; 15 | } 16 | h1 { font-size: 20px; margin: 0;} 17 | h2 { font-size: 14px; } 18 | pre { 19 | font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace; 20 | margin: 0; 21 | padding: 0; 22 | -moz-tab-size: 2; 23 | -o-tab-size: 2; 24 | tab-size: 2; 25 | } 26 | a { color:#0074D9; text-decoration:none; } 27 | a:hover { text-decoration:underline; } 28 | .strong { font-weight: bold; } 29 | .space-top1 { padding: 10px 0 0 0; } 30 | .pad2y { padding: 20px 0; } 31 | .pad1y { padding: 10px 0; } 32 | .pad2x { padding: 0 20px; } 33 | .pad2 { padding: 20px; } 34 | .pad1 { padding: 10px; } 35 | .space-left2 { padding-left:55px; } 36 | .space-right2 { padding-right:20px; } 37 | .center { text-align:center; } 38 | .clearfix { display:block; } 39 | .clearfix:after { 40 | content:''; 41 | display:block; 42 | height:0; 43 | clear:both; 44 | visibility:hidden; 45 | } 46 | .fl { float: left; } 47 | @media only screen and (max-width:640px) { 48 | .col3 { width:100%; max-width:100%; } 49 | .hide-mobile { display:none!important; } 50 | } 51 | 52 | .quiet { 53 | color: #7f7f7f; 54 | color: rgba(0,0,0,0.5); 55 | } 56 | .quiet a { opacity: 0.7; } 57 | 58 | .fraction { 59 | font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; 60 | font-size: 10px; 61 | color: #555; 62 | background: #E8E8E8; 63 | padding: 4px 5px; 64 | border-radius: 3px; 65 | vertical-align: middle; 66 | } 67 | 68 | div.path a:link, div.path a:visited { color: #333; } 69 | table.coverage { 70 | border-collapse: collapse; 71 | margin: 10px 0 0 0; 72 | padding: 0; 73 | } 74 | 75 | table.coverage td { 76 | margin: 0; 77 | padding: 0; 78 | vertical-align: top; 79 | } 80 | table.coverage td.line-count { 81 | text-align: right; 82 | padding: 0 5px 0 20px; 83 | } 84 | table.coverage td.line-coverage { 85 | text-align: right; 86 | padding-right: 10px; 87 | min-width:20px; 88 | } 89 | 90 | table.coverage td span.cline-any { 91 | display: inline-block; 92 | padding: 0 5px; 93 | width: 100%; 94 | } 95 | .missing-if-branch { 96 | display: inline-block; 97 | margin-right: 5px; 98 | border-radius: 3px; 99 | position: relative; 100 | padding: 0 4px; 101 | background: #333; 102 | color: yellow; 103 | } 104 | 105 | .skip-if-branch { 106 | display: none; 107 | margin-right: 10px; 108 | position: relative; 109 | padding: 0 4px; 110 | background: #ccc; 111 | color: white; 112 | } 113 | .missing-if-branch .typ, .skip-if-branch .typ { 114 | color: inherit !important; 115 | } 116 | .coverage-summary { 117 | border-collapse: collapse; 118 | width: 100%; 119 | } 120 | .coverage-summary tr { border-bottom: 1px solid #bbb; } 121 | .keyline-all { border: 1px solid #ddd; } 122 | .coverage-summary td, .coverage-summary th { padding: 10px; } 123 | .coverage-summary tbody { border: 1px solid #bbb; } 124 | .coverage-summary td { border-right: 1px solid #bbb; } 125 | .coverage-summary td:last-child { border-right: none; } 126 | .coverage-summary th { 127 | text-align: left; 128 | font-weight: normal; 129 | white-space: nowrap; 130 | } 131 | .coverage-summary th.file { border-right: none !important; } 132 | .coverage-summary th.pct { } 133 | .coverage-summary th.pic, 134 | .coverage-summary th.abs, 135 | .coverage-summary td.pct, 136 | .coverage-summary td.abs { text-align: right; } 137 | .coverage-summary td.file { white-space: nowrap; } 138 | .coverage-summary td.pic { min-width: 120px !important; } 139 | .coverage-summary tfoot td { } 140 | 141 | .coverage-summary .sorter { 142 | height: 10px; 143 | width: 7px; 144 | display: inline-block; 145 | margin-left: 0.5em; 146 | background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent; 147 | } 148 | .coverage-summary .sorted .sorter { 149 | background-position: 0 -20px; 150 | } 151 | .coverage-summary .sorted-desc .sorter { 152 | background-position: 0 -10px; 153 | } 154 | .status-line { height: 10px; } 155 | /* yellow */ 156 | .cbranch-no { background: yellow !important; color: #111; } 157 | /* dark red */ 158 | .red.solid, .status-line.low, .low .cover-fill { background:#C21F39 } 159 | .low .chart { border:1px solid #C21F39 } 160 | .highlighted, 161 | .highlighted .cstat-no, .highlighted .fstat-no, .highlighted .cbranch-no{ 162 | background: #C21F39 !important; 163 | } 164 | /* medium red */ 165 | .cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE } 166 | /* light red */ 167 | .low, .cline-no { background:#FCE1E5 } 168 | /* light green */ 169 | .high, .cline-yes { background:rgb(230,245,208) } 170 | /* medium green */ 171 | .cstat-yes { background:rgb(161,215,106) } 172 | /* dark green */ 173 | .status-line.high, .high .cover-fill { background:rgb(77,146,33) } 174 | .high .chart { border:1px solid rgb(77,146,33) } 175 | /* dark yellow (gold) */ 176 | .status-line.medium, .medium .cover-fill { background: #f9cd0b; } 177 | .medium .chart { border:1px solid #f9cd0b; } 178 | /* light yellow */ 179 | .medium { background: #fff4c2; } 180 | 181 | .cstat-skip { background: #ddd; color: #111; } 182 | .fstat-skip { background: #ddd; color: #111 !important; } 183 | .cbranch-skip { background: #ddd !important; color: #111; } 184 | 185 | span.cline-neutral { background: #eaeaea; } 186 | 187 | .coverage-summary td.empty { 188 | opacity: .5; 189 | padding-top: 4px; 190 | padding-bottom: 4px; 191 | line-height: 1; 192 | color: #888; 193 | } 194 | 195 | .cover-fill, .cover-empty { 196 | display:inline-block; 197 | height: 12px; 198 | } 199 | .chart { 200 | line-height: 0; 201 | } 202 | .cover-empty { 203 | background: white; 204 | } 205 | .cover-full { 206 | border-right: none !important; 207 | } 208 | pre.prettyprint { 209 | border: none !important; 210 | padding: 0 !important; 211 | margin: 0 !important; 212 | } 213 | .com { color: #999 !important; } 214 | .ignore-none { color: #999; font-weight: normal; } 215 | 216 | .wrapper { 217 | min-height: 100%; 218 | height: auto !important; 219 | height: 100%; 220 | margin: 0 auto -48px; 221 | } 222 | .footer, .push { 223 | height: 48px; 224 | } 225 | -------------------------------------------------------------------------------- /coverage/lcov-report/bin/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for bin 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files bin

23 |
24 | 25 |
26 | 80.88% 27 | Statements 28 | 55/68 29 |
30 | 31 | 32 |
33 | 50% 34 | Branches 35 | 3/6 36 |
37 | 38 | 39 |
40 | 50% 41 | Functions 42 | 1/2 43 |
44 | 45 | 46 |
47 | 80.88% 48 | Lines 49 | 55/68 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |
66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 |
FileStatementsBranchesFunctionsLines
ncm-cli.js 84 |
85 |
80.88%55/6850%3/650%1/280.88%55/68
98 |
99 |
100 |
101 | 106 | 107 | 112 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /coverage/lcov-report/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nodesource/ncm-cli/1542f412363e3dc50b5796887ce7444700f6bd53/coverage/lcov-report/favicon.png -------------------------------------------------------------------------------- /coverage/lcov-report/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for All files 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files

23 |
24 | 25 |
26 | 76.34% 27 | Statements 28 | 1246/1632 29 |
30 | 31 | 32 |
33 | 81.34% 34 | Branches 35 | 218/268 36 |
37 | 38 | 39 |
40 | 73.43% 41 | Functions 42 | 47/64 43 |
44 | 45 | 46 |
47 | 76.34% 48 | Lines 49 | 1246/1632 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |
66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 |
FileStatementsBranchesFunctionsLines
bin 84 |
85 |
80.88%55/6850%3/650%1/280.88%55/68
lib 99 |
100 |
70.37%437/62183.33%60/7270%28/4070.37%437/621
lib/report 114 |
115 |
79.95%754/94381.57%155/19081.81%18/2279.95%754/943
128 |
129 |
130 |
131 | 136 | 137 | 142 | 143 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /coverage/lcov-report/lib/help.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for lib/help.js 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files / lib help.js

23 |
24 | 25 |
26 | 60% 27 | Statements 28 | 12/20 29 |
30 | 31 | 32 |
33 | 100% 34 | Branches 35 | 1/1 36 |
37 | 38 | 39 |
40 | 0% 41 | Functions 42 | 0/1 43 |
44 | 45 | 46 |
47 | 60% 48 | Lines 49 | 12/20 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |

 66 | 
1 67 | 2 68 | 3 69 | 4 70 | 5 71 | 6 72 | 7 73 | 8 74 | 9 75 | 10 76 | 11 77 | 12 78 | 13 79 | 14 80 | 15 81 | 16 82 | 17 83 | 18 84 | 19 85 | 20 86 | 2127x 87 | 27x 88 | 27x 89 | 27x 90 | 27x 91 | 27x 92 | 27x 93 | 27x 94 | 27x 95 | 27x 96 | 27x 97 | 27x 98 |   99 |   100 |   101 |   102 |   103 |   104 |   105 |   106 |  
'use strict'
107 |  
108 | const {
109 |   header,
110 |   rawBox
111 | } = require('./ncm-style')
112 | const L = console.log
113 |  
114 | module.exports = {
115 |   helpHeader
116 | }
117 |  
118 | function helpHeader (cmd, usage, usageRaw, description) {
119 |   L()
120 |   L(header(`NCM CLI Help: ncm ${cmd}`))
121 |   description ? L(description) : L()
122 |   L('Usage:')
123 |   L(rawBox(usage, usageRaw.length || usage.length))
124 |   L()
125 | }
126 |  
127 | 128 |
129 |
130 | 135 | 136 | 141 | 142 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /coverage/lcov-report/lib/report/long.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for lib/report/long.js 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files / lib/report long.js

23 |
24 | 25 |
26 | 100% 27 | Statements 28 | 18/18 29 |
30 | 31 | 32 |
33 | 100% 34 | Branches 35 | 3/3 36 |
37 | 38 | 39 |
40 | 100% 41 | Functions 42 | 1/1 43 |
44 | 45 | 46 |
47 | 100% 48 | Lines 49 | 18/18 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |

 66 | 
1 67 | 2 68 | 3 69 | 4 70 | 5 71 | 6 72 | 7 73 | 8 74 | 9 75 | 10 76 | 11 77 | 12 78 | 13 79 | 14 80 | 15 81 | 16 82 | 17 83 | 18 84 | 1927x 85 | 27x 86 | 27x 87 | 27x 88 | 27x 89 | 27x 90 | 27x 91 | 2x 92 | 2x 93 | 2x 94 | 2x 95 | 1x 96 | 1x 97 | 1x 98 | 1x 99 | 1x 100 | 1x 101 | 2x 102 |  
'use strict'
103 |  
104 | module.exports = longReport
105 |  
106 | const summary = require('./summary')
107 | const { moduleList } = require('./util')
108 |  
109 | function longReport (report, whitelist, dir) {
110 |   summary(report, dir)
111 |  
112 |   if (whitelist.length > 0) {
113 |     moduleList(whitelist, 'Whitelisted Modules')
114 |  
115 |     moduleList(report, 'Non-whitelisted Modules')
116 |   } else {
117 |     moduleList(report, 'Modules')
118 |   }
119 | }
120 |  
121 | 122 |
123 |
124 | 129 | 130 | 135 | 136 | 137 | 138 | 139 | -------------------------------------------------------------------------------- /coverage/lcov-report/lib/report/whitelist.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for lib/report/whitelist.js 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files / lib/report whitelist.js

23 |
24 | 25 |
26 | 89.28% 27 | Statements 28 | 25/28 29 |
30 | 31 | 32 |
33 | 66.66% 34 | Branches 35 | 2/3 36 |
37 | 38 | 39 |
40 | 100% 41 | Functions 42 | 1/1 43 |
44 | 45 | 46 |
47 | 89.28% 48 | Lines 49 | 25/28 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |

 66 | 
1 67 | 2 68 | 3 69 | 4 70 | 5 71 | 6 72 | 7 73 | 8 74 | 9 75 | 10 76 | 11 77 | 12 78 | 13 79 | 14 80 | 15 81 | 16 82 | 17 83 | 18 84 | 19 85 | 20 86 | 21 87 | 22 88 | 23 89 | 24 90 | 25 91 | 26 92 | 27 93 | 28 94 | 2927x 95 | 27x 96 | 27x 97 | 27x 98 | 27x 99 | 27x 100 | 27x 101 | 27x 102 | 27x 103 | 27x 104 | 27x 105 | 27x 106 | 27x 107 | 2x 108 | 2x 109 | 2x 110 | 2x 111 | 2x 112 |   113 |   114 |   115 | 2x 116 | 2x 117 | 2x 118 | 2x 119 | 2x 120 | 2x 121 | 2x 122 |  
'use strict'
123 |  
124 | const { moduleList } = require('./util')
125 | const {
126 |   COLORS,
127 |   tooltip
128 | } = require('../ncm-style')
129 | const L = console.log
130 |  
131 | const chalk = require('chalk')
132 |  
133 | module.exports = whitelistReport
134 |  
135 | function whitelistReport (report) {
136 |   L()
137 |   L(chalk`${report.length} {${COLORS.light1} modules total}`)
138 |  
139 |   if (report.length === 0) {
140 |     L()
141 |     L(tooltip('Run `ncm whitelist --add <pkg-name>@<ver>` to add a package to the whitelist.'))
142 |     L()
143 |   } else {
144 |     /* todo: what to do if unpublished */
145 |     moduleList(report, 'Whitelisted Modules')
146 |     L(tooltip('Run `ncm whitelist --remove <pkg-name>@<ver>` to remove a package from the whitelist.'))
147 |     L()
148 |   }
149 | }
150 |  
151 | 152 |
153 |
154 | 159 | 160 | 165 | 166 | 167 | 168 | 169 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /coverage/lcov-report/sort-arrow-sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nodesource/ncm-cli/1542f412363e3dc50b5796887ce7444700f6bd53/coverage/lcov-report/sort-arrow-sprite.png -------------------------------------------------------------------------------- /coverage/lcov-report/sorter.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | var addSorting = (function() { 3 | 'use strict'; 4 | var cols, 5 | currentSort = { 6 | index: 0, 7 | desc: false 8 | }; 9 | 10 | // returns the summary table element 11 | function getTable() { 12 | return document.querySelector('.coverage-summary'); 13 | } 14 | // returns the thead element of the summary table 15 | function getTableHeader() { 16 | return getTable().querySelector('thead tr'); 17 | } 18 | // returns the tbody element of the summary table 19 | function getTableBody() { 20 | return getTable().querySelector('tbody'); 21 | } 22 | // returns the th element for nth column 23 | function getNthColumn(n) { 24 | return getTableHeader().querySelectorAll('th')[n]; 25 | } 26 | 27 | function onFilterInput() { 28 | const searchValue = document.getElementById('fileSearch').value; 29 | const rows = document.getElementsByTagName('tbody')[0].children; 30 | for (let i = 0; i < rows.length; i++) { 31 | const row = rows[i]; 32 | if ( 33 | row.textContent 34 | .toLowerCase() 35 | .includes(searchValue.toLowerCase()) 36 | ) { 37 | row.style.display = ''; 38 | } else { 39 | row.style.display = 'none'; 40 | } 41 | } 42 | } 43 | 44 | // loads the search box 45 | function addSearchBox() { 46 | var template = document.getElementById('filterTemplate'); 47 | var templateClone = template.content.cloneNode(true); 48 | templateClone.getElementById('fileSearch').oninput = onFilterInput; 49 | template.parentElement.appendChild(templateClone); 50 | } 51 | 52 | // loads all columns 53 | function loadColumns() { 54 | var colNodes = getTableHeader().querySelectorAll('th'), 55 | colNode, 56 | cols = [], 57 | col, 58 | i; 59 | 60 | for (i = 0; i < colNodes.length; i += 1) { 61 | colNode = colNodes[i]; 62 | col = { 63 | key: colNode.getAttribute('data-col'), 64 | sortable: !colNode.getAttribute('data-nosort'), 65 | type: colNode.getAttribute('data-type') || 'string' 66 | }; 67 | cols.push(col); 68 | if (col.sortable) { 69 | col.defaultDescSort = col.type === 'number'; 70 | colNode.innerHTML = 71 | colNode.innerHTML + ''; 72 | } 73 | } 74 | return cols; 75 | } 76 | // attaches a data attribute to every tr element with an object 77 | // of data values keyed by column name 78 | function loadRowData(tableRow) { 79 | var tableCols = tableRow.querySelectorAll('td'), 80 | colNode, 81 | col, 82 | data = {}, 83 | i, 84 | val; 85 | for (i = 0; i < tableCols.length; i += 1) { 86 | colNode = tableCols[i]; 87 | col = cols[i]; 88 | val = colNode.getAttribute('data-value'); 89 | if (col.type === 'number') { 90 | val = Number(val); 91 | } 92 | data[col.key] = val; 93 | } 94 | return data; 95 | } 96 | // loads all row data 97 | function loadData() { 98 | var rows = getTableBody().querySelectorAll('tr'), 99 | i; 100 | 101 | for (i = 0; i < rows.length; i += 1) { 102 | rows[i].data = loadRowData(rows[i]); 103 | } 104 | } 105 | // sorts the table using the data for the ith column 106 | function sortByIndex(index, desc) { 107 | var key = cols[index].key, 108 | sorter = function(a, b) { 109 | a = a.data[key]; 110 | b = b.data[key]; 111 | return a < b ? -1 : a > b ? 1 : 0; 112 | }, 113 | finalSorter = sorter, 114 | tableBody = document.querySelector('.coverage-summary tbody'), 115 | rowNodes = tableBody.querySelectorAll('tr'), 116 | rows = [], 117 | i; 118 | 119 | if (desc) { 120 | finalSorter = function(a, b) { 121 | return -1 * sorter(a, b); 122 | }; 123 | } 124 | 125 | for (i = 0; i < rowNodes.length; i += 1) { 126 | rows.push(rowNodes[i]); 127 | tableBody.removeChild(rowNodes[i]); 128 | } 129 | 130 | rows.sort(finalSorter); 131 | 132 | for (i = 0; i < rows.length; i += 1) { 133 | tableBody.appendChild(rows[i]); 134 | } 135 | } 136 | // removes sort indicators for current column being sorted 137 | function removeSortIndicators() { 138 | var col = getNthColumn(currentSort.index), 139 | cls = col.className; 140 | 141 | cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, ''); 142 | col.className = cls; 143 | } 144 | // adds sort indicators for current column being sorted 145 | function addSortIndicators() { 146 | getNthColumn(currentSort.index).className += currentSort.desc 147 | ? ' sorted-desc' 148 | : ' sorted'; 149 | } 150 | // adds event listeners for all sorter widgets 151 | function enableUI() { 152 | var i, 153 | el, 154 | ithSorter = function ithSorter(i) { 155 | var col = cols[i]; 156 | 157 | return function() { 158 | var desc = col.defaultDescSort; 159 | 160 | if (currentSort.index === i) { 161 | desc = !currentSort.desc; 162 | } 163 | sortByIndex(i, desc); 164 | removeSortIndicators(); 165 | currentSort.index = i; 166 | currentSort.desc = desc; 167 | addSortIndicators(); 168 | }; 169 | }; 170 | for (i = 0; i < cols.length; i += 1) { 171 | if (cols[i].sortable) { 172 | // add the click event handler on the th so users 173 | // dont have to click on those tiny arrows 174 | el = getNthColumn(i).querySelector('.sorter').parentElement; 175 | if (el.addEventListener) { 176 | el.addEventListener('click', ithSorter(i)); 177 | } else { 178 | el.attachEvent('onclick', ithSorter(i)); 179 | } 180 | } 181 | } 182 | } 183 | // adds sorting functionality to the UI 184 | return function() { 185 | if (!getTable()) { 186 | return; 187 | } 188 | cols = loadColumns(); 189 | loadData(); 190 | addSearchBox(); 191 | addSortIndicators(); 192 | enableUI(); 193 | }; 194 | })(); 195 | 196 | window.addEventListener('load', addSorting); 197 | -------------------------------------------------------------------------------- /coverage/lib/help.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for lib/help.js 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files / lib help.js

23 |
24 | 25 |
26 | 60% 27 | Statements 28 | 12/20 29 |
30 | 31 | 32 |
33 | 100% 34 | Branches 35 | 1/1 36 |
37 | 38 | 39 |
40 | 0% 41 | Functions 42 | 0/1 43 |
44 | 45 | 46 |
47 | 60% 48 | Lines 49 | 12/20 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |

 66 | 
1 67 | 2 68 | 3 69 | 4 70 | 5 71 | 6 72 | 7 73 | 8 74 | 9 75 | 10 76 | 11 77 | 12 78 | 13 79 | 14 80 | 15 81 | 16 82 | 17 83 | 18 84 | 19 85 | 20 86 | 2127x 87 | 27x 88 | 27x 89 | 27x 90 | 27x 91 | 27x 92 | 27x 93 | 27x 94 | 27x 95 | 27x 96 | 27x 97 | 27x 98 |   99 |   100 |   101 |   102 |   103 |   104 |   105 |   106 |  
'use strict'
107 |  
108 | const {
109 |   header,
110 |   rawBox
111 | } = require('./ncm-style')
112 | const L = console.log
113 |  
114 | module.exports = {
115 |   helpHeader
116 | }
117 |  
118 | function helpHeader (cmd, usage, usageRaw, description) {
119 |   L()
120 |   L(header(`NCM CLI Help: ncm ${cmd}`))
121 |   description ? L(description) : L()
122 |   L('Usage:')
123 |   L(rawBox(usage, usageRaw.length || usage.length))
124 |   L()
125 | }
126 |  
127 | 128 |
129 |
130 | 135 | 136 | 141 | 142 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /coverage/lib/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for lib 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files lib

23 |
24 | 25 |
26 | 70.37% 27 | Statements 28 | 437/621 29 |
30 | 31 | 32 |
33 | 83.33% 34 | Branches 35 | 60/72 36 |
37 | 38 | 39 |
40 | 70% 41 | Functions 42 | 28/40 43 |
44 | 45 | 46 |
47 | 70.37% 48 | Lines 49 | 437/621 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |
66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 |
FileStatementsBranchesFunctionsLines
client-request.js 84 |
85 |
62.88%61/9761.53%8/13100%2/262.88%61/97
config.js 99 |
100 |
64.07%66/10372.72%8/1150%4/864.07%66/103
help.js 114 |
115 |
60%12/20100%1/10%0/160%12/20
ncm-analyze-tree.js 129 |
130 |
97.61%123/12695.83%23/24100%9/997.61%123/126
ncm-style.js 144 |
145 |
84.34%97/11584.61%11/1366.66%8/1284.34%97/115
util.js 159 |
160 |
48.75%78/16090%9/1062.5%5/848.75%78/160
173 |
174 |
175 |
176 | 181 | 182 | 187 | 188 | 189 | 190 | 191 | -------------------------------------------------------------------------------- /coverage/lib/report/long.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for lib/report/long.js 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files / lib/report long.js

23 |
24 | 25 |
26 | 100% 27 | Statements 28 | 18/18 29 |
30 | 31 | 32 |
33 | 100% 34 | Branches 35 | 3/3 36 |
37 | 38 | 39 |
40 | 100% 41 | Functions 42 | 1/1 43 |
44 | 45 | 46 |
47 | 100% 48 | Lines 49 | 18/18 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |

 66 | 
1 67 | 2 68 | 3 69 | 4 70 | 5 71 | 6 72 | 7 73 | 8 74 | 9 75 | 10 76 | 11 77 | 12 78 | 13 79 | 14 80 | 15 81 | 16 82 | 17 83 | 18 84 | 1927x 85 | 27x 86 | 27x 87 | 27x 88 | 27x 89 | 27x 90 | 27x 91 | 2x 92 | 2x 93 | 2x 94 | 2x 95 | 1x 96 | 1x 97 | 1x 98 | 1x 99 | 1x 100 | 1x 101 | 2x 102 |  
'use strict'
103 |  
104 | module.exports = longReport
105 |  
106 | const summary = require('./summary')
107 | const { moduleList } = require('./util')
108 |  
109 | function longReport (report, whitelist, dir) {
110 |   summary(report, dir)
111 |  
112 |   if (whitelist.length > 0) {
113 |     moduleList(whitelist, 'Whitelisted Modules')
114 |  
115 |     moduleList(report, 'Non-whitelisted Modules')
116 |   } else {
117 |     moduleList(report, 'Modules')
118 |   }
119 | }
120 |  
121 | 122 |
123 |
124 | 129 | 130 | 135 | 136 | 137 | 138 | 139 | -------------------------------------------------------------------------------- /coverage/lib/report/whitelist.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for lib/report/whitelist.js 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files / lib/report whitelist.js

23 |
24 | 25 |
26 | 89.28% 27 | Statements 28 | 25/28 29 |
30 | 31 | 32 |
33 | 66.66% 34 | Branches 35 | 2/3 36 |
37 | 38 | 39 |
40 | 100% 41 | Functions 42 | 1/1 43 |
44 | 45 | 46 |
47 | 89.28% 48 | Lines 49 | 25/28 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |

 66 | 
1 67 | 2 68 | 3 69 | 4 70 | 5 71 | 6 72 | 7 73 | 8 74 | 9 75 | 10 76 | 11 77 | 12 78 | 13 79 | 14 80 | 15 81 | 16 82 | 17 83 | 18 84 | 19 85 | 20 86 | 21 87 | 22 88 | 23 89 | 24 90 | 25 91 | 26 92 | 27 93 | 28 94 | 2927x 95 | 27x 96 | 27x 97 | 27x 98 | 27x 99 | 27x 100 | 27x 101 | 27x 102 | 27x 103 | 27x 104 | 27x 105 | 27x 106 | 27x 107 | 2x 108 | 2x 109 | 2x 110 | 2x 111 | 2x 112 |   113 |   114 |   115 | 2x 116 | 2x 117 | 2x 118 | 2x 119 | 2x 120 | 2x 121 | 2x 122 |  
'use strict'
123 |  
124 | const { moduleList } = require('./util')
125 | const {
126 |   COLORS,
127 |   tooltip
128 | } = require('../ncm-style')
129 | const L = console.log
130 |  
131 | const chalk = require('chalk')
132 |  
133 | module.exports = whitelistReport
134 |  
135 | function whitelistReport (report) {
136 |   L()
137 |   L(chalk`${report.length} {${COLORS.light1} modules total}`)
138 |  
139 |   if (report.length === 0) {
140 |     L()
141 |     L(tooltip('Run `ncm whitelist --add <pkg-name>@<ver>` to add a package to the whitelist.'))
142 |     L()
143 |   } else {
144 |     /* todo: what to do if unpublished */
145 |     moduleList(report, 'Whitelisted Modules')
146 |     L(tooltip('Run `ncm whitelist --remove <pkg-name>@<ver>` to remove a package from the whitelist.'))
147 |     L()
148 |   }
149 | }
150 |  
151 | 152 |
153 |
154 | 159 | 160 | 165 | 166 | 167 | 168 | 169 | -------------------------------------------------------------------------------- /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/sort-arrow-sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nodesource/ncm-cli/1542f412363e3dc50b5796887ce7444700f6bd53/coverage/sort-arrow-sprite.png -------------------------------------------------------------------------------- /coverage/sorter.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | var addSorting = (function() { 3 | 'use strict'; 4 | var cols, 5 | currentSort = { 6 | index: 0, 7 | desc: false 8 | }; 9 | 10 | // returns the summary table element 11 | function getTable() { 12 | return document.querySelector('.coverage-summary'); 13 | } 14 | // returns the thead element of the summary table 15 | function getTableHeader() { 16 | return getTable().querySelector('thead tr'); 17 | } 18 | // returns the tbody element of the summary table 19 | function getTableBody() { 20 | return getTable().querySelector('tbody'); 21 | } 22 | // returns the th element for nth column 23 | function getNthColumn(n) { 24 | return getTableHeader().querySelectorAll('th')[n]; 25 | } 26 | 27 | function onFilterInput() { 28 | const searchValue = document.getElementById('fileSearch').value; 29 | const rows = document.getElementsByTagName('tbody')[0].children; 30 | for (let i = 0; i < rows.length; i++) { 31 | const row = rows[i]; 32 | if ( 33 | row.textContent 34 | .toLowerCase() 35 | .includes(searchValue.toLowerCase()) 36 | ) { 37 | row.style.display = ''; 38 | } else { 39 | row.style.display = 'none'; 40 | } 41 | } 42 | } 43 | 44 | // loads the search box 45 | function addSearchBox() { 46 | var template = document.getElementById('filterTemplate'); 47 | var templateClone = template.content.cloneNode(true); 48 | templateClone.getElementById('fileSearch').oninput = onFilterInput; 49 | template.parentElement.appendChild(templateClone); 50 | } 51 | 52 | // loads all columns 53 | function loadColumns() { 54 | var colNodes = getTableHeader().querySelectorAll('th'), 55 | colNode, 56 | cols = [], 57 | col, 58 | i; 59 | 60 | for (i = 0; i < colNodes.length; i += 1) { 61 | colNode = colNodes[i]; 62 | col = { 63 | key: colNode.getAttribute('data-col'), 64 | sortable: !colNode.getAttribute('data-nosort'), 65 | type: colNode.getAttribute('data-type') || 'string' 66 | }; 67 | cols.push(col); 68 | if (col.sortable) { 69 | col.defaultDescSort = col.type === 'number'; 70 | colNode.innerHTML = 71 | colNode.innerHTML + ''; 72 | } 73 | } 74 | return cols; 75 | } 76 | // attaches a data attribute to every tr element with an object 77 | // of data values keyed by column name 78 | function loadRowData(tableRow) { 79 | var tableCols = tableRow.querySelectorAll('td'), 80 | colNode, 81 | col, 82 | data = {}, 83 | i, 84 | val; 85 | for (i = 0; i < tableCols.length; i += 1) { 86 | colNode = tableCols[i]; 87 | col = cols[i]; 88 | val = colNode.getAttribute('data-value'); 89 | if (col.type === 'number') { 90 | val = Number(val); 91 | } 92 | data[col.key] = val; 93 | } 94 | return data; 95 | } 96 | // loads all row data 97 | function loadData() { 98 | var rows = getTableBody().querySelectorAll('tr'), 99 | i; 100 | 101 | for (i = 0; i < rows.length; i += 1) { 102 | rows[i].data = loadRowData(rows[i]); 103 | } 104 | } 105 | // sorts the table using the data for the ith column 106 | function sortByIndex(index, desc) { 107 | var key = cols[index].key, 108 | sorter = function(a, b) { 109 | a = a.data[key]; 110 | b = b.data[key]; 111 | return a < b ? -1 : a > b ? 1 : 0; 112 | }, 113 | finalSorter = sorter, 114 | tableBody = document.querySelector('.coverage-summary tbody'), 115 | rowNodes = tableBody.querySelectorAll('tr'), 116 | rows = [], 117 | i; 118 | 119 | if (desc) { 120 | finalSorter = function(a, b) { 121 | return -1 * sorter(a, b); 122 | }; 123 | } 124 | 125 | for (i = 0; i < rowNodes.length; i += 1) { 126 | rows.push(rowNodes[i]); 127 | tableBody.removeChild(rowNodes[i]); 128 | } 129 | 130 | rows.sort(finalSorter); 131 | 132 | for (i = 0; i < rows.length; i += 1) { 133 | tableBody.appendChild(rows[i]); 134 | } 135 | } 136 | // removes sort indicators for current column being sorted 137 | function removeSortIndicators() { 138 | var col = getNthColumn(currentSort.index), 139 | cls = col.className; 140 | 141 | cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, ''); 142 | col.className = cls; 143 | } 144 | // adds sort indicators for current column being sorted 145 | function addSortIndicators() { 146 | getNthColumn(currentSort.index).className += currentSort.desc 147 | ? ' sorted-desc' 148 | : ' sorted'; 149 | } 150 | // adds event listeners for all sorter widgets 151 | function enableUI() { 152 | var i, 153 | el, 154 | ithSorter = function ithSorter(i) { 155 | var col = cols[i]; 156 | 157 | return function() { 158 | var desc = col.defaultDescSort; 159 | 160 | if (currentSort.index === i) { 161 | desc = !currentSort.desc; 162 | } 163 | sortByIndex(i, desc); 164 | removeSortIndicators(); 165 | currentSort.index = i; 166 | currentSort.desc = desc; 167 | addSortIndicators(); 168 | }; 169 | }; 170 | for (i = 0; i < cols.length; i += 1) { 171 | if (cols[i].sortable) { 172 | // add the click event handler on the th so users 173 | // dont have to click on those tiny arrows 174 | el = getNthColumn(i).querySelector('.sorter').parentElement; 175 | if (el.addEventListener) { 176 | el.addEventListener('click', ithSorter(i)); 177 | } else { 178 | el.attachEvent('onclick', ithSorter(i)); 179 | } 180 | } 181 | } 182 | } 183 | // adds sorting functionality to the UI 184 | return function() { 185 | if (!getTable()) { 186 | return; 187 | } 188 | cols = loadColumns(); 189 | loadData(); 190 | addSearchBox(); 191 | addSortIndicators(); 192 | enableUI(); 193 | }; 194 | })(); 195 | 196 | window.addEventListener('load', addSorting); 197 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/report/github-action.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('fs') 4 | // Update import for @actions/github to fix TypeScript error 5 | let github; 6 | try { 7 | github = require('@actions/github'); 8 | } catch (err) { 9 | // Provide fallbacks if the module is not available 10 | github = { 11 | context: { repo: { owner: '', repo: '' } }, 12 | getOctokit: () => ({}) 13 | }; 14 | } 15 | const core = require('@actions/core') 16 | 17 | async function GitHubActionCheck (pkgScores, whitelist, hasFailures) { 18 | const packageJSONAction = fs.readFileSync('package.json', 'utf8') 19 | 20 | // This token is not created by the user setting up 21 | // the workflow. This is extracted by GitHub Actions 22 | // by design from the context of the job during 23 | // execution. 24 | const gToken = core.getInput('GITHUB_TOKEN') 25 | // Use getOctokit instead of GitHub constructor 26 | const octokit = github.getOctokit ? github.getOctokit(gToken) : {} 27 | const contextData = { 28 | owner: github.context?.repo?.owner || '', 29 | repo: github.context?.repo?.repo || '' 30 | } 31 | 32 | // Identify the check currently running 33 | // we don't want to create a new one, only 34 | // update the current with fail and warning 35 | // annotations. 36 | const gitOwner = github.context?.repo?.owner || '' 37 | const gitRepo = github.context?.repo?.repo || '' 38 | const gitRef = github.context?.sha || '' 39 | const listChecks = await octokit.checks.listForRef({ 40 | owner: gitOwner, 41 | repo: gitRepo, 42 | ref: gitRef 43 | }) 44 | 45 | if (hasFailures) { 46 | // Create fail annotations array 47 | const failAnnotations = [] 48 | for (const pkg of pkgScores) { 49 | const pkgName = pkg.name 50 | const pkgVersion = pkg.version 51 | // Get the line in the package.json where this module 52 | // was introduced, if not, means it's a dependency of 53 | // a dependency. If not found, we set 1 as safe placeholder. 54 | const lineInPackageJSON = getLineNumber(packageJSONAction, `"${pkgName}":+`) 55 | const parsedLine = lineInPackageJSON.length !== 0 56 | ? lineInPackageJSON[0].number 57 | : 1 58 | 59 | // This is a filter to push only the ones we found in the 60 | // package.json and ignore the rest 61 | if (parsedLine !== 1) { 62 | let failureMessages = '' 63 | if (pkg.failures.length !== 0) { 64 | for (const failure of pkg.failures) { 65 | // This is a filter to push only the ones we found in the 66 | // package.json and ignore the rest 67 | failureMessages += `\nCode: ${failure.name}\n` 68 | failureMessages += `Message: ${failure.title}\n` 69 | failureMessages += `Severity: ${failure.severity}\n` 70 | failureMessages += `Group: ${failure.group}\n` 71 | failureMessages += failure.data == null 72 | ? '' 73 | : failure.data.spdx == null 74 | ? '' 75 | : `Locations: ${failure.data.locations}\n` 76 | } 77 | failAnnotations.push({ 78 | path: 'package.json', 79 | start_line: parsedLine, 80 | end_line: parsedLine, 81 | annotation_level: 'failure', 82 | message: failureMessages, 83 | title: `[${pkgName}@${pkgVersion}] Certification Failure` 84 | }) 85 | } 86 | // This is necessary to send the las entries that were not 87 | // sent because a new chunk of 49 objects in the failAnnotations 88 | // array was not completed. 89 | await updateCheck(octokit, listChecks, failAnnotations, contextData) 90 | // Clean to avoid duplicated entry push 91 | failAnnotations.length = 0 92 | } 93 | } 94 | } 95 | 96 | // Whilist warnings should appear, no matter if the 97 | // check failed. We still need to inform about them. 98 | const warningAnnotations = [] 99 | for (const pkg of whitelist) { 100 | const lineInWhitelistPackage = getLineNumber(packageJSONAction, `"${pkg.name}":+`) 101 | const parsedWhitelistLine = lineInWhitelistPackage.length !== 0 102 | ? lineInWhitelistPackage[0].number 103 | : 1 104 | warningAnnotations.push({ 105 | path: 'package.json', 106 | start_line: parsedWhitelistLine, 107 | end_line: parsedWhitelistLine, 108 | annotation_level: 'warning', 109 | message: 'This package version is Whitelisted.', 110 | title: `[${pkg.name}@${pkg.version}] Certification Warning` 111 | }) 112 | } 113 | 114 | // Fix comparison to check array length instead 115 | if (warningAnnotations && warningAnnotations.length > 0) { 116 | await updateCheck(octokit, listChecks, warningAnnotations, contextData) 117 | } 118 | } 119 | 120 | module.exports = GitHubActionCheck 121 | 122 | async function updateCheck (octokit, listChecks, annotations, contextData) { 123 | // The check is identified, let's update with an 124 | // array of annotations 125 | 126 | const runName = core.getInput('GITHUB_JOB_NAME') 127 | const logUpdateCheck = () => 128 | console.log('Unable to be sure of the check run ID, are you sure you set a name for the Job in the workflow file and passed the same to the action?') 129 | 130 | if (!listChecks || !listChecks.data) return logUpdateCheck() 131 | 132 | for (const run of listChecks.data.check_runs) { 133 | if (run.name === runName) { 134 | await octokit.checks.update({ 135 | owner: contextData.owner, 136 | repo: contextData.repo, 137 | check_run_id: run.id, 138 | output: { 139 | title: 'NodeSource Ceritification process', 140 | summary: '', 141 | text: '', 142 | annotations 143 | } 144 | }) 145 | } else { 146 | logUpdateCheck() 147 | } 148 | } 149 | } 150 | 151 | module.exports = updateCheck 152 | 153 | function getLineNumber (file, str) { 154 | const pattern = new RegExp(str) 155 | return file.split(/\r?\n/).map((line, n) => { 156 | if (pattern.test(line)) { 157 | return { 158 | line, 159 | number: n + 1 160 | } 161 | } 162 | return false 163 | }).filter(Boolean) 164 | } 165 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lib/report/module.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = moduleReport 4 | 5 | const { 6 | W, 7 | SEVERITY_RMAP, 8 | SEVERITY_COLOR, 9 | severityMeter, 10 | shortVulnerabilityList 11 | } = require('./util') 12 | const { 13 | COLORS, 14 | boxbox 15 | } = require('../ncm-style') 16 | const chalk = require('chalk') 17 | const L = console.log 18 | 19 | function moduleReport (report) { 20 | const { scores, failures = [], license, requirePaths } = report 21 | 22 | let moduleRisks = [] 23 | const securityVulnerabilities = [] 24 | const codeQuality = [] 25 | 26 | for (const score of scores) { 27 | if (score.group === 'risk' && 28 | score.pass === false) { 29 | moduleRisks.push(score) 30 | } 31 | if (score.group === 'quality' && 32 | score.pass === false) { 33 | codeQuality.push(score) 34 | } 35 | if (score.group === 'security' && 36 | score.pass === false) { 37 | securityVulnerabilities.push(score) 38 | } 39 | } 40 | 41 | moduleRisks = moduleRisks.sort((a, b) => { 42 | if (SEVERITY_RMAP.indexOf(a.severity) > SEVERITY_RMAP.indexOf(b.severity)) return -1 43 | if (SEVERITY_RMAP.indexOf(b.severity) > SEVERITY_RMAP.indexOf(a.severity)) return 1 44 | return 0 45 | }) 46 | 47 | const maxLength = W.reduce((a, b) => a + b) 48 | 49 | /* Module Risk */ 50 | let moduleRisk = 'NONE' 51 | for (const { severity } of failures) { 52 | if (SEVERITY_RMAP.indexOf(severity) > SEVERITY_RMAP.indexOf(moduleRisk)) { 53 | moduleRisk = severity 54 | } 55 | } 56 | const riskColor = SEVERITY_COLOR[moduleRisk] 57 | 58 | L() 59 | L(boxbox( 60 | severityMeter(moduleRisk), 61 | `${moduleRisk[0].toUpperCase() + moduleRisk.slice(1).toLowerCase()} Risk`, 62 | riskColor, 63 | 4 // Need to override the symbol length because unicode. 64 | )) 65 | 66 | /* Security Overview */ 67 | L() 68 | L(chalk`{bold Security Risk:}`) 69 | shortVulnerabilityList([report]) 70 | L() 71 | 72 | /* Security Detail */ 73 | if (securityVulnerabilities.length > 0) { 74 | for (const { severity, title, data } of securityVulnerabilities) { 75 | L(boxbox( 76 | severity[0].toUpperCase(), 77 | title, 78 | SEVERITY_COLOR[severity] 79 | )) 80 | 81 | L(chalk`{${COLORS.light1} Versions ${data.vulnerable.join(' ')} (Source: Synk)}`) 82 | L(chalk`{${COLORS.blue} |➔ https://snyk.io/vuln/${data.id}}`) 83 | } 84 | } else { 85 | L(boxbox('✓', 'No Security Vulnerabilities', COLORS.green)) 86 | } 87 | 88 | /* License */ 89 | L() 90 | L(chalk`{bold License Risk:}`) 91 | const licenseName = license && 92 | license.data && 93 | license.data.spdx 94 | ? license.data.spdx 95 | : 'UNKNOWN' 96 | if (license && license.pass === true) { 97 | L(boxbox('✓', licenseName, COLORS.green)) 98 | } else { 99 | let msg = 'Unknown license' 100 | if (licenseName) { 101 | msg = `Noncompliant license: ${licenseName}` 102 | } 103 | L(boxbox('X', msg, COLORS.red)) 104 | } 105 | 106 | /* Module Risk */ 107 | L() 108 | L(chalk`{bold Module Risk:}`) 109 | if (moduleRisks.length > 0) { 110 | moduleRisks.forEach(({ title, severity }, ind) => { 111 | if (ind === 0) { 112 | L(chalk`{${COLORS.light1} ┌──────┬─${'─'.repeat(maxLength)}─┐}`) 113 | } 114 | 115 | severity = severityTextLabel(severity) 116 | 117 | const lines = lineWrap(title, maxLength) 118 | 119 | { 120 | const line = lines.shift() 121 | L(chalk`{${COLORS.light1} │} {${COLORS.red} ${severity}} {${COLORS.light1} │}` + 122 | chalk`{white ${line}} ${' '.repeat(maxLength - line.length)}{${COLORS.light1} │}`) 123 | } 124 | 125 | for (const line of lines) { 126 | L(chalk`{${COLORS.light1} │} {${COLORS.light1} │}` + 127 | chalk`{white ${line}} ${' '.repeat(maxLength - line.length)}{${COLORS.light1} │}`) 128 | } 129 | 130 | if (ind === moduleRisks.length - 1) { 131 | L(chalk`{${COLORS.light1} └──────┴─${'─'.repeat(maxLength)}─┘}`) 132 | } else { 133 | L(chalk`{${COLORS.light1} ├──────┼─${'─'.repeat(maxLength)}─┤}`) 134 | } 135 | }) 136 | } else { 137 | L(boxbox('✓', 'No Module Risk', COLORS.green)) 138 | } 139 | 140 | /* Code Quality */ 141 | L() 142 | L(chalk`{bold Code Quality} (does not affect risk score){bold :}`) 143 | if (codeQuality.length > 0) { 144 | codeQuality.forEach(({ title }, ind) => { 145 | if (ind === 0) { 146 | L(chalk`{${COLORS.light1} ┌───┬─${'─'.repeat(maxLength)}─┐}`) 147 | } 148 | 149 | const lines = lineWrap(title, maxLength) 150 | 151 | { 152 | const line = lines.shift() 153 | L(chalk`{${COLORS.light1} │} {${COLORS.yellow} !} {${COLORS.light1} │}` + 154 | chalk`{white ${line}} ${' '.repeat(maxLength - line.length)}{${COLORS.light1} │}`) 155 | } 156 | 157 | for (const line of lines) { 158 | L(chalk`{${COLORS.light1} │} {${COLORS.light1} │}` + 159 | chalk`{white ${line}} ${' '.repeat(maxLength - line.length)}{${COLORS.light1} │}`) 160 | } 161 | 162 | if (ind === codeQuality.length - 1) { 163 | L(chalk`{${COLORS.light1} └───┴─${'─'.repeat(maxLength)}─┘}`) 164 | } else { 165 | L(chalk`{${COLORS.light1} ├───┼─${'─'.repeat(maxLength)}─┤}`) 166 | } 167 | }) 168 | } else { 169 | L(boxbox('✓', 'Passes all criteria', COLORS.green)) 170 | } 171 | if (requirePaths.length > 0) { 172 | /* Required By */ 173 | L() 174 | L(chalk`{bold Required By} (leftmost is directly in your package){bold :}`) 175 | requirePaths.sort().forEach((path, ind) => { 176 | if (ind === 0) { 177 | L(chalk`{${COLORS.light1} ┌─${'─'.repeat(maxLength)}─┐}`) 178 | } 179 | 180 | let formatted = path.map(({ data }) => `${data.name} @ ${data.version}`).join(' / ') 181 | if (!formatted) formatted = '(Directly in your package)' 182 | const lines = lineWrap(formatted, maxLength, '/') 183 | 184 | for (const line of lines) { 185 | L(chalk`{${COLORS.light1} │} ${line} ${' '.repeat(maxLength - line.length)}{${COLORS.light1} │}`) 186 | } 187 | 188 | if (ind === requirePaths.length - 1) { 189 | L(chalk`{${COLORS.light1} └─${'─'.repeat(maxLength)}─┘}`) 190 | } else { 191 | L(chalk`{${COLORS.light1} ├─${'─'.repeat(maxLength)}─┤}`) 192 | } 193 | }) 194 | } 195 | L() 196 | } 197 | 198 | function severityTextLabel (severity) { 199 | const severityLabel = { 200 | CRITICAL: 'Crit', 201 | HIGH: 'High', 202 | MEDIUM: 'Med ', 203 | LOW: 'Low ', 204 | NONE: 'None' 205 | } 206 | return chalk`{${SEVERITY_COLOR[severity]} ${severityLabel[severity]}}` 207 | } 208 | 209 | function lineWrap (str, maxLength, split = ' ') { 210 | const words = str.split(split) 211 | const lines = [] 212 | let lineIdx = 0 213 | for (const word of words) { 214 | while ((lines[lineIdx] + word).length >= maxLength) { 215 | lineIdx++ 216 | } 217 | if (!lines[lineIdx]) lines[lineIdx] = lineIdx > 0 ? [''] : [] 218 | lines[lineIdx].push(word) 219 | } 220 | return lines.map(line => line.join(split).trim()) 221 | } 222 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lib/report/short.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = shortReport 4 | 5 | const summary = require('./summary') 6 | const { moduleList, SEVERITY_RMAP } = require('./util') 7 | 8 | const { 9 | COLORS, 10 | tooltip 11 | } = require('../ncm-style') 12 | const chalk = require('chalk') 13 | const L = console.log 14 | 15 | function shortReport (report, whitelist, dir, argv) { 16 | let filterSecurity = argv ? !!argv.security : false 17 | let filterCompliance = argv ? !!argv.compliance : false 18 | let filterLevel = SEVERITY_RMAP.indexOf('NONE') 19 | 20 | if (argv.filter) { 21 | const segments = argv.filter.split(',') 22 | .map(s => s.trim().toLowerCase()) 23 | 24 | if (segments.includes('compliance')) { 25 | filterCompliance = true 26 | } 27 | if (segments.includes('security')) { 28 | filterSecurity = true 29 | } 30 | if (segments.includes('c') || segments.includes('critical')) { 31 | filterLevel = SEVERITY_RMAP.indexOf('CRITICAL') 32 | } 33 | if (segments.includes('h') || segments.includes('high')) { 34 | filterLevel = SEVERITY_RMAP.indexOf('HIGH') 35 | } 36 | if (segments.includes('m') || segments.includes('medium')) { 37 | filterLevel = SEVERITY_RMAP.indexOf('MEDIUM') 38 | } 39 | if (segments.includes('l') || segments.includes('low')) { 40 | filterLevel = SEVERITY_RMAP.indexOf('LOW') 41 | } 42 | } 43 | 44 | const filterOptions = { 45 | filterCompliance, 46 | filterSecurity, 47 | filterLevel 48 | } 49 | 50 | const riskCount = summary(report, dir, filterOptions) || [0, 0, 0, 0, 0] 51 | riskCount.shift() 52 | 53 | if (whitelist.length > 0) { 54 | L(chalk` {${COLORS.yellow} !} ${whitelist.length} used modules whitelisted`) 55 | L(' ' + tooltip('Run `ncm whitelist --list` for a list')) 56 | L() 57 | } 58 | 59 | let maxSeverityOfReport = 0 60 | report.forEach(({ maxSeverity }) => { 61 | maxSeverityOfReport = Math.max(maxSeverity, maxSeverityOfReport) 62 | }) 63 | 64 | if (filterCompliance || filterSecurity || filterLevel > 0) { 65 | const filterFormat = formatFilterOptions(filterOptions) 66 | if (whitelist.length > 0) { 67 | moduleList( 68 | whitelist, 69 | `Whitelisted Filtered Modules (${filterFormat})`, 70 | filterOptions 71 | ) 72 | 73 | moduleList( 74 | report, 75 | `Non-whitelisted Filtered Modules (${filterFormat})`, 76 | filterOptions 77 | ) 78 | } else { 79 | moduleList( 80 | report, 81 | `Filtered Modules (${filterFormat})`, 82 | filterOptions 83 | ) 84 | } 85 | } else if (riskCount.reduce((a, b) => a + b, 0) && maxSeverityOfReport > 0) { 86 | moduleList(report.slice(0, 5), 'Top 5: Highest Risk Modules') 87 | } 88 | } 89 | 90 | function formatFilterOptions (filterOptions) { 91 | let str = '--filter=' 92 | if (filterOptions.filterCompliance) { 93 | str += 'compliance,' 94 | } 95 | if (filterOptions.filterSecurity) { 96 | str += 'security,' 97 | } 98 | if (filterOptions.filterLevel) { 99 | str += SEVERITY_RMAP[filterOptions.filterLevel] + ',' 100 | } 101 | if (str[str.length - 1] === ',') { 102 | str = str.slice(0, str.length - 1) 103 | } 104 | return str 105 | } 106 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lib/util.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const readline = require('readline') 4 | 5 | const url = require('url') 6 | const pDefer = require('p-defer') 7 | const clientRequest = require('./client-request') 8 | const { setTokens, api, ncmApi, getTokens, popValue } = require('./config') 9 | const { 10 | formatError 11 | } = require('../lib/ncm-style') 12 | 13 | const E = console.error 14 | 15 | module.exports = { 16 | apiRequest, 17 | formatAPIURL, 18 | graphql, 19 | queryReadline, 20 | queryReadlineHidden, 21 | refreshSession, 22 | reversedSplit 23 | } 24 | 25 | function formatAPIURL (pathname, query) { 26 | return api + url.format({ 27 | pathname, 28 | query 29 | }) 30 | } 31 | 32 | async function graphql (uri, query, variables) { 33 | const updatedUri = uri.replace('api.nodesource', 'api.ncm.nodesource') 34 | const body = await apiRequest( 35 | 'POST', 36 | updatedUri, 37 | { query, variables } 38 | ) 39 | return body.data 40 | } 41 | 42 | async function apiRequest (method, uri, reqbody) { 43 | let { token } = getTokens() 44 | let body 45 | while (!body && token) { 46 | try { 47 | const response = await clientRequest({ 48 | method, 49 | uri, 50 | headers: { 51 | Authorization: `Bearer ${token}`, 52 | 'Content-Type': 'application/json' 53 | }, 54 | json: true, 55 | body: JSON.stringify(reqbody) 56 | }) 57 | body = response.body 58 | } catch (error) { 59 | if (error.statusCode === 401) { 60 | token = await refreshSession() 61 | } else { 62 | throw error 63 | } 64 | } 65 | } 66 | return body 67 | } 68 | 69 | async function refreshSession () { 70 | // Just short-circut this if the token was provided via env... 71 | if (process.env.NCM_TOKEN) return process.env.NCM_TOKEN 72 | 73 | const refreshToken = popValue('refreshToken') 74 | if (!refreshToken || !refreshToken.trim()) { 75 | return 76 | } 77 | 78 | let refreshData 79 | try { 80 | const response = await clientRequest({ 81 | method: 'GET', 82 | uri: formatAPIURL('/accounts/auth/refresh'), 83 | json: true, 84 | headers: { 85 | Authorization: `Bearer ${refreshToken}` 86 | } 87 | }) 88 | refreshData = response.body 89 | } catch (err) { 90 | E() 91 | E(formatError('Failed to refresh session.', err)) 92 | E() 93 | process.exitCode = 1 94 | return 95 | } 96 | 97 | setTokens(refreshData) 98 | return refreshData.session 99 | } 100 | 101 | function queryReadline (query) { 102 | const rl = readline.createInterface({ 103 | input: process.stdin, 104 | output: process.stdout 105 | }) 106 | 107 | const deferred = pDefer() 108 | rl.question(query, answer => { 109 | deferred.resolve(answer) 110 | rl.close() 111 | }) 112 | 113 | return deferred.promise 114 | } 115 | 116 | // For passwords 117 | function queryReadlineHidden (query) { 118 | const rl = readline.createInterface({ 119 | input: process.stdin, 120 | output: process.stdout 121 | }) 122 | 123 | const deferred = pDefer() 124 | rl.question(query, answer => { 125 | deferred.resolve(answer) 126 | rl.close() 127 | }) 128 | 129 | // This is an unpleasant hack. 130 | // This functionality belongs in Node core. 131 | const _writeToOutput = rl._writeToOutput.bind(rl) 132 | rl._writeToOutput = function _writeHiddenToOutput (stringToWrite) { 133 | let newStr = '' 134 | // Do not interfere with a prompt. 135 | if (this._prompt && stringToWrite.startsWith(this._prompt)) { 136 | stringToWrite = stringToWrite.substring(this._prompt.length) 137 | newStr += this._prompt 138 | } 139 | // Iterate and reconstruct word characters (including spaces) to hide. 140 | for (let char of stringToWrite) { 141 | if (/^[\S ]+$/.test(char)) { 142 | char = '*' 143 | } 144 | newStr += char 145 | } 146 | stringToWrite = newStr 147 | 148 | _writeToOutput(stringToWrite) 149 | } 150 | 151 | return deferred.promise 152 | } 153 | 154 | // To be able to replicate regex lookbehinds in Node 8. 155 | function reversedSplit (str, split) { 156 | return reverseStr(str).split(split).map(reverseStr).reverse() 157 | } 158 | 159 | function reverseStr (str) { 160 | return str.split('').reverse().join('') 161 | } 162 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /tap-snapshots/test/details.js.md: -------------------------------------------------------------------------------- 1 | # Snapshot report for `test/details.js` 2 | 3 | The actual snapshot is saved in `details.js.snap`. 4 | 5 | Generated by [AVA](https://avajs.dev). 6 | 7 | ## details output matches snapshot 8 | 9 | > Snapshot 1 10 | 11 | `␊ 12 | ╔═════════════╗␊ 13 | ║ npm @ 6.8.0 ║␊ 14 | ╚═════════════╝␊ 15 | ␊ 16 | ┌──────┬─────────────┐␊ 17 | │ |||| │ Medium Risk │␊ 18 | └──────┴─────────────┘␊ 19 | ␊ 20 | Security Risk:␊ 21 | ✓ 0 security vulnerabilities found␊ 22 | C 0 critical severity␊ 23 | H 0 high severity␊ 24 | M 0 medium severity␊ 25 | L 0 low severity␊ 26 | ␊ 27 | ┌───┬─────────────────────────────┐␊ 28 | │ ✓ │ No Security Vulnerabilities │␊ 29 | └───┴─────────────────────────────┘␊ 30 | ␊ 31 | License Risk:␊ 32 | ┌───┬────────────────────────────────────┐␊ 33 | │ X │ Noncompliant license: Artistic-2.0 │␊ 34 | └───┴────────────────────────────────────┘␊ 35 | ␊ 36 | Module Risk:␊ 37 | ┌───┬────────────────┐␊ 38 | │ ✓ │ No Module Risk │␊ 39 | └───┴────────────────┘␊ 40 | ␊ 41 | Code Quality (does not affect risk score):␊ 42 | ┌───┬────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐␊ 43 | │ ! │ This package version's size on disk is 32.0 MB. │␊ 44 | ├───┼────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤␊ 45 | │ ! │ This package has 4,028 file(s). │␊ 46 | ├───┼────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤␊ 47 | │ ! │ This package has 900 dir(s). │␊ 48 | └───┴────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘␊ 49 | ␊ 50 | ` 51 | 52 | ## details dir output matches snapshot 53 | 54 | > Snapshot 1 55 | 56 | `␊ 57 | ╔═════════════════════════════════════╗␊ 58 | ║ chalk @ 2.4.2 (within mock-project) ║␊ 59 | ╚═════════════════════════════════════╝␊ 60 | ␊ 61 | ┌──────┬─────────────┐␊ 62 | │ |||| │ Medium Risk │␊ 63 | └──────┴─────────────┘␊ 64 | ␊ 65 | Security Risk:␊ 66 | ✓ 0 security vulnerabilities found␊ 67 | C 0 critical severity␊ 68 | H 0 high severity␊ 69 | M 0 medium severity␊ 70 | L 0 low severity␊ 71 | ␊ 72 | ┌───┬─────────────────────────────┐␊ 73 | │ ✓ │ No Security Vulnerabilities │␊ 74 | └───┴─────────────────────────────┘␊ 75 | ␊ 76 | License Risk:␊ 77 | ┌───┬─────┐␊ 78 | │ ✓ │ MIT │␊ 79 | └───┴─────┘␊ 80 | ␊ 81 | Module Risk:␊ 82 | ┌──────┬────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐␊ 83 | │ Med  │ This package version has an unsafe regular expression at templates.js:2:24. │␊ 84 | └──────┴────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘␊ 85 | ␊ 86 | Code Quality (does not affect risk score):␊ 87 | ┌───┬────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐␊ 88 | │ ! │ This package version's size on disk is 48.0 kB. │␊ 89 | └───┴────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘␊ 90 | ␊ 91 | Required By (leftmost is directly in your package):␊ 92 | ┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐␊ 93 | │ (Directly in your package) │␊ 94 | └────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘␊ 95 | ␊ 96 | ` 97 | -------------------------------------------------------------------------------- /tap-snapshots/test/details.js.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nodesource/ncm-cli/1542f412363e3dc50b5796887ce7444700f6bd53/tap-snapshots/test/details.js.snap -------------------------------------------------------------------------------- /tap-snapshots/test/details.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/details.js TAP details dir output matches snapshot > details-output-dir 1`] = ` 9 | 10 | ╔═════════════════════════════════════╗ 11 | ║ chalk @ 2.4.2 (within mock-project) ║ 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 | │ ✓ │ MIT │ 32 | └───┴─────┘ 33 | 34 | Module Risk: 35 | ┌──────┬────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ 36 | │ Med  │ This package version has an unsafe regular expression at templates.js:2:24. │ 37 | └──────┴────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ 38 | 39 | Code Quality (does not affect risk score): 40 | ┌───┬────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ 41 | │ ! │ This package version's size on disk is 48.0 kB. │ 42 | └───┴────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ 43 | 44 | Required By (leftmost is directly in your package): 45 | ┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ 46 | │ (Directly in your package) │ 47 | └────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ 48 | 49 | 50 | ` 51 | 52 | exports[`test/details.js TAP details output matches snapshot > details-output 1`] = ` 53 | 54 | ╔═════════════╗ 55 | ║ npm @ 6.8.0 ║ 56 | ╚═════════════╝ 57 | 58 | ┌──────┬─────────────┐ 59 | │ |||| │ Medium Risk │ 60 | └──────┴─────────────┘ 61 | 62 | Security Risk: 63 | ✓ 0 security vulnerabilities found 64 | C 0 critical severity 65 | H 0 high severity 66 | M 0 medium severity 67 | L 0 low severity 68 | 69 | ┌───┬─────────────────────────────┐ 70 | │ ✓ │ No Security Vulnerabilities │ 71 | └───┴─────────────────────────────┘ 72 | 73 | License Risk: 74 | ┌───┬────────────────────────────────────┐ 75 | │ X │ Noncompliant license: Artistic-2.0 │ 76 | └───┴────────────────────────────────────┘ 77 | 78 | Module Risk: 79 | ┌───┬────────────────┐ 80 | │ ✓ │ No Module Risk │ 81 | └───┴────────────────┘ 82 | 83 | Code Quality (does not affect risk score): 84 | ┌───┬────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ 85 | │ ! │ This package version's size on disk is 32.0 MB. │ 86 | ├───┼────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ 87 | │ ! │ This package has 4,028 file(s). │ 88 | ├───┼────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ 89 | │ ! │ This package has 900 dir(s). │ 90 | └───┴────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ 91 | 92 | 93 | ` 94 | -------------------------------------------------------------------------------- /tap-snapshots/test/help.js.md: -------------------------------------------------------------------------------- 1 | # Snapshot report for `test/help.js` 2 | 3 | The actual snapshot is saved in `help.js.snap`. 4 | 5 | Generated by [AVA](https://avajs.dev). 6 | 7 | ## help output matches help snapshot 8 | 9 | > Snapshot 1 10 | 11 | `␊ 12 | ╔═══════════════════════════════════════╗␊ 13 | ║ NodeSource Certified Modules CLI Help ║␊ 14 | ╚═══════════════════════════════════════╝␊ 15 | ␊ 16 | Usage:␊ 17 | ┌─────────────────────────┐␊ 18 | │ ncm  [options] │␊ 19 | └─────────────────────────┘␊ 20 | ␊ 21 | -h, --help Display help for any command OR this message␊ 22 | -v, --version Print ncm CLI version␊ 23 | ␊ 24 | ncm report␊ 25 | ncm report ␊ 26 | -d, --dir Another way to specify ␊ 27 | -l, --long Full module list output␊ 28 | -c --compliance Compliance failures only output␊ 29 | -s --security Security failures only output␊ 30 | ␊ 31 | ncm details ␊ 32 | -d, --dir Directory to check for dependency path␊ 33 | ␊ 34 | ncm install  [npm options]␊ 35 | ncm i  [npm options]␊ 36 | -d, --dir Directory to check for dependency path␊ 37 | ␊ 38 | ncm whitelist