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

sol2uml

Package Overview
Dependencies
Maintainers
1
Versions
84
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

sol2uml - npm Package Compare versions

Comparing version 2.3.0 to 2.3.1

7

lib/converterAST2Classes.d.ts
import { ASTNode } from '@solidity-parser/parser/dist/src/ast-types';
import { UmlClass } from './umlClass';
/**
* Convert solidity parser output of type `ASTNode` to UML classes of type `UMLClass`
* @param node output of Solidity parser of type `ASTNode`
* @param relativePath relative path from the working directory to the Solidity source file
* @param filesystem flag if Solidity source code was parsed from the filesystem or Etherscan
* @return umlClasses array of UML class definitions of type `UmlClass`
*/
export declare function convertAST2UmlClasses(node: ASTNode, relativePath: string, filesystem?: boolean): UmlClass[];

174

lib/converterAST2Classes.js

@@ -32,3 +32,10 @@ "use strict";

const debug = require('debug')('sol2uml');
let umlClasses = [];
let umlClasses;
/**
* Convert solidity parser output of type `ASTNode` to UML classes of type `UMLClass`
* @param node output of Solidity parser of type `ASTNode`
* @param relativePath relative path from the working directory to the Solidity source file
* @param filesystem flag if Solidity source code was parsed from the filesystem or Etherscan
* @return umlClasses array of UML class definitions of type `UmlClass`
*/
function convertAST2UmlClasses(node, relativePath, filesystem = false) {

@@ -47,3 +54,3 @@ const imports = [];

});
umlClass = parseContractDefinition(umlClass, childNode);
parseContractDefinition(childNode, umlClass);
debug(`Added contract ${childNode.name}`);

@@ -62,3 +69,3 @@ umlClasses.push(umlClass);

});
umlClass = parseStructDefinition(umlClass, childNode);
parseStructDefinition(childNode, umlClass);
debug(`Added struct ${umlClass.name}`);

@@ -78,3 +85,3 @@ umlClasses.push(umlClass);

debug(`Added enum ${umlClass.name}`);
umlClass = parseEnumDefinition(umlClass, childNode);
parseEnumDefinition(childNode, umlClass);
umlClasses.push(umlClass);

@@ -185,3 +192,8 @@ }

exports.convertAST2UmlClasses = convertAST2UmlClasses;
function parseStructDefinition(umlClass, node) {
/**
* Parse struct definition for UML attributes and associations.
* @param node defined in ASTNode as `StructDefinition`
* @param umlClass that has struct attributes and associations added. This parameter is mutated.
*/
function parseStructDefinition(node, umlClass) {
node.members.forEach((member) => {

@@ -196,6 +208,10 @@ const [type, attributeType] = parseTypeName(member.typeName);

// Recursively parse struct members for associations
umlClass = addAssociations(node.members, umlClass);
return umlClass;
addAssociations(node.members, umlClass);
}
function parseEnumDefinition(umlClass, node) {
/**
* Parse enum definition for UML attributes and associations.
* @param node defined in ASTNode as `EnumDefinition`
* @param umlClass that has enum attributes and associations added. This parameter is mutated.
*/
function parseEnumDefinition(node, umlClass) {
let index = 0;

@@ -209,6 +225,10 @@ node.members.forEach((member) => {

// Recursively parse struct members for associations
umlClass = addAssociations(node.members, umlClass);
return umlClass;
addAssociations(node.members, umlClass);
}
function parseContractDefinition(umlClass, node) {
/**
* Parse contract definition for UML attributes, operations and associations.
* @param node defined in ASTNode as `ContractDefinition`
* @param umlClass that has attributes, operations and associations added. This parameter is mutated.
*/
function parseContractDefinition(node, umlClass) {
umlClass.stereotype = parseContractKind(node.kind);

@@ -249,3 +269,3 @@ // For each base contract

// Recursively parse variables for associations
umlClass = addAssociations(subNode.variables, umlClass);
addAssociations(subNode.variables, umlClass);
}

@@ -273,3 +293,3 @@ else if ((0, typeGuards_1.isUsingForDeclaration)(subNode)) {

parameters: parseParameters(subNode.parameters),
isPayable: parsePayable(subNode.stateMutability),
stateMutability: subNode.stateMutability,
});

@@ -295,5 +315,5 @@ }

// Recursively parse function parameters for associations
umlClass = addAssociations(subNode.parameters, umlClass);
addAssociations(subNode.parameters, umlClass);
if (subNode.returnParameters) {
umlClass = addAssociations(subNode.returnParameters, umlClass);
addAssociations(subNode.returnParameters, umlClass);
}

@@ -309,3 +329,3 @@ // If no body to the function, it must be either an Interface or Abstract

// Recursively parse function statements for associations
umlClass = addAssociations(subNode.body.statements, umlClass);
addAssociations(subNode.body.statements, umlClass);
}

@@ -321,3 +341,3 @@ }

// Recursively parse modifier statements for associations
umlClass = addAssociations(subNode.body.statements, umlClass);
addAssociations(subNode.body.statements, umlClass);
}

@@ -332,3 +352,3 @@ }

// Recursively parse event parameters for associations
umlClass = addAssociations(subNode.parameters, umlClass);
addAssociations(subNode.parameters, umlClass);
}

@@ -342,3 +362,3 @@ else if ((0, typeGuards_1.isStructDefinition)(subNode)) {

});
parseStructDefinition(structClass, subNode);
parseStructDefinition(subNode, structClass);
umlClasses.push(structClass);

@@ -355,3 +375,3 @@ // list as contract level struct

});
parseEnumDefinition(enumClass, subNode);
parseEnumDefinition(subNode, enumClass);
umlClasses.push(enumClass);

@@ -362,9 +382,12 @@ // list as contract level enum

});
return umlClass;
}
// Recursively parse AST nodes for associations
/**
* Recursively parse a list of ASTNodes for UML associations
* @param nodes array of parser output of type ASTNode
* @param umlClass that has associations added of type `Association`. This parameter is mutated.
*/
function addAssociations(nodes, umlClass) {
if (!nodes || !Array.isArray(nodes)) {
debug('Warning - can not recursively parse AST nodes for associations. Invalid nodes array');
return umlClass;
return;
}

@@ -401,4 +424,4 @@ for (const node of nodes) {

else if (node.typeName.type === 'Mapping') {
umlClass = addAssociations([node.typeName.keyType], umlClass);
umlClass = addAssociations([
addAssociations([node.typeName.keyType], umlClass);
addAssociations([
{

@@ -436,19 +459,27 @@ ...node.typeName.valueType,

case 'Block':
umlClass = addAssociations(node.statements, umlClass);
addAssociations(node.statements, umlClass);
break;
case 'StateVariableDeclaration':
case 'VariableDeclarationStatement':
umlClass = addAssociations(node.variables, umlClass);
umlClass = parseExpression(node.initialValue, umlClass);
addAssociations(node.variables, umlClass);
parseExpression(node.initialValue, umlClass);
break;
case 'EmitStatement':
addAssociations(node.eventCall.arguments, umlClass);
parseExpression(node.eventCall.expression, umlClass);
break;
case 'FunctionCall':
addAssociations(node.arguments, umlClass);
parseExpression(node.expression, umlClass);
break;
case 'ForStatement':
if ('statements' in node.body) {
umlClass = addAssociations(node.body.statements, umlClass);
addAssociations(node.body.statements, umlClass);
}
umlClass = parseExpression(node.conditionExpression, umlClass);
umlClass = parseExpression(node.loopExpression.expression, umlClass);
parseExpression(node.conditionExpression, umlClass);
parseExpression(node.loopExpression.expression, umlClass);
break;
case 'WhileStatement':
if ('statements' in node.body) {
umlClass = addAssociations(node.body.statements, umlClass);
addAssociations(node.body.statements, umlClass);
}

@@ -458,9 +489,9 @@ break;

if ('statements' in node.body) {
umlClass = addAssociations(node.body.statements, umlClass);
addAssociations(node.body.statements, umlClass);
}
umlClass = parseExpression(node.condition, umlClass);
parseExpression(node.condition, umlClass);
break;
case 'ReturnStatement':
case 'ExpressionStatement':
umlClass = parseExpression(node.expression, umlClass);
parseExpression(node.expression, umlClass);
break;

@@ -470,6 +501,6 @@ case 'IfStatement':

if ('statements' in node.trueBody) {
umlClass = addAssociations(node.trueBody.statements, umlClass);
addAssociations(node.trueBody.statements, umlClass);
}
if ('expression' in node.trueBody) {
umlClass = parseExpression(node.trueBody.expression, umlClass);
parseExpression(node.trueBody.expression, umlClass);
}

@@ -479,9 +510,9 @@ }

if ('statements' in node.falseBody) {
umlClass = addAssociations(node.falseBody.statements, umlClass);
addAssociations(node.falseBody.statements, umlClass);
}
if ('expression' in node.falseBody) {
umlClass = parseExpression(node.falseBody.expression, umlClass);
parseExpression(node.falseBody.expression, umlClass);
}
}
umlClass = parseExpression(node.condition, umlClass);
parseExpression(node.condition, umlClass);
break;

@@ -492,33 +523,37 @@ default:

}
return umlClass;
}
/**
* Recursively parse an expression to add UML associations to other contracts, constants, enums or structs.
* @param expression defined in ASTNode as `Expression`
* @param umlClass that has associations added of type `Association`. This parameter is mutated.
*/
function parseExpression(expression, umlClass) {
if (!expression || !expression.type) {
return umlClass;
return;
}
if (expression.type === 'BinaryOperation') {
umlClass = parseExpression(expression.left, umlClass);
umlClass = parseExpression(expression.right, umlClass);
parseExpression(expression.left, umlClass);
parseExpression(expression.right, umlClass);
}
else if (expression.type === 'FunctionCall') {
umlClass = parseExpression(expression.expression, umlClass);
parseExpression(expression.expression, umlClass);
expression.arguments.forEach((arg) => {
umlClass = parseExpression(arg, umlClass);
parseExpression(arg, umlClass);
});
}
else if (expression.type === 'IndexAccess') {
umlClass = parseExpression(expression.base, umlClass);
umlClass = parseExpression(expression.index, umlClass);
parseExpression(expression.base, umlClass);
parseExpression(expression.index, umlClass);
}
else if (expression.type === 'TupleExpression') {
expression.components.forEach((component) => {
umlClass = parseExpression(component, umlClass);
parseExpression(component, umlClass);
});
}
else if (expression.type === 'MemberAccess') {
umlClass = parseExpression(expression.expression, umlClass);
parseExpression(expression.expression, umlClass);
}
else if (expression.type === 'Conditional') {
umlClass = addAssociations([expression.trueExpression], umlClass);
umlClass = addAssociations([expression.falseExpression], umlClass);
addAssociations([expression.trueExpression], umlClass);
addAssociations([expression.falseExpression], umlClass);
}

@@ -532,10 +567,15 @@ else if (expression.type === 'Identifier') {

else if (expression.type === 'NewExpression') {
umlClass = addAssociations([expression.typeName], umlClass);
addAssociations([expression.typeName], umlClass);
}
else if (expression.type === 'UnaryOperation' &&
expression.subExpression) {
umlClass = parseExpression(expression.subExpression, umlClass);
parseExpression(expression.subExpression, umlClass);
}
return umlClass;
}
/**
* Convert user defined type to class name and struct or enum.
* eg `Set.Data` has class `Set` and struct or enum of `Data`
* @param rawClassName can be `TypeName` properties `namePath`, `length.name` or `baseTypeName.namePath` as defined in the ASTNode
* @return object with `umlClassName` and `structOrEnum` of type string
*/
function parseClassName(rawClassName) {

@@ -557,2 +597,7 @@ if (!rawClassName ||

}
/**
* Converts the contract visibility to attribute or operator visibility of type `Visibility`
* @param params defined in ASTNode as VariableDeclaration.visibility, FunctionDefinition.visibility or FunctionTypeName.visibility
* @return visibility `Visibility` enum used by the `visibility` property on UML `Attribute` or `Operator`
*/
function parseVisibility(visibility) {

@@ -574,2 +619,8 @@ switch (visibility) {

}
/**
* Recursively converts contract variables to UMLClass attribute types.
* Types can be standard Solidity types, arrays, mappings or user defined types like structs and enums.
* @param typeName defined in ASTNode as `TypeName`
* @return attributeTypes array of type string and `AttributeType`
*/
function parseTypeName(typeName) {

@@ -610,2 +661,7 @@ switch (typeName.type) {

}
/**
* Converts the contract params to `Operator` properties `parameters` or `returnParameters`
* @param params defined in ASTNode as `VariableDeclaration`
* @return parameters or `returnParameters` of the `Operator` of type `Parameter`
*/
function parseParameters(params) {

@@ -625,2 +681,7 @@ if (!params || !params) {

}
/**
* Converts the contract `kind` to `UMLClass` stereotype
* @param kind defined in ASTNode as ContractDefinition.kind
* @return stereotype of the `UMLClass` with type `ClassStereotype
*/
function parseContractKind(kind) {

@@ -640,5 +701,2 @@ switch (kind) {

}
function parsePayable(stateMutability) {
return stateMutability === 'payable';
}
//# sourceMappingURL=converterAST2Classes.js.map
import { ClassOptions } from './converterClass2Dot';
import { UmlClass } from './umlClass';
/**
* Converts UML classes to Graphviz's DOT format.
* The DOT grammar defines Graphviz nodes, edges, graphs, subgraphs, and clusters http://www.graphviz.org/doc/info/lang.html
* @param umlClasses array of UML classes of type `UMLClass`
* @param clusterFolders flag if UML classes are to be clustered into folders their source code was in
* @param classOptions command line options for the `class` command
* @return dotString Graphviz's DOT format for defining nodes, edges and clusters.
*/
export declare function convertUmlClasses2Dot(umlClasses: UmlClass[], clusterFolders?: boolean, classOptions?: ClassOptions): string;
export declare function addAssociationsToDot(umlClasses: UmlClass[], classOptions?: ClassOptions): string;

@@ -9,2 +9,10 @@ "use strict";

const debug = require('debug')('sol2uml');
/**
* Converts UML classes to Graphviz's DOT format.
* The DOT grammar defines Graphviz nodes, edges, graphs, subgraphs, and clusters http://www.graphviz.org/doc/info/lang.html
* @param umlClasses array of UML classes of type `UMLClass`
* @param clusterFolders flag if UML classes are to be clustered into folders their source code was in
* @param classOptions command line options for the `class` command
* @return dotString Graphviz's DOT format for defining nodes, edges and clusters.
*/
function convertUmlClasses2Dot(umlClasses, clusterFolders = false, classOptions = {}) {

@@ -11,0 +19,0 @@ let dotString = `

import { WeightedDiGraph } from 'js-graph-algorithms';
import { UmlClass } from './umlClass';
import { ClassOptions } from './converterClass2Dot';
/**
* Filter out any UML Class types that are to be hidden.
* @param umlClasses array of UML classes of type `UMLClass`
* @param options sol2uml class options
* @return umlClasses filtered list of UML classes of type `UMLClass`
*/
export declare const filterHiddenClasses: (umlClasses: UmlClass[], options: ClassOptions) => UmlClass[];
/**
* Finds all the UML classes that have an association with a list of base contract names.
* The associated classes can be contracts, abstract contracts, interfaces, libraries, enums, structs or constants.
* @param umlClasses array of UML classes of type `UMLClass`
* @param baseContractNames array of base contract names
* @param depth limit the number of associations from the base contract.
* @return filteredUmlClasses list of UML classes of type `UMLClass`
*/
export declare const classesConnectedToBaseContracts: (umlClasses: UmlClass[], baseContractNames: string[], depth?: number) => UmlClass[];
/**
* Finds all the UML classes that have an association with a base contract name.
* The associated classes can be contracts, abstract contracts, interfaces, libraries, enums, structs or constants.
* @param umlClasses array of UML classes of type `UMLClass`
* @param baseContractName base contract name
* @param weightedDirectedGraph graph of type WeightedDiGraph from the `js-graph-algorithms` package
* @param depth limit the number of associations from the base contract.
* @return filteredUmlClasses list of UML classes of type `UMLClass`
*/
export declare const classesConnectedToBaseContract: (umlClasses: UmlClass[], baseContractName: string, weightedDirectedGraph: WeightedDiGraph, depth?: number) => {

@@ -7,0 +30,0 @@ [contractName: string]: UmlClass;

@@ -7,2 +7,8 @@ "use strict";

const associations_1 = require("./associations");
/**
* Filter out any UML Class types that are to be hidden.
* @param umlClasses array of UML classes of type `UMLClass`
* @param options sol2uml class options
* @return umlClasses filtered list of UML classes of type `UMLClass`
*/
const filterHiddenClasses = (umlClasses, options) => {

@@ -23,2 +29,10 @@ return umlClasses.filter((u) => (u.stereotype === umlClass_1.ClassStereotype.Enum && !options.hideEnums) ||

exports.filterHiddenClasses = filterHiddenClasses;
/**
* Finds all the UML classes that have an association with a list of base contract names.
* The associated classes can be contracts, abstract contracts, interfaces, libraries, enums, structs or constants.
* @param umlClasses array of UML classes of type `UMLClass`
* @param baseContractNames array of base contract names
* @param depth limit the number of associations from the base contract.
* @return filteredUmlClasses list of UML classes of type `UMLClass`
*/
const classesConnectedToBaseContracts = (umlClasses, baseContractNames, depth) => {

@@ -36,2 +50,11 @@ let filteredUmlClasses = {};

exports.classesConnectedToBaseContracts = classesConnectedToBaseContracts;
/**
* Finds all the UML classes that have an association with a base contract name.
* The associated classes can be contracts, abstract contracts, interfaces, libraries, enums, structs or constants.
* @param umlClasses array of UML classes of type `UMLClass`
* @param baseContractName base contract name
* @param weightedDirectedGraph graph of type WeightedDiGraph from the `js-graph-algorithms` package
* @param depth limit the number of associations from the base contract.
* @return filteredUmlClasses list of UML classes of type `UMLClass`
*/
const classesConnectedToBaseContract = (umlClasses, baseContractName, weightedDirectedGraph, depth = 1000) => {

@@ -38,0 +61,0 @@ // Find the base UML Class from the base contract name

import { ASTNode } from '@solidity-parser/parser/dist/src/ast-types';
import { UmlClass } from './umlClass';
export declare const networks: readonly ["mainnet", "ropsten", "kovan", "rinkeby", "goerli", "sepolia", "polygon", "testnet.polygon", "arbitrum", "testnet.arbitrum", "avalanche", "testnet.avalanche", "bsc", "testnet.bsc", "crono", "fantom", "testnet.fantom", "moonbeam", "optimistic", "kovan-optimistic", "gnosisscan"];
declare type Network = typeof networks[number];
export type Network = typeof networks[number];
export declare class EtherscanParser {

@@ -48,2 +48,1 @@ protected apikey: string;

}
export {};

@@ -0,5 +1,17 @@

import { Network } from './parserEtherscan';
import { UmlClass } from './umlClass';
export declare const parserUmlClasses: (fileFolderAddress: string, options: any) => Promise<{
export interface ParserOptions {
apiKey?: string;
network?: Network;
subfolders?: string;
ignoreFilesOrFolders?: string;
}
/**
* Parses Solidity source code from a local filesystem or verified code on Etherscan
* @param fileFolderAddress filename, folder name or contract address
* @param options of type `ParserOptions`
*/
export declare const parserUmlClasses: (fileFolderAddress: string, options: ParserOptions) => Promise<{
umlClasses: UmlClass[];
contractName?: string;
}>;

@@ -8,2 +8,7 @@ "use strict";

const debug = require('debug')('sol2uml');
/**
* Parses Solidity source code from a local filesystem or verified code on Etherscan
* @param fileFolderAddress filename, folder name or contract address
* @param options of type `ParserOptions`
*/
const parserUmlClasses = async (fileFolderAddress, options) => {

@@ -10,0 +15,0 @@ let result = {

@@ -81,2 +81,3 @@ #! /usr/bin/env node

};
// Parse Solidity code from local file system or verified source code on Etherscan.
let { umlClasses, contractName } = await (0, parserGeneral_1.parserUmlClasses)(fileFolderAddress, combinedOptions);

@@ -100,3 +101,5 @@ if (options.squash &&

}
// Convert UML classes to Graphviz dot format.
const dotString = (0, converterClasses2Dot_1.convertUmlClasses2Dot)(filteredUmlClasses, combinedOptions.clusterFolders, combinedOptions);
// Convert Graphviz dot format to file formats. eg svg or png
await (0, writerFiles_1.writeOutputFiles)(dotString, fileFolderAddress, contractName || 'classDiagram', combinedOptions.outputFormat, combinedOptions.outputFileName);

@@ -103,0 +106,0 @@ debug(`Finished generating UML`);

import { UmlClass } from './umlClass';
export declare const squashUmlClasses: (umlClasses: UmlClass[], squashContractNames: string[]) => UmlClass[];
/**
* Flattens the inheritance hierarchy for each base contract.
* @param umlClasses array of UML classes of type `UMLClass`
* @param baseContractNames array of contract names to be rendered in squashed format.
* @return squashUmlClasses array of UML classes of type `UMLClass`
*/
export declare const squashUmlClasses: (umlClasses: UmlClass[], baseContractNames: string[]) => UmlClass[];

@@ -30,11 +30,17 @@ "use strict";

const debug = require('debug')('sol2uml');
const squashUmlClasses = (umlClasses, squashContractNames) => {
/**
* Flattens the inheritance hierarchy for each base contract.
* @param umlClasses array of UML classes of type `UMLClass`
* @param baseContractNames array of contract names to be rendered in squashed format.
* @return squashUmlClasses array of UML classes of type `UMLClass`
*/
const squashUmlClasses = (umlClasses, baseContractNames) => {
let removedClassIds = [];
for (const squashContractName of squashContractNames) {
for (const baseContractName of baseContractNames) {
// Find the base UML Class to squash
let baseIndex = umlClasses.findIndex(({ name }) => {
return name === squashContractName;
return name === baseContractName;
});
if (baseIndex === undefined) {
throw Error(`Failed to find contract with name "${squashContractName}" to squash`);
throw Error(`Failed to find contract with name "${baseContractName}" to squash`);
}

@@ -59,3 +65,3 @@ const baseClass = umlClasses[baseIndex];

// Include all base contracts
squashContractNames.includes(u.name));
baseContractNames.includes(u.name));
};

@@ -62,0 +68,0 @@ exports.squashUmlClasses = squashUmlClasses;

@@ -57,3 +57,3 @@ export declare enum Visibility {

returnParameters?: Parameter[];
isPayable?: boolean;
stateMutability?: string;
modifiers?: string[];

@@ -60,0 +60,0 @@ hash?: string;

@@ -1,2 +0,2 @@

export declare type OutputFormats = 'svg' | 'png' | 'dot' | 'all';
export type OutputFormats = 'svg' | 'png' | 'dot' | 'all';
export declare const writeOutputFiles: (dot: string, fileFolderAddress: string, contractName: string, outputFormat?: OutputFormats, outputFilename?: string) => Promise<void>;

@@ -3,0 +3,0 @@ export declare function convertDot2Svg(dot: string): any;

{
"name": "sol2uml",
"version": "2.3.0",
"version": "2.3.1",
"description": "Solidity contract visualisation tool.",

@@ -23,9 +23,9 @@ "main": "./lib/index.js",

"@aduh95/viz.js": "^3.7.0",
"@solidity-parser/parser": "^0.14.3",
"axios": "^0.27.2",
"axios-debug-log": "^0.8.4",
"@solidity-parser/parser": "^0.14.5",
"axios": "^1.2.1",
"axios-debug-log": "^1.0.0",
"commander": "^9.4.1",
"convert-svg-to-png": "^0.6.4",
"debug": "^4.3.4",
"ethers": "^5.7.1",
"ethers": "^5.7.2",
"js-graph-algorithms": "^1.0.18",

@@ -35,10 +35,10 @@ "klaw": "^4.0.1"

"devDependencies": {
"@openzeppelin/contracts": "4.7.3",
"@types/jest": "^29.1.1",
"@openzeppelin/contracts": "4.8.0",
"@types/jest": "^29.2.4",
"@types/klaw": "^3.0.3",
"jest": "^29.1.2",
"prettier": "^2.7.1",
"jest": "^29.3.1",
"prettier": "^2.8.1",
"ts-jest": "^29.0.3",
"ts-node": "^10.9.1",
"typescript": "^4.8.4"
"typescript": "^4.9.4"
},

@@ -45,0 +45,0 @@ "files": [

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