| const Exclude = require('test-exclude') | ||
| const findUp = require('find-up') | ||
| const {readFileSync} = require('fs') | ||
| const yargs = require('yargs') | ||
| const parser = require('yargs-parser') | ||
| const configPath = findUp.sync(['.c8rc', '.c8rc.json']) | ||
| const config = configPath ? JSON.parse(readFileSync(configPath)) : {} | ||
| yargs | ||
| .usage('$0 [opts] [script] [opts]') | ||
| .option('reporter', { | ||
| alias: 'r', | ||
| describe: 'coverage reporter(s) to use', | ||
| default: 'text' | ||
| }) | ||
| .option('exclude', { | ||
| alias: 'x', | ||
| default: Exclude.defaultExclude, | ||
| describe: 'a list of specific files and directories that should be excluded from coverage, glob patterns are supported.' | ||
| }) | ||
| .option('include', { | ||
| alias: 'n', | ||
| default: [], | ||
| describe: 'a list of specific files that should be covered, glob patterns are supported' | ||
| }) | ||
| .option('coverage-directory', { | ||
| default: './coverage', | ||
| describe: 'directory to output coverage JSON and reports' | ||
| }) | ||
| .pkgConf('c8') | ||
| .config(config) | ||
| .demandCommand(1) | ||
| .epilog('visit https://git.io/vHysA for list of available reporters') | ||
| function hideInstrumenterArgs (yargv) { | ||
| var argv = process.argv.slice(1) | ||
| argv = argv.slice(argv.indexOf(yargv._[0])) | ||
| if (argv[0][0] === '-') { | ||
| argv.unshift(process.execPath) | ||
| } | ||
| return argv | ||
| } | ||
| function hideInstrumenteeArgs () { | ||
| let argv = process.argv.slice(2) | ||
| const yargv = parser(argv) | ||
| if (!yargv._.length) return argv | ||
| // drop all the arguments after the bin being | ||
| // instrumented by c8. | ||
| argv = argv.slice(0, argv.indexOf(yargv._[0])) | ||
| argv.push(yargv._[0]) | ||
| return argv | ||
| } | ||
| module.exports = { | ||
| yargs, | ||
| hideInstrumenterArgs, | ||
| hideInstrumenteeArgs | ||
| } |
| const libCoverage = require('istanbul-lib-coverage') | ||
| const libReport = require('istanbul-lib-report') | ||
| const reports = require('istanbul-reports') | ||
| const {readdirSync, readFileSync} = require('fs') | ||
| const {resolve} = require('path') | ||
| class Report { | ||
| constructor ({reporter, coverageDirectory, watermarks}) { | ||
| this.reporter = reporter | ||
| this.coverageDirectory = coverageDirectory | ||
| this.watermarks = watermarks | ||
| } | ||
| run () { | ||
| const map = this._getCoverageMapFromAllCoverageFiles() | ||
| var context = libReport.createContext({ | ||
| dir: './coverage', | ||
| watermarks: this.watermarks | ||
| }) | ||
| const tree = libReport.summarizers.pkg(map) | ||
| this.reporter.forEach(function (_reporter) { | ||
| tree.visit(reports.create(_reporter), context) | ||
| }) | ||
| } | ||
| _getCoverageMapFromAllCoverageFiles () { | ||
| const map = libCoverage.createCoverageMap({}) | ||
| this._loadReports().forEach(function (report) { | ||
| map.merge(report) | ||
| }) | ||
| return map | ||
| } | ||
| _loadReports () { | ||
| const tmpDirctory = resolve(this.coverageDirectory, './tmp') | ||
| const files = readdirSync(tmpDirctory) | ||
| return files.map((f) => { | ||
| return JSON.parse(readFileSync( | ||
| resolve(tmpDirctory, f), | ||
| 'utf8' | ||
| )) | ||
| }) | ||
| } | ||
| } | ||
| module.exports = function (opts) { | ||
| const report = new Report(opts) | ||
| report.run() | ||
| } |
+35
| const {spawn} = require('child_process') | ||
| const debuggerRe = /Debugger listening on ws:\/\/[^:]*:([^/]*)/ | ||
| module.exports = function (execPath, args = []) { | ||
| const info = { | ||
| port: -1 | ||
| } | ||
| return new Promise((resolve, reject) => { | ||
| const proc = spawn(execPath, args, { | ||
| stdio: [process.stdin, process.stdout, 'pipe'], | ||
| env: process.env, | ||
| cwd: process.cwd() | ||
| }) | ||
| proc.stderr.on('data', (outBuffer) => { | ||
| const outString = outBuffer.toString('utf8') | ||
| const match = outString.match(debuggerRe) | ||
| if (match && !info.url) { | ||
| info.port = Number(match[1]) | ||
| return resolve(info) | ||
| } else { | ||
| console.error(outString) | ||
| } | ||
| }) | ||
| proc.on('close', (code) => { | ||
| if (info.port === -1) { | ||
| return reject(Error('could not connect to inspector')) | ||
| } else { | ||
| process.exitCode = code | ||
| } | ||
| }) | ||
| }) | ||
| } |
+98
-6
| #!/usr/bin/env node | ||
| 'use strict' | ||
| const argv = require('yargs').parse() | ||
| const foreground = require('foreground-child') | ||
| const sw = require('spawn-wrap') | ||
| const CRI = require('chrome-remote-interface') | ||
| const Exclude = require('test-exclude') | ||
| const {isAbsolute} = require('path') | ||
| const mkdirp = require('mkdirp') | ||
| const report = require('../lib/report') | ||
| const {resolve} = require('path') | ||
| const rimraf = require('rimraf') | ||
| const spawn = require('../lib/spawn') | ||
| const uuid = require('uuid') | ||
| const v8ToIstanbul = require('v8-to-istanbul') | ||
| const {writeFileSync} = require('fs') | ||
| const { | ||
| hideInstrumenteeArgs, | ||
| hideInstrumenterArgs, | ||
| yargs | ||
| } = require('../lib/parse-args') | ||
| if (argv._.length) { | ||
| sw([require.resolve('./wrap')]) | ||
| foreground(process.argv.slice(2)) | ||
| const instrumenterArgs = hideInstrumenteeArgs() | ||
| const argv = yargs.parse(instrumenterArgs) | ||
| const exclude = Exclude({ | ||
| include: argv.include, | ||
| exclude: argv.exclude | ||
| }) | ||
| ;(async function executeWithCoverage (instrumenteeArgv) { | ||
| try { | ||
| const bin = instrumenteeArgv.shift() | ||
| const info = await spawn(bin, | ||
| [`--inspect-brk=0`].concat(instrumenteeArgv)) | ||
| const client = await CRI({port: info.port}) | ||
| const initialPause = new Promise((resolve) => { | ||
| client.once('Debugger.paused', resolve) | ||
| }) | ||
| const mainContextInfo = new Promise((resolve) => { | ||
| client.once('Runtime.executionContextCreated', (message) => { | ||
| resolve(message.context) | ||
| }) | ||
| }) | ||
| const executionComplete = new Promise((resolve) => { | ||
| client.on('Runtime.executionContextDestroyed', async (message) => { | ||
| if (message.executionContextId === (await mainContextInfo).id) { | ||
| resolve(message) | ||
| } | ||
| }) | ||
| }) | ||
| const {Debugger, Runtime, Profiler} = client | ||
| await Promise.all([ | ||
| Profiler.enable(), | ||
| Runtime.enable(), | ||
| Debugger.enable(), | ||
| Profiler.startPreciseCoverage({callCount: true, detailed: true}), | ||
| Runtime.runIfWaitingForDebugger(), | ||
| initialPause | ||
| ]) | ||
| await Debugger.resume() | ||
| await executionComplete | ||
| const allV8Coverage = await collectV8Coverage(Profiler) | ||
| writeIstanbulFormatCoverage(allV8Coverage) | ||
| await client.close() | ||
| report({ | ||
| reporter: Array.isArray(argv.reporter) ? argv.reporter : [argv.reporter], | ||
| coverageDirectory: argv.coverageDirectory, | ||
| watermarks: argv.watermarks | ||
| }) | ||
| } catch (err) { | ||
| console.error(err) | ||
| process.exit(1) | ||
| } | ||
| })(hideInstrumenterArgs(argv)) | ||
| async function collectV8Coverage (Profiler) { | ||
| let {result} = await Profiler.takePreciseCoverage() | ||
| result = result.filter(({url}) => { | ||
| url = url.replace('file://', '') | ||
| return isAbsolute(url) && exclude.shouldInstrument(url) | ||
| }) | ||
| return result | ||
| } | ||
| function writeIstanbulFormatCoverage (allV8Coverage) { | ||
| const tmpDirctory = resolve(argv.coverageDirectory, './tmp') | ||
| rimraf.sync(tmpDirctory) | ||
| mkdirp.sync(tmpDirctory) | ||
| allV8Coverage.forEach((v8) => { | ||
| const script = v8ToIstanbul(v8.url) | ||
| script.applyCoverage(v8.functions) | ||
| writeFileSync( | ||
| resolve(tmpDirctory, `./${uuid.v4()}.json`), | ||
| JSON.stringify(script.toIstanbul(), null, 2), | ||
| 'utf8' | ||
| ) | ||
| }) | ||
| } |
+22
-0
@@ -5,2 +5,24 @@ # Change Log | ||
| <a name="2.0.0"></a> | ||
| # [2.0.0](https://github.com/bcoe/c8/compare/v1.0.1...v2.0.0) (2017-12-17) | ||
| ### Bug Fixes | ||
| * tweak inspector event timing ([#6](https://github.com/bcoe/c8/issues/6)) ([01f654e](https://github.com/bcoe/c8/commit/01f654e)) | ||
| ### Features | ||
| * first pass at functional prototype without subprocess support ([#5](https://github.com/bcoe/c8/issues/5)) ([9534f56](https://github.com/bcoe/c8/commit/9534f56)) | ||
| * implement Istanbul reporting ([#8](https://github.com/bcoe/c8/issues/8)) ([8e430bf](https://github.com/bcoe/c8/commit/8e430bf)) | ||
| * switch to stderr and default port ([#7](https://github.com/bcoe/c8/issues/7)) ([bb117b7](https://github.com/bcoe/c8/commit/bb117b7)) | ||
| ### BREAKING CHANGES | ||
| * dropped subprocess support for the time being, while we march towards an initial implementation. | ||
| <a name="1.0.1"></a> | ||
@@ -7,0 +29,0 @@ ## [1.0.1](https://github.com/bcoe/c8/compare/v1.0.0...v1.0.1) (2017-10-26) |
+28
-7
| { | ||
| "name": "c8", | ||
| "version": "1.0.1", | ||
| "description": "collect test coverage using v8's profiler", | ||
| "version": "2.0.0", | ||
| "description": "collect test coverage using v8's inspector", | ||
| "main": "index.js", | ||
| "bin": "./bin/c8.js", | ||
| "scripts": { | ||
| "test": "nyc mocha test.js", | ||
| "test": "./bin/c8.js node ./node_modules/.bin/_mocha ./test/*.js", | ||
| "posttest": "standard", | ||
| "release": "standard-version" | ||
| }, | ||
| "c8": { | ||
| "exclude": [ | ||
| "test/*.js" | ||
| ] | ||
| }, | ||
| "standard": { | ||
| "ignore": [ | ||
| "test/fixtures" | ||
| ] | ||
| }, | ||
| "keywords": [ | ||
@@ -26,10 +37,20 @@ "coverage", | ||
| "chrome-remote-interface": "^0.25.2", | ||
| "foreground-child": "^1.5.6", | ||
| "get-port": "^3.2.0", | ||
| "spawn-wrap": "=1.3.8", | ||
| "yargs": "^10.0.3" | ||
| "find-up": "^2.1.0", | ||
| "istanbul-lib-coverage": "^1.1.1", | ||
| "istanbul-lib-report": "^1.1.2", | ||
| "istanbul-reports": "^1.1.3", | ||
| "mkdirp": "^0.5.1", | ||
| "rimraf": "^2.6.2", | ||
| "test-exclude": "^4.1.1", | ||
| "uuid": "^3.1.0", | ||
| "v8-to-istanbul": "^1.2.0", | ||
| "yargs": "^10.0.3", | ||
| "yargs-parser": "^8.0.0" | ||
| }, | ||
| "devDependencies": { | ||
| "chai": "^4.1.2", | ||
| "mocha": "^4.0.1", | ||
| "standard": "^10.0.3", | ||
| "standard-version": "^4.2.0" | ||
| } | ||
| } |
+16
-9
| # c8 - native v8 code-coverage | ||
| Code-coverage using [v8's Profiler](https://nodejs.org/dist/latest-v8.x/docs/api/inspector.html) | ||
| Code-coverage using [v8's Inspector](https://nodejs.org/dist/latest-v8.x/docs/api/inspector.html) | ||
| that's compatible with [Istanbul's reporters](https://istanbul.js.org/docs/advanced/alternative-reporters/). | ||
| Like [nyc](https://github.com/istanbuljs/nyc), c8 just magically works, simply: | ||
| Like [nyc](https://github.com/istanbuljs/nyc), c8 just magically works: | ||
@@ -13,15 +13,22 @@ ```bash | ||
| The above example will collect coverage for `foo.js` using v8's profiler. | ||
| The above example will collect coverage for `foo.js` using v8's inspector. | ||
| TODO: | ||
| ## remaining work | ||
| - [ ] write logic for converting v8 coverage output to [Istanbul Coverage.json format](https://github.com/gotwarlost/istanbul/blob/master/coverage.json.md). | ||
| - [ ] talk to Node.js project about silencing messages: | ||
| - [x] write logic for converting v8 coverage output to [Istanbul Coverage.json format](https://github.com/gotwarlost/istanbul/blob/master/coverage.json.md). | ||
| * https://github.com/bcoe/v8-to-istanbul | ||
| - [ ] talk to node.js project about silencing messages: | ||
| > `Debugger listening on ws://127.0.0.1:56399/e850110a-c5df-41d8-8ef2-400f6829617f`. | ||
| - [ ] figure out why `detailed` mode does not appear to be working. | ||
| - [ ] figure out a better way to determine that all processes in event loop | ||
| - [x] figure out why `detailed` mode does not appear to be working. | ||
| * this is fixed in v8, as long as you start with `--inspect-brk` you | ||
| can collect coverage in detailed mode. | ||
| - [x] figure out a better way to determine that all processes in event loop | ||
| have terminated (except the inspector session). | ||
| - [ ] process.exit() can't perform an async operation; how can we track coverage | ||
| - [x] process.exit() can't perform an async operation; how can we track coverage | ||
| for scripts that exit? | ||
| * we can now listen for the `Runtime.executionContextDestroyed` event. | ||
| - [x] figure out why instrumentation of .mjs files does not work: | ||
| * see: https://github.com/nodejs/node/issues/17336 |
-52
| const fs = require('fs') | ||
| const sw = require('spawn-wrap') | ||
| const CRI = require('chrome-remote-interface'); | ||
| const getPort = require('get-port'); | ||
| const inspector = require('inspector') | ||
| // if there are N or less active handles | ||
| // in the event loop, dump coverage and exit. | ||
| const EXIT_HANDLE_COUNT = 4 | ||
| getPort().then(async port => { | ||
| // start an inspector session on an unused port. | ||
| inspector.open(port, true) | ||
| const client = await CRI({port: port}) | ||
| const {Profiler} = client | ||
| await Profiler.enable() | ||
| await Profiler.startPreciseCoverage({callCount: true, detailed: true}) | ||
| // run the original "main" now that we've started the inspector. | ||
| sw.runMain() | ||
| // wait for everything in event loop to terminate | ||
| // except for inspector session. | ||
| setInterval(() => { | ||
| const handleCount = process._getActiveHandles().length | ||
| + process._getActiveRequests().length | ||
| if (handleCount <= EXIT_HANDLE_COUNT) { | ||
| outputCoverageAndExit(client, Profiler) | ||
| } | ||
| }, 100) | ||
| }).catch(err => { | ||
| throw err | ||
| }) | ||
| async function outputCoverageAndExit (client, Profiler) { | ||
| const IGNORED_PATHS = [ | ||
| /\/bin\/wrap.js/, | ||
| /\/node_modules\//, | ||
| /node-spawn-wrap/ | ||
| ] | ||
| let {result} = await Profiler.takePreciseCoverage() | ||
| result = result.filter(coverage => { | ||
| for (var ignored, i = 0; (ignored = IGNORED_PATHS[i]) !== undefined; i++) { | ||
| if (ignored.test(coverage.url)) return false | ||
| } | ||
| if (!/^\//.test(coverage.url)) return false | ||
| else return true | ||
| }) | ||
| console.log(JSON.stringify(result, null, 2)) | ||
| client.close() | ||
| process.exit(process.exitCode || 0) | ||
| } |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
Debug access
Supply chain riskUses debug, reflection and dynamic code execution features.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
Debug access
Supply chain riskUses debug, reflection and dynamic code execution features.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
11489
137.72%8
14.29%221
301.82%34
25.93%12
140%4
300%7
133.33%2
Infinity%+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed