Socket
Socket
Sign inDemoInstall

grunt-contrib-qunit

Package Overview
Dependencies
Maintainers
10
Versions
41
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

grunt-contrib-qunit - npm Package Compare versions

Comparing version 2.0.0 to 3.0.0

chrome/bridge.js

14

package.json
{
"name": "grunt-contrib-qunit",
"description": "Run QUnit unit tests in a headless PhantomJS instance",
"version": "2.0.0",
"description": "Run QUnit unit tests in a headless Chrome instance",
"version": "3.0.0",
"author": {

@@ -19,3 +19,5 @@ "name": "Grunt Team",

"dependencies": {
"grunt-lib-phantomjs": "^1.0.0"
"bluebird": "^3.5.1",
"eventemitter2": "^5.0.1",
"puppeteer": "1.3.x"
},

@@ -25,4 +27,4 @@ "devDependencies": {

"grunt": "^1.0.1",
"grunt-contrib-connect": "^1.0.0",
"grunt-contrib-internal": "^1.1.0",
"grunt-contrib-connect": "^1.0.2",
"grunt-contrib-internal": "^3.0.0",
"grunt-contrib-jshint": "^1.0.0",

@@ -37,5 +39,5 @@ "grunt-shell": "^1.3.0",

"tasks",
"phantomjs"
"chrome"
],
"appveyor_id": "3vd43779joyj6qji"
}

@@ -1,4 +0,4 @@

# grunt-contrib-qunit v2.0.0 [![Build Status: Linux](https://travis-ci.org/gruntjs/grunt-contrib-qunit.svg?branch=master)](https://travis-ci.org/gruntjs/grunt-contrib-qunit) [![Build Status: Windows](https://ci.appveyor.com/api/projects/status/3vd43779joyj6qji/branch/master?svg=true)](https://ci.appveyor.com/project/gruntjs/grunt-contrib-qunit/branch/master)
# grunt-contrib-qunit v3.0.0 [![Build Status: Linux](https://travis-ci.org/gruntjs/grunt-contrib-qunit.svg?branch=master)](https://travis-ci.org/gruntjs/grunt-contrib-qunit) [![Build Status: Windows](https://ci.appveyor.com/api/projects/status/3vd43779joyj6qji/branch/master?svg=true)](https://ci.appveyor.com/project/gruntjs/grunt-contrib-qunit/branch/master)
> Run QUnit unit tests in a headless PhantomJS instance
> Run QUnit unit tests in a headless Chrome instance

@@ -27,3 +27,3 @@

You have chosen to write your unit tests using [QUnit][], you have written a
You have chosen to write your unit tests using [QUnit](http://qunitjs.com/), you have written a
html page which reports the summary and indivudual details of your unit

@@ -35,3 +35,3 @@ tests, you are happy with this but realize you miss the ability to have your

This is where the `grunt-contrib-qunit` plugin comes in the play:
`grunt-contrib-qunit` lets you run your tests in the invisible [PhantomJS][]
`grunt-contrib-qunit` lets you run your tests in the invisible [Chrome][]
browser, thus converting your unit test suite into something you can run

@@ -50,23 +50,16 @@ from a script, a script you can have automatically run on travis-ci (or the

When installed by npm, this plugin will automatically download and install
[PhantomJS][] locally via the [grunt-lib-phantomjs][] library. If your
system already provides the PhantomJS program, this plugin will use the
globally installed program.
When installed by npm, this plugin will automatically download and install a local
[Chrome][] binary within the `node_modules` directory of the [puppeteer][] library,
which is used for launching a Chrome process. If your system already provides an
installation of Chrome, you can configure this plugin to use the globally installed
executable by specifying a custom `executablePath` in the puppeteer launch options.
This will almost certainly be needed in order to run Chrome in a CI environment
[PhantomJS]: http://www.phantomjs.org/
[grunt-lib-phantomjs]: https://github.com/gruntjs/grunt-lib-phantomjs
[Puppeteer]: https://pptr.dev/
Also note that running grunt with the `--debug` flag will output a lot of PhantomJS-specific debugging information. This can be very helpful in seeing what actual URIs are being requested and received by PhantomJS.
#### OS Dependencies
This plugin uses PhantomJS to run tests. PhantomJS requires these dependencies
This plugin uses Puppeteer to run tests in a Chrome process. Chrome requires a number of dependencies that must be installed, depending on your OS.
Please see Puppeteer's docs to see the latest docs for what dependencies you need for your OS:
**On Ubuntu/Debian**
`apt-get install libfontconfig1 fontconfig libfontconfig1-dev libfreetype6-dev`
**On CentOS**
`yum install fontconfig freetype`
https://github.com/GoogleChrome/puppeteer/blob/master/docs/troubleshooting.md
### Options

@@ -82,8 +75,8 @@

Type: `String`|`Array`
Default: `phantomjs/bridge.js`
Default: `chrome/bridge.js`
One or multiple (array) JavaScript file names to inject into the html test page. Defaults to the path of the QUnit-PhantomJS bridge file.
One or multiple (array) JavaScript file names to inject into the html test page. Defaults to the path of the QUnit-Chrome bridge file.
You may want to inject something different than the provided QUnit-PhantomJS bridge, or to inject more than just the provided bridge.
See [the built-in bridge](https://github.com/gruntjs/grunt-contrib-qunit/blob/master/phantomjs/bridge.js) for more information.
You may want to inject something different than the provided QUnit-Chrome bridge, or to inject more than just the provided bridge.
See [the built-in bridge](https://github.com/gruntjs/grunt-contrib-qunit/blob/master/chrome/bridge.js) for more information.

@@ -100,3 +93,3 @@ #### httpBase

Set to false to hide PhantomJS console output.
By default, `console.[log|warn|error]` output from the Chrome browser will be piped into QUnit console. Set to `false` to disable this behavior.

@@ -107,3 +100,3 @@ #### urls

Absolute `http://` or `https://` urls to be passed to PhantomJS. Specified URLs will be merged with any specified `src` files first. Note that urls must be served by a web server, and since this task doesn't contain a web server, one will need to be configured separately. The [grunt-contrib-connect plugin](https://github.com/gruntjs/grunt-contrib-connect) provides a basic web server.
Absolute `http://` or `https://` urls to be passed to Chrome. Specified URLs will be merged with any specified `src` files first. Note that urls must be served by a web server, and since this task doesn't contain a web server, one will need to be configured separately. The [grunt-contrib-connect plugin](https://github.com/gruntjs/grunt-contrib-connect) provides a basic web server.

@@ -122,7 +115,7 @@ #### force

#### (-- PhantomJS arguments)
Type: `String`
Default: (none)
#### puppeteer
Type: `Object`
Default: `{ headless: true }`
Additional `--` style arguments that need to be passed in to PhantomJS may be specified as options, like `{'--option': 'value'}`. This may be useful for specifying a cookies file, local storage file, or a proxy. See the [PhantomJS API Reference][] for a list of `--` options that PhantomJS supports.
Arguments to be used when `puppeteer.launch()` is invoked. This may be useful for specifying a custom Chrome executable path, running in non-headless mode, specifying environment variables to use when launching Chrome, etc. See the [Puppeteer API Reference][https://pptr.dev/#?product=Puppeteer&version=v1.3.0] for a list of launch options that are available.

@@ -154,3 +147,3 @@ #### noGlobals

#### Wildcards
In this example, `grunt qunit:all` will test all `.html` files in the test directory _and all subdirectories_. First, the wildcard is expanded to match each individual file. Then, each matched filename is passed to [PhantomJS][] (one at a time).
In this example, `grunt qunit:all` will test all `.html` files in the test directory _and all subdirectories_. First, the wildcard is expanded to match each individual file. Then, each matched filename is passed to [Chrome][] (one at a time).

@@ -167,3 +160,3 @@ ```js

#### Testing via http:// or https://
In circumstances where running unit tests from local files is inadequate, you can specify `http://` or `https://` URLs via the `urls` option. Each URL is passed to [PhantomJS][] (one at a time).
In circumstances where running unit tests from local files is inadequate, you can specify `http://` or `https://` URLs via the `urls` option. Each URL is passed to [Chrome][] (one at a time).

@@ -228,6 +221,6 @@ In this example, `grunt qunit` will test two files, served from the server running at `localhost:8000`.

#### Custom timeouts and PhantomJS options
In the following example, the default timeout value of `5000` is overridden with the value `10000` (timeout values are in milliseconds). Additionally, PhantomJS will read stored cookies from the specified file. See the [PhantomJS API Reference][] for a list of `--` options that PhantomJS supports.
#### Custom timeouts and Puppeteer options
In the following example, the default timeout value of `5000` is overridden with the value `10000` (timeout values are in milliseconds). Custom options to use when launching Puppeteer can be specified using `options.puppeteer`, with all property names corresponding directly to options supported by [`puppeteer.launch()`](https://pptr.dev/#?product=Puppeteer&version=v1.3.0&show=api-puppeteerlaunchoptions). For example, the following configuration sets the TZ environment variable and invokes a custom Chrome executable at "/usr/bin/chromium"
[PhantomJS API Reference]: http://phantomjs.org/api
[Puppeteer API Reference]: https://pptr.dev/

@@ -240,3 +233,8 @@ ```js

timeout: 10000,
'--cookies-file': 'misc/cookies.txt'
puppeteer: {
env: {
TZ: "UTC"
},
executablePath: "/usr/bin/chromium"
}
},

@@ -267,4 +265,4 @@ all: ['test/**/*.html']

* `qunit.spawn` `(url)`: when [PhantomJS][] is spawned for a test
* `qunit.fail.load` `(url)`: when [PhantomJS][] could not open the given url
* `qunit.spawn` `(url)`: when [Chrome][] is spawned for a test
* `qunit.fail.load` `(url)`: when [Chrome][] could not open the given url
* `qunit.fail.timeout`: when a QUnit test times out, usually due to a missing `QUnit.start()` call

@@ -284,2 +282,3 @@ * `qunit.error.onError` `(message, stackTrace)`: when a JavaScript execution error occurs

* 2018-07-24   v3.0.0   Switch to using headless chrome / puppeteer instead of phantomjs
* 2017-04-04   v2.0.0   Remove usage of `QUnit.jsDump` Upgrade qunitjs to 2.3.0 (#123) adding an Introduction to the README (#140)

@@ -310,2 +309,2 @@ * 2017-02-07   v1.3.0   Add ability to run tests in seeded-random order through `--seed` flag Add note about min version of QUnit required to use the CLI flags Implement support for todo tests and revamp reporting logic (#137)

*This file was generated on Tue Apr 04 2017 22:09:26.*
*This file was generated on Tue Jul 24 2018 17:47:21.*

@@ -11,14 +11,116 @@ /*

// Nodejs libs.
var fs = require('fs');
var path = require('path');
var url = require('url');
var EventEmitter2 = require('eventemitter2');
// NPM libs.
var Promise = require('bluebird');
var puppeteer = require('puppeteer');
// Shared functions
// Allow an error message to retain its color when split across multiple lines.
function formatMessage (str) {
return String(str).split('\n')
.map(function(s) {
return s.magenta;
})
.join('\n');
}
function createStatus () {
return {
passed: 0,
failed: 0,
skipped: 0,
todo: 0,
runtime: 0,
assertions: {
passed: 0,
failed: 0
}
};
}
function mergeStatus(statusA, statusB) {
statusA.passed += statusB.passed;
statusA.failed += statusB.failed;
statusA.skipped += statusB.skipped;
statusA.todo += statusB.todo;
statusA.runtime += statusB.runtime;
statusA.assertions.passed += statusB.assertions.passed;
statusA.assertions.failed += statusB.assertions.failed;
}
function generateMessage(status) {
var totalTests = status.passed + status.failed + status.skipped + status.todo;
var totalAssertions = status.assertions.passed + status.assertions.failed;
return [
totalTests,
' tests completed with ',
status.failed,
' failed, ' +
status.skipped,
' skipped, and ',
status.todo,
' todo. \n' +
totalAssertions,
' assertions (in ',
status.runtime,
'ms), passed: ' +
status.assertions.passed,
', failed: ',
status.assertions.failed
].join('');
}
// Copied from QUnit source code
function generateHash (module) {
var hex;
var i = 0;
var hash = 0;
var str = module + '\x1C' + undefined;
var len = str.length;
for (; i < len; i++) {
hash = ((hash << 5) - hash) + str.charCodeAt(i);
hash |= 0;
}
// Convert the possibly negative integer hash code into an 8 character
// hex string, which isn't strictly necessary but increases user understanding
// that the id is a SHA-like hash
hex = (0x100000000 + hash).toString(16);
if (hex.length < 8) {
hex = '0000000' + hex;
}
return hex.slice(-8);
}
function getFullUrl(url) {
if ((url.length < 4) || (url.substring(0,4) !== 'http')) {
return 'file://' + path.resolve(__dirname, '..', url);
} else {
return url;
}
}
module.exports = function(grunt) {
// Nodejs libs.
var path = require('path');
var url = require('url');
var eventBus = new EventEmitter2({wildcard: true, maxListeners: 0});
// External lib.
var phantomjs = require('grunt-lib-phantomjs').init(grunt);
// Keep track of the last-started module and test. Additionally, keep track
// of status for individual test files and the entire test suite.
var options, currentModule, currentTest, currentStatus, status;
var options;
var puppeteerLaunchOptions;
var currentModule;
var currentTest;
var currentStatus;
var status;
var browser;
var page;

@@ -31,9 +133,4 @@ // Keep track of the last-started test(s).

// Allow an error message to retain its color when split across multiple lines.
var formatMessage = function(str) {
return String(str).split('\n').map(function(s) { return s.magenta; }).join('\n');
};
// If options.force then log an error, otherwise exit with a warning
var warnUnlessForced = function (message) {
function warnUnlessForced (message) {
if (options && options.force) {

@@ -44,7 +141,7 @@ grunt.log.error(message);

}
};
}
// Keep track of failed assertions for pretty-printing.
var failedAssertions = [];
var logFailedAssertions = function() {
function logFailedAssertions () {
var assertion;

@@ -69,81 +166,11 @@

}
};
}
var createStatus = function() {
return {
passed: 0,
failed: 0,
skipped: 0,
todo: 0,
runtime: 0,
assertions: {
passed: 0,
failed: 0
}
};
};
var mergeStatus = function(statusA, statusB) {
statusA.passed += statusB.passed;
statusA.failed += statusB.failed;
statusA.skipped += statusB.skipped;
statusA.todo += statusB.todo;
statusA.runtime += statusB.runtime;
statusA.assertions.passed += statusB.assertions.passed;
statusA.assertions.failed += statusB.assertions.failed;
};
var generateMessage = function(status) {
var totalTests = status.passed + status.failed + status.skipped + status.todo;
var totalAssertions = status.assertions.passed + status.assertions.failed;
return [
totalTests,
" tests completed with ",
status.failed,
" failed, " +
status.skipped,
" skipped, and ",
status.todo,
" todo. \n" +
totalAssertions,
" assertions (in ",
status.runtime,
"ms), passed: " +
status.assertions.passed,
", failed: ",
status.assertions.failed
].join( "" );
};
// Copied from QUnit source code
var generateHash = function(module) {
var hex;
var i = 0;
var hash = 0;
var str = module + '\x1C' + undefined;
var len = str.length;
for (; i < len; i++) {
hash = ((hash << 5) - hash) + str.charCodeAt(i);
hash |= 0;
}
// Convert the possibly negative integer hash code into an 8 character
// hex string, which isn't strictly necessary but increases user understanding
// that the id is a SHA-like hash
hex = (0x100000000 + hash).toString(16);
if (hex.length < 8) {
hex = '0000000' + hex;
}
return hex.slice(-8);
};
// QUnit hooks.
phantomjs.on('qunit.begin', function() {
eventBus.on('qunit.begin', function() {
currentStatus = createStatus();
});
phantomjs.on('qunit.moduleStart', function(name) {
eventBus.on('qunit.moduleStart', function(name) {
unfinished[name] = true;

@@ -153,7 +180,7 @@ currentModule = name;

phantomjs.on('qunit.moduleDone', function(name/*, failed, passed, total*/) {
eventBus.on('qunit.moduleDone', function(name) {
delete unfinished[name];
});
phantomjs.on('qunit.log', function(result, actual, expected, message, source, todo) {
eventBus.on('qunit.log', function(result, actual, expected, message, source, todo) {
if (!result && !todo) {

@@ -170,3 +197,3 @@ failedAssertions.push({

phantomjs.on('qunit.testStart', function(name) {
eventBus.on('qunit.testStart', function(name) {
currentTest = (currentModule ? currentModule + ' - ' : '') + name;

@@ -176,3 +203,3 @@ grunt.verbose.write(currentTest + '...');

phantomjs.on('qunit.testDone', function(name, failed, passed, total, runtime, skipped, todo) {
eventBus.on('qunit.testDone', function(name, failed, passed, total, runtime, skipped, todo) {
var testPassed = failed > 0 ? todo : !todo;

@@ -191,22 +218,19 @@

// Log errors if necessary, otherwise success.
if (!testPassed) {
if (testPassed) {
grunt.verbose.ok().or.write('.');
// list assertions or message about todo failure
if (grunt.option('verbose')) {
grunt.log.error();
} else if (grunt.option('verbose')) {
grunt.log.error();
if (todo) {
grunt.log.error('Expected at least one failing assertion in todo test:' + name);
} else {
logFailedAssertions();
}
if (todo) {
grunt.log.error('Expected at least one failing assertion in todo test:' + name);
} else {
grunt.log.write('F'.red);
logFailedAssertions();
}
} else {
grunt.verbose.ok().or.write('.');
grunt.log.write('F'.red);
}
});
phantomjs.on('qunit.done', function(failed, passed, total, runtime) {
phantomjs.halt();
eventBus.on('qunit.done', function(failed, passed, total, runtime) {

@@ -230,4 +254,6 @@ currentStatus.runtime += runtime;

// Re-broadcast qunit events on grunt.event.
phantomjs.on('qunit.*', function() {
eventBus.on('qunit.*', function() {
var args = [this.event].concat(grunt.util.toArray(arguments));

@@ -238,7 +264,6 @@ grunt.event.emit.apply(grunt.event, args);

// Built-in error handlers.
phantomjs.on('fail.load', function(url) {
phantomjs.halt();
eventBus.on('fail.load', function(url) {
grunt.verbose.write('...');
grunt.event.emit('qunit.fail.load', url);
grunt.log.error('PhantomJS unable to load "' + url + '" URI.');
grunt.log.error('Chrome unable to load \'' + url + '\' URI.');

@@ -248,7 +273,6 @@ status.failed += 1;

phantomjs.on('fail.timeout', function() {
phantomjs.halt();
eventBus.on('fail.timeout', function() {
grunt.log.writeln();
grunt.event.emit('qunit.fail.timeout');
grunt.log.error('PhantomJS timed out, possibly due to:\n' +
grunt.log.error('Chrome timed out, possibly due to:\n' +
'- QUnit is not loaded correctly.\n- A missing QUnit start() call.\n' +

@@ -260,17 +284,17 @@ '- Or, a misconfiguration of this task.');

phantomjs.on('error.onError', function (msg, stackTrace) {
eventBus.on('error.onError', function (msg, stackTrace) {
grunt.event.emit('qunit.error.onError', msg, stackTrace);
});
grunt.registerMultiTask('qunit', 'Run QUnit unit tests in a headless PhantomJS instance.', function() {
grunt.registerMultiTask('qunit', 'Run QUnit unit tests in a headless Chrome instance.', function() {
// Merge task-specific and/or target-specific options with these defaults.
options = this.options({
// Default PhantomJS timeout.
// Default Chrome timeout.
timeout: 5000,
// QUnit-PhantomJS bridge file to be injected.
inject: asset('phantomjs/bridge.js'),
// QUnit-Chrome bridge file to be injected.
inject: asset('chrome/bridge.js'),
// Explicit non-file URLs to test.
urls: [],
force: false,
// Connect phantomjs console output to grunt output
// Connect Chrome console output to Grunt output
console: true,

@@ -281,7 +305,17 @@ // Do not use an HTTP base by default

});
puppeteerLaunchOptions = options.puppeteer || {};
// This task is asynchronous.
var done = this.async();
var urls;
var bridgeFile = fs.readFileSync(options.inject, 'utf8');
if (!bridgeFile) {
grunt.fail.fatal('Could not load the specified Chrome/QUnit bridge file.');
done(false);
return;
}
if (options.httpBase) {
//If URLs are explicitly referenced, use them still
// If URLs are explicitly referenced, use them still
urls = options.urls;

@@ -297,3 +331,13 @@ // Then create URLs for the src files

var appendToUrls = function(queryParam, value) {
// The final tasks to run before terminating the task
function finishTask(success) {
// Close the puppeteer browser
if (browser) {
browser.close();
}
// Finish the task
done(success);
}
function appendToUrls (queryParam, value) {
// Append the query param to all urls

@@ -306,3 +350,3 @@ urls = urls.map(function(testUrl) {

});
};
}

@@ -328,61 +372,106 @@ if (options.noGlobals) {

// This task is asynchronous.
var done = this.async();
// Reset status.
status = createStatus();
// Pass-through console.log statements.
if(options.console) {
phantomjs.on('console', console.log.bind(console));
}
// Instantiate headless browser
puppeteer.launch(Object.assign({
headless: true,
}, puppeteerLaunchOptions))
.then(function(b) {
browser = b;
return b.newPage();
})
.then(function(p) {
page = p;
// emit events published in bridge.js.
// This function exposure survives url navigations.
return page.exposeFunction('__grunt_contrib_qunit__', function() {
eventBus.emit.apply(eventBus, [].slice.call(arguments));
});
})
.then(function() {
// Pass through the console logs if instructed
if (options.console) {
page.on('console', function() {
var args = [].slice.apply(arguments);
var colors = {
'error': 'red',
'warning': 'yellow'
};
for (var i = 0; i < args.length; ++i) {
var txt = args[i].text();
var color = colors[args[i].type()];
grunt.log.writeln(color ? txt[color] : txt);
}
});
}
// Process each filepath in-order.
grunt.util.async.forEachSeries(urls, function(url, next) {
grunt.verbose.subhead('Testing ' + url + ' ').or.write('Testing ' + url + ' ');
// Surface uncaught exceptions
page.on('pageerror', function() {
var args = [].slice.apply(arguments);
for (var i = 0; i < args.length; i++) {
eventBus.emit('error.onError', args[i]);
}
});
// Reset current module.
currentModule = null;
// Whenever a page is loaded with a new document, before scripts execute, inject the bridge file.
// Tell the client that when DOMContentLoaded fires, it needs to tell this
// script to inject the bridge. This should ensure that the bridge gets
// injected before any other DOMContentLoaded or window.load event handler.
page.evaluateOnNewDocument('if (window.QUnit) {\n' + bridgeFile + '\n} else {\n' + 'document.addEventListener("DOMContentLoaded", function() {\n' + bridgeFile + '\n});\n}\n');
// Launch PhantomJS.
grunt.event.emit('qunit.spawn', url);
phantomjs.spawn(url, {
// Additional PhantomJS options.
options: options,
// Do stuff when done.
done: function(err) {
if (err) {
// If there was an error, abort the series.
done();
} else {
// Otherwise, process next url.
next();
}
},
});
},
// All tests have been run.
function() {
var message = generateMessage(status);
var success;
return Promise.mapSeries(urls, function(url) {
// Reset current module.
currentModule = null;
grunt.event.emit('qunit.spawn', url);
grunt.verbose.subhead('Testing ' + url + ' ').or.write('Testing ' + url + ' ');
// Log results.
if (status.failed > 0) {
warnUnlessForced(message);
} else {
grunt.verbose.writeln();
grunt.log.ok(message);
}
return Promise.all([
// Setup listeners for qunit.done / fail events
new Promise(function(resolve, reject) {
eventBus.once('qunit.done', function() {
setTimeout(resolve, 1);
});
eventBus.once('fail.*', function() {
setTimeout(function() {
reject(url);
}, 1);
});
}),
// Navigate to the url to be tested
page.goto(getFullUrl(url), {
timeout: options.timeout
})
]);
});
})
.then(function() {
// All tests have been run.
var message = generateMessage(status);
var success;
if (options && options.force) {
success = true;
} else {
success = status.failed === 0;
}
// Log results.
if (status.failed > 0) {
warnUnlessForced(message);
} else {
grunt.verbose.writeln();
grunt.log.ok(message);
}
// All done!
done(success);
});
if (options && options.force) {
success = true;
} else {
success = status.failed === 0;
}
// All done!
finishTask(success);
})
.catch(function(err) {
// If anything goes wrong, terminate the grunt task
grunt.log.error("There was an error with headless chrome");
grunt.fail.fatal(err);
finishTask(false);
});
});
};

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc