Skip to content
Open
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
92 changes: 92 additions & 0 deletions docs/json-contracts.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,98 @@ CI validates them against the packaged schemas with both a Python validator and
a dependency-free Node.js reader; consumers can use the same fixtures as
cross-language conformance tests.


## Strict JSON consumer validation with Node

Base-cli's JSON output is intended to be consumed by strict parsers such as Node's `JSON.parse`.
The following Node script validates that a base-cli JSON envelope (success or error) is valid JSON and conforms to the expected schema:

```javascript
const fs = require('fs');

// Read the JSON output from a file (or stdin)
const jsonOutput = fs.readFileSync(0, 'utf8');

try {
const parsed = JSON.parse(jsonOutput);
// Validate the envelope structure
if (parsed.schema_version !== 1) {
throw new Error('Unsupported schema_version');
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cleanup: the new "Contract fixtures and validator" section is a near-verbatim duplicate of the paragraph already present ~25 lines above it in the same file (both describe CI validating fixtures against packaged schemas via a Python validator and a Node.js reader). Consider merging to avoid having to update the same claim in two places.

if (!['base-cli.output', 'base-cli.error'].includes(parsed.schema)) {
throw new Error('Unexpected schema');
}
if (typeof parsed.code !== 'string') {
throw new Error('Missing or invalid code');
}
console.log('Valid base-cli JSON envelope');
} catch (err) {
console.error('Invalid base-cli JSON envelope:', err.message);
process.exit(1);
}
```

For NDJSON output (e.g., from `--format ndjson`), each line is a separate JSON object.
Validate each line individually:

```javascript
const fs = require('fs');
const readline = require('readline');

const rl = readline.createInterface({
input: process.stdin,
crlfDelay: Infinity,
});

rl.on('line', (line) => {
try {
const parsed = JSON.parse(line);
if (parsed.schema_version !== 1) {
throw new Error('Unsupported schema_version');
}
if (parsed.schema !== 'base-cli.record') {
throw new Error('Unexpected schema for NDJSON record');
}
// Optionally validate the record shape
} catch (err) {
console.error('Invalid NDJSON line:', err.message);
process.exit(1);
}
});
```

### Failure shape

When base-cli emits an error envelope (e.g., due to invalid usage or an internal error), the JSON will have:
- `schema: "base-cli.error"`
- `type: "error"`
- `code`: a string indicating the error type (e.g., `usage_error`, `unexpected_error`)
- `details`: an object containing `exit_code` and the captured stdout (if any)

Example error envelope for a missing required argument:

```json
{
"schema_version": 1,
"schema": "base-cli.error",
"code": "usage_error",
"type": "error",
"message": "Missing argument 'NAME'...",
"details": {
"exit_code": 2,
"stdout": ""
},
"run_id": null
}
```

### Contract fixtures and validator

Golden payloads for each public contract live in
[`tests/fixtures/contracts`](https://github.com/basefoundry/base-cli/tree/main/tests/fixtures/contracts).
The CI validates them against the packaged schemas with both a Python validator and a dependency-free Node.js reader.
Consumers can use the same fixtures as cross-language conformance tests.

## Inspection envelopes

Read-only inspection commands can use the stable inspection helpers when their
Expand Down
9 changes: 9 additions & 0 deletions docs/output-contracts.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,12 @@ For an optional polished human table, pass `rich=True` to `render_records()`.
Rich is consulted only for interactive `text`; all redirected and structured
formats retain the rules above and fall back to the built-in renderer if Rich
is unavailable or fails.

## Optional output-format dependencies

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doc accuracy: this table says a missing PyYAML surfaces a raw ImportError: No module named 'yaml', but the code never lets that propagate — require_yaml() (lib/python/base_cli/_dependencies.py) catches ImportError and re-raises a RuntimeError with an actionable install hint, which the caller wraps into OutputFormatError. A user grepping logs for the documented string won't find it, and the doc omits the actual (more useful) message the CLI produces.

Some output formats require additional dependencies. Install the corresponding extra to use them.

| Format | Extra | Install command | If missing |
|--------|-------|-----------------|------------|
| YAML | yaml | `pip install base-cli[yaml]` | `ImportError: No module named 'yaml'` |
| Rich text (when `rich=True` is used in `render_records()`) | rich | `pip install base-cli[rich]` | Falls back to the built-in text renderer (no error, but output lacks Rich styling) |
Loading