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

@cobalt-ui/plugin-css

Package Overview
Dependencies
Maintainers
1
Versions
50
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@cobalt-ui/plugin-css - npm Package Compare versions

Comparing version 0.7.0 to 0.8.0

12

CHANGELOG.md
# @cobalt-ui/plugin-css
## 0.8.0
### Minor Changes
- 9e80004: Breaking change: improve and normalize transform API
### Patch Changes
- 06927a6: Fix preset config option, fix docs
- Updated dependencies [9e80004]
- @cobalt-ui/utils@0.5.0
## 0.7.0

@@ -4,0 +16,0 @@

46

dist/index.d.ts

@@ -1,22 +0,6 @@

import type { ParsedColorToken, ParsedCubicBezierToken, ParsedBorderToken, ParsedDimensionToken, ParsedDurationToken, ParsedFontToken, ParsedGradientToken, ParsedLinkToken, ParsedShadowToken, ParsedStrokeStyleToken, ParsedTransitionToken, ParsedTypographyToken, Plugin } from '@cobalt-ui/core';
declare type TokenTransformer = {
color: (value: ParsedColorToken['$value'], token: ParsedColorToken) => string;
dimension: (value: ParsedDimensionToken['$value'], token: ParsedDimensionToken) => string;
duration: (value: ParsedDurationToken['$value'], token: ParsedDurationToken) => string;
font: (value: ParsedFontToken['$value'], token: ParsedFontToken) => string;
cubicBezier: (value: ParsedCubicBezierToken['$value'], token: ParsedCubicBezierToken) => string;
link: (value: ParsedLinkToken['$value'], token: ParsedLinkToken) => string;
strokeStyle: (value: ParsedStrokeStyleToken['$value'], token: ParsedStrokeStyleToken) => string;
border: (value: ParsedBorderToken['$value'], token: ParsedBorderToken) => string;
transition: (value: ParsedTransitionToken['$value'], token: ParsedTransitionToken) => string;
shadow: (value: ParsedShadowToken['$value'], token: ParsedShadowToken) => string;
gradient: (value: ParsedGradientToken['$value'], token: ParsedGradientToken) => string;
typography: (value: ParsedTypographyToken['$value'], token: ParsedTypographyToken) => Record<string, string | number | string[]>;
} & {
[key: string]: (value: any, token: any) => string;
};
import type { ParsedColorToken, ParsedCubicBezierToken, ParsedDimensionToken, ParsedDurationToken, ParsedFontToken, ParsedGradientToken, ParsedLinkToken, ParsedShadowToken, ParsedToken, ParsedTransitionToken, ParsedTypographyToken, Plugin } from '@cobalt-ui/core';
export interface Options {
/** set the filename inside outDir */
/** output file (default: "./tokens/tokens.css") */
filename?: string;
/** embed files? */
/** embed files in CSS? */
embedFiles?: boolean;

@@ -26,3 +10,3 @@ /** generate wrapper selectors around token modes */

/** handle different token types */
transform?: Partial<TokenTransformer>;
transform?: (token: ParsedToken, mode?: string) => string;
/** prefix variable names */

@@ -32,2 +16,22 @@ prefix?: string;

export default function css(options?: Options): Plugin;
export {};
/** transform color */
export declare function transformColor(value: ParsedColorToken['$value']): string;
/** transform dimension */
export declare function transformDimension(value: ParsedDimensionToken['$value']): string;
/** transform duration */
export declare function transformDuration(value: ParsedDurationToken['$value']): string;
/** transform font */
export declare function transformFont(value: ParsedFontToken['$value']): string;
/** transform cubic beziér */
export declare function transformCubicBezier(value: ParsedCubicBezierToken['$value']): string;
/** transform file */
export declare function transformLink(value: ParsedLinkToken['$value']): string;
/** transform shadow */
export declare function transformShadow(value: ParsedShadowToken['$value']): string;
/** transform gradient */
export declare function transformGradient(value: ParsedGradientToken['$value']): string;
/** transform transition */
export declare function transformTransition(value: ParsedTransitionToken['$value']): string;
/** transform typography */
export declare function transformTypography(value: ParsedTypographyToken['$value']): Record<string, string | number | string[]>;
export declare function defaultTransformer(token: ParsedToken, mode?: string): string | ReturnType<typeof transformTypography>;
import color from 'better-color-tools';
import { Indenter, kebabinate, FG_YELLOW, RESET } from '@cobalt-ui/utils';
import { encode, formatFontNames } from './util.js';
const DASH_PREFIX_RE = /^(-*)?/;
const DASH_PREFIX_RE = /^-+/;
const DASH_SUFFIX_RE = /-+$/;
const DOT_UNDER_GLOB_RE = /[._]/g;

@@ -11,26 +12,3 @@ const SELECTOR_BRACKET_RE = /\s*{/;

let filename = options?.filename || './tokens.css';
let prefix = options?.prefix || '';
let transform = {
...(options?.transform || {}),
};
if (!transform.color)
transform.color = transformColor;
if (!transform.dimension)
transform.dimension = transformDimension;
if (!transform.duration)
transform.duration = transformDuration;
if (!transform.font)
transform.font = transformFont;
if (!transform.cubicBezier)
transform.cubicBezier = transformCubicBezier;
if (!transform.link)
transform.link = transformLink;
if (!transform.shadow)
transform.shadow = transformShadow;
if (!transform.gradient)
transform.gradient = transformGradient;
if (!transform.transition)
transform.transition = transformTransition;
if (!transform.typography)
transform.typography = transformTypography;
let prefix = options?.prefix ? `${options.prefix.replace(DASH_PREFIX_RE, '').replace(DASH_SUFFIX_RE, '')}-` : '';
const i = new Indenter();

@@ -42,3 +20,3 @@ function makeVars(tokens, indentLv = 0, generateRoot = false) {

for (const [id, value] of Object.entries(tokens)) {
output.push(i.indent(`${id.replace(DASH_PREFIX_RE, `--${prefix}`).replace(DOT_UNDER_GLOB_RE, '-')}: ${value};`, indentLv + (generateRoot ? 1 : 0)));
output.push(i.indent(`--${prefix}${id.replace(DOT_UNDER_GLOB_RE, '-')}: ${value};`, indentLv + (generateRoot ? 1 : 0)));
}

@@ -78,6 +56,3 @@ if (generateRoot)

for (const token of tokens) {
const transformer = transform[token.$type];
if (!transformer)
throw new Error(`No transformer found for token type "${token.$type}"`);
let value = transformer(token.$value, token);
let value = (typeof options?.transform === 'function' && options.transform(token)) || defaultTransformer(token);
switch (token.$type) {

@@ -113,3 +88,3 @@ case 'link': {

modeVals[selector] = {};
let modeVal = transformer(token.$extensions.mode[modeName], token);
let modeVal = (typeof options?.transform === 'function' && options.transform(token, modeName)) || defaultTransformer(token, modeName);
switch (token.$type) {

@@ -186,35 +161,35 @@ case 'link': {

/** transform color */
function transformColor(value) {
export function transformColor(value) {
return String(value);
}
/** transform dimension */
function transformDimension(value) {
export function transformDimension(value) {
return String(value);
}
/** transform duration */
function transformDuration(value) {
export function transformDuration(value) {
return String(value);
}
/** transform font */
function transformFont(value) {
export function transformFont(value) {
return formatFontNames(value);
}
/** transform cubic beziér */
function transformCubicBezier(value) {
export function transformCubicBezier(value) {
return `cubic-bezier(${value.join(', ')})`;
}
/** transform file */
function transformLink(value) {
export function transformLink(value) {
return `url('${value}')`;
}
/** transform shadow */
function transformShadow(value) {
export function transformShadow(value) {
return [value.offsetX, value.offsetY, value.blur, value.spread, value.color].join(' ');
}
/** transform gradient */
function transformGradient(value) {
export function transformGradient(value) {
return value.map((g) => `${g.color} ${g.position * 100}%`).join(', ');
}
/** transform transition */
function transformTransition(value) {
export function transformTransition(value) {
const timingFunction = value.timingFunction ? `cubic-bezier(${value.timingFunction.join(',')})` : undefined;

@@ -224,3 +199,3 @@ return [value.duration, value.delay, timingFunction].filter((v) => v !== undefined).join(' ');

/** transform typography */
function transformTypography(value) {
export function transformTypography(value) {
const values = {};

@@ -232,2 +207,30 @@ for (const [k, v] of Object.entries(value)) {

}
export function defaultTransformer(token, mode) {
if (mode && (!token.$extensions?.mode || !token.$extensions.mode[mode]))
throw new Error(`Token ${token.id} missing "$extensions.mode.${mode}"`);
switch (token.$type) {
case 'color':
return transformColor(mode ? token.$extensions.mode[mode] : token.$value);
case 'dimension':
return transformDimension(mode ? token.$extensions.mode[mode] : token.$value);
case 'duration':
return transformDuration(mode ? token.$extensions.mode[mode] : token.$value);
case 'font':
return transformFont(mode ? token.$extensions.mode[mode] : token.$value);
case 'cubicBezier':
return transformCubicBezier(mode ? token.$extensions.mode[mode] : token.$value);
case 'link':
return transformLink(mode ? token.$extensions.mode[mode] : token.$value);
case 'shadow':
return transformShadow(mode ? token.$extensions.mode[mode] : token.$value);
case 'gradient':
return transformGradient(mode ? token.$extensions.mode[mode] : token.$value);
case 'transition':
return transformTransition(mode ? token.$extensions.mode[mode] : token.$value);
case 'typography':
return transformTypography(mode ? token.$extensions.mode[mode] : token.$value);
default:
throw new Error(`No transformer defined for $type: ${token.$type} tokens`);
}
}
/** parse modeSelector */

@@ -234,0 +237,0 @@ function parseModeSelector(modeID) {

{
"name": "@cobalt-ui/plugin-css",
"description": "Generate CSS from your design tokens schema (requires @cobalt-ui/cli)",
"version": "0.7.0",
"version": "0.8.0",
"author": {

@@ -20,3 +20,3 @@ "name": "Drew Powers",

"dependencies": {
"@cobalt-ui/utils": "^0.4.0",
"@cobalt-ui/utils": "^0.5.0",
"better-color-tools": "^0.9.1",

@@ -27,8 +27,7 @@ "mime": "^3.0.0",

"devDependencies": {
"@cobalt-ui/cli": "^0.5.1",
"@cobalt-ui/core": "^0.6.1",
"@cobalt-ui/cli": "^0.5.2",
"@cobalt-ui/core": "^0.6.2",
"@types/mime": "^2.0.3",
"@types/svgo": "^2.6.3",
"chai": "^4.3.6",
"mocha": "^10.0.0"
"@types/svgo": "^2.6.4",
"vitest": "^0.24.0"
},

@@ -38,4 +37,4 @@ "scripts": {

"dev": "tsc -w",
"test": "mocha --parallel"
"test": "vitest run"
}
}

@@ -35,6 +35,4 @@ # @cobalt-ui/plugin-css

},
/** don’t like the name of CSS variables? change ’em! */
transformVariableNames(name, group) {
return `--my-prefix-${name}`;
},
/** (optional) prefix variable names */
prefix: '--my-prefix',
}),

@@ -195,3 +193,3 @@ ],

Inside plugin options, you can specify transforms [per-type](https://cobalt-ui.pages.dev/reference/schema):
Inside plugin options, you can specify an optional `transform()` function:

@@ -203,33 +201,7 @@ ```js

pluginCSS({
transform: {
color: (value, token) => {
return value;
},
dimension: (value, token) => {
return value;
},
font: (value, token) => {
return value;
},
duration: (value, token) => {
return value;
},
cubicBezier: (value, token) => {
return value;
},
link: (value, token) => {
return value;
},
transition: (value, token) => {
return value;
},
shadow: (value, token) => {
return value;
},
gradient: (value, token) => {
return value;
},
typography: (value, token) => {
return value;
},
transform(token, mode) {
// Replace "sans-serif" with "Brand Sans" for font tokens
if (token.$type === 'font') {
return token.$value.replace('sans-serif', 'Brand Sans');
}
},

@@ -241,7 +213,7 @@ }),

⚠️ Whenever you override a transformer for a token type, it’s now up to you to handle everything! You may also need the help of utilities like [better-color-tools](https://github.com/drwpow/better-color-tools)
Your transform will only take place if you return a string; otherwise the default transformer will take place.
#### Custom tokens
If you have your own custom token type, e.g. `my-custom-type`, you can add more keys to `transform` to handle it, like so:
If you have your own custom token type, e.g. `my-custom-type`, you’ll have to handle it within `transform()`:

@@ -253,6 +225,9 @@ ```js

pluginCSS({
transform: {
'my-custom-type': (value, token) => {
return String(value);
},
transform(token, mode) {
switch (token.$type) {
case 'my-custom-type': {
return String(token.$value);
break;
}
}
},

@@ -259,0 +234,0 @@ }),

@@ -6,3 +6,2 @@ import type {

ParsedCubicBezierToken,
ParsedBorderToken,
ParsedDimensionToken,

@@ -14,3 +13,3 @@ ParsedDurationToken,

ParsedShadowToken,
ParsedStrokeStyleToken,
ParsedToken,
ParsedTransitionToken,

@@ -22,6 +21,7 @@ ParsedTypographyToken,

import color from 'better-color-tools';
import { Indenter, kebabinate, FG_YELLOW, RESET } from '@cobalt-ui/utils';
import { encode, formatFontNames } from './util.js';
import {Indenter, kebabinate, FG_YELLOW, RESET} from '@cobalt-ui/utils';
import {encode, formatFontNames} from './util.js';
const DASH_PREFIX_RE = /^(-*)?/;
const DASH_PREFIX_RE = /^-+/;
const DASH_SUFFIX_RE = /-+$/;
const DOT_UNDER_GLOB_RE = /[._]/g;

@@ -31,21 +31,6 @@ const SELECTOR_BRACKET_RE = /\s*{/;

type TokenTransformer = {
color: (value: ParsedColorToken['$value'], token: ParsedColorToken) => string;
dimension: (value: ParsedDimensionToken['$value'], token: ParsedDimensionToken) => string;
duration: (value: ParsedDurationToken['$value'], token: ParsedDurationToken) => string;
font: (value: ParsedFontToken['$value'], token: ParsedFontToken) => string;
cubicBezier: (value: ParsedCubicBezierToken['$value'], token: ParsedCubicBezierToken) => string;
link: (value: ParsedLinkToken['$value'], token: ParsedLinkToken) => string;
strokeStyle: (value: ParsedStrokeStyleToken['$value'], token: ParsedStrokeStyleToken) => string;
border: (value: ParsedBorderToken['$value'], token: ParsedBorderToken) => string;
transition: (value: ParsedTransitionToken['$value'], token: ParsedTransitionToken) => string;
shadow: (value: ParsedShadowToken['$value'], token: ParsedShadowToken) => string;
gradient: (value: ParsedGradientToken['$value'], token: ParsedGradientToken) => string;
typography: (value: ParsedTypographyToken['$value'], token: ParsedTypographyToken) => Record<string, string | number | string[]>;
} & { [key: string]: (value: any, token: any) => string };
export interface Options {
/** set the filename inside outDir */
/** output file (default: "./tokens/tokens.css") */
filename?: string;
/** embed files? */
/** embed files in CSS? */
embedFiles?: boolean;

@@ -55,3 +40,3 @@ /** generate wrapper selectors around token modes */

/** handle different token types */
transform?: Partial<TokenTransformer>;
transform?: (token: ParsedToken, mode?: string) => string;
/** prefix variable names */

@@ -64,16 +49,3 @@ prefix?: string;

let filename = options?.filename || './tokens.css';
let prefix = options?.prefix || '';
let transform = {
...(options?.transform || {}),
} as TokenTransformer;
if (!transform.color) transform.color = transformColor;
if (!transform.dimension) transform.dimension = transformDimension;
if (!transform.duration) transform.duration = transformDuration;
if (!transform.font) transform.font = transformFont;
if (!transform.cubicBezier) transform.cubicBezier = transformCubicBezier;
if (!transform.link) transform.link = transformLink;
if (!transform.shadow) transform.shadow = transformShadow;
if (!transform.gradient) transform.gradient = transformGradient;
if (!transform.transition) transform.transition = transformTransition;
if (!transform.typography) transform.typography = transformTypography;
let prefix = options?.prefix ? `${options.prefix.replace(DASH_PREFIX_RE, '').replace(DASH_SUFFIX_RE, '')}-` : '';

@@ -86,3 +58,3 @@ const i = new Indenter();

for (const [id, value] of Object.entries(tokens)) {
output.push(i.indent(`${id.replace(DASH_PREFIX_RE, `--${prefix}`).replace(DOT_UNDER_GLOB_RE, '-')}: ${value};`, indentLv + (generateRoot ? 1 : 0)));
output.push(i.indent(`--${prefix}${id.replace(DOT_UNDER_GLOB_RE, '-')}: ${value};`, indentLv + (generateRoot ? 1 : 0)));
}

@@ -116,5 +88,5 @@ if (generateRoot) output.push(i.indent('}', indentLv));

},
async build({ tokens, metadata }): Promise<BuildResult[]> {
const tokenVals: { [id: string]: any } = {};
const modeVals: { [selector: string]: { [id: string]: any } } = {};
async build({tokens, metadata}): Promise<BuildResult[]> {
const tokenVals: {[id: string]: any} = {};
const modeVals: {[selector: string]: {[id: string]: any}} = {};
const selectors: string[] = [];

@@ -124,6 +96,3 @@

for (const token of tokens) {
const transformer = transform[token.$type];
if (!transformer) throw new Error(`No transformer found for token type "${token.$type}"`);
let value = transformer(token.$value as any, token as any);
let value = (typeof options?.transform === 'function' && options.transform(token)) || defaultTransformer(token);
switch (token.$type) {

@@ -151,3 +120,2 @@ case 'link': {

if ((groupRoot && !token.id.startsWith(groupRoot)) || !token.$extensions.mode[modeName]) continue;
if (!Array.isArray(selectors)) modeSelectors = [selectors];

@@ -157,3 +125,3 @@ for (const selector of modeSelectors) {

if (!modeVals[selector]) modeVals[selector] = {};
let modeVal = transformer(token.$extensions.mode[modeName] as any, token as any);
let modeVal = (typeof options?.transform === 'function' && options.transform(token, modeName)) || defaultTransformer(token, modeName);
switch (token.$type) {

@@ -233,35 +201,35 @@ case 'link': {

/** transform color */
function transformColor(value: ParsedColorToken['$value']): string {
export function transformColor(value: ParsedColorToken['$value']): string {
return String(value);
}
/** transform dimension */
function transformDimension(value: ParsedDimensionToken['$value']): string {
export function transformDimension(value: ParsedDimensionToken['$value']): string {
return String(value);
}
/** transform duration */
function transformDuration(value: ParsedDurationToken['$value']): string {
export function transformDuration(value: ParsedDurationToken['$value']): string {
return String(value);
}
/** transform font */
function transformFont(value: ParsedFontToken['$value']): string {
export function transformFont(value: ParsedFontToken['$value']): string {
return formatFontNames(value);
}
/** transform cubic beziér */
function transformCubicBezier(value: ParsedCubicBezierToken['$value']): string {
export function transformCubicBezier(value: ParsedCubicBezierToken['$value']): string {
return `cubic-bezier(${value.join(', ')})`;
}
/** transform file */
function transformLink(value: ParsedLinkToken['$value']): string {
export function transformLink(value: ParsedLinkToken['$value']): string {
return `url('${value}')`;
}
/** transform shadow */
function transformShadow(value: ParsedShadowToken['$value']): string {
export function transformShadow(value: ParsedShadowToken['$value']): string {
return [value.offsetX, value.offsetY, value.blur, value.spread, value.color].join(' ');
}
/** transform gradient */
function transformGradient(value: ParsedGradientToken['$value']): string {
export function transformGradient(value: ParsedGradientToken['$value']): string {
return value.map((g: GradientStop) => `${g.color} ${g.position * 100}%`).join(', ');
}
/** transform transition */
function transformTransition(value: ParsedTransitionToken['$value']): string {
export function transformTransition(value: ParsedTransitionToken['$value']): string {
const timingFunction = value.timingFunction ? `cubic-bezier(${value.timingFunction.join(',')})` : undefined;

@@ -271,6 +239,6 @@ return [value.duration, value.delay, timingFunction].filter((v) => v !== undefined).join(' ');

/** transform typography */
function transformTypography(value: ParsedTypographyToken['$value']): Record<string, string | number | string[]> {
export function transformTypography(value: ParsedTypographyToken['$value']): Record<string, string | number | string[]> {
const values: Record<string, string | number | string[]> = {};
for (const [k, v] of Object.entries(value)) {
values[kebabinate(k)] = Array.isArray(v) ? formatFontNames(v) : v;
values[kebabinate(k)] = Array.isArray(v) ? formatFontNames(v) : (v as ParsedTypographyToken['$value']);
}

@@ -280,2 +248,30 @@ return values;

export function defaultTransformer(token: ParsedToken, mode?: string): string | ReturnType<typeof transformTypography> {
if (mode && (!token.$extensions?.mode || !token.$extensions.mode[mode])) throw new Error(`Token ${token.id} missing "$extensions.mode.${mode}"`);
switch (token.$type) {
case 'color':
return transformColor(mode ? ((token.$extensions as any).mode[mode] as typeof token.$value) : token.$value);
case 'dimension':
return transformDimension(mode ? ((token.$extensions as any).mode[mode] as typeof token.$value) : token.$value);
case 'duration':
return transformDuration(mode ? ((token.$extensions as any).mode[mode] as typeof token.$value) : token.$value);
case 'font':
return transformFont(mode ? ((token.$extensions as any).mode[mode] as typeof token.$value) : token.$value);
case 'cubicBezier':
return transformCubicBezier(mode ? ((token.$extensions as any).mode[mode] as typeof token.$value) : token.$value);
case 'link':
return transformLink(mode ? ((token.$extensions as any).mode[mode] as typeof token.$value) : token.$value);
case 'shadow':
return transformShadow(mode ? ((token.$extensions as any).mode[mode] as typeof token.$value) : token.$value);
case 'gradient':
return transformGradient(mode ? ((token.$extensions as any).mode[mode] as typeof token.$value) : token.$value);
case 'transition':
return transformTransition(mode ? ((token.$extensions as any).mode[mode] as typeof token.$value) : token.$value);
case 'typography':
return transformTypography(mode ? ((token.$extensions as any).mode[mode] as typeof token.$value) : token.$value);
default:
throw new Error(`No transformer defined for $type: ${token.$type} tokens`);
}
}
/** parse modeSelector */

@@ -282,0 +278,0 @@ function parseModeSelector(modeID: string): [string, string] {

import fs from 'fs';
import mime from 'mime';
import { fileURLToPath } from 'url';
import {fileURLToPath} from 'url';
import svgo from 'svgo';

@@ -5,0 +5,0 @@

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

import { build } from '@cobalt-ui/cli/dist/build.js';
import { expect } from 'chai';
import {build} from '@cobalt-ui/cli/dist/build.js';
import fs from 'fs';
import {expect, test} from 'vitest';
import pluginCSS from '../dist/index.js';
import fs from 'fs';
const FIXTURES_DIR = new URL('./fixtures/', import.meta.url);
for (const test of fs.readdirSync(FIXTURES_DIR)) {
it(test, async () => {
const base = new URL(`./${test}/`, FIXTURES_DIR);
for (const name of fs.readdirSync(FIXTURES_DIR)) {
test(name, async () => {
const base = new URL(`./${name}/`, FIXTURES_DIR);
const outDir = new URL('./dist/', base);

@@ -12,0 +12,0 @@ const given = JSON.parse(fs.readFileSync(new URL('./given.json', base)));

@@ -36,6 +36,6 @@ {

"$value": [
{ "color": "#218bff", "position": 0 },
{ "color": "#e85aad", "position": 1 }
{"color": "#218bff", "position": 0},
{"color": "#e85aad", "position": 1}
]
}
}
{
"typography": {
"family": {
"body": { "$type": "font", "$value": ["IBM Plex Sans", "-system-ui", "sans-serif"] },
"heading": { "$type": "font", "$value": "Helvetica" }
"body": {"$type": "font", "$value": ["IBM Plex Sans", "-system-ui", "sans-serif"]},
"heading": {"$type": "font", "$value": "Helvetica"}
},

@@ -7,0 +7,0 @@ "page-title": {

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