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

@balena/contrato

Package Overview
Dependencies
Maintainers
0
Versions
117
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@balena/contrato - npm Package Compare versions

Comparing version 0.10.0 to 0.11.0-build-capability-support-08ad790aacb364468275bfac19c13c1b2c812f1c-1

build/types.d.ts

9

build/blueprint.d.ts
import Contract from './contract';
import type { BlueprintLayout } from './types/types';
import type { BlueprintLayout } from './types';
export default class Blueprint extends Contract {
constructor(layout: BlueprintLayout, skeleton?: any);
sequence(contract: Contract, options?: {
allowRequirements: boolean;
}): Contract[];
reproduce(contract: Contract, asIterable?: false): Contract[];
reproduce(contract: Contract, asIterable: true): IterableIterator<Contract>;
reproduce(contract: Contract, asIterable?: boolean): IterableIterator<Contract> | Contract[];
reproduce(contract: Contract): IterableIterator<Contract>;
}

@@ -6,18 +6,8 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
const clone_1 = __importDefault(require("lodash/clone"));
const concat_1 = __importDefault(require("lodash/concat"));
const fill_1 = __importDefault(require("lodash/fill"));
const filter_1 = __importDefault(require("lodash/filter"));
const flatMap_1 = __importDefault(require("lodash/flatMap"));
const flatten_1 = __importDefault(require("lodash/flatten"));
const forEach_1 = __importDefault(require("lodash/forEach"));
const includes_1 = __importDefault(require("lodash/includes"));
const isEmpty_1 = __importDefault(require("lodash/isEmpty"));
const isEqual_1 = __importDefault(require("lodash/isEqual"));
const reduce_1 = __importDefault(require("lodash/reduce"));
const uniqWith_1 = __importDefault(require("lodash/uniqWith"));
const semver_1 = require("semver");
const contract_1 = __importDefault(require("./contract"));
const cardinality_1 = require("./cardinality");
const types_1 = require("./types/types");
const types_1 = require("./types");
const utils_1 = require("./utils");

@@ -56,5 +46,3 @@ class Blueprint extends contract_1.default {

}
sequence(contract, options = {
allowRequirements: true,
}) {
reproduce(contract) {
const layout = this.metadata.layout;

@@ -64,149 +52,2 @@ const combinations = (0, reduce_1.default)(layout.finite.selectors, (accumulator, value) => {

(0, forEach_1.default)(value, (option) => {
const combi = (0, uniqWith_1.default)(contract.getChildrenCombinations(option), (left, right) => {
return (0, isEqual_1.default)(left[0].raw, right[0].raw);
});
internalAccumulator = internalAccumulator.concat([combi]);
});
return internalAccumulator;
}, []);
(0, forEach_1.default)(combinations, (dimension) => {
dimension.sort((left, right) => {
return (0, semver_1.compare)(left[0].raw.version, right[0].raw.version);
});
});
const currentPointer = new Array(combinations.length);
(0, fill_1.default)(currentPointer, 0);
const bestPointer = new Array(combinations.length);
for (let idx = 0; idx < combinations.length; idx++) {
bestPointer[idx] = combinations[idx].length - 1;
}
const buildContextFromPointer = (pointer) => {
const context = new contract_1.default(this.raw.skeleton, {
hash: false,
});
const combination = [];
for (let idx = 0; idx < combinations.length; idx++) {
combination.push(combinations[idx][pointer[idx]]);
}
context.addChildren((0, flatten_1.default)(combination), {
rehash: true,
});
const references = context.getChildrenCrossReferencedContracts({
from: contract,
types: layout.infinite.types,
});
const contracts = references.length === 0
? contract.getChildren({
types: layout.infinite.types,
})
: references;
context.addChildren(contracts, {
rehash: false,
});
for (const reference of contracts) {
if (!context.satisfiesChildContract(reference, {
types: layout.types,
})) {
context.removeChild(reference, {
rehash: false,
});
}
}
context.interpolate();
const requirements = context.getAllNotSatisfiedChildRequirements();
const newRequirements = (0, uniqWith_1.default)((0, filter_1.default)((0, concat_1.default)(context.raw.requires, requirements)), isEqual_1.default);
if (newRequirements && !(0, isEmpty_1.default)(newRequirements)) {
if (!options.allowRequirements) {
return null;
}
context.raw.requires = newRequirements;
context.interpolate();
}
const childCapabilities = (0, filter_1.default)((0, uniqWith_1.default)((0, flatMap_1.default)(context.getChildren(), (v) => v.raw.capabilities), isEqual_1.default));
if (childCapabilities && !(0, isEmpty_1.default)(childCapabilities)) {
context.raw.capabilities = childCapabilities;
context.interpolate();
}
return context;
};
const checkSolutions = (pointer) => {
const context = buildContextFromPointer(pointer);
return !context
? false
: context.areChildrenSatisfied({
types: layout.types,
});
};
const checked = [];
const pointerValue = (pointer) => (0, reduce_1.default)(pointer, (sum, value) => sum + value, 0);
let currentBestPointer = new Array(combinations.length);
(0, fill_1.default)(currentBestPointer, 0);
const currentBestPointerValue = pointerValue(currentBestPointer);
let currentBestPath = [];
const isValidPointer = (pointer) => {
if ((0, includes_1.default)(checked, pointer)) {
return false;
}
for (let idx = 0; idx < combinations.length; idx++) {
if (pointer[idx] > bestPointer[idx]) {
return false;
}
}
if (!checkSolutions(pointer)) {
return false;
}
return true;
};
const search = (combos, pointer, path) => {
checked.push(pointer);
for (let idx = 0; idx < combos.length; idx++) {
const possiblePointer = (0, clone_1.default)(pointer);
possiblePointer[idx] += 1;
if (isValidPointer(possiblePointer)) {
const currentPath = (0, clone_1.default)(path);
currentPath.push(possiblePointer);
if ((0, isEqual_1.default)(possiblePointer, bestPointer)) {
currentBestPath = currentPath;
return true;
}
const solutionValue = pointerValue(possiblePointer);
if (solutionValue > currentBestPointerValue) {
currentBestPointer = possiblePointer;
currentBestPath = currentPath;
}
if (search(combos, possiblePointer, currentPath)) {
return true;
}
return false;
}
}
return false;
};
if (isValidPointer(currentPointer)) {
currentBestPointer = currentPointer;
currentBestPath = [currentPointer];
search(combinations, currentPointer, [currentPointer]);
}
return (0, reduce_1.default)(currentBestPath, (seq, pointer) => {
const context = buildContextFromPointer(pointer);
if (context) {
if (!context.areChildrenSatisfied({
types: layout.infinite.types,
})) {
return seq;
}
context.interpolate();
seq.push(context);
}
return seq;
}, []);
}
reproduce(contract, asIterable = false) {
if (!asIterable) {
return [...this.reproduce(contract, true)];
}
const layout = this.metadata.layout;
const combinations = (0, reduce_1.default)(layout.finite.selectors, (accumulator, value) => {
let internalAccumulator = accumulator;
(0, forEach_1.default)(value, (option) => {
internalAccumulator = internalAccumulator.concat([

@@ -213,0 +54,0 @@ contract.getChildrenCombinations(option),

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

import type { ContractObject } from './types/types';
import type { ContractObject } from './types';
export default class Contract {

@@ -42,2 +42,3 @@ metadata: any;

}): Contract[];
private isRequirementSatisfied;
getNotSatisfiedChildRequirements(contract: Contract, options?: {

@@ -44,0 +45,0 @@ types: Set<string>;

@@ -28,3 +28,3 @@ "use strict";

const hash_1 = require("./hash");
const types_1 = require("./types/types");
const types_1 = require("./types");
const template_1 = require("./template");

@@ -385,3 +385,3 @@ const variants_1 = require("./variants");

return (0, flatMap_1.default)(rang, (tcardinality) => {
return (0, js_combinatorics_1.bigCombination)(contracts, tcardinality).toArray();
return new js_combinatorics_1.Combination(contracts, tcardinality).toArray();
});

@@ -426,2 +426,32 @@ }

}
isRequirementSatisfied(requirement, options = {}) {
const shouldEvaluateType = (type) => options.types ? options.types.has(type) : true;
const hasMatch = (matcher) => {
return (this.findChildren(matcher).length > 0 ||
this.findChildrenWithCapabilities(matcher).length > 0);
};
if (requirement.raw.operation === 'or') {
const disjuncts = (0, filter_1.default)(requirement.raw.data.getAll(), (disjunct) => {
return shouldEvaluateType(disjunct.raw.data.type);
});
if (disjuncts.length === 0 || (0, some_1.default)((0, map_1.default)(disjuncts, hasMatch))) {
return true;
}
return false;
}
else if (requirement.raw.operation === 'not') {
const disjuncts = (0, filter_1.default)(requirement.raw.data.getAll(), (disjunct) => {
return shouldEvaluateType(disjunct.raw.data.type);
});
if (disjuncts.length > 0 && (0, some_1.default)((0, map_1.default)(disjuncts, hasMatch))) {
return false;
}
return true;
}
if (shouldEvaluateType(requirement.raw.data.type) &&
!hasMatch(requirement)) {
return false;
}
return true;
}
getNotSatisfiedChildRequirements(contract, options = { types: new Set() }) {

@@ -434,24 +464,7 @@ const conjuncts = (0, reduce_1.default)(contract.getChildren(), (accumulator, child) => {

}
const shouldEvaluateType = (type) => options.types ? options.types.has(type) : true;
const requirements = [];
const hasMatch = (matcher) => {
return (this.findChildren(matcher).length > 0 ||
this.findChildrenWithCapabilities(matcher).length > 0);
};
for (const conjunct of conjuncts) {
if (conjunct.raw.operation === 'or') {
const disjuncts = (0, filter_1.default)(conjunct.raw.data.getAll(), (disjunct) => {
return shouldEvaluateType(disjunct.raw.data.type);
});
if (disjuncts.length === 0 || (0, some_1.default)((0, map_1.default)(disjuncts, hasMatch))) {
continue;
}
if (!this.isRequirementSatisfied(conjunct, options)) {
requirements.push(conjunct.raw.data);
}
if (shouldEvaluateType(conjunct.raw.data.type) && !hasMatch(conjunct)) {
requirements.push(conjunct.raw.data);
}
else if (!shouldEvaluateType(conjunct.raw.data.type)) {
requirements.push(conjunct.raw.data);
}
}

@@ -467,26 +480,6 @@ return requirements;

}
const shouldEvaluateType = (type) => options.types ? options.types.has(type) : true;
const hasMatch = (matcher) => this.findChildren(matcher).length > 0;
for (const conjunct of conjuncts) {
if (conjunct.raw.operation === 'or') {
const disjuncts = (0, filter_1.default)(conjunct.raw.data.getAll(), (disjunct) => {
return shouldEvaluateType(disjunct.raw.data.type);
});
if (disjuncts.length === 0 || (0, some_1.default)((0, map_1.default)(disjuncts, hasMatch))) {
continue;
}
if (!this.isRequirementSatisfied(conjunct, options)) {
return false;
}
else if (conjunct.raw.operation === 'not') {
const disjuncts = (0, filter_1.default)(conjunct.raw.data.getAll(), (disjunct) => {
return shouldEvaluateType(disjunct.raw.data.type);
});
if (disjuncts.length > 0 && (0, some_1.default)((0, map_1.default)(disjuncts, hasMatch))) {
return false;
}
continue;
}
if (shouldEvaluateType(conjunct.raw.data.type) && !hasMatch(conjunct)) {
return false;
}
}

@@ -493,0 +486,0 @@ return true;

@@ -1,9 +0,6 @@

import { BlueprintLayout, BlueprintObject, ContractObject } from './types/types';
import { BlueprintLayout, BlueprintObject, ContractObject } from './types';
import Contract from './contract';
import Blueprint from './blueprint';
import { buildTemplate } from './partials';
import { parse as parseCardinality } from './cardinality';
export { BlueprintLayout, ContractObject, BlueprintObject, Contract, Blueprint, buildTemplate, parseCardinality, };
export declare function query(universe: Contract, layout: BlueprintLayout, skeleton: object, asIterable: true): IterableIterator<Contract>;
export declare function query(universe: Contract, layout: BlueprintLayout, skeleton: object, asIterable?: false): Contract[];
export declare const sequence: (universe: Contract, layout: BlueprintLayout, skeleton: object) => Contract[];
export { BlueprintLayout, ContractObject, BlueprintObject, Contract, Blueprint, buildTemplate, };
export declare function query(universe: Contract, layout: BlueprintLayout, skeleton: object): IterableIterator<Contract>;

@@ -6,3 +6,3 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
exports.sequence = exports.parseCardinality = exports.buildTemplate = exports.Blueprint = exports.Contract = void 0;
exports.buildTemplate = exports.Blueprint = exports.Contract = void 0;
exports.query = query;

@@ -15,9 +15,5 @@ const contract_1 = __importDefault(require("./contract"));

Object.defineProperty(exports, "buildTemplate", { enumerable: true, get: function () { return partials_1.buildTemplate; } });
const cardinality_1 = require("./cardinality");
Object.defineProperty(exports, "parseCardinality", { enumerable: true, get: function () { return cardinality_1.parse; } });
function query(universe, layout, skeleton, asIterable = false) {
return new blueprint_1.default(layout, skeleton).reproduce(universe, asIterable);
function query(universe, layout, skeleton) {
return new blueprint_1.default(layout, skeleton).reproduce(universe);
}
const sequence = (universe, layout, skeleton) => new blueprint_1.default(layout, skeleton).sequence(universe);
exports.sequence = sequence;
//# sourceMappingURL=index.js.map

@@ -10,3 +10,3 @@ import type Contract from './contract';

resetType(type: string): void;
merge(cache: MatcherCache): MatcherCache;
merge(cache: MatcherCache): this;
}
import Contract from './contract';
import type { ContractObject } from './types/types';
import type { ContractObject } from './types';
export declare const findPartial: (name: string, context: Contract, options: {

@@ -4,0 +4,0 @@ baseDirectory: string;

@@ -114,3 +114,3 @@ "use strict";

const safeContent = new hb.SafeString(partialContent.slice(0, partialContent.length - 1));
const builtContent = await hb.compile(safeContent.toString())(options.data.root);
const builtContent = await Promise.resolve(hb.compile(safeContent.toString())(options.data.root));
return new hb.SafeString(builtContent);

@@ -145,5 +145,5 @@ }

}, context.toJSON().children);
return (0, utils_1.stripExtraBlankLines)(await hb.compile(template)(data));
return (0, utils_1.stripExtraBlankLines)(await Promise.resolve(hb.compile(template)(data)));
};
exports.buildTemplate = buildTemplate;
//# sourceMappingURL=partials.js.map

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

import type { ContractObject } from './types/types';
import type { ContractObject } from './types';
export declare const compileContract: (contract: ContractObject, options?: {

@@ -3,0 +3,0 @@ blacklist?: Set<string>;

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

import type { ContractObject } from './types/types';
import type { ContractObject } from './types';
export declare const build: (contract: ContractObject) => ContractObject[];
{
"name": "@balena/contrato",
"version": "0.10.0",
"version": "0.11.0-build-capability-support-08ad790aacb364468275bfac19c13c1b2c812f1c-1",
"description": "The official contract implementation",

@@ -26,7 +26,6 @@ "homepage": "https://github.com/product-os/contrato",

"clean": "rimraf build",
"build": "npm run clean && npm run buildtypes && tsc",
"buildtypes": "ts-node --transpile-only ./scripts/build-types.ts && balena-lint --typescript --fix lib/types",
"build": "npm run clean && tsc",
"doc": "typedoc --options ./typedoc.json",
"lint": "balena-lint -t tsconfig.dev.json --typescript lib tests scripts",
"lint-fix": "balena-lint -t tsconfig.dev.json --typescript --fix lib tests scripts",
"lint": "balena-lint -t tsconfig.dev.json --typescript lib tests",
"lint-fix": "balena-lint -t tsconfig.dev.json --typescript --fix lib tests",
"test:node": "mocha -r ts-node/register/transpile-only --reporter spec \"tests/**/*.spec.ts\"",

@@ -42,11 +41,11 @@ "test": "npm run build && npm run lint && npm run test:node",

"handlebars": "^4.7.8",
"js-combinatorics": "^0.5.5",
"js-combinatorics": "^2.1.2",
"json-schema": "^0.4.0",
"lodash": "^4.17.19",
"object-hash": "^1.3.1",
"object-hash": "^3.0.0",
"promised-handlebars": "^2.0.1",
"semver": "^5.7.1"
"semver": "^7.6.3"
},
"devDependencies": {
"@balena/lint": "^8.2.8",
"@balena/lint": "^9.1.3",
"@types/chai": "^4.2.11",

@@ -64,7 +63,5 @@ "@types/chai-as-promised": "^7.1.2",

"chai-as-promised": "^7.1.1",
"cuelang-js": "^1.1.1",
"husky": "^4.2.5",
"lint-staged": "^10.1.7",
"mocha": "^10.4.0",
"openapi-typescript": "^3.2.4",
"rimraf": "^3.0.2",

@@ -80,4 +77,4 @@ "ts-node": "^8.10.1",

"versionist": {
"publishedAt": "2024-12-19T15:07:33.549Z"
"publishedAt": "2025-01-02T23:06:36.930Z"
}
}

@@ -1,11 +0,219 @@

Contrato
========
# Contrato
> The official contracts implementation
The official [contracts](#about-contracts) implementation
[![Documentation](https://github.com/product-os/contrato/actions/workflows/docs.yml/badge.svg)](https://product-os.github.io/contrato/modules/contrato.html)
## Quickstart
Tests
-----
```ts
import { Contract } from 'contrato';
const osContract = new Contract({
type: 'sw.os',
slug: 'balenaos',
version: '6.1.2',
children: [
{ type: 'sw.service', slug: 'balena-engine', version: '20.10.43' },
{ type: 'sw.service', slug: 'NetworkManager', version: '0.6.0' },
],
provides: [{ type: 'sw.feature', slug: 'secureboot' }],
});
const serviceContract = new Contract({
type: 'sw.application',
slug: 'myapp',
requires: [
{ type: 'sw.service', slug: 'balena-engine', version: '>20' },
{ type: 'sw.feature', slug: 'secureboot' },
],
});
if (osContract.satisfiesChildContract(serviceContract)) {
console.log('myapp can be installed!');
}
```
[![Documentation](https://github.com/balena-io/contrato/actions/workflows/docs.yml/badge.svg)](https://balena-io.github.io/contrato/modules/contrato.html)
## About contracts
### What is a contract?
Is a specification for describing _things_. A thing can be pretty much anything, a software library, a feature, an API, etc. Relationships between things can be established via composition and referencing (`requires` and `provides`). Through this library, contracts can be validated, composed and combined.
### Why build this?
balena.io is a complex product with a great number of inter-conecting components. Each of the components have their own requisites, capabilities, and incompatibilities. Contracts are an effort to formally document those interfaces, and a foundation on which we can build advanced tooling to ultimately automate the process of the team, increase productivity, and remove the human element from tasks that can be performed better by a machine.
The concept of contracts is generic enough that it can be applied to seemingly unrelated scenarios, from base images and OS images, to device types and backend components. Re-using the same contract "format" between them allows us to multiply the gains we get by developing complex contract-related programming modules.
### What can I do with contracts? Give me some examples
Describe a _thing_ via a contract
```json
{
"type": "sw.library",
"slug": "glibc",
"version": "2.40",
"assets": {
"license": {
"name": "GNU Lesser General Public License",
"url": "https://www.gnu.org/licenses/lgpl-3.0.html#license-text"
}
}
}
```
Describe a _thing_ that requires a _thing_
```json
{
"type": "sw.utility",
"slug": "curl",
"version": "8.11.1",
"requires": [{ "type": "sw.library", "slug": "glibc", "version": ">=2.17" }],
"data": {
"protocols": ["HTTP", "HTTPS", "FTP"]
}
}
```
Describe a complex _thing_ via a composite contract
```json
{
"type": "sw.os",
"slug": "balenaos",
"version": "4.1.5",
"children": [
{
"type": "sw.library",
"slug": "glibc",
"version": "2.16",
"assets": {
"license": {
"name": "GNU Lesser General Public License",
"url": "https://www.gnu.org/licenses/lgpl-3.0.html#license-text"
}
}
}
]
}
```
Validate requirements of a contract via [contrato](https://github.com/balena-io/contrato)
```ts
import { Contract } from 'contrato';
const osContract = new Contract({
type: 'sw.os',
slug: 'balenaos',
version: '4.1.5',
children: [
{
type: 'sw.library',
slug: 'glibc',
version: '2.16',
},
],
});
const curlContract = new Contract({
type: 'sw.utility',
slug: 'curl',
version: '8.11.1',
requires: [{ type: 'sw.library', slug: 'glibc', version: '>=2.17' }],
});
if (osContract.satisfiesChildContract(curlContract)) {
console.log('cURL requirements are met and it can be installed!');
} else {
// cannot install cURL, missing requirements: { type: 'sw.library', slug: 'glibc', version: '>=2.17' }
console.log(
'cannot install cURL, missing requirements: ',
osContract.getNotSatisfiedChildRequirements(curlContract),
);
}
```
Describe a universe of _things_
```ts
import { Contract, Universe } from 'contrato';
const universe = new Universe();
universe.addChildren([
new Contract({ type: 'sw.os', slug: 'debian' }),
new Contract({ type: 'sw.os', slug: 'fedora' }),
new Contract({
type: 'arch.sw',
slug: 'armv7hf',
requires: [{ type: 'hw.device-type', data: { arch: 'armv7hf' } }],
}),
new Contract({
type: 'arch.sw',
slug: 'amd64',
requires: [{ type: 'hw.device-type', data: { arch: 'amd64' } }],
}),
new Contract({
type: 'hw.device-type',
slug: 'raspberrypi3',
data: { arch: 'armv7hf' /* ... */ },
}),
new Contract({
type: 'hw.device-type',
slug: 'intel-nuc',
data: { arch: 'amd64' /* ... */ },
}),
]);
```
Generate combinations of _things_ with a Blueprint
```ts
import { Contract, Universe, Blueprint } from 'contrato';
const universe = new Universe();
universe.addChildren([
/* ... */
]);
const blueprint = new Blueprint(
{ 'hw.device-type': 1, 'arch.sw': 1, 'sw.os': 1 },
{ type: 'meta.context' },
);
// Generate contexts with valid combinations of the given types
const contexts = blueprint.reproduce(universe);
```
Build templates using the metadata from a combination
````ts
import { Contract, Universe, Blueprint, buildTemplate } from 'contrato';
/* ... */
// Generate contexts with valid combinations of the given types
const contexts = blueprint.reproduce(universe);
const template = ```
Welcome to {{this.sw.os.slug}}OS for {{this.hw.device-type.slug}}!
This build supports the architecture {{this.arch.sw.slug}}
```;
for (const context of contexts) {
// Welcome to OS fedoraOS for intel-nuc
// ...
console.log(buildTemplate(template, context));
}
````
### Additional information
See the [CUE](https://cuelang.org/) [contracts specification](balena-contracts.cue) for additional documentation on the contract format.
## Tests
Run the `test` npm script:

@@ -17,7 +225,6 @@

Contribute
----------
## Contribute
- Issue Tracker: [github.com/product-os/contrato/issues](https://github.com/product-os/contrato/issues)
- Source Code: [github.com/product-os/contrato](https://github.com/product-os/contrato)
- Issue Tracker: [github.com/product-os/contrato/issues](https://github.com/balena-io/contrato/issues)
- Source Code: [github.com/product-os/contrato](https://github.com/balena-io/contrato)

@@ -31,11 +238,9 @@ Before submitting a PR, please make sure that you include tests, and that the

Support
-------
## Support
If you're having any problem, please [raise an
issue](https://github.com/product-os/contrato/issues/new) on GitHub.
issue](https://github.com/balena-io/contrato/issues/new) on GitHub.
License
-------
## License
The project is licensed under the Apache 2.0 license.

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

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

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