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

pure-engine

Package Overview
Dependencies
Maintainers
1
Versions
68
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

pure-engine - npm Package Compare versions

Comparing version 0.9.15 to 0.9.16

.github/main.workflow

14

package.json
{
"name": "pure-engine",
"version": "0.9.15",
"version": "0.9.16",
"description": "Compile HTML templates into JS",

@@ -9,2 +9,3 @@ "main": "index.js",

"test": "ava 'test/spec/**/*.js'",
"coverage": "nyc npm test",
"benchmark": "ava test/benchmark.js",

@@ -43,4 +44,6 @@ "watch": "npm test -- --watch"

"dependencies": {
"abstract-syntax-tree": "^1.0.2",
"abstract-syntax-tree": "^2.0.1",
"astoptech": "^1.0.1",
"asttv": "^1.0.1",
"css-tree": "^1.0.0-alpha.29",
"himalaya": "^1.1.0",

@@ -51,3 +54,5 @@ "himalaya-walk": "^1.0.0",

"pure-conditions": "^0.1.9",
"pure-utilities": "^1.1.1",
"pure-utilities": "^1.1.8",
"string-hash": "^1.1.3",
"words-to-numbers": "^1.5.1",
"yaml-js": "^0.2.3"

@@ -59,3 +64,3 @@ },

"ansi-colors": "^3.2.1",
"ava": "^0.25.0",
"ava": "^1.0.1",
"babel-loader": "^8.0.4",

@@ -69,2 +74,3 @@ "babel-plugin-transform-react-jsx": "^6.24.1",

"mustache": "^3.0.0",
"nyc": "^13.1.0",
"preact": "^8.3.1",

@@ -71,0 +77,0 @@ "preact-compat": "^3.18.4",

@@ -40,3 +40,3 @@ # pure-engine

const template = await compile('<div>{foo}</div>')
expect(template({ foo: 'bar' })).to.equal('<div>bar</div>')
expect(template({ foo: 'bar' }, escape)).to.equal('<div>bar</div>')
```

@@ -77,3 +77,3 @@

* filters for strings, numbers, arrays, objects and more
* filters

@@ -84,3 +84,3 @@ ```html

* special attributes, e.g. inline for asset inline, width="auto" for auto sizing and more
* special attributes

@@ -91,2 +91,11 @@ ```html

```html
<div class="foo">bar</div>
<style scoped>
.foo {
color: red;
}
</style>
```
* built-in i18n support (translate tag and filter)

@@ -103,3 +112,3 @@

* compiler tag for scripts (allows custom compilers)
* compiler tag for scripts

@@ -106,0 +115,0 @@ ```html

const AbstractSyntaxTree = require('abstract-syntax-tree')
const conditions = require('pure-conditions')
const negate = require('negate-sentence')
const { negationOperatorRemoval } = require('astoptech')

@@ -14,17 +15,21 @@ function getCondition (name) {

const statement = body[0].argument
AbstractSyntaxTree.replace(statement, leaf => {
const index = params.indexOf(leaf.name)
if (leaf.type === 'Identifier' && index >= 0) {
return args[index]
AbstractSyntaxTree.replace(statement, {
enter: leaf => {
const index = params.indexOf(leaf.name)
if (leaf.type === 'Identifier' && index >= 0) {
return args[index]
}
return leaf
}
return leaf
})
return statement
} else {
AbstractSyntaxTree.replace({ type: 'BlockStatement', body }, leaf => {
const index = params.indexOf(leaf.name)
if (leaf.type === 'Identifier' && index >= 0) {
return args[index]
AbstractSyntaxTree.replace({ type: 'BlockStatement', body }, {
enter: leaf => {
const index = params.indexOf(leaf.name)
if (leaf.type === 'Identifier' && index >= 0) {
return args[index]
}
return leaf
}
return leaf
})

@@ -50,3 +55,3 @@ return {

function negateAction (argument) {
return { type: 'UnaryExpression', operator: '!', prefix: true, argument }
return negationOperatorRemoval({ type: 'UnaryExpression', operator: '!', prefix: true, argument })
}

@@ -53,0 +58,0 @@

@@ -12,3 +12,4 @@ const AbstractSyntaxTree = require('abstract-syntax-tree')

getForInLoopVariable,
getTemplateVariableDeclaration
getTemplateVariableDeclaration,
getTranslateCallExpression
} = require('./factory')

@@ -22,3 +23,3 @@ const {

} = require('./convert')
const { parseYAML, parseJSON, parseJS } = require('./translations')
const { parseYAML, parseJSON, parseJS, mergeTranslations } = require('./translations')
const walk = require('himalaya-walk')

@@ -34,18 +35,5 @@ const { SPECIAL_TAGS, SELF_CLOSING_TAGS, OPERATORS, OBJECT_VARIABLE, TEMPLATE_VARIABLE } = require('./enum')

const { placeholderName, addPlaceholders } = require('./keywords')
const { wordsToNumbers } = require('words-to-numbers')
let asyncCounter = 0
const digits = new Map([
['zero', 0],
['one', 1],
['two', 2],
['three', 3],
['four', 4],
['five', 5],
['six', 6],
['seven', 7],
['eight', 8],
['nine', 9],
['ten', 10]
])
function getFreeIdentifier (variables) {

@@ -150,16 +138,18 @@ return array.identifier(variables)

} else if (expression.type === 'BinaryExpression') {
AbstractSyntaxTree.replace(expression, (node, parent) => {
if (node.type === 'MemberExpression') {
if (node.object.type === 'Identifier' && !node.object.transformed) {
node.object.transformed = true
const object = getIdentifier(OBJECT_VARIABLE)
object.transformed = true
node.object = {
type: 'MemberExpression',
object,
property: node.object
AbstractSyntaxTree.replace(expression, {
enter: (node, parent) => {
if (node.type === 'MemberExpression') {
if (node.object.type === 'Identifier' && !node.object.transformed) {
node.object.transformed = true
const object = getIdentifier(OBJECT_VARIABLE)
object.transformed = true
node.object = {
type: 'MemberExpression',
object,
property: node.object
}
}
}
return node
}
return node
})

@@ -172,3 +162,3 @@ return expression

function resolveComponent (component, fragment, components, statistics, options) {
function resolveComponent (component, fragment, components, plugins, statistics, options) {
const localVariables = fragment.attributes

@@ -184,4 +174,25 @@ let content = component.content

let children = fragment.children
walk(htmlTree, leaf => {
const keys = leaf.attributes && leaf.attributes.map(attribute => attribute.key)
plugins.forEach(plugin => {
plugin.prepare({
tag: leaf.tagName,
keys,
fragment: leaf,
...leaf
})
})
})
walk(htmlTree, leaf => {
const keys = leaf.attributes && leaf.attributes.map(attribute => attribute.key)
leaf.imported = true
plugins.forEach(plugin => {
plugin.run({
keys,
fragment: leaf,
...leaf
})
})
if (leaf.tagName === component.name) {

@@ -227,3 +238,3 @@ leaf.root = true

if (replaced) {
attr.value = '{' + ast.toString().replace(/;$/, '') + '}'
attr.value = '{' + ast.source.replace(/;$/, '') + '}'
}

@@ -247,3 +258,3 @@ }

if (currentComponent && !current.root) {
resolveComponent(currentComponent, current, components, statistics, options)
resolveComponent(currentComponent, current, components, plugins, statistics, options)
current.used = true

@@ -291,14 +302,2 @@ }

function appendIfStatement (node, tree, ast, depth) {
tree.append({
type: 'IfStatement',
test: node,
consequent: {
type: 'BlockStatement',
body: ast.body()
},
depth
})
}
function getTest (action, keys, values, variables) {

@@ -333,3 +332,4 @@ if (action.args === 1) {

const key = attribute.key || attribute
return digits.has(key) ? getLiteral(digits.get(key)) : convertKey(key, variables)
const value = wordsToNumbers(key)
return typeof value === 'number' ? getLiteral(value) : convertKey(key, variables)
}

@@ -417,3 +417,9 @@

async function collect (tree, fragment, variables, filters, components, statistics, translations, store, depth, options, promises, errors) {
async function collect (tree, fragment, variables, filters, components, statistics, translations, plugins, store, depth, options, promises, errors) {
function collectChildren (fragment, ast) {
walk(fragment, async current => {
await collect(ast, current, variables, filters, components, statistics, translations, plugins, store, depth, options, promises, errors)
})
}
try {

@@ -428,9 +434,14 @@ if (fragment.used) return

const { languages, translationsPaths } = options
plugins.forEach(plugin => {
plugin.run({
keys,
fragment,
...fragment
})
})
if (component && !fragment.imported) {
const { localVariables } = resolveComponent(component, fragment, components, statistics, options)
const { localVariables } = resolveComponent(component, fragment, components, plugins, statistics, options)
localVariables.forEach(variable => variables.push(variable.key))
const ast = new AbstractSyntaxTree('')
walk(fragment, async current => {
await collect(ast, current, variables, filters, components, statistics, translations, store, depth, options, promises, errors)
})
collectChildren(fragment, ast)
// TODO instead of doing this we could pass the variables down the road together

@@ -452,4 +463,3 @@ // with the values and set them there instead of replacing here

})
const body = ast.body()
body.forEach(node => tree.append(node))
ast.body.forEach(node => tree.append(node))
localVariables.forEach(() => variables.pop())

@@ -466,10 +476,7 @@ } else if (tag === 'content') {

const { key } = attribute
fragment.used = true
fragment.children = [{ type: 'text', content: `{'${key}' | translate}` }]
const ast = new AbstractSyntaxTree('')
walk(fragment, async current => {
await collect(ast, current, variables, filters, components, statistics, translations, store, depth, options, promises, errors)
})
const body = ast.body()
body.forEach(node => tree.append(node))
filters.push('translate')
translations = mergeTranslations(key, translations, languages, translationsPaths)
tree.append(getTemplateAssignmentExpression(options.variables.template, getTranslateCallExpression(key)))
} else {
throw new Error('Translate tag must define a key')
}

@@ -498,4 +505,3 @@ } else if ((tag === 'script' && keys.includes('inline')) || options.inline.includes('scripts')) {

ast.each('VariableDeclarator', node => variables.push(node.id.name))
const body = ast.body()
body.forEach(node => tree.append(node))
ast.body.forEach(node => tree.append(node))
}

@@ -609,3 +615,8 @@ } else if (tag === 'script' && keys.includes('store')) {

fragment.attributes.forEach(attribute => {
content += ` ${attribute.key}="${attribute.value}"`
if (tag === 'style' && attribute.key === 'scoped') return
if (attribute.value) {
content += ` ${attribute.key}="${attribute.value}"`
} else {
content += ` ${attribute.key}`
}
})

@@ -682,5 +693,3 @@ content += '>'

})
walk(fragment, async node => {
await collect(tree, node, variables, filters, components, statistics, translations, store, depth, options, promises, errors)
})
collectChildren(fragment, tree)
if (!SELF_CLOSING_TAGS.includes(tag)) {

@@ -702,7 +711,13 @@ const attr = fragment.attributes.find(attr => attr.key === 'tag' || attr.key === 'tag.bind')

const ast = new AbstractSyntaxTree('')
walk(fragment, async current => {
await collect(ast, current, variables, filters, components, statistics, translations, store, depth, options, promises, errors)
collectChildren(fragment, ast)
const condition = getCondition(attrs, variables)
tree.append({
type: 'IfStatement',
test: condition,
consequent: {
type: 'BlockStatement',
body: ast.body
},
depth
})
const condition = getCondition(attrs, variables)
appendIfStatement(condition, tree, ast, depth)
} else if (tag === 'elseif') {

@@ -712,5 +727,3 @@ let leaf = tree.last(`IfStatement[depth="${depth}"]`)

const ast = new AbstractSyntaxTree('')
walk(fragment, async current => {
await collect(ast, current, variables, filters, components, statistics, translations, store, depth, options, promises, errors)
})
collectChildren(fragment, ast)
while (leaf.alternate && leaf.alternate.type === 'IfStatement') {

@@ -725,3 +738,3 @@ leaf = leaf.alternate

type: 'BlockStatement',
body: ast.body()
body: ast.body
},

@@ -735,5 +748,3 @@ depth

const ast = new AbstractSyntaxTree('')
walk(fragment, async current => {
await collect(ast, current, variables, filters, components, statistics, translations, store, depth, options, promises, errors)
})
collectChildren(fragment, ast)
while (leaf.alternate && leaf.alternate.type === 'IfStatement') {

@@ -744,3 +755,3 @@ leaf = leaf.alternate

type: 'BlockStatement',
body: ast.body()
body: ast.body
}

@@ -774,6 +785,4 @@ }

ast.append(getForLoopVariable(variable, name, variables, index, range))
walk(fragment, async current => {
await collect(ast, current, variables, filters, components, statistics, translations, store, depth, options, promises, errors)
})
tree.append(getForLoop(name, ast.body(), variables, index, guard, range))
collectChildren(fragment, ast)
tree.append(getForLoop(name, ast.body, variables, index, guard, range))
variables.pop()

@@ -794,6 +803,4 @@ variables.pop()

walk(fragment, async current => {
await collect(ast, current, variables, filters, components, statistics, translations, store, depth, options, promises, errors)
})
tree.append(getForInLoop(keyIdentifier, name, ast.body()))
collectChildren(fragment, ast)
tree.append(getForInLoop(keyIdentifier, name, ast.body))
variables.pop()

@@ -804,7 +811,4 @@ variables.pop()

const ast = new AbstractSyntaxTree('')
walk(fragment, async current => {
await collect(ast, current, variables, filters, components, statistics, translations, store, depth, options, promises, errors)
})
const body = ast.body()
body.forEach(node => tree.append(node))
collectChildren(fragment, ast)
ast.body.forEach(node => tree.append(node))
} else if (tag === 'try') {

@@ -815,5 +819,3 @@ const ast = new AbstractSyntaxTree('')

options.variables.template = variable
walk(fragment, async current => {
await collect(ast, current, variables, filters, components, statistics, translations, store, depth, options, promises, errors)
})
collectChildren(fragment, ast)
ast.append(getTemplateAssignmentExpression(TEMPLATE_VARIABLE, { type: 'Identifier', name: variable }))

@@ -825,3 +827,3 @@ options.variables.template = TEMPLATE_VARIABLE

type: 'BlockStatement',
body: ast.body()
body: ast.body
}

@@ -833,5 +835,3 @@ })

const ast = new AbstractSyntaxTree('')
walk(fragment, async current => {
await collect(ast, current, variables, filters, components, statistics, translations, store, depth, options, promises, errors)
})
collectChildren(fragment, ast)
leaf.handler = {

@@ -845,3 +845,3 @@ type: 'CatchClause',

type: 'BlockStatement',
body: ast.body()
body: ast.body
}

@@ -852,5 +852,3 @@ }

const ast = new AbstractSyntaxTree('')
walk(fragment, async current => {
await collect(ast, current, variables, filters, components, statistics, translations, store, depth, options, promises, errors)
})
collectChildren(fragment, ast)
const { key } = attrs[0]

@@ -867,3 +865,3 @@ tree.append({

type: 'BlockStatement',
body: ast.body()
body: ast.body
},

@@ -876,5 +874,3 @@ depth

const ast = new AbstractSyntaxTree('')
walk(fragment, async current => {
await collect(ast, current, variables, filters, components, statistics, translations, store, depth, options, promises, errors)
})
collectChildren(fragment, ast)
while (leaf.alternate && leaf.alternate.type === 'IfStatement') {

@@ -894,3 +890,3 @@ leaf = leaf.alternate

type: 'BlockStatement',
body: ast.body()
body: ast.body
},

@@ -922,5 +918,3 @@ depth

const ast = new AbstractSyntaxTree('')
walk(fragment, async current => {
await collect(ast, current, variables, filters, components, statistics, translations, store, depth, options, promises, errors)
})
collectChildren(fragment, ast)
ast.append({

@@ -932,3 +926,3 @@ type: 'BreakStatement',

type: 'SwitchCase',
consequent: ast.body(),
consequent: ast.body,
test: condition

@@ -941,5 +935,3 @@ })

const ast = new AbstractSyntaxTree('')
walk(fragment, async current => {
await collect(ast, current, variables, filters, components, statistics, translations, store, depth, options, promises, errors)
})
collectChildren(fragment, ast)
ast.append({

@@ -951,3 +943,3 @@ type: 'BreakStatement',

type: 'SwitchCase',
consequent: ast.body(),
consequent: ast.body,
test: null

@@ -972,5 +964,3 @@ })

}
walk(fragment, async current => {
await collect(ast, current, variables, filters, components, statistics, translations, store, depth, options, promises, errors)
})
collectChildren(fragment, ast)
if (left) {

@@ -1014,3 +1004,3 @@ variables.pop()

type: 'BlockStatement',
body: ast.body()
body: ast.body
}

@@ -1017,0 +1007,0 @@ }

@@ -12,2 +12,3 @@ const AbstractSyntaxTree = require('abstract-syntax-tree')

const Statistics = require('./Statistics')
const ScopedStylesPlugin = require('./plugins/ScopedStylesPlugin')

@@ -29,7 +30,21 @@ async function render (htmltree, options) {

const errors = []
const plugins = [
new ScopedStylesPlugin()
]
let depth = 0
tree.append(getTemplateVariableDeclaration(TEMPLATE_VARIABLE))
walk(htmltree, async fragment => {
await collect(tree, fragment, variables, filters, components, statistics, translations, store, depth, options, promises, errors)
plugins.forEach(plugin => {
plugin.prepare({
tag: fragment.tagName,
keys: fragment.attributes && fragment.attributes.map(attribute => attribute.key),
fragment,
...fragment
})
})
})
walk(htmltree, async fragment => {
await collect(tree, fragment, variables, filters, components, statistics, translations, plugins, store, depth, options, promises, errors)
})
await Promise.all(promises)

@@ -54,3 +69,3 @@ const used = []

type: 'BlockStatement',
body: template.body()
body: template.body
},

@@ -65,3 +80,3 @@ handler: {

type: 'BlockStatement',
body: rescue.body()
body: rescue.body
}

@@ -119,3 +134,3 @@ }

optimizer.optimize()
const compiled = new Function(`return function render(${params}) {\n${program.toString()}}`)() // eslint-disable-line
const compiled = new Function(`return function render(${params}) {\n${program.source}}`)() // eslint-disable-line
return { template: compiled, statistics, errors }

@@ -122,0 +137,0 @@ }

@@ -63,3 +63,3 @@ const { OBJECT_VARIABLE, ESCAPE_VARIABLE, BOOLEAN_ATTRIBUTES, UNESCAPED_NAMES, GLOBAL_VARIABLES, RESERVED_KEYWORDS } = require('./enum')

})
const { expression } = tree.ast.body[0]
const { expression } = tree.body[0]
return expression

@@ -158,35 +158,37 @@ }

].includes(expression.type)) {
AbstractSyntaxTree.replace(expression, (node, parent) => {
if (node.type === 'Property' && node.key === node.value) {
if (!variables.includes(node.key.name)) {
const object = getIdentifier(OBJECT_VARIABLE)
object.omit = true
node.value = {
type: 'MemberExpression',
object,
property: node.value
AbstractSyntaxTree.replace(expression, {
enter: (node, parent) => {
if (node.type === 'Property' && node.key === node.value) {
if (!variables.includes(node.key.name)) {
const object = getIdentifier(OBJECT_VARIABLE)
object.omit = true
node.value = {
type: 'MemberExpression',
object,
property: node.value
}
node.shorthand = false
node.value.omit = true
node.value.property.omit = true
}
node.shorthand = false
node.value.omit = true
node.value.property.omit = true
}
} else if (parent.type === 'Property' && node.type === 'Identifier' && parent.key === node) {
node.omit = true
} else if (node.type === 'MemberExpression' && node.property.type === 'Identifier' && !node.computed) {
node.property.omit = true
} else if (node.type === 'Identifier' && !node.omit && !GLOBAL_VARIABLES.includes(node.name)) {
node.omit = true
if (!variables.includes(node.name)) {
const object = getIdentifier(OBJECT_VARIABLE)
object.omit = true
node = {
type: 'MemberExpression',
object,
property: node
} else if (parent.type === 'Property' && node.type === 'Identifier' && parent.key === node) {
node.omit = true
} else if (node.type === 'MemberExpression' && node.property.type === 'Identifier' && !node.computed) {
node.property.omit = true
} else if (node.type === 'Identifier' && !node.omit && !GLOBAL_VARIABLES.includes(node.name)) {
node.omit = true
if (!variables.includes(node.name)) {
const object = getIdentifier(OBJECT_VARIABLE)
object.omit = true
node = {
type: 'MemberExpression',
object,
property: node
}
}
}
return node
}
return node
})
if (unescape) {
if (unescape || isBooleanReturnFromExpression(expression)) {
return expression

@@ -200,2 +202,10 @@ }

function isComparisonOperator (operator) {
return (/^(==|===|!=|!==|<|>|<=|>=)$/).test(operator)
}
function isBooleanReturnFromExpression (node) {
return node.type === 'BinaryExpression' && isComparisonOperator(node.operator)
}
function convertText (text, variables, currentFilters, translations, languages, translationsPaths) {

@@ -231,3 +241,3 @@ const nodes = extract(text).map(({ value, filters = [] }, index) => {

const tree = new AbstractSyntaxTree(filter)
const node = tree.body()[0].expression
const node = tree.body[0].expression
if (node.type === 'CallExpression') {

@@ -234,0 +244,0 @@ return {

@@ -89,3 +89,8 @@ module.exports = {

'for'
],
VOID_TAGS: [
'import',
'require',
'translate'
]
}

@@ -30,2 +30,10 @@ const {

function getObjectMemberExpression (name) {
return {
type: 'MemberExpression',
object: getIdentifier(OBJECT_VARIABLE),
property: getIdentifier(name)
}
}
function getEscapeCallExpression (node) {

@@ -61,9 +69,3 @@ return {

},
getObjectMemberExpression (name) {
return {
type: 'MemberExpression',
object: getIdentifier(OBJECT_VARIABLE),
property: getIdentifier(name)
}
},
getObjectMemberExpression,
getForLoop (name, body, variables, index, guard, range) {

@@ -159,3 +161,19 @@ return {

getIdentifier,
getLiteral
getLiteral,
getTranslateCallExpression (key) {
return {
type: 'CallExpression',
callee: {
type: 'Identifier',
name: 'translate'
},
arguments: [
{
type: 'Literal',
value: key
},
getObjectMemberExpression('language')
]
}
}
}

@@ -60,3 +60,3 @@ const AbstractSyntaxTree = require('abstract-syntax-tree')

const tree = new AbstractSyntaxTree(filter)
const node = tree.body()[0].expression
const node = tree.body[0].expression
return node.type === 'CallExpression' ? node.callee.name : node.name

@@ -83,3 +83,3 @@ }

const leaf = new AbstractSyntaxTree(method.toString())
const fn = leaf.body()[0]
const fn = leaf.body[0]
if (name === 'translate') {

@@ -86,0 +86,0 @@ fn.body.body[0].declarations[0].init.properties = serializeProperties(translations)

@@ -22,3 +22,3 @@ const { TEMPLATE_VARIABLE } = require('./enum')

concatenateLiterals () {
let body = this.program.body()
let body = this.program.body
body = body.reduce((result, node) => {

@@ -42,3 +42,3 @@ const last = result[result.length - 1]

simplifyReturnValue () {
const body = this.program.body()
const body = this.program.body
this.program.wrap(() => body)

@@ -45,0 +45,0 @@ if (body.length === 2) {

const { parse, parseDefaults } = require('himalaya')
const { VOID_TAGS } = require('./enum')
module.exports = function (source, options) {
const voidTags = parseDefaults.voidTags.filter(tag => {
const voidTags = VOID_TAGS.concat(parseDefaults.voidTags).filter(tag => {
const regexp = new RegExp(`<import\\s+${tag}\\s+`)

@@ -6,0 +7,0 @@ return !regexp.test(source)

const { string: { singlespace } } = require('pure-utilities')
function isCurlyTag (value) {
return value.startsWith('{') && value.endsWith('}')
}
function extract (value) {

@@ -47,2 +51,2 @@ let objects = []

module.exports = {extract, getName}
module.exports = {extract, getName, isCurlyTag}

@@ -25,4 +25,4 @@ const AbstractSyntaxTree = require('abstract-syntax-tree')

try {
const ast = new AbstractSyntaxTree(content)
const node = ast.first('ExportDefaultDeclaration')
const tree = new AbstractSyntaxTree(content)
const node = tree.first('ExportDefaultDeclaration')
return convert(node.declaration)

@@ -29,0 +29,0 @@ } catch (exception) {

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