@amritk/mjst 0.16.1 → 0.17.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 CHANGED
@@ -46,7 +46,8 @@ mjst compile-api ./routes.ts --out ./dist/handler.ts
46
46
 
47
47
  `--schema` / `--schema-dir`, `--out-dir` / `--out-file`, `--input`,
48
48
  `--validators`, `--examples`, `--types-only`, `--build`, `--strict`,
49
- `--strip-unknown`, `--readonly`, `--import-ext ts|js`, `--config <path>`,
49
+ `--strip-unknown`, `--unknown-keys count-keys|count-enumerable`, `--readonly`,
50
+ `--import-ext ts|js`, `--config <path>`,
50
51
  `--resolve-remote` / `--allowed-hosts` (SSRF-guarded remote `$ref`s).
51
52
 
52
53
  ## Lint gotchas
package/README.md CHANGED
@@ -37,7 +37,7 @@ yarn add -D @amritk/mjst
37
37
  bun add -d @amritk/mjst
38
38
  ```
39
39
 
40
- The package ships a `mjst` bin that runs under Node ≥ 20 (or Bun), so you can invoke it via `npx mjst`, `pnpm dlx mjst`, `yarn dlx mjst`, `bunx mjst`, or as a script in `package.json`.
40
+ The package ships a `mjst` bin that runs under Node ≥ 20 (or Bun), so you can invoke it via `npx mjst`, `pnpm exec mjst`, `yarn mjst`, `bunx mjst`, or as a script in `package.json`.
41
41
 
42
42
  ---
43
43
 
@@ -64,7 +64,7 @@ npx mjst --schema ./schema.json --out-file ./generated/schema.ts --types-only
64
64
  ### Recursive — a whole folder of schemas
65
65
 
66
66
  Point `--schema-dir` at a directory of JSON Schemas and mjst generates parsers for every
67
- `*.json` file it finds, mirroring the directory layout under `--out-dir`:
67
+ `*.json` file it finds (skipping `node_modules/` and dot-directories), mirroring the directory layout under `--out-dir`:
68
68
 
69
69
  ```bash
70
70
  npx mjst --schema-dir ./schemas --out-dir ./generated
@@ -188,7 +188,7 @@ npx mjst --config ./mjst.config.json
188
188
  ```
189
189
 
190
190
  > [!NOTE]
191
- > Validate your config against the bundled JSON Schema: [`config.schema.json`](./config.schema.json)
191
+ > Validate your config against the repository's JSON Schema: [`config.schema.json`](./config.schema.json) (not included in the npm package)
192
192
 
193
193
  ---
194
194
 
@@ -220,7 +220,7 @@ With no `-r`, mjst discovers a `.lint.{yaml,yml,json,js,mjs}` ruleset by walking
220
220
  | Flag | Description |
221
221
  | --- | --- |
222
222
  | `<documents..>` | Files or globs to lint (positional). |
223
- | `-r`, `--ruleset` | Path to a ruleset file. Defaults to `.lint.*` discovery. |
223
+ | `-r`, `--ruleset` | Path to a ruleset file, or a built-in preset (`oas`, `asyncapi`). Defaults to `.lint.*` discovery. |
224
224
  | `-F`, `--fail-severity` | Minimum severity that fails the run: `error` (default), `warn`, `info`, `hint`. |
225
225
  | `-D`, `--display-only-failures` | Only show findings at or above `--fail-severity`. |
226
226
  | `--stdin-filepath` | Path to associate with piped input (labels findings and enables ruleset discovery). |
@@ -230,6 +230,7 @@ With no `-r`, mjst discovers a `.lint.{yaml,yml,json,js,mjs}` ruleset by walking
230
230
  | `--allowed-hosts` | Restrict remote `$ref` fetches to these hosts (implies `--resolve-remote`). |
231
231
  | `--allow-private-hosts` | Permit remote `$ref`s to private/loopback hosts (SSRF guard, off by default). |
232
232
  | `--allowed-roots` | Extra directories a local `$ref` may resolve into, on top of the document's own (repeat the flag). |
233
+ | `--encoding` | Input encoding for the linted files (default `utf8`). |
233
234
  | `-q`, `--quiet` | Suppress the findings report (the exit code still reflects findings). |
234
235
 
235
236
  By default `mjst lint` dereferences `$ref`, `$dynamicRef`/`$dynamicAnchor`, and `$recursiveRef`/`$recursiveAnchor` before running rules, so rules with `resolved: true` (the ruleset default) see through references. Internal and local cross-file refs resolve from disk — a finding on a cross-file node reports that file's own `line:column` — as long as they stay inside the linted document's own directory; use `--allowed-roots` to let a document reach a shared sibling folder. Remote refs are only fetched when you opt in with `--resolve-remote` or `--allowed-hosts`.
@@ -250,7 +251,7 @@ npx mjst compile-api src/routes.ts --out dist/handler.js
250
251
  npx mjst compile-api src/routes.ts --out dist/handler.js --options compile-options.json
251
252
  ```
252
253
 
253
- Every export of the routes module that looks like a route contract (`method`, `path`, `responses`) is compiled; the generated module imports those contracts back through `--routes-import` (default: the relative path from the out file to the routes module). The routes module is imported at build time, so it must be loadable by the runtime running mjst — run via `bunx` for TypeScript sources, or under Node install a loader such as `tsx` (`node --import tsx`).
254
+ Every *named* export of the routes module that looks like a route contract (`method`, `path`, `responses`) is compiled (a `default` export is ignored, because the generated module imports each contract by name); the generated module imports those contracts back through `--routes-import` (default: the relative path from the out file to the routes module). The routes module is imported at build time, so it must be loadable by the runtime running mjst — run via `bunx` for TypeScript sources, or under Node install a loader such as `tsx` (`node --import tsx`).
254
255
 
255
256
  | Flag | Description |
256
257
  | --- | --- |
@@ -303,7 +304,7 @@ The exit code is `0` on success, `1` when compilation fails (unloadable module,
303
304
  <td align="center"><code>"json"</code></td>
304
305
  </tr>
305
306
  <tr>
306
- <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. 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></td>
307
+ <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>
307
308
  </tr>
308
309
  <tr>
309
310
  <td>📦 <code>export</code></td>
@@ -312,7 +313,7 @@ The exit code is `0` on success, `1` when compilation fails (unloadable module,
312
313
  <td align="center"></td>
313
314
  </tr>
314
315
  <tr>
315
- <td colspan="4">Which export of the schema module to use when 'input' names a module format (anything but 'json' and 'avro', which are read as data). Defaults to the default export, or the sole named export when the module has exactly one.</td>
316
+ <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>
316
317
  </tr>
317
318
  <tr>
318
319
  <td>📁 <code>outDir</code></td>
@@ -423,6 +424,15 @@ The exit code is `0` on success, `1` when compilation fails (unloadable module,
423
424
  <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>
424
425
  </tr>
425
426
  <tr>
427
+ <td>🔢 <code>unknownKeys</code></td>
428
+ <td><code>--unknown-keys &lt;strategy&gt;</code></td>
429
+ <td><code>string</code></td>
430
+ <td align="center"><code>"count-keys"</code></td>
431
+ </tr>
432
+ <tr>
433
+ <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>
434
+ </tr>
435
+ <tr>
426
436
  <td>🧰 <code>helpers</code></td>
427
437
  <td><code>--helpers &lt;mode&gt;</code></td>
428
438
  <td><code>string</code></td>
@@ -510,7 +520,7 @@ The exit code is `0` on success, `1` when compilation fails (unloadable module,
510
520
  <td align="center"></td>
511
521
  </tr>
512
522
  <tr>
513
- <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, 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>
523
+ <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>
514
524
  </tr>
515
525
  </tbody>
516
526
  </table>
@@ -559,6 +569,16 @@ The exit code is `0` on success, `1` when compilation fails (unloadable module,
559
569
  }
560
570
  ```
561
571
 
572
+ **Strict — throw on invalid input**
573
+
574
+ ```json
575
+ {
576
+ "schema": "./schema.json",
577
+ "outDir": "./generated",
578
+ "strict": true
579
+ }
580
+ ```
581
+
562
582
  ---
563
583
 
564
584
  ## Scripts
@@ -1,4 +1,5 @@
1
1
  import type { SourceFormat } from '@amritk/adapters/source-format';
2
+ import type { UnknownKeysStrategy } from '@amritk/helpers/unknown-keys-strategy';
2
3
  /**
3
4
  * Configuration for the CLI tool.
4
5
  * These properties map 1:1 with CLI flags and config file keys.
@@ -105,6 +106,14 @@ export type CliConfig = {
105
106
  * path is unaffected.
106
107
  */
107
108
  readonly caseInsensitive?: boolean;
109
+ /**
110
+ * How generated fast paths prove a closed object (`additionalProperties:
111
+ * false`) carries no undeclared key. `'count-keys'` (the default) compares
112
+ * `Object.keys(obj).length` and is the faster form on JavaScriptCore (Bun);
113
+ * `'count-enumerable'` counts with `for…in` and is the faster form on V8
114
+ * (Node). Applies to parsers and validators alike.
115
+ */
116
+ readonly unknownKeys?: UnknownKeysStrategy;
108
117
  /**
109
118
  * Controls how generated parsers reference their runtime helpers.
110
119
  * - `'package'`: emit `import ... from '@amritk/helpers/...'`.
package/dist/cli.js CHANGED
@@ -3,6 +3,7 @@ import { execFile } from "node:child_process";
3
3
  import { readdir, unlink, writeFile } from "node:fs/promises";
4
4
  import { basename, dirname, join, relative, resolve, sep } from "node:path";
5
5
  import { promisify } from "node:util";
6
+ import { extractAsyncApi, listMessageSchemas } from "@amritk/asyncapi";
6
7
  import { buildSchema } from "@amritk/generate-parsers";
7
8
  import { buildValidatorSchema } from "@amritk/generate-validators";
8
9
  import { deriveRootTypeName } from "@amritk/helpers/derive-root-type-name";
@@ -13,6 +14,7 @@ import { detectHelpersMode } from "./detect-helpers-mode.js";
13
14
  import { emitExamples } from "./emit-examples.js";
14
15
  import { ensureOutputDir } from "./ensure-output-dir.js";
15
16
  import { HELP_TEXT } from "./help-text.js";
17
+ import { loadAsyncApiDocument } from "./load-asyncapi-document.js";
16
18
  import { loadConfig } from "./load-config.js";
17
19
  import { loadSchema } from "./load-schema.js";
18
20
  import { parseCliArgs } from "./parse-cli-args.js";
@@ -134,7 +136,7 @@ const findJsonSchemas = async (dir) => {
134
136
  return results;
135
137
  };
136
138
  const runValidators = async (config, schema, rootTypeName, writer, validatorsSubDir) => {
137
- const files = await buildValidatorSchema(schema, rootTypeName, config.typeSuffix);
139
+ const files = await buildValidatorSchema(schema, rootTypeName, config.typeSuffix, void 0, config.unknownKeys);
138
140
  const staged = [];
139
141
  for (const file of files) {
140
142
  const content = config.banner ? resolveBanner(config.banner) + file.content : file.content;
@@ -150,7 +152,7 @@ const runSingle = async (config, schemaPath, outputDir) => {
150
152
  const helpersMode = resolveHelpersMode(config, outputDir);
151
153
  const rootTypeName = config.rootType ?? deriveRootTypeName(schema, schemaBaseName(schemaPath));
152
154
  console.log(`Root type: ${rootTypeName}`);
153
- const files = await buildSchema(schema, rootTypeName, void 0, config.typesOnly, config.logWarnings, config.strict, helpersMode, void 0, config.readonly, config.stripUnknown, config.typeSuffix, resolveImportExt(config), config.caseInsensitive);
155
+ const files = await buildSchema(schema, rootTypeName, void 0, config.typesOnly, config.logWarnings, config.strict, helpersMode, void 0, config.readonly, config.stripUnknown, config.typeSuffix, resolveImportExt(config), config.caseInsensitive, void 0, config.unknownKeys);
154
156
  const writer = await createOutputWriter(outputDir, config.force);
155
157
  const written = await commitOrDiscard(writer, async () => {
156
158
  for (const file of files) {
@@ -179,7 +181,7 @@ const runSingleFile = async (config, schemaPath, outFilePath) => {
179
181
  await ensureOutputDir(outputDir);
180
182
  const rootTypeName = config.rootType ?? deriveRootTypeName(schema, schemaBaseName(schemaPath));
181
183
  console.log(`Root type: ${rootTypeName}`);
182
- const files = await buildSchema(schema, rootTypeName, void 0, config.typesOnly, config.logWarnings, config.strict, config.helpers ?? "package", void 0, config.readonly, config.stripUnknown, config.typeSuffix, resolveImportExt(config), config.caseInsensitive);
184
+ const files = await buildSchema(schema, rootTypeName, void 0, config.typesOnly, config.logWarnings, config.strict, config.helpers ?? "package", void 0, config.readonly, config.stripUnknown, config.typeSuffix, resolveImportExt(config), config.caseInsensitive, void 0, config.unknownKeys);
183
185
  const combined = combineGeneratedFiles(files);
184
186
  const writer = await createOutputWriter(outputDir, config.force);
185
187
  const written = await commitOrDiscard(writer, async () => {
@@ -221,7 +223,7 @@ const runRecursive = async (config, schemaDir, outputDir) => {
221
223
  const rootTypeName = deriveRootTypeName(schema, schemaBaseName(schemaFile));
222
224
  console.log(`
223
225
  ${relPath} \u2192 ${relNoExt}/ (root type: ${rootTypeName})`);
224
- const files = await buildSchema(schema, rootTypeName, void 0, config.typesOnly, config.logWarnings, config.strict, helpersMode, helpersImportPrefix, config.readonly, config.stripUnknown, config.typeSuffix, resolveImportExt(config), config.caseInsensitive);
226
+ const files = await buildSchema(schema, rootTypeName, void 0, config.typesOnly, config.logWarnings, config.strict, helpersMode, helpersImportPrefix, config.readonly, config.stripUnknown, config.typeSuffix, resolveImportExt(config), config.caseInsensitive, void 0, config.unknownKeys);
225
227
  for (const file of files) {
226
228
  if (file.filename.startsWith("_helpers/")) {
227
229
  sharedHelpers.set(file.filename, file.content);
@@ -253,6 +255,62 @@ ${relPath} \u2192 ${relNoExt}/ (root type: ${rootTypeName})`);
253
255
  Total files generated: ${writtenTsFiles.length}`);
254
256
  }
255
257
  };
258
+ const runAsyncApi = async (config, documentPath, outputDir) => {
259
+ const document = await loadAsyncApiDocument(config, documentPath);
260
+ const model = extractAsyncApi(document);
261
+ const schemas = listMessageSchemas(model);
262
+ for (const issue of model.issues) {
263
+ console.warn(`Warning: ${issue.path}: ${issue.message}`);
264
+ }
265
+ if (schemas.length === 0) {
266
+ console.error(`Error: no generatable message schemas found in ${documentPath}. Every message either declares no payload/headers or uses a non-JSON-Schema schemaFormat (see warnings above).`);
267
+ process.exit(1);
268
+ }
269
+ console.log(`AsyncAPI ${model.version}: ${model.channels.length} channel(s), ${schemas.length} schema(s) to generate`);
270
+ await ensureOutputDir(outputDir);
271
+ const helpersMode = resolveHelpersMode(config, outputDir);
272
+ const sharedHelpers = /* @__PURE__ */ new Map();
273
+ const writer = await createOutputWriter(outputDir, config.force);
274
+ const exampleTasks = [];
275
+ const stageAll = async () => {
276
+ for (const extracted of schemas) {
277
+ const { subDir, rootTypeName, schema } = extracted;
278
+ const depth = subDir.split("/").filter(Boolean).length;
279
+ const helpersImportPrefix = "../".repeat(depth);
280
+ console.log(`
281
+ ${subDir}/ (root type: ${rootTypeName})`);
282
+ const files = await buildSchema(schema, rootTypeName, void 0, config.typesOnly, config.logWarnings, config.strict, helpersMode, helpersImportPrefix, config.readonly, config.stripUnknown, config.typeSuffix, resolveImportExt(config), config.caseInsensitive);
283
+ for (const file of files) {
284
+ if (file.filename.startsWith("_helpers/")) {
285
+ sharedHelpers.set(file.filename, file.content);
286
+ continue;
287
+ }
288
+ const content = config.banner ? resolveBanner(config.banner) + file.content : file.content;
289
+ await writer.stage(join(subDir, file.filename), content);
290
+ }
291
+ if (config.validators) {
292
+ await runValidators(config, schema, rootTypeName, writer, join("validators", subDir));
293
+ }
294
+ if (config.examples)
295
+ exampleTasks.push({ schema, rootTypeName, subDir });
296
+ }
297
+ for (const [filename, content] of sharedHelpers) {
298
+ await writer.stage(filename, content);
299
+ }
300
+ };
301
+ const writtenTsFiles = await commitOrDiscard(writer, stageAll);
302
+ for (const filename of writtenTsFiles)
303
+ console.log(`Generated: ${filename}`);
304
+ if (exampleTasks.length > 0) {
305
+ await runExamples(config, outputDir, exampleTasks);
306
+ }
307
+ if (config.build) {
308
+ await buildOutput(outputDir, writtenTsFiles, config.typesOnly);
309
+ } else {
310
+ console.log(`
311
+ Total files generated: ${writtenTsFiles.length}`);
312
+ }
313
+ };
256
314
  const run = async () => {
257
315
  const args = process.argv.slice(2);
258
316
  if (args[0] === "lint") {
@@ -310,6 +368,24 @@ const run = async () => {
310
368
  console.error("Error: --validators cannot be combined with --out-file. Use --out-dir to emit validator files.");
311
369
  process.exit(1);
312
370
  }
371
+ if (config.input === "asyncapi") {
372
+ if (config.schemaDir) {
373
+ console.error("Error: --input asyncapi reads a single document via --schema; it cannot be combined with --schema-dir.");
374
+ process.exit(1);
375
+ }
376
+ if (config.outFile) {
377
+ console.error("Error: --input asyncapi generates a directory of files per message; use --out-dir, not --out-file.");
378
+ process.exit(1);
379
+ }
380
+ if (config.rootType) {
381
+ console.error("Error: --root-type cannot be combined with --input asyncapi; each message derives its own root type.");
382
+ process.exit(1);
383
+ }
384
+ if (config.export) {
385
+ console.error("Error: --export applies to module inputs (typebox, zod, ...); an AsyncAPI document has no exports.");
386
+ process.exit(1);
387
+ }
388
+ }
313
389
  if (config.outFile) {
314
390
  if (!config.typesOnly) {
315
391
  console.error("Error: --out-file currently supports only --types-only output. Add --types-only or use --out-dir.");
@@ -331,6 +407,14 @@ const run = async () => {
331
407
  process.exit(1);
332
408
  }
333
409
  const outputDir = resolve(config.outDir);
410
+ if (config.input === "asyncapi") {
411
+ if (!config.schema) {
412
+ console.error("Error: --schema is required. Provide a path to an AsyncAPI document (JSON or YAML).");
413
+ process.exit(1);
414
+ }
415
+ await runAsyncApi(config, resolve(config.schema), outputDir);
416
+ return;
417
+ }
334
418
  if (config.schemaDir) {
335
419
  await runRecursive(config, resolve(config.schemaDir), outputDir);
336
420
  return;
@@ -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 --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 --build Compile the generated files to .js/.d.ts (implies --import-ext js)\n --force Overwrite pre-existing files in the output destination\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\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\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 --build Compile the generated files to .js/.d.ts (implies --import-ext js)\n --force Overwrite pre-existing files in the output destination\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\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
@@ -12,7 +12,8 @@ Subcommands (each with its own flags and --help):
12
12
  Input:
13
13
  --schema <path> Schema to process: a JSON Schema file, or a module when --input is set
14
14
  --schema-dir <dir> Directory of JSON Schemas, processed recursively (instead of --schema)
15
- --input <format> Schema source format: json (default), typebox, zod, valibot, effect, avro
15
+ --input <format> Schema source format: json (default), typebox, zod, valibot, effect, avro,
16
+ or asyncapi (an AsyncAPI 2.x/3.0 document, JSON or YAML)
16
17
  --export <name> Which export of the schema module to use when --input names a module format
17
18
 
18
19
  Output:
@@ -35,6 +36,8 @@ Validation:
35
36
  --strip-unknown Silently drop undeclared input keys at every nesting level
36
37
  --case-insensitive Normalize mis-cased enum/const strings to their exact casing
37
38
  --log-warnings console.warn on input keys not declared in the schema
39
+ --unknown-keys <s> How closed objects are proven free of undeclared keys:
40
+ count-keys (default, fastest on Bun) or count-enumerable (fastest on Node)
38
41
 
39
42
  Reference resolution:
40
43
  --resolve-remote Fetch http(s) $refs while resolving cross-file/remote refs (off by default)
package/dist/lint/run.js CHANGED
@@ -13,6 +13,16 @@ const SEVERITY_BY_NAME = {
13
13
  hint: DiagnosticSeverity.Hint
14
14
  };
15
15
  const SEVERITY_LABEL = ["error", "warning", "info", "hint"];
16
+ const buildAsyncApiPreset = async () => (await import("@amritk/lint/rules/asyncapi")).createAsyncApiRuleset();
17
+ const buildOpenApiPreset = async () => (await import("@amritk/lint/rules/openapi")).createOpenApiRuleset();
18
+ const PRESET_RULESETS = /* @__PURE__ */ new Map([
19
+ ["asyncapi", buildAsyncApiPreset],
20
+ ["loupe:asyncapi", buildAsyncApiPreset],
21
+ ["spectral:asyncapi", buildAsyncApiPreset],
22
+ ["oas", buildOpenApiPreset],
23
+ ["loupe:oas", buildOpenApiPreset],
24
+ ["spectral:oas", buildOpenApiPreset]
25
+ ]);
16
26
  const formatReport = (findings) => {
17
27
  if (findings.length === 0)
18
28
  return "No problems found\n";
@@ -51,7 +61,11 @@ const parseArgs = async (argv) => {
51
61
  try {
52
62
  const parsed = await yargs(argv).scriptName("lint").parserConfiguration({ "greedy-arrays": false }).strict().exitProcess(false).fail((message, error) => {
53
63
  throw error ?? new Error(message);
54
- }).usage("$0 [documents..]", "Lint JSON/YAML documents against a ruleset").positional("documents", { describe: "Documents or globs to lint", type: "string", array: true }).option("ruleset", { alias: "r", type: "string", describe: "Path to a ruleset file" }).option("encoding", { type: "string", default: "utf8", describe: "Input encoding" }).option("fail-severity", { alias: "F", type: "string", default: "error", choices: Object.keys(SEVERITY_BY_NAME) }).option("display-only-failures", { alias: "D", type: "boolean", default: false }).option("verbose", { type: "boolean", default: false }).option("quiet", { alias: "q", type: "boolean", default: false }).option("stdin-filepath", {
64
+ }).usage("$0 [documents..]", "Lint JSON/YAML documents against a ruleset").positional("documents", { describe: "Documents or globs to lint", type: "string", array: true }).option("ruleset", {
65
+ alias: "r",
66
+ type: "string",
67
+ describe: "Path to a ruleset file, or a built-in preset: asyncapi, oas"
68
+ }).option("encoding", { type: "string", default: "utf8", describe: "Input encoding" }).option("fail-severity", { alias: "F", type: "string", default: "error", choices: Object.keys(SEVERITY_BY_NAME) }).option("display-only-failures", { alias: "D", type: "boolean", default: false }).option("verbose", { type: "boolean", default: false }).option("quiet", { alias: "q", type: "boolean", default: false }).option("stdin-filepath", {
55
69
  type: "string",
56
70
  describe: "Path to associate with stdin input (labels findings and enables ruleset discovery)"
57
71
  }).option("concurrency", {
@@ -127,9 +141,15 @@ Run \`mjst lint --help\` for the supported flags.
127
141
  let rulesetDefinition;
128
142
  let rulesetBasePath;
129
143
  if (parsed.ruleset) {
130
- rulesetDefinition = await loadRuleset(parsed.ruleset);
131
- rulesetBasePath = dirname(isAbsolute(parsed.ruleset) ? parsed.ruleset : resolve(process.cwd(), parsed.ruleset));
132
- reportRulesetProblems(rulesetDefinition, parsed.ruleset);
144
+ const preset = PRESET_RULESETS.get(parsed.ruleset);
145
+ if (preset) {
146
+ rulesetDefinition = await preset();
147
+ } else {
148
+ const definition = await loadRuleset(parsed.ruleset);
149
+ rulesetDefinition = definition;
150
+ rulesetBasePath = dirname(isAbsolute(parsed.ruleset) ? parsed.ruleset : resolve(process.cwd(), parsed.ruleset));
151
+ reportRulesetProblems(definition, parsed.ruleset);
152
+ }
133
153
  }
134
154
  const documents = parsed.documents ?? [];
135
155
  const targets = await fg(documents, { dot: true, onlyFiles: true });
@@ -0,0 +1,12 @@
1
+ import type { CliConfig } from './cli-config.js';
2
+ /**
3
+ * Reads and parses an AsyncAPI document (JSON or YAML) off disk. When the
4
+ * document carries cross-file or remote `$ref`s, the whole document is
5
+ * dereferenced with `@amritk/resolve-refs` under the same policy flags as JSON
6
+ * Schema input — offline by default, SSRF-guarded, confined to the document's
7
+ * directory. A document with only same-document `#/...` references (the common
8
+ * case) skips that pass entirely: the extractor follows them itself, and keeps
9
+ * `#/components/schemas/...` pointers *as names* so the generators emit one
10
+ * type per component instead of an inlined blob.
11
+ */
12
+ export declare const loadAsyncApiDocument: (config: Partial<CliConfig>, documentPath: string) => Promise<unknown>;
@@ -0,0 +1,91 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { resolve } from "node:path";
3
+ import { assignKey } from "@amritk/helpers/assign-key";
4
+ import { readKey } from "@amritk/helpers/read-key";
5
+ import { resolveRefsFromFile } from "@amritk/resolve-refs";
6
+ import { parseDocument } from "@amritk/yaml";
7
+ import { hasExternalRefs } from "./has-external-refs.js";
8
+ import { buildResolveOptions, formatResolveErrors } from "./resolve-policy.js";
9
+ const parseDocumentText = (content, location) => {
10
+ const text = content.charCodeAt(0) === 65279 ? content.slice(1) : content;
11
+ if (/\.json$/i.test(location))
12
+ return JSON.parse(text);
13
+ const doc = parseDocument(text);
14
+ if (doc.errors.length > 0) {
15
+ const details = doc.errors.slice(0, 5).map((error) => ` - ${error.message}`).join("\n");
16
+ throw new Error(`Failed to parse ${location} as YAML:
17
+ ${details}`);
18
+ }
19
+ if (doc.warnings.some((warning) => warning.code === "MULTIPLE_DOCUMENTS")) {
20
+ throw new Error(`${location} contains multiple YAML documents; an AsyncAPI document must be a single-document file.`);
21
+ }
22
+ return doc.toJS();
23
+ };
24
+ const nodeAt = (root, pointer) => {
25
+ let node = root;
26
+ for (const segment of pointer) {
27
+ if (Array.isArray(node)) {
28
+ node = node[typeof segment === "number" ? segment : Number(segment)];
29
+ } else if (typeof node === "object" && node !== null) {
30
+ node = readKey(node, String(segment));
31
+ } else {
32
+ return void 0;
33
+ }
34
+ }
35
+ return node;
36
+ };
37
+ const canonicalizeAliases = (resolved, origins, rootLocation) => {
38
+ const canonical = /* @__PURE__ */ new Map();
39
+ for (const [copy, origin] of origins) {
40
+ if (origin.location !== rootLocation)
41
+ continue;
42
+ const target = nodeAt(resolved, origin.pointer);
43
+ if (typeof target === "object" && target !== null && target !== copy)
44
+ canonical.set(copy, target);
45
+ }
46
+ if (canonical.size === 0)
47
+ return;
48
+ const seen = /* @__PURE__ */ new Set();
49
+ const rewrite = (node) => {
50
+ if (typeof node !== "object" || node === null || seen.has(node))
51
+ return;
52
+ seen.add(node);
53
+ if (Array.isArray(node)) {
54
+ for (let index = 0; index < node.length; index++) {
55
+ const value = node[index];
56
+ const replacement = typeof value === "object" && value !== null ? canonical.get(value) : void 0;
57
+ if (replacement !== void 0)
58
+ node[index] = replacement;
59
+ else
60
+ rewrite(value);
61
+ }
62
+ return;
63
+ }
64
+ for (const [key, value] of Object.entries(node)) {
65
+ const replacement = typeof value === "object" && value !== null ? canonical.get(value) : void 0;
66
+ if (replacement !== void 0)
67
+ assignKey(node, key, replacement);
68
+ else
69
+ rewrite(value);
70
+ }
71
+ };
72
+ rewrite(resolved);
73
+ };
74
+ const loadAsyncApiDocument = async (config, documentPath) => {
75
+ const document = parseDocumentText(await readFile(documentPath, "utf-8"), documentPath);
76
+ if (!hasExternalRefs(document))
77
+ return document;
78
+ const { resolved, errors, origins } = await resolveRefsFromFile(documentPath, {
79
+ ...buildResolveOptions(config, documentPath),
80
+ parse: parseDocumentText,
81
+ trackOrigins: true
82
+ });
83
+ if (errors.length > 0)
84
+ throw new Error(formatResolveErrors(documentPath, errors));
85
+ if (origins !== void 0)
86
+ canonicalizeAliases(resolved, origins, resolve(documentPath));
87
+ return resolved;
88
+ };
89
+ export {
90
+ loadAsyncApiDocument
91
+ };
@@ -1,7 +1,8 @@
1
1
  import { readFile } from "node:fs/promises";
2
2
  import { resolve } from "node:path";
3
+ import { isUnknownKeysStrategy } from "@amritk/helpers/unknown-keys-strategy";
3
4
  import { validateConfig } from "./validate-config.js";
4
- const SOURCE_FORMATS = ["json", "typebox", "zod", "valibot", "effect", "avro"];
5
+ const SOURCE_FORMATS = ["json", "typebox", "zod", "valibot", "effect", "avro", "asyncapi"];
5
6
  const isSourceFormat = (value) => typeof value === "string" && SOURCE_FORMATS.includes(value);
6
7
  const isHelpersMode = (value) => value === "package" || value === "embedded";
7
8
  const isImportExt = (value) => value === "js" || value === "ts";
@@ -36,6 +37,7 @@ const loadConfig = async (configPath) => {
36
37
  ...typeof obj["stripUnknown"] === "boolean" && { stripUnknown: obj["stripUnknown"] },
37
38
  ...typeof obj["caseInsensitive"] === "boolean" && { caseInsensitive: obj["caseInsensitive"] },
38
39
  ...typeof obj["readonly"] === "boolean" && { readonly: obj["readonly"] },
40
+ ...isUnknownKeysStrategy(obj["unknownKeys"]) && { unknownKeys: obj["unknownKeys"] },
39
41
  ...isHelpersMode(obj["helpers"]) && { helpers: obj["helpers"] },
40
42
  ...isImportExt(obj["importExt"]) && { importExt: obj["importExt"] },
41
43
  ...typeof obj["rootType"] === "string" && { rootType: obj["rootType"] },
@@ -1,25 +1,9 @@
1
1
  import { readFile } from "node:fs/promises";
2
- import { dirname, resolve } from "node:path";
3
2
  import { getAdapter } from "@amritk/adapters/get-adapter";
4
3
  import { resolveRefsFromFile } from "@amritk/resolve-refs";
5
- import { withAllowedRootsHint } from "./allowed-roots-hint.js";
6
4
  import { hasExternalRefs } from "./has-external-refs.js";
7
5
  import { loadSchemaModule } from "./load-schema-module.js";
8
- const buildResolveOptions = (config, schemaPath) => {
9
- const remote = (config.resolveRemote ?? false) || (config.allowedHosts?.length ?? 0) > 0;
10
- const extraRoots = (config.allowedRoots ?? []).map((root) => resolve(root));
11
- return {
12
- remote,
13
- ...extraRoots.length > 0 ? { allowedRoots: [dirname(resolve(schemaPath)), ...extraRoots] } : {},
14
- ...config.allowedHosts ? { allowedHosts: [...config.allowedHosts] } : {},
15
- ...config.allowPrivateHosts ? { allowPrivateHosts: config.allowPrivateHosts } : {}
16
- };
17
- };
18
- const formatResolveErrors = (schemaPath, errors) => {
19
- const details = errors.map((error) => ` - ${withAllowedRootsHint(error.message)}`).join("\n");
20
- return `Failed to resolve $refs in ${schemaPath}:
21
- ${details}`;
22
- };
6
+ import { buildResolveOptions, formatResolveErrors } from "./resolve-policy.js";
23
7
  const loadSchema = async (config, schemaPath) => {
24
8
  const inputFormat = config.input ?? "json";
25
9
  if (inputFormat === "json") {
@@ -1,4 +1,5 @@
1
- const SOURCE_FORMATS = ["json", "typebox", "zod", "valibot", "effect", "avro"];
1
+ import { isUnknownKeysStrategy, UNKNOWN_KEYS_STRATEGIES } from "@amritk/helpers/unknown-keys-strategy";
2
+ const SOURCE_FORMATS = ["json", "typebox", "zod", "valibot", "effect", "avro", "asyncapi"];
2
3
  const LIST_KEYS = /* @__PURE__ */ new Set(["allowedHosts", "allowedRoots"]);
3
4
  const BOOLEAN_KEYS = /* @__PURE__ */ new Set([
4
5
  "typesOnly",
@@ -24,7 +25,8 @@ const VALUE_KEYS = /* @__PURE__ */ new Set([
24
25
  "helpers",
25
26
  "typeSuffix",
26
27
  "importExt",
27
- "rootType"
28
+ "rootType",
29
+ "unknownKeys"
28
30
  ]);
29
31
  const EXTERNAL_VALUE_KEYS = /* @__PURE__ */ new Set(["config"]);
30
32
  const toCamelCase = (key) => key.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
@@ -96,6 +98,13 @@ const assignValue = (config, key, value) => {
96
98
  config.importExt = parsed;
97
99
  return true;
98
100
  }
101
+ case "unknownKeys": {
102
+ if (!isUnknownKeysStrategy(value)) {
103
+ throw new Error(`Invalid --unknown-keys value "${value}". Expected one of: ${UNKNOWN_KEYS_STRATEGIES.join(", ")}.`);
104
+ }
105
+ config.unknownKeys = value;
106
+ return true;
107
+ }
99
108
  default:
100
109
  return false;
101
110
  }
@@ -0,0 +1,33 @@
1
+ import type { ResolveError } from '@amritk/resolve-refs';
2
+ import type { CliConfig } from './cli-config.js';
3
+ /**
4
+ * Builds the `@amritk/resolve-refs` options from the CLI config, mirroring the
5
+ * safety posture of `mjst lint`: remote (`http(s)`) `$ref`s are refused unless
6
+ * opted into, a non-empty `allowedHosts` implies remote fetching, private/
7
+ * loopback hosts stay blocked as an SSRF guard unless explicitly permitted, and
8
+ * a local `$ref` may not leave the document's own directory unless
9
+ * `allowedRoots` names somewhere else.
10
+ *
11
+ * `allowedRoots` is passed only when the user asked for it, and it *adds* to the
12
+ * document's own directory rather than replacing it. The library option replaces
13
+ * the default outright, which would make `--allowed-roots ../common` quietly
14
+ * revoke the folder the document itself lives in — the one directory a user
15
+ * would never think to list. Entries are resolved against the process cwd (both
16
+ * from the flag and from a config file), matching how `schema` and `outDir` are
17
+ * treated in `cli.ts`.
18
+ *
19
+ * Shared by every document loader (plain JSON Schema, AsyncAPI) so one flag set
20
+ * means one policy.
21
+ */
22
+ export declare const buildResolveOptions: (config: Partial<CliConfig>, documentPath: string) => {
23
+ remote: boolean;
24
+ allowedRoots?: string[];
25
+ allowedHosts?: string[];
26
+ allowPrivateHosts?: true;
27
+ };
28
+ /**
29
+ * Renders resolver failures into a single CLI error message. A confinement
30
+ * refusal is rewritten to name `--allowed-roots`, because the raw library text
31
+ * tells the user to set an option the CLI does not otherwise expose.
32
+ */
33
+ export declare const formatResolveErrors: (documentPath: string, errors: readonly ResolveError[]) => string;
@@ -0,0 +1,21 @@
1
+ import { dirname, resolve } from "node:path";
2
+ import { withAllowedRootsHint } from "./allowed-roots-hint.js";
3
+ const buildResolveOptions = (config, documentPath) => {
4
+ const remote = (config.resolveRemote ?? false) || (config.allowedHosts?.length ?? 0) > 0;
5
+ const extraRoots = (config.allowedRoots ?? []).map((root) => resolve(root));
6
+ return {
7
+ remote,
8
+ ...extraRoots.length > 0 ? { allowedRoots: [dirname(resolve(documentPath)), ...extraRoots] } : {},
9
+ ...config.allowedHosts ? { allowedHosts: [...config.allowedHosts] } : {},
10
+ ...config.allowPrivateHosts ? { allowPrivateHosts: config.allowPrivateHosts } : {}
11
+ };
12
+ };
13
+ const formatResolveErrors = (documentPath, errors) => {
14
+ const details = errors.map((error) => ` - ${withAllowedRootsHint(error.message)}`).join("\n");
15
+ return `Failed to resolve $refs in ${documentPath}:
16
+ ${details}`;
17
+ };
18
+ export {
19
+ buildResolveOptions,
20
+ formatResolveErrors
21
+ };
@@ -1,8 +1,9 @@
1
1
  import { readKey } from "@amritk/helpers/read-key";
2
+ import { UNKNOWN_KEYS_STRATEGIES } from "@amritk/helpers/unknown-keys-strategy";
2
3
  const CONFIG_KEYS = {
3
4
  schema: { type: "string" },
4
5
  schemaDir: { type: "string" },
5
- input: { type: "string", enum: ["json", "typebox", "zod", "valibot", "effect", "avro"] },
6
+ input: { type: "string", enum: ["json", "typebox", "zod", "valibot", "effect", "avro", "asyncapi"] },
6
7
  export: { type: "string" },
7
8
  outDir: { type: "string" },
8
9
  outFile: { type: "string" },
@@ -16,6 +17,7 @@ const CONFIG_KEYS = {
16
17
  stripUnknown: { type: "boolean" },
17
18
  caseInsensitive: { type: "boolean" },
18
19
  readonly: { type: "boolean" },
20
+ unknownKeys: { type: "string", enum: [...UNKNOWN_KEYS_STRATEGIES] },
19
21
  helpers: { type: "string", enum: ["package", "embedded"] },
20
22
  typeSuffix: { type: "string" },
21
23
  banner: { type: "boolean|string" },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amritk/mjst",
3
- "version": "0.16.1",
3
+ "version": "0.17.0",
4
4
  "description": "Generate TypeScript parsers and type definitions from JSON Schemas.",
5
5
  "type": "module",
6
6
  "engines": {
@@ -49,13 +49,14 @@
49
49
  "./package.json": "./package.json"
50
50
  },
51
51
  "dependencies": {
52
- "@amritk/adapters": "^0.5.2",
53
- "@amritk/api": "^0.16.2",
54
- "@amritk/generate-examples": "^0.8.1",
55
- "@amritk/generate-parsers": "^0.20.1",
56
- "@amritk/generate-validators": "^0.15.1",
57
- "@amritk/helpers": "^0.18.0",
58
- "@amritk/lint": "^0.5.2",
52
+ "@amritk/adapters": "^0.6.0",
53
+ "@amritk/api": "^0.16.3",
54
+ "@amritk/asyncapi": "^0.1.0",
55
+ "@amritk/generate-examples": "^0.8.2",
56
+ "@amritk/generate-parsers": "^0.21.0",
57
+ "@amritk/generate-validators": "^0.16.0",
58
+ "@amritk/helpers": "^0.19.0",
59
+ "@amritk/lint": "^0.5.3",
59
60
  "@amritk/resolve-refs": "^0.7.1",
60
61
  "@amritk/yaml": "^0.7.2",
61
62
  "fast-glob": "^3.3.2",
@@ -63,7 +64,7 @@
63
64
  "yargs": "^17.7.2"
64
65
  },
65
66
  "devDependencies": {
66
- "@amritk/generate-markdown": "^0.5.0",
67
+ "@amritk/generate-markdown": "^0.6.0",
67
68
  "@types/yargs": "^17.0.33"
68
69
  }
69
70
  }