@amritk/mjst 0.25.1 → 0.26.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/README.md +1 -1
- package/dist/cli-config.d.ts +20 -6
- package/dist/describe-resolve-error.d.ts +12 -0
- package/dist/describe-resolve-error.js +5 -0
- package/dist/lint/resolver.d.ts +5 -0
- package/dist/lint/resolver.js +48 -20
- package/dist/load-asyncapi-document.js +11 -13
- package/dist/load-schema.js +4 -1
- package/dist/parse-yaml-strict.d.ts +28 -0
- package/dist/parse-yaml-strict.js +31 -0
- package/dist/resolve-policy.js +2 -2
- package/package.json +9 -9
package/README.md
CHANGED
|
@@ -437,7 +437,7 @@ Under `--table` only the content between `<!-- config-table-start -->
|
|
|
437
437
|
<td align="center"><code>false</code></td>
|
|
438
438
|
</tr>
|
|
439
439
|
<tr>
|
|
440
|
-
<td colspan="4">Also emit a coerceX alongside each validateX. It moves scalars toward the type the schema declares and then runs the very same validateX over the result, so a config value written the YAML way reads the same whether it came from a config file or an extension. The input is never modified and nothing is substituted: a value that cannot be coerced into a valid one reaches the validator untouched, so the error names what the caller actually wrote, with the keyword and params that rejected it. The table is Ajv's coerceTypes minus the cells where Ajv guesses — no whitespace-to-zero, no hex or Infinity strings, and nothing coerced to or from null — so every
|
|
440
|
+
<td colspan="4">Also emit a coerceX alongside each validateX. It moves scalars toward the type the schema declares and then runs the very same validateX over the result, so a config value written the YAML way reads the same whether it came from a config file or an extension. The input is never modified and nothing is substituted: a value that cannot be coerced into a valid one reaches the validator untouched, so the error names what the caller actually wrote, with the keyword and params that rejected it. The table is Ajv's coerceTypes minus the cells where Ajv guesses — no whitespace-to-zero, no hex or Infinity strings, and nothing coerced to or from null, deliberately, since null usually means not set — so every scalar this coerces, Ajv coerces to the same value, and the rest become errors rather than silent repairs. Coercion reaches through $ref, allOf, if/then/else and unions. At a union a value that already matches a branch as written is left alone; otherwise each branch coerces it its own way, and the result is taken only when every branch that then accepts it agrees on it. So the answer does not depend on the order the union was written in, and a const false branch keeps false where Ajv turns it into the string false through an earlier string branch. A valid document comes back as the very same object, and against Ajv cloning its input first coerceX runs 5–36× faster on valid input and 1.4–2.1× faster on input that needs coercing. Requires validators.</td>
|
|
441
441
|
</tr>
|
|
442
442
|
<tr>
|
|
443
443
|
<td>🔧 <code>repair</code></td>
|
package/dist/cli-config.d.ts
CHANGED
|
@@ -96,12 +96,26 @@ export type CliConfig = {
|
|
|
96
96
|
*
|
|
97
97
|
* The table is Ajv's `coerceTypes` minus the cells where Ajv guesses — no
|
|
98
98
|
* whitespace-to-zero, no `0x`/`Infinity` strings, and nothing coerced to or
|
|
99
|
-
* from `null`.
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
99
|
+
* from `null`. `null` is left alone deliberately: it usually means "not set",
|
|
100
|
+
* and reading it as `""`, `0` or `false` erases that. Every scalar this
|
|
101
|
+
* coerces, Ajv coerces to the same value; the cells Ajv guesses at become
|
|
102
|
+
* errors instead of silent repairs.
|
|
103
|
+
*
|
|
104
|
+
* Coercion reaches through `$ref`, `allOf`, `if`/`then`/`else` and unions. At
|
|
105
|
+
* a union (`anyOf`, `oneOf`, a `type` array) a value that already matches a
|
|
106
|
+
* branch as written is left alone. Otherwise each branch coerces it its own
|
|
107
|
+
* way, and the result is taken only when every branch that then accepts it
|
|
108
|
+
* agrees on it; two different readings leave the value as written for the
|
|
109
|
+
* validator to report. So the answer never depends on the order the union was
|
|
110
|
+
* written in, which under Ajv it does — Ajv coerces into the first branch that
|
|
111
|
+
* will take the value, and turns `false` into `"false"` through a string
|
|
112
|
+
* branch listed before a `const: false` one.
|
|
113
|
+
*
|
|
114
|
+
* A document that is already valid is answered by `isX` alone where that is a
|
|
115
|
+
* standalone guard, and handed back as the very same object. Against Ajv
|
|
116
|
+
* cloning its input first — which it must to leave the caller's document
|
|
117
|
+
* alone — `coerceX` runs 5–36× faster on valid input and 1.4–2.1× faster on
|
|
118
|
+
* input that needs coercing (`bench:validators:coerce`).
|
|
105
119
|
*
|
|
106
120
|
* Requires `validators`. `validateX` and `isX` are unchanged either way.
|
|
107
121
|
*/
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turns a `ResolveError` message from `@amritk/resolve-refs` into the text the
|
|
3
|
+
* CLI shows.
|
|
4
|
+
*
|
|
5
|
+
* The resolver records a document that failed to load as `String(err)`, so the
|
|
6
|
+
* message opens with the error's class name — `Error: ENOENT: …`, `Error:
|
|
7
|
+
* Failed to parse …` — which tells the reader nothing and pushes the part that
|
|
8
|
+
* does to the right. Only that leading prefix is dropped; the library keeps its
|
|
9
|
+
* wording, since other callers may match on it. A confinement refusal also gets
|
|
10
|
+
* the `--allowed-roots` hint (see {@link withAllowedRootsHint}).
|
|
11
|
+
*/
|
|
12
|
+
export declare const describeResolveError: (message: string) => string;
|
package/dist/lint/resolver.d.ts
CHANGED
|
@@ -27,5 +27,10 @@ export type ResolverOptions = {
|
|
|
27
27
|
* refs is resolved from disk **only when its source file reads back exactly as
|
|
28
28
|
* the linted input** — so a piped document (`--stdin-filepath`) never resolves
|
|
29
29
|
* against a different on-disk file — with remote fetching gated by `options`.
|
|
30
|
+
*
|
|
31
|
+
* A multi-document root (a YAML `---` stream) is resolved one document at a
|
|
32
|
+
* time, each as a root of its own — `#/…` points into the document the `$ref`
|
|
33
|
+
* is written in, and so does a `$ref` naming the root file — and the results
|
|
34
|
+
* are put back into the array the linter reads the stream as.
|
|
30
35
|
*/
|
|
31
36
|
export declare const createLintResolver: (options?: ResolverOptions) => LintResolver;
|
package/dist/lint/resolver.js
CHANGED
|
@@ -3,19 +3,20 @@ import { dirname, resolve as resolvePath } from "node:path";
|
|
|
3
3
|
import { createDocument, resolveSourceOriginFromMap } from "@amritk/lint";
|
|
4
4
|
import { DiagnosticSeverity } from "@amritk/lint/types";
|
|
5
5
|
import { resolveRefs, resolveRefsFromFile } from "@amritk/resolve-refs";
|
|
6
|
-
import {
|
|
7
|
-
import { withAllowedRootsHint } from "../allowed-roots-hint.js";
|
|
6
|
+
import { describeResolveError } from "../describe-resolve-error.js";
|
|
8
7
|
import { hasExternalRefs } from "../has-external-refs.js";
|
|
8
|
+
import { parseYamlStrict } from "../parse-yaml-strict.js";
|
|
9
9
|
const isRemote = (location) => /^https?:\/\//i.test(location);
|
|
10
|
+
const parseReferencedYaml = (content, location) => parseYamlStrict(content, location, { what: "a $ref target", singleLine: true, uniqueKeys: false });
|
|
10
11
|
const parseDoc = (content, location) => {
|
|
11
12
|
if (/\.ya?ml$/i.test(location))
|
|
12
|
-
return
|
|
13
|
+
return parseReferencedYaml(content, location);
|
|
13
14
|
if (/\.json$/i.test(location))
|
|
14
15
|
return JSON.parse(content);
|
|
15
16
|
try {
|
|
16
17
|
return JSON.parse(content);
|
|
17
18
|
} catch {
|
|
18
|
-
return
|
|
19
|
+
return parseReferencedYaml(content, location);
|
|
19
20
|
}
|
|
20
21
|
};
|
|
21
22
|
const readsBackAs = (absolute, input) => {
|
|
@@ -52,7 +53,7 @@ const toFindings = (errors, root) => errors.map((error) => {
|
|
|
52
53
|
const location = error.path.length > 0 ? root.getLocationForJsonPath(error.path, true) : void 0;
|
|
53
54
|
const finding = {
|
|
54
55
|
code: "unresolved-ref",
|
|
55
|
-
message:
|
|
56
|
+
message: describeResolveError(error.message),
|
|
56
57
|
path: error.path,
|
|
57
58
|
severity: DiagnosticSeverity.Error,
|
|
58
59
|
range: location?.range ?? DOCUMENT_START
|
|
@@ -61,6 +62,18 @@ const toFindings = (errors, root) => errors.map((error) => {
|
|
|
61
62
|
finding.source = root.source;
|
|
62
63
|
return finding;
|
|
63
64
|
});
|
|
65
|
+
const isMultiDocument = (document) => Array.isArray(document.data) && document.data.length > 1 && document.getLocationForJsonPath([]) === void 0;
|
|
66
|
+
const rebaseOntoStream = ({ errors, origins, location }, index, rootLocation, into) => {
|
|
67
|
+
if (origins) {
|
|
68
|
+
for (const [node, origin] of origins) {
|
|
69
|
+
if (into.has(node))
|
|
70
|
+
continue;
|
|
71
|
+
const inThisDocument = origin.location === location;
|
|
72
|
+
into.set(node, inThisDocument ? { location: rootLocation, pointer: [index, ...origin.pointer] } : origin);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return errors.map((error) => ({ message: error.message, path: [index, ...error.path] }));
|
|
76
|
+
};
|
|
64
77
|
const createLintResolver = (options = {}) => {
|
|
65
78
|
const fromFileOptions = {
|
|
66
79
|
remote: options.remote ?? false,
|
|
@@ -70,26 +83,41 @@ const createLintResolver = (options = {}) => {
|
|
|
70
83
|
trackOrigins: true
|
|
71
84
|
};
|
|
72
85
|
const extraRoots = (options.allowedRoots ?? []).map((root) => resolvePath(root));
|
|
86
|
+
const resolveDocument = async (data, absolute) => {
|
|
87
|
+
if (absolute !== void 0 && hasExternalRefs(data)) {
|
|
88
|
+
const result = await resolveRefsFromFile(absolute, {
|
|
89
|
+
...fromFileOptions,
|
|
90
|
+
// The linter has parsed this very text already; hand that value over
|
|
91
|
+
// rather than have the root read and parsed a second time (the most
|
|
92
|
+
// expensive step of linting a large spec). It also keeps a root with a
|
|
93
|
+
// syntax error of its own (already a `parser` finding) out of the
|
|
94
|
+
// strict parse that referenced files get.
|
|
95
|
+
rootDocument: data,
|
|
96
|
+
...extraRoots.length > 0 ? { allowedRoots: [dirname(absolute), ...extraRoots] } : {}
|
|
97
|
+
});
|
|
98
|
+
return { ...result, location: absolute };
|
|
99
|
+
}
|
|
100
|
+
return { ...resolveRefs(data, { trackOrigins: true }), location: "" };
|
|
101
|
+
};
|
|
73
102
|
return async (document, { input }) => {
|
|
74
103
|
const { source } = document;
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
resolved: resolved2,
|
|
84
|
-
sources: buildSourceSet(document, resolved2, origins2, absolute),
|
|
85
|
-
...errors2.length > 0 ? { diagnostics: toFindings(errors2, document) } : {}
|
|
86
|
-
};
|
|
87
|
-
}
|
|
104
|
+
const absolute = source && hasExternalRefs(document.data) && readsBackAs(resolvePath(source), input) ? resolvePath(source) : void 0;
|
|
105
|
+
if (!isMultiDocument(document)) {
|
|
106
|
+
const { resolved: resolved2, origins: origins2, errors: errors2, location } = await resolveDocument(document.data, absolute);
|
|
107
|
+
return {
|
|
108
|
+
resolved: resolved2,
|
|
109
|
+
sources: buildSourceSet(document, resolved2, origins2, location),
|
|
110
|
+
...errors2.length > 0 ? { diagnostics: toFindings(errors2, document) } : {}
|
|
111
|
+
};
|
|
88
112
|
}
|
|
89
|
-
const
|
|
113
|
+
const rootLocation = absolute ?? "";
|
|
114
|
+
const results = await Promise.all(document.data.map((data) => resolveDocument(data, absolute)));
|
|
115
|
+
const origins = /* @__PURE__ */ new Map();
|
|
116
|
+
const errors = results.flatMap((result, index) => rebaseOntoStream(result, index, rootLocation, origins));
|
|
117
|
+
const resolved = results.map((result) => result.resolved);
|
|
90
118
|
return {
|
|
91
119
|
resolved,
|
|
92
|
-
sources: buildSourceSet(document, resolved, origins,
|
|
120
|
+
sources: buildSourceSet(document, resolved, origins, rootLocation),
|
|
93
121
|
...errors.length > 0 ? { diagnostics: toFindings(errors, document) } : {}
|
|
94
122
|
};
|
|
95
123
|
};
|
|
@@ -3,24 +3,19 @@ import { resolve } from "node:path";
|
|
|
3
3
|
import { assignKey } from "@amritk/helpers/assign-key";
|
|
4
4
|
import { readKey } from "@amritk/helpers/read-key";
|
|
5
5
|
import { resolveRefsFromFile } from "@amritk/resolve-refs";
|
|
6
|
-
import { parseDocument } from "@amritk/yaml";
|
|
7
6
|
import { hasExternalRefs } from "./has-external-refs.js";
|
|
7
|
+
import { parseYamlStrict } from "./parse-yaml-strict.js";
|
|
8
8
|
import { buildResolveOptions, formatResolveErrors } from "./resolve-policy.js";
|
|
9
|
-
const parseDocumentText = (content, location) => {
|
|
9
|
+
const parseDocumentText = (content, location, referenced = false) => {
|
|
10
10
|
const text = content.charCodeAt(0) === 65279 ? content.slice(1) : content;
|
|
11
11
|
if (/\.json$/i.test(location))
|
|
12
12
|
return JSON.parse(text);
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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();
|
|
13
|
+
return parseYamlStrict(text, location, {
|
|
14
|
+
what: referenced ? "a $ref target" : "an AsyncAPI document",
|
|
15
|
+
singleLine: referenced
|
|
16
|
+
});
|
|
23
17
|
};
|
|
18
|
+
const parseReferencedText = (content, location) => parseDocumentText(content, location, true);
|
|
24
19
|
const nodeAt = (root, pointer) => {
|
|
25
20
|
let node = root;
|
|
26
21
|
for (const segment of pointer) {
|
|
@@ -77,7 +72,10 @@ const loadAsyncApiDocument = async (config, documentPath) => {
|
|
|
77
72
|
return document;
|
|
78
73
|
const { resolved, errors, origins } = await resolveRefsFromFile(documentPath, {
|
|
79
74
|
...buildResolveOptions(config, documentPath),
|
|
80
|
-
parse
|
|
75
|
+
// Parsed just above; without this the resolver would read and parse the
|
|
76
|
+
// whole document a second time.
|
|
77
|
+
rootDocument: document,
|
|
78
|
+
parse: parseReferencedText,
|
|
81
79
|
trackOrigins: true
|
|
82
80
|
});
|
|
83
81
|
if (errors.length > 0)
|
package/dist/load-schema.js
CHANGED
|
@@ -10,7 +10,10 @@ const loadSchema = async (config, schemaPath) => {
|
|
|
10
10
|
const data = JSON.parse(await readFile(schemaPath, "utf-8"));
|
|
11
11
|
if (!hasExternalRefs(data))
|
|
12
12
|
return data;
|
|
13
|
-
const { resolved, errors } = await resolveRefsFromFile(schemaPath,
|
|
13
|
+
const { resolved, errors } = await resolveRefsFromFile(schemaPath, {
|
|
14
|
+
...buildResolveOptions(config, schemaPath),
|
|
15
|
+
rootDocument: data
|
|
16
|
+
});
|
|
14
17
|
if (errors.length > 0)
|
|
15
18
|
throw new Error(formatResolveErrors(schemaPath, errors));
|
|
16
19
|
return resolved;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type ParseOptions } from '@amritk/yaml';
|
|
2
|
+
export type ParseYamlStrictOptions = ParseOptions & {
|
|
3
|
+
/**
|
|
4
|
+
* What the file is required to be, for the multi-document message — `an
|
|
5
|
+
* AsyncAPI document`, `a $ref target` — so the reader learns why a perfectly
|
|
6
|
+
* valid YAML stream was refused.
|
|
7
|
+
*/
|
|
8
|
+
what: string;
|
|
9
|
+
/**
|
|
10
|
+
* Join the listed problems into one line (`; `) instead of one ` - ` bullet
|
|
11
|
+
* per line. For reports that print one line per finding, where a line break
|
|
12
|
+
* in the message would read as a new, unlabelled finding.
|
|
13
|
+
*/
|
|
14
|
+
singleLine?: boolean;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Parses `text` as a single YAML document and returns its value, throwing
|
|
18
|
+
* instead of handing back anything the parser had to guess at.
|
|
19
|
+
*
|
|
20
|
+
* `@amritk/yaml` collects problems rather than throwing, and `parse` returns
|
|
21
|
+
* the salvage of a malformed document as if it were fine — an unclosed `[`
|
|
22
|
+
* swallows the lines after it into a bogus key, and the caller builds on data
|
|
23
|
+
* the file does not contain. It also reads only the first document of a `---`
|
|
24
|
+
* stream, flagging the rest with a `MULTIPLE_DOCUMENTS` warning, so a file
|
|
25
|
+
* whose second half silently vanished would be reported as a success. Both are
|
|
26
|
+
* turned into an error here, naming `location` and each problem's `line:col`.
|
|
27
|
+
*/
|
|
28
|
+
export declare const parseYamlStrict: (text: string, location: string, options: ParseYamlStrictOptions) => unknown;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { lineCounter, parseDocument } from "@amritk/yaml";
|
|
2
|
+
const MAX_LISTED_PROBLEMS = 5;
|
|
3
|
+
const describeParseErrors = (errors, text, location, singleLine) => {
|
|
4
|
+
const { linePos } = lineCounter(text);
|
|
5
|
+
const listed = errors.slice(0, MAX_LISTED_PROBLEMS).map((error) => {
|
|
6
|
+
const { line, col } = linePos(error.start);
|
|
7
|
+
return `${location}:${line}:${col}: ${error.message}`;
|
|
8
|
+
});
|
|
9
|
+
if (errors.length > MAX_LISTED_PROBLEMS)
|
|
10
|
+
listed.push(`\u2026and ${errors.length - MAX_LISTED_PROBLEMS} more`);
|
|
11
|
+
const body = singleLine ? ` ${listed.join("; ")}` : listed.map((problem) => `
|
|
12
|
+
- ${problem}`).join("");
|
|
13
|
+
return `Failed to parse YAML:${body}`;
|
|
14
|
+
};
|
|
15
|
+
const describeMultipleDocuments = (warning, text, location, what) => {
|
|
16
|
+
const { line, col } = lineCounter(text).linePos(warning.start);
|
|
17
|
+
return `${location}:${line}:${col}: this file contains multiple YAML documents (another one follows this marker); ${what} must be a single-document file.`;
|
|
18
|
+
};
|
|
19
|
+
const parseYamlStrict = (text, location, options) => {
|
|
20
|
+
const { what, singleLine, ...parseOptions } = options;
|
|
21
|
+
const doc = parseDocument(text, parseOptions);
|
|
22
|
+
if (doc.errors.length > 0)
|
|
23
|
+
throw new Error(describeParseErrors(doc.errors, text, location, singleLine));
|
|
24
|
+
const multiple = doc.warnings.find((warning) => warning.code === "MULTIPLE_DOCUMENTS");
|
|
25
|
+
if (multiple !== void 0)
|
|
26
|
+
throw new Error(describeMultipleDocuments(multiple, text, location, what));
|
|
27
|
+
return doc.toJS();
|
|
28
|
+
};
|
|
29
|
+
export {
|
|
30
|
+
parseYamlStrict
|
|
31
|
+
};
|
package/dist/resolve-policy.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { dirname, resolve } from "node:path";
|
|
2
|
-
import {
|
|
2
|
+
import { describeResolveError } from "./describe-resolve-error.js";
|
|
3
3
|
const buildResolveOptions = (config, documentPath) => {
|
|
4
4
|
const remote = (config.resolveRemote ?? false) || (config.allowedHosts?.length ?? 0) > 0;
|
|
5
5
|
const extraRoots = (config.allowedRoots ?? []).map((root) => resolve(root));
|
|
@@ -11,7 +11,7 @@ const buildResolveOptions = (config, documentPath) => {
|
|
|
11
11
|
};
|
|
12
12
|
};
|
|
13
13
|
const formatResolveErrors = (documentPath, errors) => {
|
|
14
|
-
const details = errors.map((error) => ` - ${
|
|
14
|
+
const details = errors.map((error) => ` - ${describeResolveError(error.message)}`).join("\n");
|
|
15
15
|
return `Failed to resolve $refs in ${documentPath}:
|
|
16
16
|
${details}`;
|
|
17
17
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@amritk/mjst",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.0",
|
|
4
4
|
"description": "Generate TypeScript parsers and type definitions from JSON Schemas.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -49,16 +49,16 @@
|
|
|
49
49
|
"./package.json": "./package.json"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@amritk/adapters": "^0.6.
|
|
52
|
+
"@amritk/adapters": "^0.6.8",
|
|
53
53
|
"@amritk/api": "^0.16.6",
|
|
54
|
-
"@amritk/asyncapi": "^0.3.
|
|
55
|
-
"@amritk/generate-examples": "^0.8.
|
|
54
|
+
"@amritk/asyncapi": "^0.3.6",
|
|
55
|
+
"@amritk/generate-examples": "^0.8.10",
|
|
56
56
|
"@amritk/generate-markdown": "^0.11.0",
|
|
57
|
-
"@amritk/helpers": "^0.
|
|
58
|
-
"@amritk/lint": "^0.
|
|
59
|
-
"@amritk/validation": "^0.
|
|
60
|
-
"@amritk/resolve-refs": "^0.
|
|
61
|
-
"@amritk/yaml": "^0.
|
|
57
|
+
"@amritk/helpers": "^0.24.0",
|
|
58
|
+
"@amritk/lint": "^0.7.0",
|
|
59
|
+
"@amritk/validation": "^0.4.0",
|
|
60
|
+
"@amritk/resolve-refs": "^0.9.0",
|
|
61
|
+
"@amritk/yaml": "^0.8.0",
|
|
62
62
|
"fast-glob": "^3.3.2",
|
|
63
63
|
"json-schema-typed": "^8.0.1",
|
|
64
64
|
"yargs": "^17.7.2"
|