Skip to content

About

A validation framework for checking documents and APIs against specifications using Spectral rulesets.

Resources

Stars

0 stars

Watchers

1 watching

Forks

Repository files navigation

@geonovum/standards-checker

A validation framework for checking documents and APIs against specifications using OpenLint rulesets. Ships a CLI toolkit, a programmatic engine, an embeddable React UI, and shared build/lint/format/typescript configs so consumer apps stay small.

npm

Release notes are in CHANGELOG.md; see Versioning & releasing for how it is produced.

Checker apps built on this framework

App Description
ogc-checker Validates JSON-FG documents and OGC API endpoints (Features, Processes, Records)
don-checker Validates OpenAPI specs (ADR 2.0.2/2.1.0) and publiccode.yml files

Quick start

CLI

Each checker app ships its own CLI with baked-in standards and versions:

ogc-checker validate --standard json-fg --input ./data/spec.json
don-checker validate --standard adr --version 2.0.2 --input ./openapi.json

Or via stdin:

cat spec.json | ogc-checker validate --standard json-fg

--version is optional and defaults to the latest final version. The old --ruleset <slug> flag still works as a deprecated alias — it warns on stderr and resolves the old slug to the same standard/version.

CLI flags

Flag Description Default
--standard <slug> Standard to validate against (required)
--version <id> Version of the standard latest final
--ruleset <slug> Deprecated alias for --standard/--version —
--input <file|-> Input file, URL, or - for stdin -
--format <fmt> Output: table, json table
--fail-on <level> Exit code policy: none, warn, error error

Exit codes: 0 = pass, 1 = failed per --fail-on policy, >1 = unexpected error.

External $refs

Relative $refs are resolved against the document's own location, so a schema next to the input file resolves no matter which directory you run from:

don-checker validate --standard adr --input media/openapi.json   # "$ref": "./ProblemJson.schema.json"

The same holds for --input <url> (relative $refs are fetched relative to that URL, following redirects) and for absolute http(s) $refs anywhere. Stdin has no location, so relative $refs there resolve against the working directory.

Rules apply to the resolved document, which means a violation can land in a referenced file. Each diagnostic therefore carries both:

Field Meaning
ruleset Conformance class that flagged it
source File or URL it lives in — the referenced document, not necessarily the input

Web UI

import { mount } from '@geonovum/standards-checker/ui';
import '@geonovum/standards-checker/index.css';
import standards from './standards';

// `standards` is a `Standard[]` — each standard owns an ordered list of versions.
mount(document.getElementById('root')!, standards, {
  title: 'My Checker',
});

The user picks a version-less standard in the header; a dependent version dropdown then refreshes and auto-selects the default version (explicit defaultVersion, else the latest final). Switching version keeps the editor content and only re-lints; switching standard resets the editor to the new standard's example. The URL anchor is /#/{standard}/{version}; old single-slug links (and CLI --ruleset slugs) redirect to their new {standard}/{version} anchor.


Package entry points

One package, many subpaths:

Subpath What it is
@geonovum/standards-checker Core validation engine, types, and utilities
@geonovum/standards-checker/ui React components, router, mount()
@geonovum/standards-checker/cli createCli toolkit for building a CLI entry point
@geonovum/standards-checker/vite Shared Vite config factory (createConfig) — React + YAML
@geonovum/standards-checker/vitest Shared Vitest config (createVitestConfig + default export)
@geonovum/standards-checker/vitest-matchers Setup file registering the toContainViolation custom matcher
@geonovum/standards-checker/openlint/core Re-export of @openlint/openlint-core (OpenLint, Document, ruleset types)
@geonovum/standards-checker/openlint/functions Re-export of @openlint/openlint-functions
@geonovum/standards-checker/openlint/parsers Re-export of @openlint/openlint-parsers
@geonovum/standards-checker/openlint/rulesets Re-export of @openlint/openlint-rulesets (oas, asyncapi, arazzo, OAS functions)
@geonovum/standards-checker/spectral/* Deprecated alias of openlint/*; removed in the next major
@geonovum/standards-checker/eslint.config Shared ESLint flat config
@geonovum/standards-checker/prettier Shared Prettier config (use via "prettier": field)
@geonovum/standards-checker/tsconfig.app.json Base TS config for app source
@geonovum/standards-checker/tsconfig.node.json Base TS config for Node-side scripts (vite.config.ts)
@geonovum/standards-checker/client *.css and *?raw module declarations for TS
@geonovum/standards-checker/vitest-client Vitest matcher type augmentation (toContainViolation)
@geonovum/standards-checker/index.css Pre-built CSS (Tailwind compiled at build time)

Bin shipped with the package: build-cli — drives the tsdown JS API with the standard CLI-build options plus a plugin implementing Vite-style ?raw raw-text imports (rolldown doesn't support the suffix natively). Usage is build-cli <entry> [...entry]; the build is fully standard — a consumer tsdown.config.ts is not loaded. vite and vitest are peer deps that live in the consumer's tree; scripts invoke their native bins directly.


Building a checker app

Minimum consumer package.json:

{
  "type": "module",
  "scripts": {
    "dev": "vite --open",
    "build": "tsc -b && pnpm run build:cli && vite build",
    "build:cli": "build-cli src/cli.ts",
    "lint": "eslint .",
    "test": "vitest",
  },
  "prettier": "@geonovum/standards-checker/prettier",
  "dependencies": {
    "@geonovum/standards-checker": "^1.1.0",
    "react": "^19.0.0",
    "react-dom": "^19.0.0",
    "react-router-dom": "^7.0.0",
  },
  "devDependencies": {
    "esbuild": "^0.28.0",
    "eslint": "^10.0.0",
    "prettier": "^3.0.0",
    "typescript": "^6.0.0",
    "vite": "^8.0.0",
    "vitest": "^4.0.0",
  },
}

The package ships pre-built CSS (@geonovum/standards-checker/index.css), so consumers don't install Tailwind, PostCSS, or any CSS plugins.

Minimum consumer config files:

// vite.config.ts
import { createConfig } from '@geonovum/standards-checker/vite';
export default createConfig({ base: '/my-checker/' });
// eslint.config.js
export { default } from '@geonovum/standards-checker/eslint.config';
// tsconfig.app.json
{ "extends": "@geonovum/standards-checker/tsconfig.app.json", "include": ["src"] }
// vitest.config.ts
export { default } from '@geonovum/standards-checker/vitest';

The shipped Vitest config registers the toContainViolation matcher via its setup file. To override any option, use the createVitestConfig factory:

// vitest.config.ts
import { createVitestConfig } from '@geonovum/standards-checker/vitest';
export default createVitestConfig({ test: { environment: 'jsdom' } });

Typical project layout:

my-checker/
├── src/
│   ├── main.ts             # Web app entry point (mount(root, standards, ...))
│   ├── cli.ts              # CLI entry point (createCli({ name, standards }))
│   └── standards/
│       ├── index.ts        # exports the Standard[] — used by BOTH main.ts and cli.ts
│       └── my-standard/
│           ├── standard.ts # Standard definition (name, slug, versions[] with raw rulesets)
│           ├── rulesets/
│           ├── examples/
│           └── functions/
├── tsconfig.app.json
├── tsconfig.node.json
├── eslint.config.js
├── vite.config.ts
├── vitest.config.ts
└── package.json

Development

Prerequisites

  • Node.js 22.18+ or 24+
  • pnpm 10+

Setup

pnpm install
pnpm build

Commands

Command Description
pnpm build Type-check + bundle via tsdown + Tailwind CLI (via tsdown onSuccess)
pnpm dev Watch mode (tsdown --watch)
pnpm test Run vitest
pnpm lint Check for lint and formatting issues
pnpm lint:fix Auto-fix lint and formatting issues

Local development with a checker app

The package is unbundled: each source file becomes its own dist file with bare imports preserved. Vite's resolver follows symlinks' realpath through the installed package, which lets it find this package's own node_modules and resolve transitive deps automatically — no custom Vite plugin needed.

To iterate on standards-checker against a consumer app, use pnpm link:

# Terminal 1: watch mode
cd standards-checker
pnpm dev

# Terminal 2: link the local checkout, then run the checker app
cd ../ogc-checker
pnpm link ../standards-checker
pnpm dev

Undo with pnpm unlink @geonovum/standards-checker && pnpm install. A subsequent pnpm install in the consumer can overwrite the symlink with a registry install — re-run pnpm link ../standards-checker if that happens.

The peer deps (React family, vite, vitest, esbuild) are already listed in each consumer's package.json because the consumer uses them directly (React imports, vite/vitest/esbuild bins), so peer resolution across the link works without extra steps.

Versioning & releasing

Versioning, the changelog, and npm publishing are driven by Changesets. Describing a change is decoupled from cutting a release:

  1. Add a changeset with your change. Run pnpm changeset, pick the bump (major / minor / patch), and write a one-line summary. This creates a .changeset/<name>.md file — commit it with your PR. Omit only for changes that don't affect the published package (CI, internal docs, tests).

  2. Cut the release. Run pnpm version-packages (alias for changeset version). It consumes the pending .changeset/*.md files, bumps package.json to the computed version (the highest pending bump wins), and prepends the summaries to CHANGELOG.md. Review and commit the result. Don't hand-edit the version field — Changesets owns it.

  3. Publish. Tag the released version and push; the publish.yml workflow verifies package.json matches the tag, runs build + lint + tests, and publishes to npm with provenance:

    git tag v1.2.0
    git push --tags

    pnpm release (pnpm build && changeset publish) publishes locally if you need to bypass the workflow.

Pre-releases

To publish -beta.N prereleases before a final release, enter Changesets pre-release mode first; steps 2–3 above then produce prereleases until you exit:

pnpm changeset pre enter beta   # writes .changeset/pre.json — commit it

While in pre mode:

  • pnpm version-packages computes e.g. 1.2.0-beta.0; running it again after new changesets land bumps to -beta.1, and so on. Final releases are blocked until you exit pre mode.
  • Tag and push as usual (git tag v1.2.0-beta.0). publish.yml publishes any version containing a - under the npm dist-tag beta, so latest keeps pointing at the last final release.
  • Consumed .changeset/*.md files are kept (tracked in .changeset/pre.json) so the final release's changelog can aggregate them — don't delete them by hand.
  • The -beta.N counter is derived from the current package.json version — one more reason never to hand-edit version; a hand-set version shifts the counter.

When the release is ready to go final, exit pre mode and version again:

pnpm changeset pre exit         # removes .changeset/pre.json
pnpm version-packages           # computes the final version and cleans up the consumed changesets

Commit, tag (v1.2.0), and push — the same workflow now publishes under latest.


License

EUPL-1.2

About

A validation framework for checking documents and APIs against specifications using Spectral rulesets.

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Contributors

Languages