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

fluent-ffmpeg

Package Overview
Dependencies
Maintainers
4
Versions
54
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

fluent-ffmpeg - npm Package Compare versions

Comparing version 2.0.1 to 2.1.0

12

lib/capabilities.js

@@ -302,3 +302,3 @@ /*jshint node:true*/

this._spawnFfmpeg(['-filters'], { captureStdout: true }, function (err, stdout) {
this._spawnFfmpeg(['-filters'], { captureStdout: true, stdoutLines: 0 }, function (err, stdoutRing) {
if (err) {

@@ -308,2 +308,3 @@ return callback(err);

var stdout = stdoutRing.get();
var lines = stdout.split('\n');

@@ -359,3 +360,3 @@ var data = {};

this._spawnFfmpeg(['-codecs'], { captureStdout: true }, function(err, stdout) {
this._spawnFfmpeg(['-codecs'], { captureStdout: true, stdoutLines: 0 }, function(err, stdoutRing) {
if (err) {

@@ -365,2 +366,3 @@ return callback(err);

var stdout = stdoutRing.get();
var lines = stdout.split(lineBreakRegexp);

@@ -462,3 +464,3 @@ var data = {};

this._spawnFfmpeg(['-encoders'], { captureStdout: true }, function(err, stdout) {
this._spawnFfmpeg(['-encoders'], { captureStdout: true, stdoutLines: 0 }, function(err, stdoutRing) {
if (err) {

@@ -468,2 +470,3 @@ return callback(err);

var stdout = stdoutRing.get();
var lines = stdout.split(lineBreakRegexp);

@@ -520,3 +523,3 @@ var data = {};

// Run ffmpeg -formats
this._spawnFfmpeg(['-formats'], { captureStdout: true }, function (err, stdout) {
this._spawnFfmpeg(['-formats'], { captureStdout: true, stdoutLines: 0 }, function (err, stdoutRing) {
if (err) {

@@ -527,2 +530,3 @@ return callback(err);

// Parse output
var stdout = stdoutRing.get();
var lines = stdout.split(lineBreakRegexp);

@@ -529,0 +533,0 @@ var data = {};

@@ -12,16 +12,23 @@ /*jshint node:true, laxcomma:true*/

var lines = out.split(/\r\n|\r|\n/);
lines = lines.filter(function (line) {
return line.length > 0;
});
var data = {
streams: []
streams: [],
format: {},
chapters: []
};
function parseBlock() {
function parseBlock(name) {
var data = {};
var line = lines.shift();
while (line) {
if (line.match(/^\[\//)) {
while (typeof line !== 'undefined') {
if (line.toLowerCase() == '[/'+name+']') {
return data;
} else if (line.match(/^\[/)) {
lines.unshift(line);
return data;
line = lines.shift();
continue;
}

@@ -45,8 +52,11 @@

var line = lines.shift();
while (line) {
while (typeof line !== 'undefined') {
if (line.match(/^\[stream/i)) {
var stream = parseBlock();
var stream = parseBlock('stream');
data.streams.push(stream);
} else if (line.match(/^\[chapter/i)) {
var chapter = parseBlock('chapter');
data.chapters.push(chapter);
} else if (line.toLowerCase() === '[format]') {
data.format = parseBlock();
data.format = parseBlock('format');
}

@@ -83,14 +93,40 @@

*
* @param {Number} [index] 0-based index of input to probe (defaults to last input)
* @param {?Number} [index] 0-based index of input to probe (defaults to last input)
* @param {?String[]} [options] array of output options to return
* @param {FfmpegCommand~ffprobeCallback} callback callback function
*
*/
proto.ffprobe = function(index, callback) {
var input;
proto.ffprobe = function() {
var input, index = null, options = [], callback;
if (typeof callback === 'undefined') {
callback = index;
// the last argument should be the callback
var callback = arguments[arguments.length - 1];
var ended = false
function handleCallback(err, data) {
if (!ended) {
ended = true;
callback(err, data);
}
};
// map the arguments to the correct variable names
switch (arguments.length) {
case 3:
index = arguments[0];
options = arguments[1];
break;
case 2:
if (typeof arguments[0] === 'number') {
index = arguments[0];
} else if (Array.isArray(arguments[0])) {
options = arguments[0];
}
break;
}
if (index === null) {
if (!this._currentInput) {
return callback(new Error('No input specified'));
return handleCallback(new Error('No input specified'));
}

@@ -103,16 +139,12 @@

if (!input) {
return callback(new Error('Invalid input index'));
return handleCallback(new Error('Invalid input index'));
}
}
if (input.isStream) {
return callback(new Error('Cannot run ffprobe on stream input'));
}
// Find ffprobe
this._getFfprobePath(function(err, path) {
if (err) {
return callback(err);
return handleCallback(err);
} else if (!path) {
return callback(new Error('Cannot find ffprobe'));
return handleCallback(new Error('Cannot find ffprobe'));
}

@@ -126,12 +158,25 @@

// Spawn ffprobe
var ffprobe = spawn(path, [
'-show_streams',
'-show_format',
input.source
]);
var src = input.isStream ? 'pipe:0' : input.source;
var ffprobe = spawn(path, ['-show_streams', '-show_format'].concat(options, src));
ffprobe.on('error', function(err) {
callback(err);
});
if (input.isStream) {
// Skip errors on stdin. These get thrown when ffprobe is complete and
// there seems to be no way hook in and close stdin before it throws.
ffprobe.stdin.on('error', function(err) {
if (['ECONNRESET', 'EPIPE'].indexOf(err.code) >= 0) { return; }
handleCallback(err);
});
// Once ffprobe's input stream closes, we need no more data from the
// input
ffprobe.stdin.on('close', function() {
input.source.pause();
input.source.unpipe(ffprobe.stdin);
});
input.source.pipe(ffprobe.stdin);
}
ffprobe.on('error', callback);
// Ensure we wait for captured streams to end before calling callback

@@ -150,3 +195,3 @@ var exitError = null;

return callback(exitError);
return handleCallback(exitError);
}

@@ -159,26 +204,28 @@

[data.format].concat(data.streams).forEach(function(target) {
var legacyTagKeys = Object.keys(target).filter(legacyTag);
if (target) {
var legacyTagKeys = Object.keys(target).filter(legacyTag);
if (legacyTagKeys.length) {
target.tags = target.tags || {};
if (legacyTagKeys.length) {
target.tags = target.tags || {};
legacyTagKeys.forEach(function(tagKey) {
target.tags[tagKey.substr(4)] = target[tagKey];
delete target[tagKey];
});
}
legacyTagKeys.forEach(function(tagKey) {
target.tags[tagKey.substr(4)] = target[tagKey];
delete target[tagKey];
});
}
var legacyDispositionKeys = Object.keys(target).filter(legacyDisposition);
var legacyDispositionKeys = Object.keys(target).filter(legacyDisposition);
if (legacyDispositionKeys.length) {
target.disposition = target.disposition || {};
if (legacyDispositionKeys.length) {
target.disposition = target.disposition || {};
legacyDispositionKeys.forEach(function(dispositionKey) {
target.disposition[dispositionKey.substr(12)] = target[dispositionKey];
delete target[dispositionKey];
});
legacyDispositionKeys.forEach(function(dispositionKey) {
target.disposition[dispositionKey.substr(12)] = target[dispositionKey];
delete target[dispositionKey];
});
}
}
});
callback(null, data);
handleCallback(null, data);
}

@@ -222,2 +269,1 @@ }

};

@@ -27,2 +27,3 @@ /*jshint node:true*/

* @param {String} [options.preset="fluent-ffmpeg/lib/presets"] alias for `presets`
* @param {String} [options.stdoutLines=100] maximum lines of ffmpeg output to keep in memory, use 0 for unlimited
* @param {Number} [options.timeout=<no timeout>] ffmpeg processing timeout in seconds

@@ -65,2 +66,3 @@ * @param {String|ReadableStream} [options.source=<no input>] alias for the `input` parameter

// Set default option values
options.stdoutLines = 'stdoutLines' in options ? options.stdoutLines : 100;
options.presets = options.presets || options.preset || path.join(__dirname, 'presets');

@@ -214,4 +216,5 @@ options.niceness = options.niceness || options.priority || 0;

FfmpegCommand.ffprobe = function(file, callback) {
(new FfmpegCommand(file)).ffprobe(callback);
FfmpegCommand.ffprobe = function(file) {
var instance = new FfmpegCommand(file);
instance.ffprobe.apply(instance, Array.prototype.slice.call(arguments, 1));
};

@@ -218,0 +221,0 @@

@@ -10,2 +10,3 @@ /*jshint node:true*/

var nlRegexp = /\r\n|\r|\n/g;

@@ -52,2 +53,9 @@ /*

/**
* Emitted when ffmpeg outputs to stderr
*
* @event FfmpegCommand#stderr
* @param {String} line stderr output line
*/
/**
* Emitted when ffmpeg reports input codec data

@@ -77,3 +85,4 @@ *

* @event FfmpegCommand#end
* @param {Array|null} [filenames] generated filenames when taking screenshots, null otherwise
* @param {Array|String|null} [filenames|stdout] generated filenames when taking screenshots, ffmpeg stdout when not outputting to a stream, null otherwise
* @param {String|null} stderr ffmpeg stderr
*/

@@ -87,4 +96,5 @@

* - 'niceness': specify process niceness, ignored on Windows (default: 0)
* - `cwd`: change working directory
* - 'captureStdout': capture stdout and pass it to 'endCB' as its 2nd argument (default: false)
* - 'captureStderr': capture stderr and pass it to 'endCB' as its 3rd argument (default: false)
* - 'stdoutLines': override command limit (default: use command limit)
*

@@ -100,4 +110,4 @@ * The 'processCB' callback, if present, is called as soon as the process is created and

* @param {Object} [options] spawn options (see above)
* @param {Function} [processCB] callback called with process object when it has been created
* @param {Function} endCB callback with signature (err, stdout, stderr)
* @param {Function} [processCB] callback called with process object and stdout/stderr ring buffers when process has been created
* @param {Function} endCB callback called with error (if applicable) and stdout/stderr ring buffers when process finished
* @private

@@ -119,2 +129,4 @@ */

var maxLines = 'stdoutLines' in options ? options.stdoutLines : this.options.stdoutLines;
// Find ffmpeg

@@ -134,6 +146,6 @@ this._getFfmpegPath(function(err, command) {

var stdout = null;
var stdoutRing = utils.linesRing(maxLines);
var stdoutClosed = false;
var stderr = null;
var stderrRing = utils.linesRing(maxLines);
var stderrClosed = false;

@@ -144,3 +156,3 @@

if (ffmpegProc.stderr && options.captureStderr) {
if (ffmpegProc.stderr) {
ffmpegProc.stderr.setEncoding('utf8');

@@ -160,6 +172,4 @@ }

if (processExited &&
(stdoutClosed || !options.captureStdout) &&
(stderrClosed || !options.captureStderr)) {
endCB(exitError, stdout, stderr);
if (processExited && (stdoutClosed || !options.captureStdout) && stderrClosed) {
endCB(exitError, stdoutRing, stderrRing);
}

@@ -184,9 +194,8 @@ }

if (options.captureStdout) {
stdout = '';
ffmpegProc.stdout.on('data', function(data) {
stdout += data;
stdoutRing.append(data);
});
ffmpegProc.stdout.on('close', function() {
stdoutRing.close();
stdoutClosed = true;

@@ -198,17 +207,14 @@ handleExit();

// Capture stderr if specified
if (options.captureStderr) {
stderr = '';
ffmpegProc.stderr.on('data', function(data) {
stderrRing.append(data);
});
ffmpegProc.stderr.on('data', function(data) {
stderr += data;
});
ffmpegProc.stderr.on('close', function() {
stderrRing.close();
stderrClosed = true;
handleExit();
});
ffmpegProc.stderr.on('close', function() {
stderrClosed = true;
handleExit();
});
}
// Call process callback
processCB(ffmpegProc);
processCB(ffmpegProc, stdoutRing, stderrRing);
});

@@ -437,10 +443,11 @@ };

// Run ffmpeg
var stdout = null;
var stderr = '';
self._spawnFfmpeg(
args,
{
captureStdout: !outputStream,
niceness: self.options.niceness,
cwd: self.options.cwd
},
{ niceness: self.options.niceness },
function processCB(ffmpegProc) {
function processCB(ffmpegProc, stdoutRing, stderrRing) {
self.ffmpegProc = ffmpegProc;

@@ -470,3 +477,3 @@ self.emit('start', 'ffmpeg ' + args.join(' '));

emitEnd(new Error(msg), stdout, stderr);
emitEnd(new Error(msg), stdoutRing.get(), stderrRing.get());
ffmpegProc.kill();

@@ -476,2 +483,3 @@ }, self.options.timeout * 1000);

if (outputStream) {

@@ -497,22 +505,29 @@ // Pipe ffmpeg stdout to output stream

self.logger.debug('Output stream error, killing ffmpgeg process');
emitEnd(new Error('Output stream error: ' + err.message));
emitEnd(new Error('Output stream error: ' + err.message), stdoutRing.get(), stderrRing.get());
ffmpegProc.kill();
});
} else {
// Gather ffmpeg stdout
stdout = '';
ffmpegProc.stdout.on('data', function (data) {
stdout += data;
});
}
// Process ffmpeg stderr data
self._codecDataSent = false;
ffmpegProc.stderr.on('data', function (data) {
stderr += data;
// Setup stderr handling
if (stderrRing) {
if (!self._codecDataSent && self.listeners('codecData').length) {
utils.extractCodecData(self, stderr);
// 'stderr' event
if (self.listeners('stderr').length) {
stderrRing.callback(function(line) {
self.emit('stderr', line);
});
}
// 'codecData' event
if (self.listeners('codecData').length) {
var codecDataSent = false;
var codecObject = {};
stderrRing.callback(function(line) {
if (!codecDataSent)
codecDataSent = utils.extractCodecData(self, line, codecObject);
});
}
// 'progress' event
if (self.listeners('progress').length) {

@@ -525,8 +540,10 @@ var duration = 0;

utils.extractProgress(self, stderr, duration);
stderrRing.callback(function(line) {
utils.extractProgress(self, line, duration);
});
}
});
}
},
function endCB(err) {
function endCB(err, stdoutRing, stderrRing) {
delete self.ffmpegProc;

@@ -537,6 +554,6 @@

// Add ffmpeg error message
err.message += ': ' + utils.extractError(stderr);
err.message += ': ' + utils.extractError(stderrRing.get());
}
emitEnd(err, stdout, stderr);
emitEnd(err, stdoutRing.get(), stderrRing.get());
} else {

@@ -550,3 +567,6 @@ // Find out which outputs need flv metadata

self._getFlvtoolPath(function(err, flvtool) {
// No possible error here, getFlvtoolPath was already called by _prepare
if (err) {
return emitEnd(err);
}
async.each(

@@ -576,3 +596,3 @@ flvmeta,

} else {
emitEnd(null, stdout, stderr);
emitEnd(null, stdoutRing.get(), stderrRing.get());
}

@@ -583,3 +603,3 @@ }

} else {
emitEnd(null, stdout, stderr);
emitEnd(null, stdoutRing.get(), stderrRing.get());
}

@@ -586,0 +606,0 @@ }

@@ -193,2 +193,5 @@ /*jshint node:true*/

var duration = Number(vstream.duration);
if (isNaN(duration)) {
duration = Number(meta.format.duration);
}

@@ -195,0 +198,0 @@ if (isNaN(duration)) {

@@ -6,2 +6,3 @@ /*jshint node:true*/

var isWindows = require('os').platform().match(/win(32|64)/);
var which = require('which');

@@ -220,14 +221,8 @@ var nlRegexp = /\r\n|\r|\n/g;

var cmd = 'which ' + name;
if (isWindows) {
cmd = 'where ' + name + '.exe';
}
exec(cmd, function(err, stdout) {
which(name, function(err, result){
if (err) {
// Treat errors as not found
callback(null, whichCache[name] = '');
} else {
callback(null, whichCache[name] = stdout.trim());
return callback(null, whichCache[name] = '');
}
callback(null, whichCache[name] = result);
});

@@ -274,38 +269,51 @@ },

* Extract codec data from ffmpeg stderr and emit 'codecData' event if appropriate
* Call it with an initially empty codec object once with each line of stderr output until it returns true
*
* @param {FfmpegCommand} command event emitter
* @param {String} stderr ffmpeg stderr output
* @param {String} stderrLine ffmpeg stderr output line
* @param {Object} codecObject object used to accumulate codec data between calls
* @return {Boolean} true if codec data is complete (and event was emitted), false otherwise
* @private
*/
extractCodecData: function(command, stderr) {
var format= /Input #[0-9]+, ([^ ]+),/.exec(stderr);
var dur = /Duration\: ([^,]+)/.exec(stderr);
var audio = /Audio\: (.*)/.exec(stderr);
var video = /Video\: (.*)/.exec(stderr);
var codecObject = { format: '', audio: '', video: '', duration: '' };
extractCodecData: function(command, stderrLine, codecsObject) {
var inputPattern = /Input #[0-9]+, ([^ ]+),/;
var durPattern = /Duration\: ([^,]+)/;
var audioPattern = /Audio\: (.*)/;
var videoPattern = /Video\: (.*)/;
if (format && format.length > 1) {
codecObject.format = format[1];
if (!('inputStack' in codecsObject)) {
codecsObject.inputStack = [];
codecsObject.inputIndex = -1;
codecsObject.inInput = false;
}
if (dur && dur.length > 1) {
codecObject.duration = dur[1];
}
var inputStack = codecsObject.inputStack;
var inputIndex = codecsObject.inputIndex;
var inInput = codecsObject.inInput;
if (audio && audio.length > 1) {
var format, dur, audio, video;
if (format = stderrLine.match(inputPattern)) {
inInput = codecsObject.inInput = true;
inputIndex = codecsObject.inputIndex = codecsObject.inputIndex + 1;
inputStack[inputIndex] = { format: format[1], audio: '', video: '', duration: '' };
} else if (inInput && (dur = stderrLine.match(durPattern))) {
inputStack[inputIndex].duration = dur[1];
} else if (inInput && (audio = stderrLine.match(audioPattern))) {
audio = audio[1].split(', ');
codecObject.audio = audio[0];
codecObject.audio_details = audio;
}
if (video && video.length > 1) {
inputStack[inputIndex].audio = audio[0];
inputStack[inputIndex].audio_details = audio;
} else if (inInput && (video = stderrLine.match(videoPattern))) {
video = video[1].split(', ');
codecObject.video = video[0];
codecObject.video_details = video;
inputStack[inputIndex].video = video[0];
inputStack[inputIndex].video_details = video;
} else if (/Output #\d+/.test(stderrLine)) {
inInput = codecsObject.inInput = false;
} else if (/Stream mapping:|Press (\[q\]|ctrl-c) to stop/.test(stderrLine)) {
command.emit.apply(command, ['codecData'].concat(inputStack));
return true;
}
var codecInfoPassed = /Press (\[q\]|ctrl-c) to stop/.test(stderr);
if (codecInfoPassed) {
command.emit('codecData', codecObject);
command._codecDataSent = true;
}
return false;
},

@@ -318,15 +326,9 @@

* @param {FfmpegCommand} command event emitter
* @param {String} stderr ffmpeg stderr data
* @param {String} stderrLine ffmpeg stderr data
* @param {Number} [duration=0] expected output duration in seconds
* @private
*/
extractProgress: function(command, stderr, duration) {
var lines = stderr.split(nlRegexp);
var lastline = lines[lines.length - 2];
var progress;
extractProgress: function(command, stderrLine, duration) {
var progress = parseProgressLine(stderrLine);
if (lastline) {
progress = parseProgressLine(lastline);
}
if (progress) {

@@ -337,3 +339,3 @@ // build progress report object

currentFps: parseInt(progress.fps, 10),
currentKbps: parseFloat(progress.bitrate.replace('kbits/s', '')),
currentKbps: progress.bitrate ? parseFloat(progress.bitrate.replace('kbits/s', '')) : 0,
targetSize: parseInt(progress.size, 10),

@@ -370,3 +372,90 @@ timemark: progress.time

}, []).join('\n');
},
/**
* Creates a line ring buffer object with the following methods:
* - append(str) : appends a string or buffer
* - get() : returns the whole string
* - close() : prevents further append() calls and does a last call to callbacks
* - callback(cb) : calls cb for each line (incl. those already in the ring)
*
* @param {Numebr} maxLines maximum number of lines to store (<= 0 for unlimited)
*/
linesRing: function(maxLines) {
var cbs = [];
var lines = [];
var current = null;
var closed = false
var max = maxLines - 1;
function emit(line) {
cbs.forEach(function(cb) { cb(line); });
}
return {
callback: function(cb) {
lines.forEach(function(l) { cb(l); });
cbs.push(cb);
},
append: function(str) {
if (closed) return;
if (str instanceof Buffer) str = '' + str;
if (!str || str.length === 0) return;
var newLines = str.split(nlRegexp);
if (newLines.length === 1) {
if (current !== null) {
current = current + newLines.shift();
} else {
current = newLines.shift();
}
} else {
if (current !== null) {
current = current + newLines.shift();
emit(current);
lines.push(current);
}
current = newLines.pop();
newLines.forEach(function(l) {
emit(l);
lines.push(l);
});
if (max > -1 && lines.length > max) {
lines.splice(0, lines.length - max);
}
}
},
get: function() {
if (current !== null) {
return lines.concat([current]).join('\n');
} else {
return lines.join('\n');
}
},
close: function() {
if (closed) return;
if (current !== null) {
emit(current);
lines.push(current);
if (max > -1 && lines.length > max) {
lines.shift();
}
current = null;
}
closed = true;
}
};
}
};
{
"name": "fluent-ffmpeg",
"version": "2.0.1",
"version": "2.1.0",
"description": "A fluent API to FFMPEG (http://www.ffmpeg.org)",

@@ -15,2 +15,3 @@ "keywords": [

],
"license": "MIT",
"bugs": {

@@ -24,9 +25,7 @@ "mail": "schaermu@gmail.com",

"should": "latest",
"grunt-contrib-watch": "~0.4.4",
"grunt-shell": "~0.3.0",
"grunt": "~0.4.1",
"jsdoc": "latest"
},
"dependencies": {
"async": ">=0.2.9"
"async": ">=0.2.9",
"which": "^1.1.1"
},

@@ -33,0 +32,0 @@ "engines": {

@@ -100,2 +100,3 @@ # Fluent ffmpeg-API for node.js [![Build Status](https://secure.travis-ci.org/fluent-ffmpeg/node-fluent-ffmpeg.svg?branch=master)](http://travis-ci.org/fluent-ffmpeg/node-fluent-ffmpeg)

* `logger`: logger object with `debug()`, `info()`, `warn()` and `error()` methods (defaults to no logging)
* `stdoutLines`: maximum number of lines from ffmpeg stdout/stderr to keep in memory (defaults to 100, use 0 for unlimited storage)

@@ -850,2 +851,13 @@

#### 'stderr': FFmpeg output
The `stderr` event is emitted every time FFmpeg outputs a line to `stderr`. It is emitted with a string containing the line of stderr (minus trailing new line characters).
```js
ffmpeg('/path/to/file.avi')
.on('stderr', function(stderrLine) {
console.log('Stderr output: ' + stderrLine);
});
```
#### 'error': transcoding error

@@ -866,7 +878,7 @@

The `end` event is emitted when processing has finished. Listeners receive no arguments, except when generating thumbnails (see below), in which case they receive an array of the generated filenames.
The `end` event is emitted when processing has finished. Listeners receive ffmpeg standard output and standard error as arguments, except when generating thumbnails (see below), in which case they receive an array of the generated filenames.
```js
ffmpeg('/path/to/file.avi')
.on('end', function() {
.on('end', function(stdout, stderr) {
console.log('Transcoding succeeded !');

@@ -876,3 +888,5 @@ });

`stdout` is empty when the command outputs to a stream. Both `stdout` and `stderr` are limited by the `stdoutLines` option (defaults to 100 lines).
### Starting FFmpeg processing

@@ -1129,2 +1143,4 @@

**Warning:** ffprobe may be called with an input stream, but in this case *it will consume data from the stream*, and this data will no longer be available for ffmpeg. Using both ffprobe and a transcoding command on the same input stream will most likely fail unless the stream is a live stream. Only do this if you know what you're doing.
The returned object is the same that is returned by running the following command from your shell (depending on your ffmpeg version you may have to replace `-of` with `-print_format`) :

@@ -1131,0 +1147,0 @@

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

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