Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

ajv-cmd

Package Overview
Dependencies
Maintainers
1
Versions
58
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

ajv-cmd - npm Package Compare versions

Comparing version
0.11.0
to
0.12.0
+20
commands/_utils.js
// Copyright 2026 will Farrell, and ajv-cmd contributors.
// SPDX-License-Identifier: MIT
import { readFile, stat } from "node:fs/promises";
export const assertFile = async (filepath) => {
const stats = await stat(filepath);
if (!stats.isFile()) {
throw new Error(`${filepath} is not a file`);
}
};
export const readJson = async (filepath) => {
const raw = await readFile(filepath, { encoding: "utf8" });
return JSON.parse(raw);
};
export const loadRefSchemas = async (paths) => {
if (!paths?.length) return undefined;
return Promise.all(paths.map(readJson));
};
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var deref_exports = {};
__export(deref_exports, {
default: () => deref_default,
deref: () => deref
});
module.exports = __toCommonJS(deref_exports);
var import_json_schema_ref_parser = require("@apidevtools/json-schema-ref-parser");
const deref = async (schema, options = {}) => {
if (options.schemas?.length) installFetchCache(options.schemas);
return (0, import_json_schema_ref_parser.dereference)(schema);
};
var deref_default = deref;
const installFetchCache = (schemas) => {
const originalFetch = fetch;
const cache = {};
const enc = new TextEncoder();
for (let i = schemas.length; i--; ) {
const schema = schemas[i];
if (schema.$id) {
cache[schema.$id] = enc.encode(JSON.stringify(schema));
}
}
globalThis.fetch = async (...args) => {
if (cache[args[0].href]) {
return {
status: 200,
body: true,
arrayBuffer: async () => cache[args[0].href]
};
}
return originalFetch(...args);
};
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
deref
});
// Copyright 2026 will Farrell, and ajv-cmd contributors.
// SPDX-License-Identifier: MIT
import { dereference } from "@apidevtools/json-schema-ref-parser";
export const deref = async (schema, options = {}) => {
if (options.schemas?.length) installFetchCache(options.schemas);
return dereference(schema);
};
export default deref;
const installFetchCache = (schemas) => {
const originalFetch = fetch;
const cache = {};
const enc = new TextEncoder();
for (let i = schemas.length; i--; ) {
const schema = schemas[i];
if (schema.$id) {
cache[schema.$id] = enc.encode(JSON.stringify(schema));
}
}
globalThis.fetch = async (...args) => {
if (cache[args[0].href]) {
return {
status: 200,
body: true,
arrayBuffer: async () => cache[args[0].href],
};
}
return originalFetch(...args);
};
};
+29
-7
#!/usr/bin/env -S node --disable-warning=DEP0040
// Copyright 2026 will Farrell, and ajv-cmd contributors.
// SPDX-License-Identifier: MIT
// --disable-warning=DEP0040 [DEP0040] DeprecationWarning: The `punycode` module is deprecated. Please use a userland alternative instead.
// #!/usr/bin/env -S node --experimental-json-modules --no-warnings --no-deprecation
// --disable-warning=DEP0040 suppresses: [DEP0040] DeprecationWarning: The `punycode` module is deprecated.
import { createRequire } from "node:module";
import { Command, Option } from "commander";

@@ -14,9 +14,9 @@ import deref from "./commands/deref.js";

//import metadata from './package.json' assert { type: 'json' }
const { version } = createRequire(import.meta.url)("./package.json");
const program = new Command()
.name("ajv")
//.version(metadata.version)
.version(version)
.description(
"Transpile JSON-Schema (.json) files to JavaScript (.js or .mjs) using ajv",
"Validate, transpile, dereference, and audit JSON-Schema files using AJV",
);

@@ -75,4 +75,2 @@

.argument("<input>", "Path to the JSON-Schema file to transpile")
//.addOption(new Option('--ftl <ftl>', 'Path to ftl file')
// Docs: https://ajv.js.org/packages/ajv-cli.html

@@ -172,2 +170,26 @@ .addOption(

new Option(
"--ignore <ignore...>",
"Suppress errors by `instancePath` or `instancePath:keyword` (exact match).",
),
)
.addOption(
new Option(
"--offline",
"Skip DNS lookups for remote $ref URLs (disables SSRF resolution).",
).preset(true),
)
.addOption(
new Option(
"--dns-timeout-ms <dnsTimeoutMs>",
"Per-hostname DNS lookup timeout in ms for SSRF checks (default 5000).",
),
)
.addOption(
new Option(
"--dns-concurrency <dnsConcurrency>",
"Max concurrent DNS lookups for SSRF checks (default 10).",
),
)
.addOption(
new Option(
"-o, --output <output>",

@@ -174,0 +196,0 @@ "Path to store the resulting JSON issues file.",

+8
-47
// Copyright 2026 will Farrell, and ajv-cmd contributors.
// SPDX-License-Identifier: MIT
import { readFile, stat, writeFile } from "node:fs/promises";
import { dereference } from "@apidevtools/json-schema-ref-parser";
import { writeFile } from "node:fs/promises";
import deref from "../deref.js";
import { assertFile, loadRefSchemas, readJson } from "./_utils.js";
const fileExists = async (filepath) => {
const stats = await stat(filepath);
if (!stats.isFile()) {
throw new Error(`${filepath} is not a file`);
}
};
export default async (input, options) => {
await fileExists(input);
const jsonSchema = await readFile(input, { encoding: "utf8" }).then((res) =>
JSON.parse(res),
);
await assertFile(input);
const jsonSchema = await readJson(input);
if (options?.refSchemaFiles) {
const refSchemas = [];
for (const schemaFilePath of options.refSchemaFiles) {
const refSchemaFile = await readFile(schemaFilePath, {
encoding: "utf8",
}).then((res) => JSON.parse(res));
refSchemas.push(refSchemaFile);
}
mockFetch(refSchemas);
options.schemas = await loadRefSchemas(options.refSchemaFiles);
}
const json = await dereference(jsonSchema);
const json = await deref(jsonSchema, options);

@@ -40,26 +26,1 @@ if (typeof options.output === "string") {

};
const mockFetch = (schemas) => {
const _fetch = fetch;
const cache = {};
const enc = new TextEncoder();
for (let i = schemas.length; i--; ) {
const schema = schemas[i];
if (schema.$id) {
cache[schema.$id] = enc.encode(JSON.stringify(schema));
}
}
globalThis.fetch = async (...args) => {
if (cache[args[0].href]) {
return Promise.resolve({
status: 200,
body: true,
arrayBuffer: async () => cache[args[0].href],
});
}
return _fetch(...args);
};
};
// Copyright 2026 will Farrell, and ajv-cmd contributors.
// SPDX-License-Identifier: MIT
import { readFile, stat, writeFile } from "node:fs/promises";
import { readFile, writeFile } from "node:fs/promises";
import transpile from "../ftl.js";
import { assertFile } from "./_utils.js";
const fileExists = async (filepath) => {
const stats = await stat(filepath);
if (!stats.isFile()) {
throw new Error(`${filepath} is not a file`);
}
};
export default async (input, options) => {
await assertFile(input);
const ftl = async (input, options) => {
await fileExists(input);
const ftl = await readFile(input, { encoding: "utf8" });

@@ -26,3 +20,1 @@

};
export default ftl;
// Copyright 2026 will Farrell, and ajv-cmd contributors.
// SPDX-License-Identifier: MIT
import { lookup } from "node:dns/promises";
import { readFile, stat, writeFile } from "node:fs/promises";
import { isSafePattern } from "redos-detector";
import sast from "../sast.js";
import { writeFile } from "node:fs/promises";
import { analyze } from "../sast.js";
import { assertFile, loadRefSchemas, readJson } from "./_utils.js";
const fileExists = async (filepath) => {
const stats = await stat(filepath);
if (!stats.isFile()) {
throw new Error(`${filepath} is not a file`);
}
};
export default async (input, options) => {
await fileExists(input);
await assertFile(input);
const jsonSchema = await readFile(input, { encoding: "utf8" }).then((res) =>
JSON.parse(res),
);
const jsonSchema = await readJson(input);
if (options?.refSchemaFiles) {
const refSchemas = [];
for (const schemaFilePath of options.refSchemaFiles) {
const refSchemaFile = await readFile(schemaFilePath, {
encoding: "utf8",
}).then((res) => JSON.parse(res));
refSchemas.push(refSchemaFile);
}
options.schemas = refSchemas;
options.schemas = await loadRefSchemas(options.refSchemaFiles);
}
const maxDepth =
options.overrideMaxDepth != null
? Number(options.overrideMaxDepth)
: MAX_DEPTH;
const errors = await analyze(jsonSchema, options);
// Single crawl: depth, min/max, patterns, $ref collection
const crawl = crawlSchema(jsonSchema, maxDepth);
let errors = [];
if (crawl.depthExceeded) {
errors.push({
instancePath: "",
schemaPath: "#/depth",
keyword: "depth",
params: { depth: crawl.depth, limit: maxDepth },
message: `must NOT have depth greater than ${maxDepth}`,
});
} else {
// Only run meta-schema validation if depth is safe
const validate = sast(jsonSchema, options);
validate(jsonSchema, options);
if (validate.errors) errors.push(...validate.errors);
// Append crawl findings after validate errors
errors.push(...crawl.errors);
const ssrfErrors = await resolveSSRFRefs(crawl.refs);
errors.push(...ssrfErrors);
if (options.overrideMaxItems != null && errors.length) {
const limit = Number(options.overrideMaxItems);
errors = errors.filter((err) => {
if (err.schemaPath === "#/definitions/safeArrayItemsLimits/maxItems") {
const arr = resolveInstancePath(jsonSchema, err.instancePath);
return !Array.isArray(arr) || arr.length > limit;
}
return true;
});
}
if (options.overrideMaxProperties != null && errors.length) {
const limit = Number(options.overrideMaxProperties);
errors = errors.filter((err) => {
if (
err.schemaPath ===
"#/definitions/safeObjectPropertiesLimits/maxProperties"
) {
const obj = resolveInstancePath(jsonSchema, err.instancePath);
if (typeof obj !== "object" || obj === null) return true;
return Object.keys(obj).length > limit;
}
return true;
});
}
}
if (errors.length) {

@@ -95,3 +24,3 @@ if (typeof options.output === "string") {

} else {
console.log(input, "has issues", stringify(errors));
console.log(input, "has issues", JSON.stringify(errors, null, 2));
}

@@ -105,244 +34,1 @@ if (options.fail) {

};
const MAX_DEPTH = 32;
const crawlSchema = (obj, maxDepth) => {
const result = { depth: 0, depthExceeded: false, errors: [], refs: [] };
if (typeof obj !== "object" || obj === null) return result;
result.depth = 1;
const stack = [[obj, "", 1]];
while (stack.length > 0) {
const [current, path, currentDepth] = stack.pop();
// minLength / maxLength
if (
Object.hasOwn(current, "minLength") &&
Object.hasOwn(current, "maxLength") &&
current.minLength > current.maxLength
) {
result.errors.push({
instancePath: path,
schemaPath: "#/minLength",
keyword: "minLength",
params: {
minLength: current.minLength,
maxLength: current.maxLength,
},
message: "minLength must be less than or equal to maxLength",
});
}
// minimum / exclusiveMinimum / maximum / exclusiveMaximum
{
const hasMin = Object.hasOwn(current, "minimum");
const hasExMin = Object.hasOwn(current, "exclusiveMinimum");
const hasMax = Object.hasOwn(current, "maximum");
const hasExMax = Object.hasOwn(current, "exclusiveMaximum");
if ((hasMin || hasExMin) && (hasMax || hasExMax)) {
const effectiveMin =
hasMin && hasExMin
? Math.max(current.minimum, current.exclusiveMinimum)
: hasMin
? current.minimum
: current.exclusiveMinimum;
const effectiveMax =
hasMax && hasExMax
? Math.min(current.maximum, current.exclusiveMaximum)
: hasMax
? current.maximum
: current.exclusiveMaximum;
if (!(effectiveMin < effectiveMax)) {
result.errors.push({
instancePath: path,
schemaPath: "#/minimum",
keyword: "minimum",
params: {
...(hasMin && { minimum: current.minimum }),
...(hasExMin && {
exclusiveMinimum: current.exclusiveMinimum,
}),
...(hasMax && { maximum: current.maximum }),
...(hasExMax && {
exclusiveMaximum: current.exclusiveMaximum,
}),
},
message: "minimum must be less than maximum",
});
}
}
}
// minItems / maxItems
if (
Object.hasOwn(current, "minItems") &&
Object.hasOwn(current, "maxItems") &&
current.minItems > current.maxItems
) {
result.errors.push({
instancePath: path,
schemaPath: "#/minItems",
keyword: "minItems",
params: {
minItems: current.minItems,
maxItems: current.maxItems,
},
message: "minItems must be less than or equal to maxItems",
});
}
// minContains / maxContains
if (
Object.hasOwn(current, "minContains") &&
Object.hasOwn(current, "maxContains") &&
current.minContains > current.maxContains
) {
result.errors.push({
instancePath: path,
schemaPath: "#/minContains",
keyword: "minContains",
params: {
minContains: current.minContains,
maxContains: current.maxContains,
},
message: "minContains must be less than or equal to maxContains",
});
}
// ReDoS pattern check
if (
Object.hasOwn(current, "pattern") &&
typeof current.pattern === "string"
) {
const patternResult = isSafePattern(current.pattern);
if (!patternResult.safe) {
result.errors.push({
instancePath: `${path}/pattern`,
schemaPath: "#/redos",
keyword: "pattern",
params: { pattern: current.pattern },
message: "pattern is vulnerable to ReDoS",
});
}
}
// Collect remote $ref URLs for DNS resolution
if (
Object.hasOwn(current, "$ref") &&
typeof current.$ref === "string" &&
!current.$ref.startsWith("#")
) {
try {
const url = new URL(current.$ref);
result.refs.push({
hostname: url.hostname,
ref: current.$ref,
path: `${path}/$ref`,
});
} catch {
// not a valid URL, skip
}
}
// Traverse children, tracking depth
for (const key in current) {
if (Object.hasOwn(current, key)) {
const value = current[key];
if (typeof value === "object" && value !== null) {
const newDepth = currentDepth + 1;
if (newDepth > result.depth) result.depth = newDepth;
if (result.depth > maxDepth) {
result.depthExceeded = true;
return result;
}
stack.push([value, `${path}/${key}`, newDepth]);
}
}
}
}
return result;
};
const isPrivateIP = (ip) => {
const parts = ip.split(".").map(Number);
if (parts.length === 4 && parts.every((p) => p >= 0 && p <= 255)) {
if (parts[0] === 10) return true; // 10.0.0.0/8
if (parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31) return true; // 172.16.0.0/12
if (parts[0] === 192 && parts[1] === 168) return true; // 192.168.0.0/16
if (parts[0] === 127) return true; // 127.0.0.0/8
if (parts[0] === 169 && parts[1] === 254) return true; // 169.254.0.0/16
if (parts[0] === 0) return true; // 0.0.0.0/8
if (parts[0] === 100 && parts[1] >= 64 && parts[1] <= 127) return true; // 100.64.0.0/10 (CGN)
if (parts[0] === 198 && parts[1] >= 18 && parts[1] <= 19) return true; // 198.18.0.0/15
}
// IPv6 private/reserved
const lower = ip.toLowerCase();
if (lower === "::1" || lower === "::") return true;
if (lower.startsWith("fc") || lower.startsWith("fd")) return true; // unique local
if (lower.startsWith("fe80")) return true; // link-local
if (lower.startsWith("::ffff:")) {
return isPrivateIP(lower.slice(7)); // IPv4-mapped IPv6
}
return false;
};
const resolveSSRFRefs = async (refs) => {
const errors = [];
const hostnameMap = new Map();
for (const entry of refs) {
if (!hostnameMap.has(entry.hostname)) {
hostnameMap.set(entry.hostname, []);
}
hostnameMap.get(entry.hostname).push(entry);
}
for (const [hostname, entries] of hostnameMap) {
try {
const { address } = await lookup(hostname);
if (isPrivateIP(address)) {
for (const { ref, path } of entries) {
errors.push({
instancePath: path,
schemaPath: "#/ssrf",
keyword: "ssrf",
params: { ref, hostname, resolvedIP: address },
message: `$ref hostname "${hostname}" resolves to private IP ${address}`,
});
}
}
} catch {
for (const { ref, path } of entries) {
errors.push({
instancePath: path,
schemaPath: "#/ssrf",
keyword: "ssrf",
params: { ref, hostname },
message: `$ref hostname "${hostname}" does not resolve`,
});
}
}
}
return errors;
};
const resolveInstancePath = (obj, pointer) => {
if (typeof obj !== "object" || obj === null) return undefined;
if (!pointer) return obj;
const parts = pointer.split("/").slice(1);
let current = obj;
for (const part of parts) {
if (typeof current !== "object" || current === null) return undefined;
if (!Object.hasOwn(current, part)) return undefined;
current = current[part];
}
return current;
};
const stringify = (arr) => {
let str = "[\n";
for (let i = 0, l = arr.length; i < l; i++) {
str += `${JSON.stringify(arr[i]) + (i < l - 1 ? "," : "")}\n`;
}
return `${str}]`;
};
// Copyright 2026 will Farrell, and ajv-cmd contributors.
// SPDX-License-Identifier: MIT
import { readFile, stat, writeFile } from "node:fs/promises";
import { writeFile } from "node:fs/promises";
import transpile from "../transpile.js";
import { assertFile, loadRefSchemas, readJson } from "./_utils.js";
const fileExists = async (filepath) => {
const stats = await stat(filepath);
if (!stats.isFile()) {
throw new Error(`${filepath} is not a file`);
}
};
export default async (input, options) => {
await fileExists(input);
await assertFile(input);
const jsonSchema = await readFile(input, { encoding: "utf8" }).then((res) =>
JSON.parse(res),
);
const jsonSchema = await readJson(input);
if (options?.refSchemaFiles) {
const refSchemas = [];
for (const schemaFilePath of options.refSchemaFiles) {
const refSchemaFile = await readFile(schemaFilePath, {
encoding: "utf8",
}).then((res) => JSON.parse(res));
refSchemas.push(refSchemaFile);
}
options.schemas = refSchemas;
options.schemas = await loadRefSchemas(options.refSchemaFiles);
}

@@ -30,0 +15,0 @@

// Copyright 2026 will Farrell, and ajv-cmd contributors.
// SPDX-License-Identifier: MIT
import { readFile, stat } from "node:fs/promises";
import validate from "../validate.js";
import { assertFile, loadRefSchemas, readJson } from "./_utils.js";
const fileExists = async (filepath) => {
const stats = await stat(filepath);
if (!stats.isFile()) {
throw new Error(`${filepath} is not a file`);
}
};
export default async (input, options) => {
await fileExists(input);
await assertFile(input);
const jsonSchema = await readFile(input, { encoding: "utf8" }).then((res) =>
JSON.parse(res),
);
const jsonSchema = await readJson(input);
if (options?.refSchemaFiles) {
const refSchemas = [];
for (const schemaFilePath of options.refSchemaFiles) {
const refSchemaFile = await readFile(schemaFilePath, {
encoding: "utf8",
}).then((res) => JSON.parse(res));
refSchemas.push(refSchemaFile);
}
options.schemas = refSchemas;
options.schemas = await loadRefSchemas(options.refSchemaFiles);
}
if (options?.testDataFiles) {
const testDataFiles = [];
for (const testDataFilePath of options.testDataFiles) {
const testDataFile = await readFile(testDataFilePath, {
encoding: "utf8",
}).then((res) => JSON.parse(res));
testDataFiles.push(testDataFile);
}
options.testData = testDataFiles;
options.testData = await loadRefSchemas(options.testDataFiles);
}

@@ -41,0 +18,0 @@

@@ -33,2 +33,3 @@ "use strict";

default: () => index_default,
deref: () => deref,
ftl: () => ftl,

@@ -41,2 +42,3 @@ instance: () => instance,

var import_compile = require("./compile.js");
var import_deref = __toESM(require("./deref.js"), 1);
var import_ftl = __toESM(require("./ftl.js"), 1);

@@ -47,2 +49,3 @@ var import_transpile = __toESM(require("./transpile.js"), 1);

const compile = import_compile.compile;
const deref = import_deref.default;
const transpile = import_transpile.default;

@@ -54,2 +57,3 @@ const validate = import_validate.default;

compile,
deref,
ftl,

@@ -62,2 +66,3 @@ transpile,

compile,
deref,
ftl,

@@ -64,0 +69,0 @@ instance,

@@ -7,2 +7,3 @@ // Copyright 2026 will Farrell, and ajv-cmd contributors.

} from "./compile.js";
import derefImport from "./deref.js";
import ftlImport from "./ftl.js";

@@ -14,2 +15,3 @@ import transpileImport from "./transpile.js";

export const compile = compileImport;
export const deref = derefImport;
export const transpile = transpileImport;

@@ -22,2 +24,3 @@ export const validate = validateImport;

compile,
deref,
ftl,

@@ -24,0 +27,0 @@ transpile,

MIT License
Copyright (c) 2022 will Farrell
Copyright (c) 2026 will Farrell

@@ -5,0 +5,0 @@ Permission is hereby granted, free of charge, to any person obtaining a copy

{
"name": "ajv-cmd",
"version": "0.11.0",
"version": "0.12.0",
"description": "Deref, Validate, Transpile, and Test JSON-Schema (.json) files using ajv",

@@ -21,2 +21,4 @@ "type": "module",

"compile.js",
"deref.cjs",
"deref.js",
"ftl.cjs",

@@ -28,3 +30,2 @@ "ftl.js",

"validate.js",
"sast.cjs",
"sast.js"

@@ -69,5 +70,2 @@ ],

"default": "./sast.js"
},
"require": {
"default": "./sast.cjs"
}

@@ -93,15 +91,17 @@ },

"scripts": {
"prepare": "husky",
"prepare": "husky || true",
"prepublishOnly": "npm run build",
"build": "npm run build:index && npm run build:compile && npm run build:ftl && npm run build:transpile && npm run build:validate && npm run build:sast",
"build": "npm run build:index && npm run build:compile && npm run build:deref && npm run build:ftl && npm run build:transpile && npm run build:validate",
"build:index": "./node_modules/.bin/esbuild index.js --platform=node --format=cjs --target=node18 --allow-overwrite --outfile=index.cjs",
"build:compile": "./node_modules/.bin/esbuild compile.js --platform=node --format=cjs --target=node18 --allow-overwrite --outfile=compile.cjs",
"build:deref": "./node_modules/.bin/esbuild deref.js --platform=node --format=cjs --target=node18 --allow-overwrite --outfile=deref.cjs",
"build:ftl": "./node_modules/.bin/esbuild ftl.js --platform=node --format=cjs --target=node18 --bundle --allow-overwrite --outfile=ftl.cjs",
"build:transpile": "./node_modules/.bin/esbuild transpile.js --platform=node --format=cjs --target=node18 --allow-overwrite --outfile=transpile.cjs",
"build:validate": "./node_modules/.bin/esbuild validate.js --platform=node --format=cjs --target=node18 --allow-overwrite --outfile=validate.cjs",
"build:sast": "./node_modules/.bin/esbuild sast.js --platform=node --format=cjs --target=node18 --allow-overwrite --outfile=sast.cjs",
"test": "npm run test:lint && npm run test:unit && npm run test:types && npm run test:sast && npm run test:perf && npm run test:dast",
"pretest": "npm run build",
"test": "npm run test:lint && npm run test:unit && npm run test:e2e && npm run test:types && npm run test:sast && npm run test:perf && npm run test:dast",
"test:unit": "node --test --experimental-test-coverage --test-coverage-lines=95 --test-coverage-branches=90 --test-coverage-functions=90 ./*.test.js ./commands/*.test.js",
"test:e2e": "./__test__/e2e.sh",
"test:sast": "npm run test:sast:license && npm run test:sast:lockfile && npm run test:sast:semgrep && npm run test:sast:trufflehog && npm run test:sast:trivy",
"test:sast:license": "license-check-and-add check -f license.json",
"test:sast:license": "license-check-and-add check -f .license.config.json",
"test:sast:lockfile": "lockfile-lint --path package-lock.json --type npm --allowed-hosts npm --validate-https",

@@ -118,9 +118,10 @@ "test:sast:semgrep": "semgrep scan --config auto",

"git:commit-msg": "commitlint --config commitlint.config.cjs --edit",
"git:lint-staged": "npm run test:lint",
"git:lint-staged": "npm run test:lint:staged",
"git:unit-staged": "node --test",
"git:test-staged": "npm run git:unit-staged",
"lint": "biome check --write --no-errors-on-unmatched",
"test:lint": "biome check --staged --no-errors-on-unmatched",
"release:license:add": "license-check-and-add add -f license.json",
"release:license:remove": "license-check-and-add remove -f license.json"
"test:lint": "biome ci --no-errors-on-unmatched",
"test:lint:staged": "biome check --staged --no-errors-on-unmatched",
"release:license:add": "license-check-and-add add -f .license.config.json",
"release:license:remove": "license-check-and-add remove -f .license.config.json"
},

@@ -156,4 +157,3 @@ "repository": {

"esbuild": "^0.28.0",
"redos-detector": "6.1.4",
"sast-json-schema": "^0.1.0"
"sast-json-schema": "0.2.3"
},

@@ -160,0 +160,0 @@ "devDependencies": {

+9
-23
// Copyright 2026 will Farrell, and ajv-cmd contributors.
// SPDX-License-Identifier: MIT
import { createRequire } from "node:module";
import Ajv from "ajv/dist/2020.js";
//import sastSchema from 'sast-json-schema/index.json' with { type: 'json' }
const defaultOptions = {
strictTypes: false,
allErrors: true,
};
const sastSchema = createRequire(import.meta.url)(
"sast-json-schema/index.json",
);
export const sast = (_schema, options = {}) => {
options = { ...defaultOptions, ...options };
const ajv = new Ajv(options);
const validate = ajv.compile(sastSchema);
return validate;
};
export default sast;
export {
analyze,
crawlSchema,
isPrivateIP,
MAX_DEPTH,
resolveSSRFRefs,
sast,
sast as default,
} from "sast-json-schema/cli";

@@ -93,19 +93,23 @@ "use strict";

const cleanupFiles = [file];
if (needsBridge) {
await (0, import_promises.writeFile)(bridgeFile, bridgeModuleContent, "utf8");
cleanupFiles.push(bridgeFile);
if (needsBridge) cleanupFiles.push(bridgeFile);
try {
if (needsBridge) {
await (0, import_promises.writeFile)(bridgeFile, bridgeModuleContent, "utf8");
}
await (0, import_promises.writeFile)(file, js, "utf8");
await (0, import_esbuild.build)({
entryPoints: [file],
platform: "node",
format: "esm",
bundle: true,
minify: true,
legalComments: "none",
allowOverwrite: true,
outfile: file
});
js = await (0, import_promises.readFile)(file, { encoding: "utf8" });
} finally {
await Promise.all(cleanupFiles.map((f) => (0, import_promises.unlink)(f).catch(() => {
})));
}
await (0, import_promises.writeFile)(file, js, "utf8");
await (0, import_esbuild.build)({
entryPoints: [file],
platform: "node",
format: "esm",
bundle: true,
minify: true,
legalComments: "none",
allowOverwrite: true,
outfile: file
});
js = await (0, import_promises.readFile)(file, { encoding: "utf8" });
await Promise.all(cleanupFiles.map((f) => (0, import_promises.unlink)(f)));
return js;

@@ -112,0 +116,0 @@ };

@@ -76,22 +76,25 @@ // Copyright 2026 will Farrell, and ajv-cmd contributors.

const cleanupFiles = [file];
if (needsBridge) {
await writeFile(bridgeFile, bridgeModuleContent, "utf8");
cleanupFiles.push(bridgeFile);
}
if (needsBridge) cleanupFiles.push(bridgeFile);
await writeFile(file, js, "utf8");
try {
if (needsBridge) {
await writeFile(bridgeFile, bridgeModuleContent, "utf8");
}
await writeFile(file, js, "utf8");
await build({
entryPoints: [file],
platform: "node",
format: "esm",
bundle: true,
minify: true,
legalComments: "none",
allowOverwrite: true,
outfile: file,
});
await build({
entryPoints: [file],
platform: "node",
format: "esm",
bundle: true,
minify: true,
legalComments: "none",
allowOverwrite: true,
outfile: file,
});
js = await readFile(file, { encoding: "utf8" });
await Promise.all(cleanupFiles.map((f) => unlink(f)));
js = await readFile(file, { encoding: "utf8" });
} finally {
await Promise.all(cleanupFiles.map((f) => unlink(f).catch(() => {})));
}

@@ -98,0 +101,0 @@ return js;

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

default: () => validate_default,
test: () => test
validate: () => validate
});

@@ -31,16 +31,16 @@ module.exports = __toCommonJS(validate_exports);

};
const test = async (schema, options = {}) => {
const validate = async (schema, options = {}) => {
options = { ...defaultOptions, ...options };
let validate, valid;
let compiled;
try {
validate = (0, import_compile.compile)(schema, options);
compiled = (0, import_compile.compile)(schema, options);
} catch (e) {
console.error(e.message);
return valid;
return void 0;
}
let testSuccess = true;
for (const data of options?.testData ?? []) {
valid = validate(structuredClone(data));
const valid = compiled(structuredClone(data));
if (!valid) {
console.error(validate.errors);
console.error(compiled.errors);
testSuccess = false;

@@ -51,6 +51,6 @@ }

};
var validate_default = test;
var validate_default = validate;
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
test
validate
});

@@ -5,4 +5,2 @@ // Copyright 2026 will Farrell, and ajv-cmd contributors.

//import spec from 'ajv/dist/refs/json-schema-2020-12/schema.json' assert { type: 'json' }
const defaultOptions = {

@@ -12,30 +10,18 @@ allErrors: true, // required for `errorMessage`

export const test = async (schema, options = {}) => {
export const validate = async (schema, options = {}) => {
options = { ...defaultOptions, ...options };
let validate, valid;
// Spec check
// const ajv = new Ajv(options)
// validate = ajv.compile(spec)
// valid = validate(schema)
// ajv.removeSchema()
// if (!valid) {
// console.error('Schema is not spec compliant')
// return valid
// }
// Compile check
let compiled;
try {
validate = compile(schema, options);
compiled = compile(schema, options);
} catch (e) {
console.error(e.message);
return valid;
return undefined;
}
// Data Check
let testSuccess = true;
for (const data of options?.testData ?? []) {
valid = validate(structuredClone(data));
const valid = compiled(structuredClone(data));
if (!valid) {
console.error(validate.errors);
console.error(compiled.errors);
testSuccess = false;

@@ -48,2 +34,2 @@ }

export default test;
export default validate;
{
"license": "license.template",
"licenseFormats": {
"js|ts": {
"eachLine": {
"prepend": "// "
}
}
},
"ignoreFile": ".gitignore",
"ignore": [
".github/**/*",
".husky/**/*",
"fixtures/*",
"commitlint.config.cjs",
"LICENSE",
"license.template",
"**/.gitignore",
"**/*.fuzz.js",
"**/*.perf.js",
"**/*.test.js",
"**/*.cjs",
"**/*.md",
"**/*.yml",
"**/.DS_Store",
"**/*.sh",
"**/*.ftl"
]
}

Sorry, the diff of this file is not supported yet

"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var sast_exports = {};
__export(sast_exports, {
default: () => sast_default,
sast: () => sast
});
module.exports = __toCommonJS(sast_exports);
var import_node_module = require("node:module");
var import__ = __toESM(require("ajv/dist/2020.js"), 1);
const import_meta = {};
const defaultOptions = {
strictTypes: false,
allErrors: true
};
const sastSchema = (0, import_node_module.createRequire)(import_meta.url)(
"sast-json-schema/index.json"
);
const sast = (_schema, options = {}) => {
options = { ...defaultOptions, ...options };
const ajv = new import__.default(options);
const validate = ajv.compile(sastSchema);
return validate;
};
var sast_default = sast;
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
sast
});