Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

chokidar

Package Overview
Dependencies
Maintainers
2
Versions
106
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

chokidar - npm Package Compare versions

Comparing version 3.2.1 to 3.2.2

lib/constants.js

136

index.js

@@ -15,3 +15,39 @@ 'use strict';

const FsEventsHandler = require('./lib/fsevents-handler');
const {
EV_ALL,
EV_READY,
EV_ADD,
EV_CHANGE,
EV_UNLINK,
EV_ADD_DIR,
EV_UNLINK_DIR,
EV_RAW,
EV_ERROR,
STR_CLOSE,
STR_END,
BACK_SLASH_RE,
DOUBLE_SLASH_RE,
SLASH_OR_BACK_SLASH_RE,
DOT_RE,
REPLACER_RE,
SLASH,
BRACE_START,
BANG,
ONE_DOT,
TWO_DOTS,
GLOBSTAR,
SLASH_GLOBSTAR,
ANYMATCH_OPTS,
STRING_TYPE,
FUNCTION_TYPE,
EMPTY_STR,
EMPTY_FN,
isWindows,
isMacos
} = require('./lib/constants');
const stat = promisify(fs.stat);

@@ -40,30 +76,2 @@ const readdir = promisify(fs.readdir);

// Optimize RAM usage.
const BACK_SLASH_RE = /\\/g;
const SLASH = '/';
const DOUBLE_SLASH = /\/\//;
const SLASH_OR_BACK_SLASH = /[\/\\]/;
const BRACE_START = '{';
const BANG = '!';
const ONE_DOT = '.';
const TWO_DOTS = '..';
const GLOBSTAR = '**';
const SLASH_GLOBSTAR = '/**';
const DOT_RE = /\..*\.(sw[px])$|\~$|\.subl.*\.tmp/;
const REPLACER_RE = /^\.[\/\\]/;
const ANYMATCH_OPTS = {dot: true};
const STRING_TYPE = 'string';
const isWindows = process.platform === 'win32';
const EMPTY_FN = () => {};
const EV_ALL = 'all';
const EV_READY = 'ready';
const EV_ADD = 'add';
const EV_CHANGE = 'change';
const EV_UNLINK = 'unlink';
const EV_ADD_DIR = 'addDir';
const EV_UNLINK_DIR = 'unlinkDir';
const EV_RAW = 'raw';
const EV_ERROR = 'error';
const arrify = (value = []) => Array.isArray(value) ? value : [value];

@@ -89,4 +97,3 @@ const flatten = (list, result = []) => {

}
paths = paths.map(normalizePathToUnix);
return paths;
return paths.map(normalizePathToUnix);
};

@@ -96,4 +103,4 @@

let str = string.replace(BACK_SLASH_RE, SLASH);
while (str.match(DOUBLE_SLASH)) {
str = str.replace(DOUBLE_SLASH, SLASH);
while (str.match(DOUBLE_SLASH_RE)) {
str = str.replace(DOUBLE_SLASH_RE, SLASH);
}

@@ -107,3 +114,3 @@ return str;

const normalizeIgnored = (cwd = '') => (path) => {
const normalizeIgnored = (cwd = EMPTY_STR) => (path) => {
if (typeof path !== STRING_TYPE) return path;

@@ -143,9 +150,13 @@ return normalizePathToUnix(sysPath.isAbsolute(path) ? path : sysPath.join(cwd, path));

add(item) {
if (item !== ONE_DOT && item !== TWO_DOTS) this.items.add(item);
const {items} = this;
if (!items) return;
if (item !== ONE_DOT && item !== TWO_DOTS) items.add(item);
}
async remove(item) {
this.items.delete(item);
const {items} = this;
if (!items) return;
items.delete(item);
if (!this.items.size) {
if (!items.size) {
const dir = this.path;

@@ -161,3 +172,5 @@ try {

has(item) {
return this.items.has(item);
const {items} = this;
if (!items) return;
return items.has(item);
}

@@ -169,10 +182,22 @@

getChildren() {
return Array.from(this.items.values());
const {items} = this;
if (!items) return;
return Array.from(items.values());
}
dispose() {
this.items.clear();
delete this.path;
delete this._removeWatcher;
delete this.items;
Object.freeze(this);
}
}
const STAT_METHOD_F = 'stat';
const STAT_METHOD_L = 'lstat';
class WatchHelper {
constructor(path, watchPath, follow, fsw) {
this.fsw = fsw;
this.path = path = path.replace(REPLACER_RE, '');
this.path = path = path.replace(REPLACER_RE, EMPTY_STR);
this.watchPath = watchPath;

@@ -182,3 +207,3 @@ this.fullWatchPath = sysPath.resolve(watchPath);

/** @type {object|boolean} */
if (path === '') this.hasGlob = false;
if (path === EMPTY_STR) this.hasGlob = false;
this.globSymlink = this.hasGlob && follow ? undefined : false;

@@ -191,3 +216,3 @@ this.globFilter = this.hasGlob ? anymatch(path, undefined, ANYMATCH_OPTS) : false;

this.followSymlinks = follow;
this.statMethod = follow ? 'stat' : 'lstat';
this.statMethod = follow ? STAT_METHOD_F : STAT_METHOD_L;
}

@@ -197,3 +222,3 @@

// only need to resolve once
// first entry should always have entry.parentDir === ''
// first entry should always have entry.parentDir === EMPTY_STR
if (this.globSymlink === undefined) {

@@ -221,3 +246,3 @@ this.globSymlink = entry.fullParentDir === this.fullWatchPath ?

const resolvedPath = this.entryPath(entry);
const matchesGlob = this.hasGlob && typeof this.globFilter === 'function' ?
const matchesGlob = this.hasGlob && typeof this.globFilter === FUNCTION_TYPE ?
this.globFilter(resolvedPath) : true;

@@ -232,7 +257,5 @@ return matchesGlob &&

const parts = [];
const expandedPath = path.includes(BRACE_START)
? braces.expand(path)
: [path];
const expandedPath = path.includes(BRACE_START) ? braces.expand(path) : [path];
expandedPath.forEach((path) => {
parts.push(sysPath.relative(this.watchPath, path).split(SLASH_OR_BACK_SLASH));
parts.push(sysPath.relative(this.watchPath, path).split(SLASH_OR_BACK_SLASH_RE));
});

@@ -309,3 +332,3 @@ return parts;

if (undef(opts, 'usePolling') && !opts.useFsEvents) {
opts.usePolling = process.platform === 'darwin';
opts.usePolling = isMacos;
}

@@ -488,2 +511,3 @@

this._readyEmitted = false;
this._watched.forEach(dirent => dirent.dispose());
['closers', 'watched', 'streams', 'symlinkPaths', 'throttled'].forEach(key => {

@@ -697,3 +721,3 @@ this['_' + key].clear();

this._pendingWrites.delete(path);
awfEmit(null, curStat);
awfEmit(undefined, curStat);
} else {

@@ -745,9 +769,4 @@ timeoutHandler = setTimeout(

.map((path) => path + SLASH_GLOBSTAR);
this._userIgnored = anymatch(
this._getGlobIgnored()
.map(normalizeIgnored(cwd))
.concat(ignored)
.concat(paths)
, undefined, ANYMATCH_OPTS
);
const list = this._getGlobIgnored().map(normalizeIgnored(cwd)).concat(ignored, paths);
this._userIgnored = anymatch(list, undefined, ANYMATCH_OPTS);
}

@@ -898,9 +917,10 @@

_readdirp(root, opts) {
if (this.closed) return;
const options = Object.assign({type: EV_ALL, alwaysStat: true, lstat: true}, opts);
let stream = readdirp(root, options);
this._streams.add(stream);
stream.once('close', () => {
stream.once(STR_CLOSE, () => {
stream = undefined;
});
stream.once('end', () => {
stream.once(STR_END, () => {
if (stream) {

@@ -907,0 +927,0 @@ this._streams.delete(stream);

@@ -16,4 +16,4 @@ 'use strict';

if (mtch && mtch[1] && mtch[2]) {
let maj = parseInt(mtch[1]);
let min = parseInt(mtch[2]);
let maj = Number.parseInt(mtch[1]);
let min = Number.parseInt(mtch[2]);
if (maj === 8 && min < 16) {

@@ -25,9 +25,30 @@ fsevents = undefined;

const EV_ADD = 'add';
const EV_CHANGE = 'change';
const EV_ADD_DIR = 'addDir';
const EMPTY_FN = () => {};
const {
EV_ADD,
EV_CHANGE,
EV_ADD_DIR,
EV_UNLINK,
EV_ERROR,
STR_DATA,
STR_END,
FSEVENT_CREATED,
FSEVENT_MODIFIED,
FSEVENT_DELETED,
FSEVENT_MOVED,
// FSEVENT_CLONED,
FSEVENT_UNKNOWN,
FSEVENT_TYPE_DIRECTORY,
FSEVENT_TYPE_SYMLINK,
const Option = (key, value) => isNaN(value) ? {} : {[key]: value};
ROOT_GLOBSTAR,
DIR_SUFFIX,
DOT_SLASH,
FUNCTION_TYPE,
EMPTY_FN,
IDENTITY_FN
} = require('./constants');
const FS_MODE_READ = 'r';
const Depth = (value) => isNaN(value) ? {} : {depth: value};
const stat = promisify(fs.stat);

@@ -177,3 +198,3 @@ const open = promisify(fs.open);

// determines subdirectory traversal levels from root to path
const depth = (path, root) => {
const calcDepth = (path, root) => {
let i = 0;

@@ -200,3 +221,3 @@ while (!path.indexOf(root) && (path = sysPath.dirname(path)) !== root) i++;

if (stats && stats.isDirectory()) {
ipaths.add(path + '/**/*');
ipaths.add(path + ROOT_GLOBSTAR);
}

@@ -206,3 +227,3 @@ return true;

ipaths.delete(path);
ipaths.delete(path + '/**/*');
ipaths.delete(path + ROOT_GLOBSTAR);
}

@@ -218,16 +239,12 @@ }

try {
const fd = await open(path, 'r');
if (this.fsw.closed) {
return;
}
const fd = await open(path, FS_MODE_READ);
if (this.fsw.closed) return;
await close(fd);
if (this.fsw.closed) {
return;
}
if (this.fsw.closed) return;
this.addOrChange(path, fullPath, realPath, parent, watchedDir, item, info, opts);
} catch (error) {
if (error.code !== 'EACCES') {
this.handleEvent('unlink', path, fullPath, realPath, parent, watchedDir, item, info, opts);
if (error.code === 'EACCES') {
this.addOrChange(path, fullPath, realPath, parent, watchedDir, item, info, opts);
} else {
this.addOrChange(path, fullPath, realPath, parent, watchedDir, item, info, opts);
this.handleEvent(EV_UNLINK, path, fullPath, realPath, parent, watchedDir, item, info, opts);
}

@@ -240,5 +257,5 @@ }

if (event === 'unlink') {
if (event === EV_UNLINK) {
// suppress unlink events on never before seen files
if (info.type === 'directory' || watchedDir.has(item)) {
if (info.type === FSEVENT_TYPE_DIRECTORY || watchedDir.has(item)) {
this.fsw._remove(parent, item);

@@ -249,8 +266,8 @@ }

// track new directories
if (info.type === 'directory') this.fsw._getWatchedDir(path);
if (info.type === FSEVENT_TYPE_DIRECTORY) this.fsw._getWatchedDir(path);
if (info.type === 'symlink' && opts.followSymlinks) {
if (info.type === FSEVENT_TYPE_SYMLINK && opts.followSymlinks) {
// push symlinks back to the top of the stack to get handled
const curDepth = opts.depth === undefined ?
undefined : depth(fullPath, realPath) + 1;
undefined : calcDepth(fullPath, realPath) + 1;
return this._addToFsEvents(path, false, true, curDepth);

@@ -266,3 +283,3 @@ } else {

*/
const eventName = info.type === 'directory' ? event + 'Dir' : event;
const eventName = info.type === FSEVENT_TYPE_DIRECTORY ? event + DIR_SUFFIX : event;
this.fsw._emit(eventName, path);

@@ -289,3 +306,3 @@ if (eventName === EV_ADD_DIR) this._addToFsEvents(path, false, true);

opts.depth !== undefined &&
depth(fullPath, realPath) > opts.depth
calcDepth(fullPath, realPath) > opts.depth
) return;

@@ -300,8 +317,8 @@ const path = transform(sysPath.join(

const watchedDir = this.fsw._getWatchedDir(
info.type === 'directory' ? path : parent
info.type === FSEVENT_TYPE_DIRECTORY ? path : parent
);
// correct for wrong events emitted
if (wrongEventFlags.has(flags) || info.event === 'unknown') {
if (typeof opts.ignored === 'function') {
if (wrongEventFlags.has(flags) || info.event === FSEVENT_UNKNOWN) {
if (typeof opts.ignored === FUNCTION_TYPE) {
let stats;

@@ -311,5 +328,3 @@ try {

} catch (error) {}
if (this.fsw.closed) {
return;
}
if (this.fsw.closed) return;
if (this.checkIgnored(path, stats)) return;

@@ -319,3 +334,3 @@ if (stats) {

} else {
this.handleEvent('unlink', path, fullPath, realPath, parent, watchedDir, item, info, opts);
this.handleEvent(EV_UNLINK, path, fullPath, realPath, parent, watchedDir, item, info, opts);
}

@@ -327,7 +342,7 @@ } else {

switch (info.event) {
case 'created':
case 'modified':
case FSEVENT_CREATED:
case FSEVENT_MODIFIED:
return this.addOrChange(path, fullPath, realPath, parent, watchedDir, item, info, opts);
case 'deleted':
case 'moved':
case FSEVENT_DELETED:
case FSEVENT_MOVED:
return this.checkFd(path, fullPath, realPath, parent, watchedDir, item, info, opts);

@@ -367,5 +382,3 @@ }

const linkTarget = await realpath(linkPath);
if (this.fsw.closed) {
return;
}
if (this.fsw.closed) return;
if (this.fsw._isIgnored(linkTarget)) {

@@ -380,7 +393,6 @@ return this.fsw._emitReady();

this._addToFsEvents(linkTarget || linkPath, (path) => {
const dotSlash = '.' + sysPath.sep;
let aliasedPath = linkPath;
if (linkTarget && linkTarget !== dotSlash) {
if (linkTarget && linkTarget !== DOT_SLASH) {
aliasedPath = path.replace(linkTarget, linkPath);
} else if (path !== dotSlash) {
} else if (path !== DOT_SLASH) {
aliasedPath = sysPath.join(linkPath, path);

@@ -442,3 +454,3 @@ }

const opts = this.fsw.options;
const processPath = typeof transform === 'function' ? transform : (val => val);
const processPath = typeof transform === FUNCTION_TYPE ? transform : IDENTITY_FN;

@@ -450,5 +462,3 @@ const wh = this.fsw._getWatchHelpers(path);

const stats = await statMethods[wh.statMethod](wh.watchPath);
if (this.fsw.closed) {
return;
}
if (this.fsw.closed) return;
if (this.fsw._isIgnored(wh.watchPath, stats)) {

@@ -468,4 +478,4 @@ throw null;

directoryFilter: entry => wh.filterDir(entry),
...Option("depth", opts.depth - (priorDepth || 0))
}).on('data', (entry) => {
...Depth(opts.depth - (priorDepth || 0))
}).on(STR_DATA, (entry) => {
// need to check filterPath on dirs b/c filterDir is less restrictive

@@ -484,3 +494,3 @@ if (this.fsw.closed) {

const curDepth = opts.depth === undefined ?
undefined : depth(joinedPath, sysPath.resolve(wh.watchPath)) + 1;
undefined : calcDepth(joinedPath, sysPath.resolve(wh.watchPath)) + 1;

@@ -491,3 +501,3 @@ this._handleFsEventsSymlink(joinedPath, fullPath, processPath, curDepth);

}
}).on('error', EMPTY_FN).on('end', () => {
}).on(EV_ERROR, EMPTY_FN).on(STR_END, () => {
this.fsw._emitReady();

@@ -508,5 +518,5 @@ });

if (opts.persistent && forceAdd !== true) {
if (typeof transform === 'function') {
if (typeof transform === FUNCTION_TYPE) {
// realpath has already been resolved
this.initWatch(null, path, wh, processPath);
this.initWatch(undefined, path, wh, processPath);
} else {

@@ -513,0 +523,0 @@ let realPath;

@@ -7,3 +7,20 @@ 'use strict';

const { promisify } = require('util');
const {
isWindows,
EMPTY_FN,
EMPTY_STR,
KEY_LISTENERS,
KEY_ERR,
KEY_RAW,
HANDLER_KEYS,
EV_CHANGE,
EV_ADD,
EV_ADD_DIR,
EV_ERROR,
STR_DATA,
STR_END
} = require('./constants');
const THROTTLE_MODE_WATCH = 'watch';
const open = promisify(fs.open);

@@ -16,13 +33,3 @@ const stat = promisify(fs.stat);

const statMethods = { lstat, stat };
const isWindows = process.platform === 'win32';
const emptyFn = () => {};
const KEY_LISTENERS = 'listeners';
const KEY_ERR = 'errHandlers';
const KEY_RAW = 'rawEmitters';
const HANDLER_KEYS = [KEY_LISTENERS, KEY_ERR, KEY_RAW];
const EV_CHANGE = 'change';
const EV_ADD = 'add';
const EV_ADD_DIR = 'addDir';
// TODO: emit errors properly. Example: EMFILE on Macos.

@@ -166,3 +173,3 @@ const foreach = (val, fn) => {

if (!watcher) return;
watcher.on('error', async (error) => {
watcher.on(EV_ERROR, async (error) => {
const broadcastErr = fsWatchBroadcast.bind(null, fullPath, KEY_ERR);

@@ -310,3 +317,3 @@ cont.watcherUnusable = true; // documented since Node 10.4.1

const options = {persistent: opts.persistent};
if (!listener) listener = emptyFn;
if (!listener) listener = EMPTY_FN;

@@ -353,9 +360,7 @@ let closer;

const closer = this._watchWithNodeFs(file, async (path, newStats) => {
if (!this.fsw._throttle('watch', file, 5)) return;
if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5)) return;
if (!newStats || newStats && newStats.mtimeMs === 0) {
try {
const newStats = await stat(file);
if (this.fsw.closed) {
return;
}
if (this.fsw.closed) return;
// Check that change event was not fired because of changed only accessTime.

@@ -412,5 +417,3 @@ const at = newStats.atimeMs;

const linkPath = await fsrealpath(path);
if (this.fsw.closed) {
return;
}
if (this.fsw.closed) return;
if (dir.has(item)) {

@@ -440,3 +443,3 @@ if (this.fsw._symlinkPaths.get(full) !== linkPath) {

// Normalize the directory name on Windows
directory = sysPath.join(directory, '');
directory = sysPath.join(directory, EMPTY_STR);

@@ -451,8 +454,9 @@ if (!wh.hasGlob) {

const stream = this.fsw._readdirp(directory, {
let stream = this.fsw._readdirp(directory, {
fileFilter: entry => wh.filterPath(entry),
directoryFilter: entry => wh.filterDir(entry),
depth: 0,
}).on('data', async (entry) => {
}).on(STR_DATA, async (entry) => {
if (this.fsw.closed) {
stream = undefined;
return;

@@ -469,5 +473,5 @@ }

if (this.fsw.closed) {
stream = undefined;
return;
}
// Files that present in current directory snapshot

@@ -484,9 +488,13 @@ // but absent in previous are added to watch list and

}
}).on('error', this._boundHandleError);
return new Promise(res =>
stream
.on('end', () => {
}).on(EV_ERROR, this._boundHandleError);
return new Promise(resolve =>
stream.once(STR_END, () => {
if (this.fsw.closed) {
stream = undefined;
return;
}
const wasThrottled = throttler ? throttler.clear() : false;
res();
resolve();

@@ -496,4 +504,4 @@ // Files that absent in current directory snapshot

// and are removed from @watched[directory].
previous.getChildren().filter((item) =>
item !== directory &&
previous.getChildren().filter((item) => {
return item !== directory &&
!current.has(item) &&

@@ -505,7 +513,9 @@ // in case of intersecting globs;

fullPath: sysPath.resolve(directory, item)
}))
).forEach((item) => {
}));
}).forEach((item) => {
this.fsw._remove(directory, item);
});
stream = undefined;
// one more time for any missed in case changes came in extremely quickly

@@ -547,5 +557,3 @@ if (wasThrottled) this._handleRead(directory, false, wh, target, dir, depth, throttler);

await this._handleRead(dir, initialAdd, wh, target, dir, depth, throttler);
if (this.fsw.closed) {
return;
}
if (this.fsw.closed) return;
}

@@ -591,5 +599,3 @@

const stats = await statMethods[wh.statMethod](wh.watchPath);
if (this.fsw.closed) {
return;
}
if (this.fsw.closed) return;
if (this.fsw._isIgnored(wh.watchPath, stats)) {

@@ -604,9 +610,5 @@ ready();

const targetPath = follow ? await fsrealpath(path) : path;
if (this.fsw.closed) {
return;
}
if (this.fsw.closed) return;
closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
if (this.fsw.closed) {
return;
}
if (this.fsw.closed) return;
// preserve this symlink's target path

@@ -618,5 +620,3 @@ if (path !== targetPath && targetPath !== undefined) {

const targetPath = follow ? await fsrealpath(path) : path;
if (this.fsw.closed) {
return;
}
if (this.fsw.closed) return;
const parent = sysPath.dirname(wh.watchPath);

@@ -626,5 +626,3 @@ this.fsw._getWatchedDir(parent).add(wh.watchPath);

closer = await this._handleDir(parent, stats, initialAdd, depth, path, wh, targetPath);
if (this.fsw.closed) {
return;
}
if (this.fsw.closed) return;

@@ -631,0 +629,0 @@ // preserve this symlink's target path

{
"name": "chokidar",
"description": "A neat wrapper around node.js fs.watch / fs.watchFile / fsevents.",
"version": "3.2.1",
"version": "3.2.2",
"homepage": "https://github.com/paulmillr/chokidar",

@@ -21,6 +21,6 @@ "author": "Paul Miller (https://paulmillr.com)",

"normalize-path": "~3.0.0",
"readdirp": "~3.1.3"
"readdirp": "~3.2.0"
},
"optionalDependencies": {
"fsevents": "~2.1.0"
"fsevents": "~2.1.1"
},

@@ -76,3 +76,3 @@ "devDependencies": {

"expr": true,
"esversion": 8,
"esversion": 9,
"predef": [

@@ -79,0 +79,0 @@ "toString"

@@ -119,3 +119,4 @@ # Chokidar [![Weekly downloads](https://img.shields.io/npm/dw/chokidar.svg)](https://github.com/paulmillr/chokidar) [![Yearly downloads](https://img.shields.io/npm/dy/chokidar.svg)](https://github.com/paulmillr/chokidar)

// Full list of options. See below for descriptions. (do not use this example)
// Full list of options. See below for descriptions.
// Do not use this example!
chokidar.watch('file', {

@@ -130,3 +131,3 @@ persistent: true,

usePolling: true,
usePolling: false,
interval: 100,

@@ -195,3 +196,3 @@ binaryInterval: 300,

* _Polling-specific settings_ (effective when `usePolling: true`)
* `interval` (default: `100`). Interval of file system polling. You may also
* `interval` (default: `100`). Interval of file system polling, in milliseconds. You may also
set the CHOKIDAR_INTERVAL env variable to override this option.

@@ -228,3 +229,3 @@ * `binaryInterval` (default: `300`). Interval of file system

milliseconds for a file size to remain constant before emitting its event.
* `awaitWriteFinish.pollInterval` (default: 100). File size polling interval.
* `awaitWriteFinish.pollInterval` (default: 100). File size polling interval, in milliseconds.

@@ -288,3 +289,3 @@ #### Errors

- **v3.2 (Oct 1, 2019):** Improve Linux RAM usage by 50%. Stability optimizations. Race condition fixes. Windows glob fixes.
- **v3.2 (Oct 1, 2019):** Improve Linux RAM usage by 50%. Race condition fixes. Windows glob fixes. Improve stability by using tight range of dependency versions.
- **v3.1 (Sep 16, 2019):** dotfiles are no longer filtered out by default. Use `ignored` option if needed. Improve initial Linux scan time by 50%.

@@ -291,0 +292,0 @@ - **v3 (Apr 30, 2019):** massive CPU & RAM consumption improvements; reduces deps / package size by a factor of 17x and bumps Node.js requirement to v8.16 and higher.

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