Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,15 @@ updates:
directory: "/"
schedule:
interval: "weekly"
cooldown-period: 15
cooldown:
default-days: 15
groups:
dev-dependencies:
dependency-type: "development"
update-types:
- "minor"
- "patch"
prod-dependencies:
dependency-type: "production"
update-types:
- "patch"
1 change: 1 addition & 0 deletions .github/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"name": "@willfarrell/ajv-cmd-github-workflows",
"version": "0.0.1",
"description": "Workspace for CI-only dev tools (e.g. lockfile-lint) that should not be installed by users or bloat the top-level dependency graph.",
"private": true,
"engines": {
"node": ">=24.0"
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ossf-scorecard.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
id-token: write
steps:
- name: 'Checkout code'
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: 'Run analysis'
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ permissions:
jobs:
build:
name: Build
if: ${{ github.event.pull_request.merged }}
if: ${{ github.event.pull_request.merged && github.event.pull_request.head.repo.full_name == github.repository }}
runs-on: ubuntu-latest
permissions:
contents: read
Expand All @@ -25,6 +25,8 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Setup Node.js ${{ env.NODE_VERSION }}
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/test-dast.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Setup Node.js ${{ env.NODE_VERSION }}
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
Expand Down
8 changes: 5 additions & 3 deletions .github/workflows/test-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@ jobs:

steps:
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Setup Node.js ${{ env.NODE_VERSION }}
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: ${{ env.NODE_VERSION }}
registry-url: https://registry.npmjs.org
Expand All @@ -27,4 +29,4 @@ jobs:
npm ci --ignore-scripts
- name: Linting
run: |
./node_modules/.bin/biome ci --no-errors-on-unmatched
npm run test:lint
2 changes: 2 additions & 0 deletions .github/workflows/test-perf.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Setup Node.js ${{ env.NODE_VERSION }}
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
Expand Down
32 changes: 32 additions & 0 deletions .github/workflows/test-sast.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Trivy
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # v0.35.0
with:
Expand All @@ -38,6 +40,8 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Setup Node.js ${{ env.NODE_VERSION }}
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
Expand All @@ -57,13 +61,37 @@ jobs:
exit-code: 0
format: table

license:
name: "License headers"
runs-on: ubuntu-latest
if: (github.actor != 'dependabot[bot]')
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Setup Node.js ${{ env.NODE_VERSION }}
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: ${{ env.NODE_VERSION }}
registry-url: https://registry.npmjs.org
cache: npm
- name: Install dependencies
run: |
npm ci --ignore-scripts
- name: License check
run: |
npm run test:sast:license

lockfile:
name: "lockfile-lint: SAST package-lock.json"
runs-on: ubuntu-latest
if: (github.actor != 'dependabot[bot]')
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Setup Node.js ${{ env.NODE_VERSION }}
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
Expand Down Expand Up @@ -91,6 +119,8 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Initialize CodeQL
uses: github/codeql-action/init@babb554ede22fd5605947329c4d04d8e7a0b8155 # v2.27.7
with:
Expand All @@ -112,5 +142,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: semgrep
run: semgrep ci
2 changes: 2 additions & 0 deletions .github/workflows/test-types.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Setup Node.js ${{ env.NODE_VERSION }}
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/test-unit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Setup Node.js ${{ env.NODE_VERSION }}
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
Expand All @@ -32,3 +34,6 @@ jobs:
- name: Unit tests
run: |
npm run test:unit
- name: E2E tests
run: |
npm run test:e2e
4 changes: 2 additions & 2 deletions license.json → .license.config.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"license": "license.template",
"license": ".license.template",
"licenseFormats": {
"js|ts": {
"eachLine": {
Expand All @@ -14,7 +14,7 @@
"fixtures/*",
"commitlint.config.cjs",
"LICENSE",
"license.template",
".license.template",
"**/.gitignore",
"**/*.fuzz.js",
"**/*.perf.js",
Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

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

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
45 changes: 27 additions & 18 deletions __test__/e2e.sh
Original file line number Diff line number Diff line change
@@ -1,23 +1,32 @@
#!/usr/bin/env bash
set -euo pipefail

function bundle {
echo "validate ${1}"
node cli.js validate ${1} --valid \
--strict true --coerce-types array --all-errors true --use-defaults empty

echo "sast audit ${1}"
node cli.js sast ${1}

echo "transpile ${1}"
node cli.js transpile ${1} \
--strict true --coerce-types array --all-errors true --use-defaults empty \
-o ${1%.json}.js

#cat ${1/schema/data}

echo "test ${1}"
node --input-type=module -e "import validate from '${1%.json}.js'; import data from '${1/schema/data}' assert {type:'json'}; const valid = validate(data); console.log(valid, JSON.stringify(validate.errors))"
bundle() {
local schema="$1"
local data="${schema/schema/data}"
local out="${schema%.json}.js"

echo "validate ${schema}"
node cli.js validate "${schema}" --valid \
--strict true --coerce-types array --all-errors --use-defaults empty

echo "sast ${schema}"
node cli.js sast "${schema}"

echo "transpile ${schema}"
node cli.js transpile "${schema}" \
--strict true --coerce-types array --all-errors --use-defaults empty \
-o "${out}"

echo "test ${schema}"
node --input-type=module -e "
import validate from '${out}';
import data from '${data}' with { type: 'json' };
const valid = validate(data);
console.log(valid, JSON.stringify(validate.errors));
"

rm -f "${out}"
}

bundle ./__test__/formats.schema.json

2 changes: 2 additions & 0 deletions __test__/formats.schema.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/schemas/formats",
"type": "object",
"additionalProperties": true,
"properties": {
Expand Down
2 changes: 2 additions & 0 deletions __test__/large-enum.schema.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/schemas/large-enum",
"type": "object",
"properties": {
"status": {
Expand Down
2 changes: 2 additions & 0 deletions __test__/secure.schema.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/schemas/secure",
"type": "object",
"properties": {
"name": {
Expand Down
2 changes: 1 addition & 1 deletion biome.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"$schema": "https://biomejs.dev/schemas/2.4.11/schema.json",
"$schema": "https://biomejs.dev/schemas/2.4.12/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
Expand Down
36 changes: 29 additions & 7 deletions cli.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
#!/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";
import deref from "./commands/deref.js";
import ftl from "./commands/ftl.js";
import sast from "./commands/sast.js";
import transpile from "./commands/transpile.js";
import validate from "./commands/validate.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",
);

program
Expand Down Expand Up @@ -71,8 +71,6 @@ program
program
.command("transpile")
.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
.addOption(
new Option(
Expand Down Expand Up @@ -165,6 +163,30 @@ program
"Override the max properties limit (default 1024). Removes maxProperties errors when the property count is within this limit. Values <= 1024 are a no-op.",
),
)
.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>",
Expand Down
20 changes: 20 additions & 0 deletions commands/_utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// 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));
};
Loading
Loading