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

postcss-loader

Package Overview
Dependencies
Maintainers
4
Versions
86
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

postcss-loader - npm Package Compare versions

Comparing version 7.0.1 to 7.3.3

CHANGELOG.md

73

dist/index.js

@@ -7,18 +7,9 @@ "use strict";

exports.default = loader;
var _path = _interopRequireDefault(require("path"));
var _semver = require("semver");
var _package = _interopRequireDefault(require("postcss/package.json"));
var _Warning = _interopRequireDefault(require("./Warning"));
var _options = _interopRequireDefault(require("./options.json"));
var _utils = require("./utils");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
let hasExplicitDependencyOnPostCSS = false;
let hasExplicitDependencyOnPostCSS = false;
/**

@@ -37,3 +28,2 @@ * **PostCSS Loader**

*/
async function loader(content, sourceMap, meta) {

@@ -43,11 +33,14 @@ const options = this.getOptions(_options.default);

const configOption = typeof options.postcssOptions === "undefined" || typeof options.postcssOptions.config === "undefined" ? true : options.postcssOptions.config;
const postcssFactory = (0, _utils.getPostcssImplementation)(this, options.implementation);
if (!postcssFactory) {
let implementation;
try {
implementation = (0, _utils.getPostcssImplementation)(this, options.implementation);
} catch (error) {
callback(error);
return;
}
if (!implementation) {
callback(new Error(`The Postcss implementation "${options.implementation}" not found`));
return;
}
let loadedConfig;
if (configOption) {

@@ -61,4 +54,2 @@ try {

}
const useSourceMap = typeof options.sourceMap !== "undefined" ? options.sourceMap : this.sourceMap;
const {

@@ -68,3 +59,3 @@ plugins,

} = await (0, _utils.getPostcssOptions)(this, loadedConfig, options.postcssOptions);
const useSourceMap = typeof options.sourceMap !== "undefined" ? options.sourceMap : this.sourceMap;
if (useSourceMap) {

@@ -77,10 +68,11 @@ processOptions.map = {

}
if (sourceMap && processOptions.map) {
processOptions.map.prev = (0, _utils.normalizeSourceMap)(sourceMap, this.context);
}
let root;
let root; // Reuse PostCSS AST from other loaders
if (meta && meta.ast && meta.ast.type === "postcss" && (0, _semver.satisfies)(meta.ast.version, `^${_package.default.version}`)) {
// Reuse PostCSS AST from other loaders
if (meta && meta.ast && meta.ast.type === "postcss" &&
// eslint-disable-next-line global-require
require("semver").satisfies(meta.ast.version, `^${_package.default.version}`)) {
({

@@ -90,3 +82,2 @@ root

}
if (!root && options.execute) {

@@ -96,8 +87,6 @@ // eslint-disable-next-line no-param-reassign

}
let result;
let processor;
try {
processor = postcssFactory(plugins);
processor = implementation(plugins);
result = await processor.process(root || content, processOptions);

@@ -113,19 +102,16 @@ } catch (error) {

const packageJSONDir = (0, _utils.findPackageJSONDir)(process.cwd(), this.fs.statSync);
if (packageJSONDir) {
let bufferOfPackageJSON;
try {
bufferOfPackageJSON = this.fs.readFileSync(_path.default.resolve(packageJSONDir, "package.json"), "utf8");
} catch (_error) {// Nothing
} catch (_error) {
// Nothing
}
if (bufferOfPackageJSON) {
let pkg;
try {
pkg = JSON.parse(bufferOfPackageJSON);
} catch (_error) {// Nothing
} catch (_error) {
// Nothing
}
if (pkg) {

@@ -141,11 +127,8 @@ if (!pkg.dependencies.postcss && !pkg.devDependencies.postcss) {

}
(0, _utils.reportError)(this, callback, error);
return;
}
for (const warning of result.warnings()) {
this.emitWarning(new _Warning.default(warning));
this.emitWarning((0, _utils.warningFactory)(warning));
}
for (const message of result.messages) {

@@ -157,19 +140,14 @@ // eslint-disable-next-line default-case

break;
case "build-dependency":
this.addBuildDependency(message.file);
break;
case "missing-dependency":
this.addMissingDependency(message.file);
break;
case "context-dependency":
this.addContextDependency(message.file);
break;
case "dir-dependency":
this.addContextDependency(message.dir);
break;
case "asset":

@@ -179,15 +157,11 @@ if (message.content && message.file) {

}
}
} // eslint-disable-next-line no-undefined
}
// eslint-disable-next-line no-undefined
let map = result.map ? result.map.toJSON() : undefined;
if (map && useSourceMap) {
map = (0, _utils.normalizeSourceMapAfterPostcss)(map, this.context);
}
let ast;
try {

@@ -203,3 +177,2 @@ ast = {

}
callback(null, result.css, map, {

@@ -206,0 +179,0 @@ ast

@@ -14,17 +14,9 @@ "use strict";

exports.reportError = reportError;
exports.warningFactory = warningFactory;
var _path = _interopRequireDefault(require("path"));
var _url = _interopRequireDefault(require("url"));
var _module = _interopRequireDefault(require("module"));
var _full = require("klona/full");
var _cosmiconfig = require("cosmiconfig");
var _Error = _interopRequireDefault(require("./Error"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
const parentModule = module;
const stat = (inputFileSystem, filePath) => new Promise((resolve, reject) => {

@@ -35,7 +27,5 @@ inputFileSystem.stat(filePath, (err, stats) => {

}
resolve(stats);
});
});
function exec(code, loaderContext) {

@@ -46,16 +36,16 @@ const {

} = loaderContext;
const module = new _module.default(resource, parentModule); // eslint-disable-next-line no-underscore-dangle
const module = new _module.default(resource, parentModule);
// eslint-disable-next-line no-underscore-dangle
module.paths = _module.default._nodeModulePaths(context);
module.filename = resource; // eslint-disable-next-line no-underscore-dangle
module.filename = resource;
// eslint-disable-next-line no-underscore-dangle
module._compile(code, resource);
return module.exports;
}
let tsLoader;
async function loadConfig(loaderContext, config, postcssOptions) {
const searchPath = typeof config === "string" ? _path.default.resolve(config) : _path.default.dirname(loaderContext.resourcePath);
let stats;
try {

@@ -66,6 +56,63 @@ stats = await stat(loaderContext.fs, searchPath);

}
const explorer = (0, _cosmiconfig.cosmiconfig)("postcss");
const moduleName = "postcss";
const searchPlaces = [
// Prefer popular format
"package.json", `${moduleName}.config.js`, `${moduleName}.config.mjs`, `${moduleName}.config.cjs`, `${moduleName}.config.ts`, `${moduleName}.config.mts`, `${moduleName}.config.cts`, `.${moduleName}rc`, `.${moduleName}rc.json`, `.${moduleName}rc.js`, `.${moduleName}rc.mjs`, `.${moduleName}rc.cjs`, `.${moduleName}rc.ts`, `.${moduleName}rc.mts`, `.${moduleName}rc.cts`, `.${moduleName}rc.yaml`, `.${moduleName}rc.yml`, `.config/${moduleName}rc`, `.config/${moduleName}rc.json`, `.config/${moduleName}rc.yaml`, `.config/${moduleName}rc.yml`, `.config/${moduleName}rc.js`, `.config/${moduleName}rc.mjs`, `.config/${moduleName}rc.cjs`, `.config/${moduleName}rc.ts`, `.config/${moduleName}rc.mts`, `.config/${moduleName}rc.cts`];
const loaders = {
".js": async (...args) => {
let result;
try {
result = _cosmiconfig.defaultLoadersSync[".js"](...args);
} catch (error) {
let importESM;
try {
// eslint-disable-next-line no-new-func
importESM = new Function("id", "return import(id);");
} catch (e) {
importESM = null;
}
if (error.code === "ERR_REQUIRE_ESM" && _url.default.pathToFileURL && importESM) {
const urlForConfig = _url.default.pathToFileURL(args[0]);
result = await importESM(urlForConfig);
} else {
throw error;
}
}
return result;
},
".cjs": _cosmiconfig.defaultLoadersSync[".cjs"],
".mjs": async (...args) => {
let result;
let importESM;
try {
// eslint-disable-next-line no-new-func
importESM = new Function("id", "return import(id);");
} catch (e) {
importESM = null;
}
if (_url.default.pathToFileURL && importESM) {
const urlForConfig = _url.default.pathToFileURL(args[0]);
result = await importESM(urlForConfig);
} else {
throw new Error("ESM is not supported");
}
return result;
}
};
if (!tsLoader) {
const opts = {
interopDefault: true
};
// eslint-disable-next-line global-require, import/no-extraneous-dependencies
const jiti = require("jiti")(__filename, opts);
tsLoader = filepath => jiti(filepath);
}
loaders[".cts"] = tsLoader;
loaders[".mts"] = tsLoader;
loaders[".ts"] = tsLoader;
const explorer = (0, _cosmiconfig.cosmiconfig)(moduleName, {
searchPlaces,
loaders
});
let result;
try {

@@ -80,14 +127,10 @@ if (stats.isFile()) {

}
if (!result) {
return {};
}
loaderContext.addBuildDependency(result.filepath);
loaderContext.addDependency(result.filepath);
if (result.isEmpty) {
return result;
}
if (typeof result.config === "function") {

@@ -103,9 +146,9 @@ const api = {

};
result.config = result.config(api);
return {
...result,
config: result.config(api)
};
}
result = (0, _full.klona)(result);
return result;
}
function loadPlugin(plugin, options, file) {

@@ -115,11 +158,8 @@ try {

let loadedPlugin = require(plugin);
if (loadedPlugin.default) {
loadedPlugin = loadedPlugin.default;
}
if (!options || Object.keys(options).length === 0) {
return loadedPlugin;
}
return loadedPlugin(options);

@@ -130,3 +170,2 @@ } catch (error) {

}
function pluginFactory() {

@@ -138,3 +177,2 @@ const listOfPlugins = new Map();

}
if (Array.isArray(plugins)) {

@@ -150,3 +188,2 @@ for (const plugin of plugins) {

const options = plugin[name];
if (options === false) {

@@ -163,3 +200,2 @@ listOfPlugins.delete(name);

const objectPlugins = Object.entries(plugins);
for (const [name, options] of objectPlugins) {

@@ -173,10 +209,7 @@ if (options === false) {

}
return listOfPlugins;
};
}
async function tryRequireThenImport(module) {
let exports;
try {

@@ -188,3 +221,2 @@ // eslint-disable-next-line import/no-dynamic-require, global-require

let importESM;
try {

@@ -196,3 +228,2 @@ // eslint-disable-next-line no-new-func

}
if (requireError.code === "ERR_REQUIRE_ESM" && importESM) {

@@ -202,32 +233,23 @@ exports = await importESM(module);

}
throw requireError;
}
}
async function getPostcssOptions(loaderContext, loadedConfig = {}, postcssOptions = {}) {
const file = loaderContext.resourcePath;
let normalizedPostcssOptions = postcssOptions;
if (typeof normalizedPostcssOptions === "function") {
normalizedPostcssOptions = normalizedPostcssOptions(loaderContext);
}
let plugins = [];
try {
const factory = pluginFactory();
if (loadedConfig.config && loadedConfig.config.plugins) {
factory(loadedConfig.config.plugins);
}
factory(normalizedPostcssOptions.plugins);
plugins = [...factory()].map(item => {
const [plugin, options] = item;
if (typeof plugin === "string") {
return loadPlugin(plugin, options, file);
}
return plugin;

@@ -238,28 +260,31 @@ });

}
const processOptionsFromConfig = loadedConfig.config || {};
const processOptionsFromConfig = {
...loadedConfig.config
} || {};
if (processOptionsFromConfig.from) {
processOptionsFromConfig.from = _path.default.resolve(_path.default.dirname(loadedConfig.filepath), processOptionsFromConfig.from);
}
if (processOptionsFromConfig.to) {
processOptionsFromConfig.to = _path.default.resolve(_path.default.dirname(loadedConfig.filepath), processOptionsFromConfig.to);
} // No need them for processOptions
delete processOptionsFromConfig.plugins;
const processOptionsFromOptions = (0, _full.klona)(normalizedPostcssOptions);
}
const processOptionsFromOptions = {
...normalizedPostcssOptions
};
if (processOptionsFromOptions.from) {
processOptionsFromOptions.from = _path.default.resolve(loaderContext.rootContext, processOptionsFromOptions.from);
}
if (processOptionsFromOptions.to) {
processOptionsFromOptions.to = _path.default.resolve(loaderContext.rootContext, processOptionsFromOptions.to);
} // No need them for processOptions
}
delete processOptionsFromOptions.config;
delete processOptionsFromOptions.plugins;
// No need `plugins` and `config` for processOptions
const {
plugins: __plugins,
...optionsFromConfig
} = processOptionsFromConfig;
const {
config: _config,
plugins: _plugins,
...optionsFromOptions
} = processOptionsFromOptions;
const processOptions = {

@@ -269,6 +294,5 @@ from: file,

map: false,
...processOptionsFromConfig,
...processOptionsFromOptions
...optionsFromConfig,
...optionsFromOptions
};
if (typeof processOptions.parser === "string") {

@@ -281,3 +305,2 @@ try {

}
if (typeof processOptions.stringifier === "string") {

@@ -290,3 +313,2 @@ try {

}
if (typeof processOptions.syntax === "string") {

@@ -299,3 +321,2 @@ try {

}
if (processOptions.map === true) {

@@ -307,3 +328,2 @@ // https://github.com/postcss/postcss/blob/master/docs/source-maps.md

}
return {

@@ -314,6 +334,4 @@ plugins,

}
const IS_NATIVE_WIN32_PATH = /^[a-z]:[/\\]|^\\\\/i;
const ABSOLUTE_SCHEME = /^[a-z0-9+\-.]+:/i;
function getURLType(source) {

@@ -324,21 +342,17 @@ if (source[0] === "/") {

}
return "path-absolute";
}
if (IS_NATIVE_WIN32_PATH.test(source)) {
return "path-absolute";
}
return ABSOLUTE_SCHEME.test(source) ? "absolute" : "path-relative";
}
function normalizeSourceMap(map, resourceContext) {
let newMap = map;
function normalizeSourceMap(map, resourceContext) {
let newMap = map; // Some loader emit source map as string
// Some loader emit source map as string
// Strip any JSON XSSI avoidance prefix from the string (as documented in the source maps specification), and then parse the string as JSON.
if (typeof newMap === "string") {
newMap = JSON.parse(newMap);
}
delete newMap.file;

@@ -349,7 +363,7 @@ const {

delete newMap.sourceRoot;
if (newMap.sources) {
newMap.sources = newMap.sources.map(source => {
const sourceType = getURLType(source); // Do no touch `scheme-relative` and `absolute` URLs
const sourceType = getURLType(source);
// Do no touch `scheme-relative` and `absolute` URLs
if (sourceType === "path-relative" || sourceType === "path-absolute") {

@@ -359,19 +373,19 @@ const absoluteSource = sourceType === "path-relative" && sourceRoot ? _path.default.resolve(sourceRoot, _path.default.normalize(source)) : _path.default.normalize(source);

}
return source;
});
}
return newMap;
}
function normalizeSourceMapAfterPostcss(map, resourceContext) {
const newMap = map;
function normalizeSourceMapAfterPostcss(map, resourceContext) {
const newMap = map; // result.map.file is an optional property that provides the output filename.
// result.map.file is an optional property that provides the output filename.
// Since we don't know the final filename in the webpack build chain yet, it makes no sense to have it.
// eslint-disable-next-line no-param-reassign
delete newMap.file;
delete newMap.file; // eslint-disable-next-line no-param-reassign
// eslint-disable-next-line no-param-reassign
newMap.sourceRoot = "";
newMap.sourceRoot = ""; // eslint-disable-next-line no-param-reassign
// eslint-disable-next-line no-param-reassign
newMap.sources = newMap.sources.map(source => {

@@ -381,9 +395,8 @@ if (source.indexOf("<") === 0) {

}
const sourceType = getURLType(source);
const sourceType = getURLType(source); // Do no touch `scheme-relative`, `path-absolute` and `absolute` types
// Do no touch `scheme-relative`, `path-absolute` and `absolute` types
if (sourceType === "path-relative") {
return _path.default.resolve(resourceContext, source);
}
return source;

@@ -393,6 +406,4 @@ });

}
function findPackageJSONDir(cwd, statSync) {
let dir = cwd;
for (;;) {

@@ -403,7 +414,6 @@ try {

}
} catch (error) {// Nothing
} catch (error) {
// Nothing
}
const parent = _path.default.dirname(dir);
if (dir === parent) {

@@ -413,29 +423,18 @@ dir = null;

}
dir = parent;
}
return dir;
}
function getPostcssImplementation(loaderContext, implementation) {
let resolvedImplementation = implementation;
if (!implementation || typeof implementation === "string") {
const postcssImplPkg = implementation || "postcss";
try {
// eslint-disable-next-line import/no-dynamic-require, global-require
resolvedImplementation = require(postcssImplPkg);
} catch (error) {
loaderContext.emitError(error); // eslint-disable-next-line consistent-return
// eslint-disable-next-line import/no-dynamic-require, global-require
resolvedImplementation = require(postcssImplPkg);
}
return;
}
} // eslint-disable-next-line consistent-return
// eslint-disable-next-line consistent-return
return resolvedImplementation;
}
function reportError(loaderContext, callback, error) {

@@ -445,8 +444,45 @@ if (error.file) {

}
if (error.name === "CssSyntaxError") {
callback(new _Error.default(error));
callback(syntaxErrorFactory(error));
} else {
callback(error);
}
}
function warningFactory(warning) {
let message = "";
if (typeof warning.line !== "undefined") {
message += `(${warning.line}:${warning.column}) `;
}
if (typeof warning.plugin !== "undefined") {
message += `from "${warning.plugin}" plugin: `;
}
message += warning.text;
if (warning.node) {
message += `\n\nCode:\n ${warning.node.toString()}\n`;
}
const obj = new Error(message, {
cause: warning
});
obj.stack = null;
return obj;
}
function syntaxErrorFactory(error) {
let message = "\nSyntaxError\n\n";
if (typeof error.line !== "undefined") {
message += `(${error.line}:${error.column}) `;
}
if (typeof error.plugin !== "undefined") {
message += `from "${error.plugin}" plugin: `;
}
message += error.file ? `${error.file} ` : "<css input> ";
message += `${error.reason}`;
const code = error.showSourceCode();
if (code) {
message += `\n\n${code}\n`;
}
const obj = new Error(message, {
cause: error
});
obj.stack = null;
return obj;
}
{
"name": "postcss-loader",
"version": "7.0.1",
"version": "7.3.3",
"description": "PostCSS loader for webpack",

@@ -27,3 +27,7 @@ "license": "MIT",

"lint:js": "eslint --cache .",
"lint:spelling": "cspell \"**/*.*\"",
"lint": "npm-run-all -l -p \"lint:**\"",
"fix:js": "npm run lint:js -- --fix",
"fix:prettier": "npm run lint:prettier -- --write",
"fix": "npm-run-all -l fix:js fix:prettier",
"test:only": "cross-env NODE_ENV=test jest",

@@ -45,42 +49,46 @@ "test:watch": "npm run test:only -- --watch",

"dependencies": {
"cosmiconfig": "^7.0.0",
"klona": "^2.0.5",
"semver": "^7.3.7"
"cosmiconfig": "^8.2.0",
"jiti": "^1.18.2",
"semver": "^7.3.8"
},
"devDependencies": {
"@babel/cli": "^7.18.6",
"@babel/core": "^7.18.6",
"@babel/preset-env": "^7.18.6",
"@commitlint/cli": "^17.0.3",
"@commitlint/config-conventional": "^17.0.3",
"@babel/cli": "^7.21.0",
"@babel/core": "^7.21.4",
"@babel/preset-env": "^7.21.4",
"@commitlint/cli": "^17.5.1",
"@commitlint/config-conventional": "^17.4.4",
"@webpack-contrib/eslint-config-webpack": "^3.0.0",
"babel-jest": "^28.1.2",
"babel-jest": "^29.4.3",
"cross-env": "^7.0.3",
"cssnano": "^5.1.12",
"cspell": "^6.31.1",
"cssnano": "^6.0.0",
"del": "^6.1.1",
"del-cli": "^4.0.1",
"eslint": "^8.18.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-import": "^2.26.0",
"husky": "^8.0.1",
"jest": "^28.1.2",
"less": "^4.1.2",
"less-loader": "^11.0.0",
"lint-staged": "^12.5.0",
"memfs": "^3.4.7",
"del-cli": "^5.0.0",
"eslint": "^8.37.0",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-import": "^2.27.5",
"husky": "^8.0.3",
"jest": "^29.4.3",
"less": "^4.1.3",
"less-loader": "^11.1.0",
"lint-staged": "^13.2.0",
"memfs": "^3.4.13",
"midas": "^2.0.3",
"npm-run-all": "^4.1.5",
"postcss": "^8.4.13",
"postcss": "^8.4.21",
"postcss-dark-theme-class": "^0.7.3",
"postcss-import": "^14.1.0",
"postcss-js": "^4.0.0",
"postcss-nested": "^5.0.6",
"postcss-import": "^15.1.0",
"postcss-js": "^4.0.1",
"postcss-load-config": "^4.0.1",
"postcss-nested": "^6.0.1",
"postcss-short": "^5.0.0",
"prettier": "^2.7.1",
"sass": "^1.53.0",
"sass-loader": "^13.0.2",
"prettier": "^2.8.7",
"sass": "^1.60.0",
"sass-loader": "^13.2.2",
"standard-version": "^9.3.2",
"strip-ansi": "^6.0.0",
"sugarss": "^4.0.1",
"webpack": "^5.73.0"
"ts-jest": "^29.1.0",
"ts-node": "^10.9.1",
"webpack": "^5.77.0"
},

@@ -87,0 +95,0 @@ "keywords": [

@@ -33,3 +33,3 @@ <div align="center">

Webpack chat: [![chat][chat]][chat-url]
Webpack discussion: [![discussion][discussion]][discussion-url]

@@ -194,22 +194,23 @@ PostCSS chat: [![chat-postcss][chat-postcss]][chat-postcss-url]

See the file [`./src/config.d.ts`](./src/config.d.ts).
Type:
```ts
type postcssOptions =
| {
from: string;
map: boolean | SourceMapOptions;
parser: string | object | (() => Parser);
stringifier: Stringifier | Syntax;
syntax: Syntax;
to: string;
}
| ((loaderContext: LoaderContext) => {
from: string;
map: boolean | SourceMapOptions;
parser: string | object | (() => Parser);
stringifier: Stringifier | Syntax;
syntax: Syntax;
to: string;
});
import type { Config as PostCSSConfig } from "postcss-load-config";
import type { LoaderContext } from "webpack";
type PostCSSLoaderContext = LoaderContext<PostCSSConfig>;
interface PostCSSLoaderAPI {
mode: PostCSSLoaderContext["mode"];
file: PostCSSLoaderContext["resourcePath"];
webpackLoaderContext: PostCSSLoaderContext;
env: PostCSSLoaderContext["mode"];
options: PostCSSConfig;
}
export type PostCSSLoaderOptions =
| PostCSSConfig
| ((api: PostCSSLoaderAPI) => PostCSSConfig);
```

@@ -227,2 +228,6 @@

For large projects, to optimize performance of the loader, it is better to provide `postcssOptions` in loader
config and specify `config: false`. This approach removes the need to lookup and load external config files multiple
times during compilation.
#### `object`

@@ -411,3 +416,3 @@

Default: `undefined`
Default: `true`

@@ -1169,4 +1174,4 @@ Allows to set options using config files.

[cover-url]: https://codecov.io/gh/webpack-contrib/postcss-loader
[chat]: https://badges.gitter.im/webpack/webpack.svg
[chat-url]: https://gitter.im/webpack/webpack
[discussion]: https://img.shields.io/github/discussions/webpack/webpack
[discussion-url]: https://github.com/webpack/webpack/discussions
[chat-postcss]: https://badges.gitter.im/postcss/postcss.svg

@@ -1173,0 +1178,0 @@ [chat-postcss-url]: https://gitter.im/postcss/postcss

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