@amritk/mjst 0.20.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AI.md +7 -2
- package/README.md +325 -1
- package/dist/cli.js +9 -0
- package/dist/help-text.d.ts +1 -1
- package/dist/help-text.js +1 -0
- package/dist/markdown/help-text.d.ts +6 -0
- package/dist/markdown/help-text.js +34 -0
- package/dist/markdown/parse-markdown-args.d.ts +34 -0
- package/dist/markdown/parse-markdown-args.js +97 -0
- package/dist/markdown/run.d.ts +9 -0
- package/dist/markdown/run.js +72 -0
- package/package.json +2 -2
package/AI.md
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# @amritk/mjst (the `mjst` CLI) — notes for AI coding agents
|
|
2
2
|
|
|
3
3
|
The command-line entry point to mjst: generate TypeScript parsers, validators,
|
|
4
|
-
and types from JSON Schema, plus `lint
|
|
4
|
+
and types from JSON Schema, plus `lint`, `compile-api`, and `markdown`
|
|
5
|
+
subcommands. Full
|
|
5
6
|
reference is [README.md](./README.md); config options are in
|
|
6
7
|
[config.schema.json](./config.schema.json).
|
|
7
8
|
|
|
@@ -9,7 +10,7 @@ reference is [README.md](./README.md); config options are in
|
|
|
9
10
|
> package ships **only a `mjst` binary** — there are no JS exports. For the
|
|
10
11
|
> programmatic API import `@amritk/generate-parsers` etc. instead.
|
|
11
12
|
|
|
12
|
-
## The
|
|
13
|
+
## The four commands
|
|
13
14
|
|
|
14
15
|
```bash
|
|
15
16
|
# 1. Codegen (default command)
|
|
@@ -20,6 +21,10 @@ mjst lint "**/*.{yaml,json}" -r .lint.yaml
|
|
|
20
21
|
|
|
21
22
|
# 3. Compile an @amritk/api routes module to a fused, eval-free handler
|
|
22
23
|
mjst compile-api ./routes.ts --out ./dist/handler.ts
|
|
24
|
+
|
|
25
|
+
# 4. Render a schema as documentation (pages by default, --table for one table)
|
|
26
|
+
mjst markdown ./config.schema.json --out-dir ./docs
|
|
27
|
+
mjst markdown ./config.schema.json --table --readme ./README.md
|
|
23
28
|
```
|
|
24
29
|
|
|
25
30
|
## Codegen gotchas
|
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
|
|
22
22
|
Options can be supplied via CLI flags or a JSON config file. **CLI flags always take precedence over config file values.**
|
|
23
23
|
|
|
24
|
-
It also carries a `lint` subcommand — `mjst lint <files>` — a format-agnostic JSON/YAML style-guide linter powered by [`@amritk/lint`](../lint) (see [Linting](#linting)),
|
|
24
|
+
It also carries a `lint` subcommand — `mjst lint <files>` — a format-agnostic JSON/YAML style-guide linter powered by [`@amritk/lint`](../lint) (see [Linting](#linting)), a `compile-api` subcommand — `mjst compile-api <routes-module> --out <file>` — that compiles [`@amritk/api`](../api) route contracts into a production fetch-handler module (see [Compiling an API](#compiling-an-api)), and a `markdown` subcommand — `mjst markdown <schema>` — that renders a schema as documentation with [`@amritk/generate-markdown`](../generate-markdown) (see [Generating markdown docs](#generating-markdown-docs)).
|
|
25
25
|
|
|
26
26
|
---
|
|
27
27
|
|
|
@@ -279,6 +279,330 @@ The exit code is `0` on success, `1` when compilation fails (unloadable module,
|
|
|
279
279
|
|
|
280
280
|
---
|
|
281
281
|
|
|
282
|
+
## Generating markdown docs
|
|
283
|
+
|
|
284
|
+
`mjst markdown` renders a JSON Schema as documentation with
|
|
285
|
+
[`@amritk/generate-markdown`](../generate-markdown), so a config's docs cannot
|
|
286
|
+
drift from the schema that validates it. Like `lint` and `compile-api`, it has
|
|
287
|
+
its own flags (and its own `--help`), independent of the generation flags above.
|
|
288
|
+
|
|
289
|
+
```bash
|
|
290
|
+
# The prose reference: a heading, a type, the description and an example per
|
|
291
|
+
# property, across as many pages as the schema's x-doc keyword declares
|
|
292
|
+
npx mjst markdown ./config.schema.json --out-dir ./docs
|
|
293
|
+
|
|
294
|
+
# The other shape: one HTML table, spliced into an existing markdown file
|
|
295
|
+
npx mjst markdown ./config.schema.json --table --readme ./README.md
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
| Flag | Description |
|
|
299
|
+
| --- | --- |
|
|
300
|
+
| `<schema>` | The JSON Schema to document (positional, required). |
|
|
301
|
+
| `--out-dir` | Directory the prose reference pages are written to (default: the current directory). |
|
|
302
|
+
| `--file` | Output path of the index page (default: the schema's `x-doc.file`, then `index.md`). |
|
|
303
|
+
| `--title` | Page title (default: the schema's `title`). |
|
|
304
|
+
| `--language` | Fence language for derived examples and literals (default: `json`). |
|
|
305
|
+
| `--layout` | Default layout for nested properties: `headings` (default), `table`, or `none`. |
|
|
306
|
+
| `--sort` | Property order: `schema` (default) or `alphabetical`. |
|
|
307
|
+
| `--heading-level` | Heading level of the page title, 1-6 (default: `1`). |
|
|
308
|
+
| `--table` | Render the HTML config table instead of the prose pages. |
|
|
309
|
+
| `--readme` | Markdown file the table is spliced into (default: `README.md`). Requires `--table`. |
|
|
310
|
+
|
|
311
|
+
Under `--table` only the content between `<!-- config-table-start -->
|
|
312
|
+
<table>
|
|
313
|
+
<thead>
|
|
314
|
+
<tr>
|
|
315
|
+
<th>Property</th>
|
|
316
|
+
<th>CLI Flag</th>
|
|
317
|
+
<th>Type</th>
|
|
318
|
+
<th align="center">Default</th>
|
|
319
|
+
</tr>
|
|
320
|
+
</thead>
|
|
321
|
+
<tbody>
|
|
322
|
+
<tr>
|
|
323
|
+
<td>📄 <code>schema</code></td>
|
|
324
|
+
<td><code>--schema <path></code></td>
|
|
325
|
+
<td><code>string</code></td>
|
|
326
|
+
<td align="center"></td>
|
|
327
|
+
</tr>
|
|
328
|
+
<tr>
|
|
329
|
+
<td colspan="4">Path to the schema to process. With the default 'json' input this is a JSON Schema file that is read and parsed. With any other input format it is a JS/TS module that exports a schema, which is loaded and converted to JSON Schema via the matching adapter. Either 'schema' or 'schemaDir' is required.<br><strong>Examples:</strong> <code>"./schema.json"</code></td>
|
|
330
|
+
</tr>
|
|
331
|
+
<tr>
|
|
332
|
+
<td>🗂️ <code>schemaDir</code></td>
|
|
333
|
+
<td><code>--schema-dir <dir></code></td>
|
|
334
|
+
<td><code>string</code></td>
|
|
335
|
+
<td align="center"></td>
|
|
336
|
+
</tr>
|
|
337
|
+
<tr>
|
|
338
|
+
<td colspan="4">Path to a directory of JSON Schema files. When set, the CLI walks the directory recursively, generates parsers for every '*.json' schema it finds, and mirrors the directory layout under outDir (each schema lands in its own subdirectory). The runtime helpers are emitted once into a shared outDir/_helpers/ that every nested parser imports from. Mutually exclusive with 'schema'; when both are present 'schemaDir' wins. Only JSON Schema input is supported in this mode.<br><strong>Examples:</strong> <code>"./schemas"</code></td>
|
|
339
|
+
</tr>
|
|
340
|
+
<tr>
|
|
341
|
+
<td>🔌 <code>input</code></td>
|
|
342
|
+
<td><code>--input <format></code></td>
|
|
343
|
+
<td><code>string</code></td>
|
|
344
|
+
<td align="center"><code>"json"</code></td>
|
|
345
|
+
</tr>
|
|
346
|
+
<tr>
|
|
347
|
+
<td colspan="4">Source format of the schema. 'json' (default) reads a JSON Schema file directly. 'avro' reads an Apache Avro '.avsc' JSON document and converts it, needing no extra library. 'asyncapi' reads an AsyncAPI 2.x/3.0 document (JSON or YAML) and generates from every message payload/headers schema it declares. Any other format loads 'schema' as a module and converts it to JSON Schema with the matching adapter. Supported: 'typebox', 'zod' (zod v4+), 'valibot' (with @valibot/to-json-schema), and 'effect' — each requires the corresponding library installed in your project.<br><strong>Allowed:</strong> <code>"json"</code>, <code>"typebox"</code>, <code>"zod"</code>, <code>"valibot"</code>, <code>"effect"</code>, <code>"avro"</code>, <code>"asyncapi"</code></td>
|
|
348
|
+
</tr>
|
|
349
|
+
<tr>
|
|
350
|
+
<td>📦 <code>export</code></td>
|
|
351
|
+
<td><code>--export <name></code></td>
|
|
352
|
+
<td><code>string</code></td>
|
|
353
|
+
<td align="center"></td>
|
|
354
|
+
</tr>
|
|
355
|
+
<tr>
|
|
356
|
+
<td colspan="4">Which export of the schema module to use when 'input' names a module format (anything but 'json', 'avro', and 'asyncapi', which are read as data). Defaults to the default export, or the sole named export when the module has exactly one.</td>
|
|
357
|
+
</tr>
|
|
358
|
+
<tr>
|
|
359
|
+
<td>📁 <code>outDir</code></td>
|
|
360
|
+
<td><code>--out-dir <dir></code></td>
|
|
361
|
+
<td><code>string</code></td>
|
|
362
|
+
<td align="center"></td>
|
|
363
|
+
</tr>
|
|
364
|
+
<tr>
|
|
365
|
+
<td colspan="4">Output directory for generated TypeScript files. The directory is created automatically if it does not exist. Subdirectories are created as needed when a generated file includes a nested path. Mutually exclusive with 'outFile'.<br><strong>Examples:</strong> <code>"./generated"</code></td>
|
|
366
|
+
</tr>
|
|
367
|
+
<tr>
|
|
368
|
+
<td>📄 <code>outFile</code></td>
|
|
369
|
+
<td><code>--out-file <file></code></td>
|
|
370
|
+
<td><code>string</code></td>
|
|
371
|
+
<td align="center"></td>
|
|
372
|
+
</tr>
|
|
373
|
+
<tr>
|
|
374
|
+
<td colspan="4">Output everything to a single TypeScript file instead of a directory. Every generated definition is concatenated into one self-contained file and the cross-file imports are dropped. Mutually exclusive with 'outDir'. Currently supported only together with 'typesOnly'.<br><strong>Examples:</strong> <code>"./generated/schema.ts"</code></td>
|
|
375
|
+
</tr>
|
|
376
|
+
<tr>
|
|
377
|
+
<td>🏷️ <code>typesOnly</code></td>
|
|
378
|
+
<td><code>--types-only</code></td>
|
|
379
|
+
<td><code>boolean</code></td>
|
|
380
|
+
<td align="center"><code>false</code></td>
|
|
381
|
+
</tr>
|
|
382
|
+
<tr>
|
|
383
|
+
<td colspan="4">Generate only TypeScript type definitions without parser functions. Useful when you only need the type shapes and do not need runtime validation.</td>
|
|
384
|
+
</tr>
|
|
385
|
+
<tr>
|
|
386
|
+
<td>✅ <code>validators</code></td>
|
|
387
|
+
<td><code>--validators</code></td>
|
|
388
|
+
<td><code>boolean</code></td>
|
|
389
|
+
<td align="center"><code>false</code></td>
|
|
390
|
+
</tr>
|
|
391
|
+
<tr>
|
|
392
|
+
<td colspan="4">Also emit validation functions alongside the parsers. For every generated type X the CLI writes a validateX (returning a rich ValidationResult with JSON-Pointer error paths) and an isX boolean type guard. The files land in a validators/ subdirectory of the output so they never collide with the parser files, which share the same schema-derived names. Works with both schema and schemaDir. Incompatible with typesOnly and outFile, which produce no runtime code.</td>
|
|
393
|
+
</tr>
|
|
394
|
+
<tr>
|
|
395
|
+
<td>🎲 <code>examples</code></td>
|
|
396
|
+
<td><code>--examples</code></td>
|
|
397
|
+
<td><code>boolean</code></td>
|
|
398
|
+
<td align="center"><code>false</code></td>
|
|
399
|
+
</tr>
|
|
400
|
+
<tr>
|
|
401
|
+
<td colspan="4">Also emit test-data files for every schema into an examples/ subdirectory of the output destination: a fast-check arbitrary (FooArbitrary) that produces schema-valid values and a concrete fooExample value. Under schemaDir the examples mirror the schema layout so they never collide with the parser output. The generated arbitraries import fast-check, which consumers must install as a (dev) dependency; the static example values have no runtime dependencies.</td>
|
|
402
|
+
</tr>
|
|
403
|
+
<tr>
|
|
404
|
+
<td>🔌 <code>messageContracts</code></td>
|
|
405
|
+
<td><code>--message-contracts</code></td>
|
|
406
|
+
<td><code>boolean</code></td>
|
|
407
|
+
<td align="center"><code>false</code></td>
|
|
408
|
+
</tr>
|
|
409
|
+
<tr>
|
|
410
|
+
<td colspan="4">Also emit a @amritk/api channel contract per AsyncAPI channel into a contracts/ subdirectory: one defineMessages({ ... }) module naming every message that channel carries — keyed by the value its discriminator property carries on the wire, with that property stripped out of each payload because the runtime removes it before validating — plus a barrel re-exporting them. Requires input 'asyncapi', which is the only input that has channels and operations to project from. Incompatible with typesOnly: a contract is a runtime value, not a type. The generated modules import @amritk/api, which the consuming project must install; it is not a dependency of the generated parsers.</td>
|
|
411
|
+
</tr>
|
|
412
|
+
<tr>
|
|
413
|
+
<td>🏷️ <code>discriminator</code></td>
|
|
414
|
+
<td><code>--discriminator <prop></code></td>
|
|
415
|
+
<td><code>string</code></td>
|
|
416
|
+
<td align="center"><code>"type"</code></td>
|
|
417
|
+
</tr>
|
|
418
|
+
<tr>
|
|
419
|
+
<td colspan="4">Property name that says which message a frame is, for the contracts messageContracts emits. Defaults to 'type', matching @amritk/api. A channel that declares its own with 'x-mjst': { 'discriminator': '...' } keeps it — one flag covers a whole run, and a run may span channels that disagree. Only meaningful together with messageContracts.<br><strong>Examples:</strong> <code>"event"</code></td>
|
|
420
|
+
</tr>
|
|
421
|
+
<tr>
|
|
422
|
+
<td>🔨 <code>build</code></td>
|
|
423
|
+
<td><code>--build</code></td>
|
|
424
|
+
<td><code>boolean</code></td>
|
|
425
|
+
<td align="center"><code>false</code></td>
|
|
426
|
+
</tr>
|
|
427
|
+
<tr>
|
|
428
|
+
<td colspan="4">Compile the generated TypeScript files to .js and .d.ts output. A temporary tsconfig is written to the output directory, tsc is invoked, and the intermediate .ts source files are removed when compilation succeeds.</td>
|
|
429
|
+
</tr>
|
|
430
|
+
<tr>
|
|
431
|
+
<td>💥 <code>force</code></td>
|
|
432
|
+
<td><code>--force</code></td>
|
|
433
|
+
<td><code>boolean</code></td>
|
|
434
|
+
<td align="center"><code>false</code></td>
|
|
435
|
+
</tr>
|
|
436
|
+
<tr>
|
|
437
|
+
<td colspan="4">Deprecated and ignored. Generated files always replace whatever is at their path, so this flag no longer does anything; it is still accepted so existing scripts and config files keep running. Reverting an unwanted replacement is version control's job. Compilation under build still only removes the intermediate sources the run itself generated.</td>
|
|
438
|
+
</tr>
|
|
439
|
+
<tr>
|
|
440
|
+
<td>⚠️ <code>logWarnings</code></td>
|
|
441
|
+
<td><code>--log-warnings</code></td>
|
|
442
|
+
<td><code>boolean</code></td>
|
|
443
|
+
<td align="center"><code>false</code></td>
|
|
444
|
+
</tr>
|
|
445
|
+
<tr>
|
|
446
|
+
<td colspan="4">Emit a console.warn in the generated parsers for every input key that is not declared in the schema's properties. Useful for detecting schema drift or unexpected data shapes at runtime.</td>
|
|
447
|
+
</tr>
|
|
448
|
+
<tr>
|
|
449
|
+
<td>🚫 <code>strict</code></td>
|
|
450
|
+
<td><code>--strict</code></td>
|
|
451
|
+
<td><code>boolean</code></td>
|
|
452
|
+
<td align="center"><code>false</code></td>
|
|
453
|
+
</tr>
|
|
454
|
+
<tr>
|
|
455
|
+
<td colspan="4">Generate parsers that throw on type/shape mismatches (wrong type, missing required property, enum/pattern/min/max violations) instead of coercing invalid input to default values. When a schema sets additionalProperties: false, undeclared keys throw too; otherwise they are still allowed.</td>
|
|
456
|
+
</tr>
|
|
457
|
+
<tr>
|
|
458
|
+
<td>🧹 <code>stripUnknown</code></td>
|
|
459
|
+
<td><code>--strip-unknown</code></td>
|
|
460
|
+
<td><code>boolean</code></td>
|
|
461
|
+
<td align="center"><code>false</code></td>
|
|
462
|
+
</tr>
|
|
463
|
+
<tr>
|
|
464
|
+
<td colspan="4">Build each parser's result from the schema's declared properties only, silently dropping undeclared input keys at every nesting level (zod's .strip()). Extras are never a validation error, so this composes with strict (which still throws on wrong types and missing required properties) and yields to additionalProperties: false, which rejects rather than strips in strict mode.</td>
|
|
465
|
+
</tr>
|
|
466
|
+
<tr>
|
|
467
|
+
<td>🔡 <code>caseInsensitive</code></td>
|
|
468
|
+
<td><code>--case-insensitive</code></td>
|
|
469
|
+
<td><code>boolean</code></td>
|
|
470
|
+
<td align="center"><code>false</code></td>
|
|
471
|
+
</tr>
|
|
472
|
+
<tr>
|
|
473
|
+
<td colspan="4">Normalize a mis-cased string to the exact casing of a declared enum/const member it matches case-insensitively (e.g. hElLo → hello) instead of coercing to the default. Coerce mode only — strict parsers still reject a casing mismatch. Correctly-cased input keeps the exact-match fast path, so the hot path is unaffected.</td>
|
|
474
|
+
</tr>
|
|
475
|
+
<tr>
|
|
476
|
+
<td>🔒 <code>readonly</code></td>
|
|
477
|
+
<td><code>--readonly</code></td>
|
|
478
|
+
<td><code>boolean</code></td>
|
|
479
|
+
<td align="center"><code>false</code></td>
|
|
480
|
+
</tr>
|
|
481
|
+
<tr>
|
|
482
|
+
<td colspan="4">Emit every property, array, and record in the generated type definitions as readonly, producing deeply immutable types. Affects type definitions only; the generated parsers still build and return plain objects.</td>
|
|
483
|
+
</tr>
|
|
484
|
+
<tr>
|
|
485
|
+
<td>🔢 <code>unknownKeys</code></td>
|
|
486
|
+
<td><code>--unknown-keys <strategy></code></td>
|
|
487
|
+
<td><code>string</code></td>
|
|
488
|
+
<td align="center"><code>"count-keys"</code></td>
|
|
489
|
+
</tr>
|
|
490
|
+
<tr>
|
|
491
|
+
<td colspan="4">How generated fast paths (parsers and validators alike) prove a closed object — additionalProperties: false, or a stripUnknown build — carries no undeclared key. 'count-keys' compares Object.keys(obj).length and is the faster form on JavaScriptCore (Bun), where a for…in over a non-extensible object takes the engine's slow path and halves strict-parse throughput. 'count-enumerable' counts with for…in and allocates nothing; it was the faster form on Node 22, but on Node 26 'count-keys' ties or wins there too. Keep the default unless you are pinned to an older V8 and have measured your own shapes; the generated code never detects its runtime.<br><strong>Allowed:</strong> <code>"count-enumerable"</code>, <code>"count-keys"</code></td>
|
|
492
|
+
</tr>
|
|
493
|
+
<tr>
|
|
494
|
+
<td>🧪 <code>formats</code></td>
|
|
495
|
+
<td><code>--formats <list></code></td>
|
|
496
|
+
<td><code>string | array</code></td>
|
|
497
|
+
<td align="center"></td>
|
|
498
|
+
</tr>
|
|
499
|
+
<tr>
|
|
500
|
+
<td colspan="4">String formats the generated validators enforce: "all", or a comma-separated list (e.g. uuid,date-time,email). Omitted, format stays an annotation — JSON Schema's own reading, and what @amritk/runtime-validators does when given no formats. Set it to whatever validates the same schemas at runtime (@amritk/lint and createApi({ formats }) run the interpreter with formats on) so the build-time and runtime answers agree. Both validateX and the flat isX check them, against a formats.ts emitted alongside the validators.</td>
|
|
501
|
+
</tr>
|
|
502
|
+
<tr>
|
|
503
|
+
<td>🧰 <code>helpers</code></td>
|
|
504
|
+
<td><code>--helpers <mode></code></td>
|
|
505
|
+
<td><code>string</code></td>
|
|
506
|
+
<td align="center"></td>
|
|
507
|
+
</tr>
|
|
508
|
+
<tr>
|
|
509
|
+
<td colspan="4">Controls how generated parsers reference their runtime helpers. 'package' emits imports from @amritk/helpers (requires it to be installed in the consumer project). 'embedded' ships the helper source under outDir/_helpers/ so the output is self-contained. When omitted, the CLI auto-detects: it picks 'package' only when @amritk/helpers is a declared dependency (dependencies/devDependencies) of the nearest package.json above outDir, otherwise 'embedded'. Requiring a declaration (rather than mere resolvability) keeps auto-detected 'package' output portable across pnpm/isolated installs, where an undeclared transitive @amritk/helpers is not reachable at runtime.<br><strong>Allowed:</strong> <code>"package"</code>, <code>"embedded"</code></td>
|
|
510
|
+
</tr>
|
|
511
|
+
<tr>
|
|
512
|
+
<td>🏷️ <code>typeSuffix</code></td>
|
|
513
|
+
<td><code>--type-suffix <suffix></code></td>
|
|
514
|
+
<td><code>string</code></td>
|
|
515
|
+
<td align="center"><code>""</code></td>
|
|
516
|
+
</tr>
|
|
517
|
+
<tr>
|
|
518
|
+
<td colspan="4">Suffix appended to every generated type name derived from a $ref (e.g. 'Object' turns Contact into ContactObject). Defaults to no suffix. The root type name is used verbatim and is unaffected.</td>
|
|
519
|
+
</tr>
|
|
520
|
+
<tr>
|
|
521
|
+
<td>🪧 <code>banner</code></td>
|
|
522
|
+
<td><code>--banner [text]</code></td>
|
|
523
|
+
<td><code>boolean | string</code></td>
|
|
524
|
+
<td align="center"><code>false</code></td>
|
|
525
|
+
</tr>
|
|
526
|
+
<tr>
|
|
527
|
+
<td colspan="4">Prepend a comment header to every generated file (excluding _helpers/). true uses the default message ('This file was auto-generated by @amritk/mjst…'); a string uses that text as the message body, wrapped in a JSDoc block; false or omitted emits no header. On the CLI, --banner alone enables the default message and --banner <text> sets a custom one.</td>
|
|
528
|
+
</tr>
|
|
529
|
+
<tr>
|
|
530
|
+
<td>🔗 <code>importExt</code></td>
|
|
531
|
+
<td><code>--import-ext <ext></code></td>
|
|
532
|
+
<td><code>string</code></td>
|
|
533
|
+
<td align="center"><code>"ts"</code></td>
|
|
534
|
+
</tr>
|
|
535
|
+
<tr>
|
|
536
|
+
<td colspan="4">Extension emitted on every relative import specifier in the generated output (cross-file $ref imports, the index barrel, and embedded-helper imports). 'ts' (default) emits the literal on-disk paths so the generated .ts sources load directly under Bun, Node's type stripping (Node 22.6+ with --experimental-strip-types, unflagged from 22.18/23), and tsc when the consumer sets allowImportingTsExtensions — no build step required. 'js' is the standard TS NodeNext form ('./x.js' resolving to a sibling x.ts) for output you compile; it is selected automatically under 'build' since tsc refuses to emit from .ts specifiers. Passing --import-ext ts together with --build is an error.<br><strong>Allowed:</strong> <code>"js"</code>, <code>"ts"</code></td>
|
|
537
|
+
</tr>
|
|
538
|
+
<tr>
|
|
539
|
+
<td>🌱 <code>rootType</code></td>
|
|
540
|
+
<td><code>--root-type <name></code></td>
|
|
541
|
+
<td><code>string</code></td>
|
|
542
|
+
<td align="center"></td>
|
|
543
|
+
</tr>
|
|
544
|
+
<tr>
|
|
545
|
+
<td colspan="4">Name for the root type of a single --schema run (e.g. 'Program' yields parseProgram / validateProgram). When omitted, the name is derived from the schema's title, falling back to the schema filename in PascalCase (spec-plan.json → SpecPlan) and then to 'Document'. Not supported with --schema-dir, where each schema derives its own root type from its filename.<br><strong>Examples:</strong> <code>"Program"</code></td>
|
|
546
|
+
</tr>
|
|
547
|
+
<tr>
|
|
548
|
+
<td>🌐 <code>resolveRemote</code></td>
|
|
549
|
+
<td><code>--resolve-remote</code></td>
|
|
550
|
+
<td><code>boolean</code></td>
|
|
551
|
+
<td align="center"><code>false</code></td>
|
|
552
|
+
</tr>
|
|
553
|
+
<tr>
|
|
554
|
+
<td colspan="4">Allow fetching http(s) $refs while dereferencing a schema that contains cross-file or remote references. Off by default, so generation stays offline unless asked: a schema with a remote $ref fails rather than making a network call. Local cross-file $refs are always resolved and do not need this flag. A non-empty allowedHosts implies this.</td>
|
|
555
|
+
</tr>
|
|
556
|
+
<tr>
|
|
557
|
+
<td>🛡️ <code>allowedHosts</code></td>
|
|
558
|
+
<td><code>--allowed-hosts <hosts></code></td>
|
|
559
|
+
<td><code>array</code></td>
|
|
560
|
+
<td align="center"></td>
|
|
561
|
+
</tr>
|
|
562
|
+
<tr>
|
|
563
|
+
<td colspan="4">Restrict remote $ref fetches to these hosts (e.g. api.example.com). Accepts a comma-separated list or the flag repeated. Providing an allow-list is itself an opt-in to remote fetching (it implies resolveRemote), and each listed host bypasses the private-host guard.<br><strong>Examples:</strong> <code>["schemas.example.com"]</code></td>
|
|
564
|
+
</tr>
|
|
565
|
+
<tr>
|
|
566
|
+
<td>🔓 <code>allowPrivateHosts</code></td>
|
|
567
|
+
<td><code>--allow-private-hosts</code></td>
|
|
568
|
+
<td><code>boolean</code></td>
|
|
569
|
+
<td align="center"><code>false</code></td>
|
|
570
|
+
</tr>
|
|
571
|
+
<tr>
|
|
572
|
+
<td colspan="4">Permit remote $refs to private, loopback, and link-local hosts. Off by default: such targets are refused as a best-effort SSRF guard (notably the 169.254.169.254 cloud-metadata endpoint). An explicit allowedHosts entry always bypasses this guard.</td>
|
|
573
|
+
</tr>
|
|
574
|
+
<tr>
|
|
575
|
+
<td>📂 <code>allowedRoots</code></td>
|
|
576
|
+
<td><code>--allowed-roots <dirs></code></td>
|
|
577
|
+
<td><code>array</code></td>
|
|
578
|
+
<td align="center"></td>
|
|
579
|
+
</tr>
|
|
580
|
+
<tr>
|
|
581
|
+
<td colspan="4">Extra directories a local (cross-file) $ref may resolve into. By default a local $ref is confined to the schema's own directory, so '{"$ref": "../../../secrets.env"}' (or an absolute path) reads nothing. List a root here for the ordinary split-spec layout where v1/api.json refers to a shared common/user.json one level up. The schema's own directory is always allowed, so these roots widen the default rather than replacing it, and a $ref that still lands outside every root is refused. Accepts a comma-separated list or the flag repeated; relative entries resolve against the current working directory, from a config file as well as from the flag.<br><strong>Examples:</strong> <code>["./specs"]</code></td>
|
|
582
|
+
</tr>
|
|
583
|
+
<tr>
|
|
584
|
+
<td>⚙️ <code>config</code></td>
|
|
585
|
+
<td><code>--config <path></code></td>
|
|
586
|
+
<td><code>string</code></td>
|
|
587
|
+
<td align="center"></td>
|
|
588
|
+
</tr>
|
|
589
|
+
<tr>
|
|
590
|
+
<td colspan="4">Path to a JSON config file. Keys match the option names in this schema (schema, schemaDir, input, export, outDir, outFile, typesOnly, validators, examples, build, force, logWarnings, strict, stripUnknown, caseInsensitive, readonly, unknownKeys, helpers, typeSuffix, banner, importExt, rootType, resolveRemote, allowedHosts, allowPrivateHosts, allowedRoots) and are validated: an unknown key or a wrong value type fails the run instead of being ignored. CLI flags take precedence over config file values.<br><strong>Examples:</strong> <code>"./mjst.config.json"</code></td>
|
|
591
|
+
</tr>
|
|
592
|
+
</tbody>
|
|
593
|
+
</table>
|
|
594
|
+
<!-- config-table-end -->` is replaced; a file that exists without both markers
|
|
595
|
+
is left untouched and the run fails, rather than clobbering hand-written prose.
|
|
596
|
+
The prose pages have no such deal — the generator owns them and overwrites them
|
|
597
|
+
wholesale. The table and page flags are mutually exclusive: passing a page flag
|
|
598
|
+
with `--table` is a usage error rather than a silently ignored option.
|
|
599
|
+
|
|
600
|
+
The exit code is `0` on success, `1` when generation fails (malformed schema, a
|
|
601
|
+
readme without markers, a page declaration the schema contradicts), and `2` on a
|
|
602
|
+
usage error. Run `mjst markdown --help` for details.
|
|
603
|
+
|
|
604
|
+
---
|
|
605
|
+
|
|
282
606
|
## Configuration reference
|
|
283
607
|
|
|
284
608
|
<!-- config-table-start -->
|
package/dist/cli.js
CHANGED
|
@@ -368,6 +368,15 @@ const run = async () => {
|
|
|
368
368
|
process.stderr.write(result.stderr);
|
|
369
369
|
process.exit(result.code);
|
|
370
370
|
}
|
|
371
|
+
if (args[0] === "markdown") {
|
|
372
|
+
const { run: markdownRun } = await import("./markdown/run.js");
|
|
373
|
+
const result = await markdownRun(args.slice(1));
|
|
374
|
+
if (result.stdout)
|
|
375
|
+
process.stdout.write(result.stdout);
|
|
376
|
+
if (result.stderr)
|
|
377
|
+
process.stderr.write(result.stderr);
|
|
378
|
+
process.exit(result.code);
|
|
379
|
+
}
|
|
371
380
|
const metaRequest = parseMetaRequest(args);
|
|
372
381
|
if (metaRequest === "version") {
|
|
373
382
|
console.log(await readVersion());
|
package/dist/help-text.d.ts
CHANGED
|
@@ -4,4 +4,4 @@
|
|
|
4
4
|
* documents these flags (config.schema.json) is not shipped in the published
|
|
5
5
|
* package; the help-text test cross-checks that every flag is listed.
|
|
6
6
|
*/
|
|
7
|
-
export declare const HELP_TEXT = "mjst \u2014 generate TypeScript parsers and type definitions from JSON Schemas\n\nUsage:\n mjst --schema <path> --out-dir <dir> [options]\n mjst --schema-dir <dir> --out-dir <dir> [options]\n mjst --schema <path> --out-file <file> --types-only [options]\n\nSubcommands (each with its own flags and --help):\n mjst lint <files> Lint JSON/YAML documents against a ruleset\n mjst compile-api <module> --out <file> Compile @amritk/api route contracts into a fetch-handler module\n\nInput:\n --schema <path> Schema to process: a JSON Schema file, or a module when --input is set\n --schema-dir <dir> Directory of JSON Schemas, processed recursively (instead of --schema)\n --input <format> Schema source format: json (default), typebox, zod, valibot, effect, avro,\n or asyncapi (an AsyncAPI 2.x/3.0 document, JSON or YAML)\n --export <name> Which export of the schema module to use when --input names a module format\n\nOutput:\n --out-dir <dir> Output directory for the generated files\n --out-file <file> Single-file output instead of a directory (requires --types-only)\n --types-only Generate type definitions only, without parser functions\n --validators Also emit validateX/isX validators into a validators/ subdir\n --examples Also emit fast-check arbitraries + example values under examples/\n --message-contracts Also emit @amritk/api channel contracts under contracts/ (--input asyncapi only)\n --discriminator <p> Property naming which message a frame is, for --message-contracts (default: type)\n --build Compile the generated files to .js/.d.ts (implies --import-ext js)\n --force Deprecated; ignored (generated files always overwrite)\n --import-ext <ext> Extension on generated relative imports: ts (default) or js\n --helpers <mode> Runtime helpers: package or embedded (default: auto-detect from package.json)\n --root-type <name> Root type name for a single --schema run (default: schema title or filename)\n --type-suffix <s> Suffix appended to every $ref-derived type name\n --banner [text] Prepend a header comment to every generated file\n --readonly Emit deeply readonly type definitions\n\nValidation:\n --strict Throw on type/shape mismatches instead of coercing to defaults\n --strip-unknown Silently drop undeclared input keys at every nesting level\n --case-insensitive Normalize mis-cased enum/const strings to their exact casing\n --log-warnings console.warn on input keys not declared in the schema\n --unknown-keys <s> How closed objects are proven free of undeclared keys:\n count-keys (default, fastest on Bun) or count-enumerable (fastest on Node)\n --formats <f> String formats the generated validators enforce: \"all\", or a\n comma-separated list (e.g. uuid,date-time,email). Off by default,\n which leaves \"format\" an annotation as JSON Schema reads it.\n\nReference resolution:\n --resolve-remote Fetch http(s) $refs while resolving cross-file/remote refs (off by default)\n --allowed-hosts <h> Restrict remote $ref fetches to these hosts (comma-separated; implies --resolve-remote)\n --allow-private-hosts Permit remote $refs to private/loopback hosts (SSRF guard, off by default)\n --allowed-roots <d> Extra directories a local $ref may resolve into, on top of the schema's own\n (comma-separated; relative to the current directory)\n\nMisc:\n --config <path> JSON config file with the same keys; CLI flags take precedence\n --version, -v Print the CLI version\n --help, -h Print this help\n\nDocs: https://github.com/amritk/mjst/tree/main/packages/cli#readme\n";
|
|
7
|
+
export declare const HELP_TEXT = "mjst \u2014 generate TypeScript parsers and type definitions from JSON Schemas\n\nUsage:\n mjst --schema <path> --out-dir <dir> [options]\n mjst --schema-dir <dir> --out-dir <dir> [options]\n mjst --schema <path> --out-file <file> --types-only [options]\n\nSubcommands (each with its own flags and --help):\n mjst lint <files> Lint JSON/YAML documents against a ruleset\n mjst compile-api <module> --out <file> Compile @amritk/api route contracts into a fetch-handler module\n mjst markdown <schema> --out-dir <dir> Generate markdown documentation from a JSON Schema\n\nInput:\n --schema <path> Schema to process: a JSON Schema file, or a module when --input is set\n --schema-dir <dir> Directory of JSON Schemas, processed recursively (instead of --schema)\n --input <format> Schema source format: json (default), typebox, zod, valibot, effect, avro,\n or asyncapi (an AsyncAPI 2.x/3.0 document, JSON or YAML)\n --export <name> Which export of the schema module to use when --input names a module format\n\nOutput:\n --out-dir <dir> Output directory for the generated files\n --out-file <file> Single-file output instead of a directory (requires --types-only)\n --types-only Generate type definitions only, without parser functions\n --validators Also emit validateX/isX validators into a validators/ subdir\n --examples Also emit fast-check arbitraries + example values under examples/\n --message-contracts Also emit @amritk/api channel contracts under contracts/ (--input asyncapi only)\n --discriminator <p> Property naming which message a frame is, for --message-contracts (default: type)\n --build Compile the generated files to .js/.d.ts (implies --import-ext js)\n --force Deprecated; ignored (generated files always overwrite)\n --import-ext <ext> Extension on generated relative imports: ts (default) or js\n --helpers <mode> Runtime helpers: package or embedded (default: auto-detect from package.json)\n --root-type <name> Root type name for a single --schema run (default: schema title or filename)\n --type-suffix <s> Suffix appended to every $ref-derived type name\n --banner [text] Prepend a header comment to every generated file\n --readonly Emit deeply readonly type definitions\n\nValidation:\n --strict Throw on type/shape mismatches instead of coercing to defaults\n --strip-unknown Silently drop undeclared input keys at every nesting level\n --case-insensitive Normalize mis-cased enum/const strings to their exact casing\n --log-warnings console.warn on input keys not declared in the schema\n --unknown-keys <s> How closed objects are proven free of undeclared keys:\n count-keys (default, fastest on Bun) or count-enumerable (fastest on Node)\n --formats <f> String formats the generated validators enforce: \"all\", or a\n comma-separated list (e.g. uuid,date-time,email). Off by default,\n which leaves \"format\" an annotation as JSON Schema reads it.\n\nReference resolution:\n --resolve-remote Fetch http(s) $refs while resolving cross-file/remote refs (off by default)\n --allowed-hosts <h> Restrict remote $ref fetches to these hosts (comma-separated; implies --resolve-remote)\n --allow-private-hosts Permit remote $refs to private/loopback hosts (SSRF guard, off by default)\n --allowed-roots <d> Extra directories a local $ref may resolve into, on top of the schema's own\n (comma-separated; relative to the current directory)\n\nMisc:\n --config <path> JSON config file with the same keys; CLI flags take precedence\n --version, -v Print the CLI version\n --help, -h Print this help\n\nDocs: https://github.com/amritk/mjst/tree/main/packages/cli#readme\n";
|
package/dist/help-text.js
CHANGED
|
@@ -8,6 +8,7 @@ Usage:
|
|
|
8
8
|
Subcommands (each with its own flags and --help):
|
|
9
9
|
mjst lint <files> Lint JSON/YAML documents against a ruleset
|
|
10
10
|
mjst compile-api <module> --out <file> Compile @amritk/api route contracts into a fetch-handler module
|
|
11
|
+
mjst markdown <schema> --out-dir <dir> Generate markdown documentation from a JSON Schema
|
|
11
12
|
|
|
12
13
|
Input:
|
|
13
14
|
--schema <path> Schema to process: a JSON Schema file, or a module when --input is set
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Usage text for `mjst markdown --help`. Hand-written like the top-level
|
|
3
|
+
* HELP_TEXT: the flag list is small and stable, and the test cross-checks that
|
|
4
|
+
* every flag is listed.
|
|
5
|
+
*/
|
|
6
|
+
export declare const MARKDOWN_HELP_TEXT = "mjst markdown \u2014 generate markdown documentation from a JSON Schema\n\nUsage:\n mjst markdown <schema> --out-dir <dir> [options]\n mjst markdown <schema> --table --readme <file>\n\nTwo shapes come out of the same schema. By default it writes the prose\nreference \u2014 a heading, a type, the description and an example per property,\nacross as many pages as the schema's x-doc keyword declares. With --table it\nrenders one HTML table instead and splices it into an existing markdown file\nbetween <!-- config-table-start --> and <!-- config-table-end -->, leaving the\nrest of that file alone.\n\nProse reference options:\n --out-dir <dir> Directory the pages are written to (default: the current directory)\n --file <path> Output path of the index page (default: the schema's x-doc.file, then index.md)\n --title <text> Page title (default: the schema's title)\n --language <lang> Fence language for derived examples and literals (default: json)\n --layout <mode> Default layout for nested properties: headings (default), table, or none\n --sort <order> Property order: schema (default) or alphabetical\n --heading-level <n> Heading level of the page title, 1-6 (default: 1)\n\nTable options:\n --table Render the HTML config table instead of the prose pages\n --readme <path> Markdown file the table is spliced into (default: README.md)\n\nMisc:\n --help, -h Print this help\n\nDocs: https://github.com/amritk/mjst/tree/main/packages/cli#readme\n";
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const MARKDOWN_HELP_TEXT = `mjst markdown \u2014 generate markdown documentation from a JSON Schema
|
|
2
|
+
|
|
3
|
+
Usage:
|
|
4
|
+
mjst markdown <schema> --out-dir <dir> [options]
|
|
5
|
+
mjst markdown <schema> --table --readme <file>
|
|
6
|
+
|
|
7
|
+
Two shapes come out of the same schema. By default it writes the prose
|
|
8
|
+
reference \u2014 a heading, a type, the description and an example per property,
|
|
9
|
+
across as many pages as the schema's x-doc keyword declares. With --table it
|
|
10
|
+
renders one HTML table instead and splices it into an existing markdown file
|
|
11
|
+
between <!-- config-table-start --> and <!-- config-table-end -->, leaving the
|
|
12
|
+
rest of that file alone.
|
|
13
|
+
|
|
14
|
+
Prose reference options:
|
|
15
|
+
--out-dir <dir> Directory the pages are written to (default: the current directory)
|
|
16
|
+
--file <path> Output path of the index page (default: the schema's x-doc.file, then index.md)
|
|
17
|
+
--title <text> Page title (default: the schema's title)
|
|
18
|
+
--language <lang> Fence language for derived examples and literals (default: json)
|
|
19
|
+
--layout <mode> Default layout for nested properties: headings (default), table, or none
|
|
20
|
+
--sort <order> Property order: schema (default) or alphabetical
|
|
21
|
+
--heading-level <n> Heading level of the page title, 1-6 (default: 1)
|
|
22
|
+
|
|
23
|
+
Table options:
|
|
24
|
+
--table Render the HTML config table instead of the prose pages
|
|
25
|
+
--readme <path> Markdown file the table is spliced into (default: README.md)
|
|
26
|
+
|
|
27
|
+
Misc:
|
|
28
|
+
--help, -h Print this help
|
|
29
|
+
|
|
30
|
+
Docs: https://github.com/amritk/mjst/tree/main/packages/cli#readme
|
|
31
|
+
`;
|
|
32
|
+
export {
|
|
33
|
+
MARKDOWN_HELP_TEXT
|
|
34
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { DocLayout, DocSort } from '@amritk/generate-markdown';
|
|
2
|
+
/** The flags and positional the `markdown` subcommand understands. */
|
|
3
|
+
export type MarkdownArgs = {
|
|
4
|
+
/** Path to the JSON Schema to document (the sole positional). */
|
|
5
|
+
schema?: string;
|
|
6
|
+
/** Directory the prose reference pages are written to (`--out-dir`). */
|
|
7
|
+
outDir?: string;
|
|
8
|
+
/** Output path of the index page (`--file`). */
|
|
9
|
+
file?: string;
|
|
10
|
+
/** Page title override (`--title`). */
|
|
11
|
+
title?: string;
|
|
12
|
+
/** Fence language for derived examples (`--language`). */
|
|
13
|
+
language?: string;
|
|
14
|
+
/** Default layout for nested properties (`--layout`). */
|
|
15
|
+
layout?: DocLayout;
|
|
16
|
+
/** Property order (`--sort`). */
|
|
17
|
+
sort?: DocSort;
|
|
18
|
+
/** Heading level of the page title (`--heading-level`). */
|
|
19
|
+
headingLevel?: number;
|
|
20
|
+
/** True when `--table` was passed: render the HTML table instead of the pages. */
|
|
21
|
+
table?: boolean;
|
|
22
|
+
/** Markdown file the table is spliced into (`--readme`). */
|
|
23
|
+
readme?: string;
|
|
24
|
+
/** True when `--help`/`-h` was passed. */
|
|
25
|
+
help?: boolean;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Parses the `markdown` argv into a typed args object. Mirrors the other
|
|
29
|
+
* subcommand parsers: kebab-case and camelCase flag spellings, both
|
|
30
|
+
* `--flag value` and `--flag=value`, and a loud error on unknown flags so a
|
|
31
|
+
* typo cannot silently drop an option. Throws on usage errors; the caller turns
|
|
32
|
+
* that into the exit-code-2 path.
|
|
33
|
+
*/
|
|
34
|
+
export declare const parseMarkdownArgs: (argv: readonly string[]) => MarkdownArgs;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
const VALUE_KEYS = /* @__PURE__ */ new Set(["outDir", "file", "title", "language", "layout", "sort", "headingLevel", "readme"]);
|
|
2
|
+
const BOOLEAN_KEYS = /* @__PURE__ */ new Set(["table"]);
|
|
3
|
+
const LAYOUTS = ["headings", "table", "none"];
|
|
4
|
+
const SORTS = ["schema", "alphabetical"];
|
|
5
|
+
const toCamelCase = (key) => key.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
6
|
+
const parseChoice = (flag, value, choices) => {
|
|
7
|
+
const match = choices.find((choice) => choice === value);
|
|
8
|
+
if (match === void 0) {
|
|
9
|
+
throw new Error(`Invalid --${flag} value "${value}". Expected one of: ${choices.join(", ")}.`);
|
|
10
|
+
}
|
|
11
|
+
return match;
|
|
12
|
+
};
|
|
13
|
+
const parseHeadingLevel = (value) => {
|
|
14
|
+
const parsed = Number(value);
|
|
15
|
+
if (!Number.isInteger(parsed) || parsed < 1 || parsed > 6) {
|
|
16
|
+
throw new Error(`Invalid --heading-level value "${value}". Expected an integer from 1 to 6.`);
|
|
17
|
+
}
|
|
18
|
+
return parsed;
|
|
19
|
+
};
|
|
20
|
+
const assignValue = (args, key, value) => {
|
|
21
|
+
switch (key) {
|
|
22
|
+
case "outDir":
|
|
23
|
+
args.outDir = value;
|
|
24
|
+
return;
|
|
25
|
+
case "file":
|
|
26
|
+
args.file = value;
|
|
27
|
+
return;
|
|
28
|
+
case "title":
|
|
29
|
+
args.title = value;
|
|
30
|
+
return;
|
|
31
|
+
case "language":
|
|
32
|
+
args.language = value;
|
|
33
|
+
return;
|
|
34
|
+
case "layout":
|
|
35
|
+
args.layout = parseChoice("layout", value, LAYOUTS);
|
|
36
|
+
return;
|
|
37
|
+
case "sort":
|
|
38
|
+
args.sort = parseChoice("sort", value, SORTS);
|
|
39
|
+
return;
|
|
40
|
+
case "headingLevel":
|
|
41
|
+
args.headingLevel = parseHeadingLevel(value);
|
|
42
|
+
return;
|
|
43
|
+
case "readme":
|
|
44
|
+
args.readme = value;
|
|
45
|
+
return;
|
|
46
|
+
default:
|
|
47
|
+
throw new Error(`Unknown flag "--${key}".`);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
const parseMarkdownArgs = (argv) => {
|
|
51
|
+
const args = {};
|
|
52
|
+
for (let i = 0; i < argv.length; i++) {
|
|
53
|
+
const arg = argv[i];
|
|
54
|
+
if (arg === void 0)
|
|
55
|
+
continue;
|
|
56
|
+
if (arg === "--help" || arg === "-h") {
|
|
57
|
+
args.help = true;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (!arg.startsWith("--")) {
|
|
61
|
+
if (args.schema !== void 0) {
|
|
62
|
+
throw new Error(`Unexpected argument "${arg}". markdown takes a single schema path.`);
|
|
63
|
+
}
|
|
64
|
+
args.schema = arg;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
const equalsIndex = arg.indexOf("=");
|
|
68
|
+
if (equalsIndex !== -1) {
|
|
69
|
+
const name = arg.slice(2, equalsIndex);
|
|
70
|
+
const key2 = toCamelCase(name);
|
|
71
|
+
if (BOOLEAN_KEYS.has(key2))
|
|
72
|
+
throw new Error(`Flag "--${name}" is a switch and takes no value.`);
|
|
73
|
+
if (!VALUE_KEYS.has(key2))
|
|
74
|
+
throw new Error(`Unknown flag "--${name}".`);
|
|
75
|
+
assignValue(args, key2, arg.slice(equalsIndex + 1));
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
const flagName = arg.slice(2);
|
|
79
|
+
const key = toCamelCase(flagName);
|
|
80
|
+
if (BOOLEAN_KEYS.has(key)) {
|
|
81
|
+
args.table = true;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (!VALUE_KEYS.has(key))
|
|
85
|
+
throw new Error(`Unknown flag "--${flagName}".`);
|
|
86
|
+
const value = argv[i + 1];
|
|
87
|
+
if (value === void 0 || value.startsWith("--")) {
|
|
88
|
+
throw new Error(`Flag "--${flagName}" expects a value.`);
|
|
89
|
+
}
|
|
90
|
+
assignValue(args, key, value);
|
|
91
|
+
i++;
|
|
92
|
+
}
|
|
93
|
+
return args;
|
|
94
|
+
};
|
|
95
|
+
export {
|
|
96
|
+
parseMarkdownArgs
|
|
97
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { RunResult } from '../lint/run.js';
|
|
2
|
+
/**
|
|
3
|
+
* Runs `mjst markdown` over `argv`: renders a JSON Schema as documentation,
|
|
4
|
+
* either as the prose reference pages (the default) or as the HTML config table
|
|
5
|
+
* spliced into an existing markdown file (`--table`). Returns the exit code and
|
|
6
|
+
* the text it would print (rather than writing to the process streams) so tests
|
|
7
|
+
* can drive it in-process — the same shape as the other subcommands' `run`.
|
|
8
|
+
*/
|
|
9
|
+
export declare const run: (argv: string[]) => Promise<RunResult>;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { relative, resolve } from "node:path";
|
|
2
|
+
import { generateConfigTable, generateDocs } from "@amritk/generate-markdown";
|
|
3
|
+
import { MARKDOWN_HELP_TEXT } from "./help-text.js";
|
|
4
|
+
import { parseMarkdownArgs } from "./parse-markdown-args.js";
|
|
5
|
+
const usageError = (message) => ({ code: 2, stdout: "", stderr: `Error: ${message}
|
|
6
|
+
` });
|
|
7
|
+
const failure = (message) => ({ code: 1, stdout: "", stderr: `Error: ${message}
|
|
8
|
+
` });
|
|
9
|
+
const PAGE_FLAGS = ["outDir", "file", "title", "language", "layout", "sort", "headingLevel"];
|
|
10
|
+
const run = async (argv) => {
|
|
11
|
+
let args;
|
|
12
|
+
try {
|
|
13
|
+
args = parseMarkdownArgs(argv);
|
|
14
|
+
} catch (error) {
|
|
15
|
+
return usageError(error instanceof Error ? error.message : String(error));
|
|
16
|
+
}
|
|
17
|
+
if (args.help || argv.length === 0) {
|
|
18
|
+
return { code: 0, stdout: MARKDOWN_HELP_TEXT, stderr: "" };
|
|
19
|
+
}
|
|
20
|
+
if (args.schema === void 0) {
|
|
21
|
+
return usageError("A schema path is required. Usage: mjst markdown <schema> --out-dir <dir>");
|
|
22
|
+
}
|
|
23
|
+
if (args.table) {
|
|
24
|
+
const stray = PAGE_FLAGS.filter((flag) => args[flag] !== void 0);
|
|
25
|
+
if (stray.length > 0) {
|
|
26
|
+
return usageError(`--table renders one table into a single file, so it cannot be combined with ${stray.map((flag) => `--${flag.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`)}`).join(", ")}. Drop --table to generate the prose reference pages.`);
|
|
27
|
+
}
|
|
28
|
+
} else if (args.readme !== void 0) {
|
|
29
|
+
return usageError("--readme names the file the config table is spliced into, so it requires --table.");
|
|
30
|
+
}
|
|
31
|
+
const schemaPath = resolve(args.schema);
|
|
32
|
+
const outDir = resolve(args.outDir ?? ".");
|
|
33
|
+
if (args.table) {
|
|
34
|
+
let readmePath;
|
|
35
|
+
try {
|
|
36
|
+
readmePath = await generateConfigTable({
|
|
37
|
+
schemaPath,
|
|
38
|
+
...args.readme !== void 0 ? { readmePath: resolve(args.readme) } : {}
|
|
39
|
+
});
|
|
40
|
+
} catch (error) {
|
|
41
|
+
return failure(error instanceof Error ? error.message : String(error));
|
|
42
|
+
}
|
|
43
|
+
return { code: 0, stdout: `Generated config table: ${relative(process.cwd(), readmePath)}
|
|
44
|
+
`, stderr: "" };
|
|
45
|
+
}
|
|
46
|
+
const options = {
|
|
47
|
+
schemaPath,
|
|
48
|
+
outDir,
|
|
49
|
+
file: args.file,
|
|
50
|
+
title: args.title,
|
|
51
|
+
language: args.language,
|
|
52
|
+
layout: args.layout,
|
|
53
|
+
sort: args.sort,
|
|
54
|
+
headingLevel: args.headingLevel
|
|
55
|
+
};
|
|
56
|
+
let files;
|
|
57
|
+
try {
|
|
58
|
+
files = await generateDocs(options);
|
|
59
|
+
} catch (error) {
|
|
60
|
+
return failure(error instanceof Error ? error.message : String(error));
|
|
61
|
+
}
|
|
62
|
+
const stdout = [
|
|
63
|
+
...files.map((file) => `Generated: ${relative(process.cwd(), resolve(outDir, file.filename))}`),
|
|
64
|
+
"",
|
|
65
|
+
`Total files generated: ${files.length}`,
|
|
66
|
+
""
|
|
67
|
+
].join("\n");
|
|
68
|
+
return { code: 0, stdout, stderr: "" };
|
|
69
|
+
};
|
|
70
|
+
export {
|
|
71
|
+
run
|
|
72
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@amritk/mjst",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"description": "Generate TypeScript parsers and type definitions from JSON Schemas.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -53,6 +53,7 @@
|
|
|
53
53
|
"@amritk/api": "^0.16.4",
|
|
54
54
|
"@amritk/asyncapi": "^0.3.0",
|
|
55
55
|
"@amritk/generate-examples": "^0.8.4",
|
|
56
|
+
"@amritk/generate-markdown": "^0.8.0",
|
|
56
57
|
"@amritk/generate-parsers": "^0.23.0",
|
|
57
58
|
"@amritk/generate-validators": "^0.17.0",
|
|
58
59
|
"@amritk/helpers": "^0.21.0",
|
|
@@ -64,7 +65,6 @@
|
|
|
64
65
|
"yargs": "^17.7.2"
|
|
65
66
|
},
|
|
66
67
|
"devDependencies": {
|
|
67
|
-
"@amritk/generate-markdown": "^0.7.0",
|
|
68
68
|
"@types/yargs": "^17.0.33"
|
|
69
69
|
}
|
|
70
70
|
}
|