Skip to content

F27 — Standalone Command-Line Interface

Overview

Every non-preview capability of this project — validation, linting, diff, bundling, migration — already lives in pure, vscode-free modules (a deliberate architecture principle; see the constitution's Article III and the *-NFR "pure module" requirements throughout specs/). Those modules only have a VS Code front end today. This spec adds a second, equally thin front end: a standalone CLI that reuses the exact same core so the tools work in CI, pre-commit hooks, and any editor-less workflow — without VS Code, and without duplicating a single line of logic.

The CLI ships as its own npm package so a consumer can npx it or install it globally, independent of the extension. This directly serves the project's agnosticity principle: the value lives in tool-neutral, reusable modules, and the extension is just one of several possible front ends.

The existing schema:compat script (F26) is the proof of concept — a headless CLI over schemaDiff + schemaCompat. F27 generalises that pattern into a first-class, multi-command binary.

User Stories

  • As a platform engineer, I want to validate data files and lint schemas in CI without installing VS Code, reusing the same engine my team uses in the editor.
  • As a schema author, I want npx <cli> diff old.json new.json --check to fail a pull request on a breaking change, from any CI provider.
  • As a build-tooling author, I want machine-readable (--json) output so I can wire these checks into my own pipeline.
  • As a maintainer, I want the CLI to reuse the extension's pure core so a fix in one place fixes both, with no divergence.

Functional Requirements

Packaging & invocation

  • F27-FR-01 The CLI MUST be a standalone, publishable npm package with a bin entry, runnable without VS Code. It MUST NOT re-implement any core logic: every subcommand MUST call the same pure modules the extension uses.
  • F27-FR-02 Invoked with no subcommand, --help, or -h, the CLI MUST print usage listing every subcommand and exit 0. --version MUST print the CLI package version and exit 0.
  • F27-FR-03 An unknown subcommand MUST print an error naming it plus the usage summary, and exit with the usage-error code (64).

Subcommands

  • F27-FR-04 validate <data-file> --schema <schema-file> MUST parse the data file by its extension (JSON/JSONC/JSONL/YAML/TOML, reusing F03/F20's parseDataText), validate it against the schema with the draft-aware Ajv pipeline (F03), print each violation with its 1-based line when locatable, and exit 0 when valid or 1 when there is at least one violation.
  • F27-FR-05 lint <schema-file> MUST run the F17 schema-quality rules and print each finding with its rule id and 1-based line. It MUST exit 1 when any finding is at warning severity, else 0 (so lint can gate CI without failing on advisory hints).
  • F27-FR-06 diff <old-schema> <new-schema> MUST print the F15 grouped change report. With --check it MUST additionally compute the F26 compatibility verdict and adopt F26's exit codes (0 compatible, 1 breaking, 2 strict-mode "unknown"); --strict MUST apply F26 strict mode.
  • F27-FR-07 bundle <schema-file> MUST produce a single self-contained schema (F14) by resolving external $refs, and print it to stdout. Local (relative/absolute path) refs MUST be resolved from the filesystem; remote (http(s)) refs MUST be fetched. --dereference MUST inline refs (F14's dereference mode) instead of collecting them under $defs.
  • F27-FR-08 migrate <schema-file> --to <2020-12|2019-09|draft-07> MUST transform the schema to the target draft (F22) and print the migrated schema, reporting the list of changes to stderr (so stdout stays a clean schema).

Subcommands over the remaining non-interactive core

These extend the CLI toward parity with the extension's editor-free features. Each MUST reuse the same pure module the extension uses (F27-FR-01); none may re-implement the logic.

  • F27-FR-11 infer <data-file> [--to <2020-12|2019-09|draft-07>] MUST infer a JSON Schema from a data file (F06, genson-js), declaring the target draft's meta-schema (F22's META_SCHEMA map), and print it to stdout. --to defaults to 2020-12 (the latest draft); an unrecognised value is a usage error (64). The data file MUST be parsed by its extension (JSON/JSONC/JSONL/YAML/TOML); a JSONL file infers over the array of its records.
  • F27-FR-12 sample <schema-file> MUST generate a valid sample instance from a schema (F16) and print it to stdout, resolving same-document $refs. When the schema is unsatisfiable or will not compile it MUST report the failing keyword(s) and exit with the data-error code (65), never emitting an invalid document.
  • F27-FR-13 types <schema-file> [--lang <id>] MUST generate typed source from a schema (F18), bundling external $refs first (F14) so the generator runs on a self-contained document. --lang selects a target from F18's supported set (default typescript); an unknown target is a usage error (64). The generated code MUST be printed to stdout.
  • F27-FR-14 coverage <data-file...> --schema <schema> MUST report which of a schema's declared properties the data exercises (F23) and print the coverage report. It MUST accept one or more data files as positional arguments (any supported format, including JSONL) and union coverage across every record of every file, matching JSONL's existing per-file record union.
  • F27-FR-15 validate <dir> --workspace MUST scan a directory for supported data files, validate each file that carries an inline $schema binding (F10) against that schema (F03/F20), and print the F20 grouped Markdown report. It MUST exit 1 when any file has a validation or binding error, else 0. Files whose $schema is a meta-schema (i.e. schema files, F20-FR-02) MUST NOT be treated as data bindings.
  • F27-FR-16 graph <schema-file> [--svg] MUST print the schema's $ref dependency graph (F24): an adjacency list by default, or the SVG rendering with --svg. External and unresolved refs MUST be shown without fetching.

Output & exit codes

  • F27-FR-09 Every subcommand MUST accept --json, emitting a machine-readable object (result payload + exitCode) on stdout instead of the human report, for pipeline consumption.
  • F27-FR-10 Exit codes MUST be consistent across subcommands: 0 success / clean, 1 a finding or failed check, 2 reserved for diff --check --strict "unknown", 64 a usage error (bad arguments), 65 a data error (a file that cannot be read or parsed). A data or usage error MUST print to stderr and MUST NOT be confused with a clean/finding exit.

Non-Functional Requirements

  • F27-NFR-01 The CLI MUST NOT import the vscode module, directly or transitively — it depends only on the pure core (agnosticity: the CLI must build and run with no editor present). Enforced by the bundle build (a vscode import would fail to resolve outside the extension host).
  • F27-NFR-02 The CLI's argument parsing, command routing, and report formatting MUST live in a pure, vscode-free, I/O-injected module (runCli(argv, io) returning { stdout, stderr, code }) with ≥ 80 % unit-test coverage (Article V). Only the thin executable entry point that wires real stdio/filesystem/fetch and calls process.exit may be I/O-bound, and it is the CLI's sole coverage-excluded file.
  • F27-NFR-03 The published CLI package MUST be self-contained: its bundle MUST inline the core modules it uses so the package has no runtime dependency on the extension's build output (out/ or dist/extension.js).

Acceptance Criteria

  1. node cli/dist/cli.js --help lists validate, lint, diff, bundle, migrate and exits 0.
  2. validate against a schema the data violates exits 1 and prints the offending path with a line number; against valid data it exits 0.
  3. diff a.json b.json --check exits 1 when b drops a required-less type or otherwise breaks compatibility, matching the extension's verdict.
  4. bundle on a schema with a relative $ref prints one document with the ref resolved; running it needs no VS Code.
  5. migrate old.json --to 2020-12 prints a draft-2020-12 schema on stdout and the change list on stderr.

History

  • 2026-07-19 — Initial spec: standalone CLI reusing the pure core (validate/lint/diff/bundle/migrate), published as its own npm package.
  • 2026-07-21 — Added F27-FR-11..16: infer (F06), sample (F16), types (F18), coverage (F23), validate --workspace (F20), and graph (F24), extending the CLI toward the extension's non-interactive surface.
  • 2026-07-25 — F27-FR-11: infer gained --to <draft> to select the declared meta-schema, defaulting to 2020-12 instead of always draft-07. F27-FR-14: coverage now accepts multiple data files, unioning coverage across all of them.

Requirement traceability

Status of each requirement in specs/traceability.json. Test coverage is auto-discovered from [ID] tags in test titles and is not listed here.

RequirementStatusImplementationNote
F27-FR-01manualsrc/cli/bin.ts, webpack.config.js, cli/package.jsonStandalone publishable npm package (cli/) with a bin, built from the pure src/ core; packaging verified by the build/publish, core-reuse is structural (cli.ts imports the same modules).
F27-FR-02implementedsrc/cli/cli.tsrunCli routes no-arg/--help/-h/help to usage and --version to the injected version.
F27-FR-03implementedsrc/cli/cli.tsUnknown subcommand → named error + usage, exit 64.
F27-FR-04implementedsrc/cli/cli.tsvalidate: parseDataText + validateInstances (F03/F20); exit 0 valid / 1 finding.
F27-FR-05implementedsrc/cli/cli.tslint: lintSchema (F17); exit 1 only on a warning-severity finding.
F27-FR-06implementedsrc/cli/cli.tsdiff: diffSchemas (F15); --check adopts F26 verdict + exit codes; --strict.
F27-FR-07implementedsrc/cli/cli.tsbundle: bundleSchema/dereferenceSchema (F14) with a fs/http resolver.
F27-FR-08implementedsrc/cli/cli.tsmigrate: migrateSchema (F22); schema to stdout, changes to stderr.
F27-FR-09implementedsrc/cli/cli.tsEvery subcommand honours --json (payload + exitCode).
F27-FR-10implementedsrc/cli/cli.tsShared exit codes 0/1/2/64/65; data & usage errors go to stderr.
F27-FR-11implementedsrc/cli/cli.tsinfer: genson-js createSchema + META_SCHEMA[--to] (F06/F22), default 2020-12; JSONL infers over records.
F27-FR-12implementedsrc/cli/cli.tssample: generateAndValidate (F16) with a same-document $ref resolver; exit 65 on unsatisfiable.
F27-FR-13implementedsrc/cli/cli.tstypes: bundleSchema (F14) then generateCode (F18); --lang selects a target, unknown → 64.
F27-FR-14implementedsrc/cli/cli.tscoverage: computeCoverage + renderCoverageReport (F23); accepts multiple data-file positionals, instances concatenated.
F27-FR-15implementedsrc/cli/cli.tsvalidate --workspace: walk dir, validate inline-$schema data files (F10/F20), renderMarkdownReport; exit 1 on any error.
F27-FR-16implementedsrc/cli/cli.tsgraph: buildRefGraph + renderAdjacencyList / renderGraphSvg (F24).
F27-NFR-01manualsrc/cli/cli.ts, src/cli/bin.tsNo vscode import, direct or transitive; enforced by the CLI bundle build (a vscode import fails webpack resolution outside the extension host).
F27-NFR-02implementedsrc/cli/cli.ts, src/cli/bin.tsPure, I/O-injected runCli(argv, io) fully unit-tested via in-memory IO; bin.ts is the sole thin, coverage/mutation-excluded entry.
F27-NFR-03manualwebpack.config.js, cli/package.jsonPublished bundle inlines the core (self-contained); verified by the webpack build — no runtime dependency on out/ or dist/extension.js.

Documented in

Documentation sections tagged spec:F27 (or one of its requirement ids) — 631 of ~953 expected words (66%). The expectation comes from this spec's evaluated complexity of 23.8 (19 documentable requirements — 16 FR, 3 NFR — weighted by kind and by each definition's length).

Changes over time

  • 19c80702026-07-25feat(F27): infer --to draft selection, coverage multi-file input
  • 52278b32026-07-21feat(F27): add infer, sample, types, coverage, workspace & graph CLI commands
  • e3211392026-07-20feat(F27): standalone json-schema-tools CLI over the pure core

Released under the MIT License.