@wylan24/mockgen 0.0.0-stage → 0.1.0-alpha.2
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/LICENSE +21 -0
- package/README.md +188 -2
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +278 -0
- package/dist/cli.js.map +1 -0
- package/dist/collections.d.ts +46 -0
- package/dist/collections.js +215 -0
- package/dist/collections.js.map +1 -0
- package/dist/generate.d.ts +19 -0
- package/dist/generate.js +414 -0
- package/dist/generate.js.map +1 -0
- package/dist/heuristics.d.ts +28 -0
- package/dist/heuristics.js +139 -0
- package/dist/heuristics.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/infer.d.ts +10 -0
- package/dist/infer.js +166 -0
- package/dist/infer.js.map +1 -0
- package/dist/load.d.ts +3 -0
- package/dist/load.js +15 -0
- package/dist/load.js.map +1 -0
- package/dist/model.d.ts +34 -0
- package/dist/model.js +4 -0
- package/dist/model.js.map +1 -0
- package/dist/openapi.d.ts +17 -0
- package/dist/openapi.js +209 -0
- package/dist/openapi.js.map +1 -0
- package/dist/samples.d.ts +14 -0
- package/dist/samples.js +115 -0
- package/dist/samples.js.map +1 -0
- package/dist/schema.d.ts +38 -0
- package/dist/schema.js +321 -0
- package/dist/schema.js.map +1 -0
- package/dist/server.d.ts +45 -0
- package/dist/server.js +175 -0
- package/dist/server.js.map +1 -0
- package/dist/validate.d.ts +21 -0
- package/dist/validate.js +164 -0
- package/dist/validate.js.map +1 -0
- package/package.json +68 -4
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Schema } from './model.js';
|
|
2
|
+
export interface ValidationResult {
|
|
3
|
+
valid: boolean;
|
|
4
|
+
/** Human-readable errors when invalid. */
|
|
5
|
+
errors?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare class ResponseValidator {
|
|
8
|
+
private ajv;
|
|
9
|
+
private ids;
|
|
10
|
+
private failed;
|
|
11
|
+
private nextId;
|
|
12
|
+
private readonly openapi30;
|
|
13
|
+
/** @param openapi the spec's `openapi` version; 3.0 schemas get converted. */
|
|
14
|
+
constructor(openapi?: string);
|
|
15
|
+
/** Validate a value; throws if the schema itself can't be compiled (e.g. a non-JS regex). */
|
|
16
|
+
validate(schema: Schema, value: unknown): ValidationResult;
|
|
17
|
+
private register;
|
|
18
|
+
private child;
|
|
19
|
+
}
|
|
20
|
+
/** Rewrite a dereferenced (possibly cyclic) OpenAPI schema as one self-contained JSON Schema. */
|
|
21
|
+
export declare function toJsonSchema(root: Schema, openapi30: boolean): Schema;
|
package/dist/validate.js
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { Ajv2020 } from 'ajv/dist/2020.js';
|
|
2
|
+
import addFormatsModule from 'ajv-formats';
|
|
3
|
+
import { discriminatorOf } from './schema.js';
|
|
4
|
+
// Validates generated responses against their schemas. OpenAPI schemas aren't quite
|
|
5
|
+
// JSON Schema (3.0 has `nullable` and boolean exclusive bounds), and dereferenced
|
|
6
|
+
// specs contain object cycles Ajv can't compile, so each schema is first rewritten
|
|
7
|
+
// into plain JSON Schema with shared and circular parts moved into `$defs`.
|
|
8
|
+
const addFormats = addFormatsModule;
|
|
9
|
+
/** Keywords whose values are schemas, maps of schemas, or arrays of schemas. */
|
|
10
|
+
const SCHEMA_KEYWORDS = new Set(['additionalProperties', 'items', 'not', 'if', 'then', 'else', 'contains', 'propertyNames', 'unevaluatedProperties', 'unevaluatedItems']);
|
|
11
|
+
const SCHEMA_ARRAY_KEYWORDS = new Set(['allOf', 'anyOf', 'oneOf', 'prefixItems']);
|
|
12
|
+
const SCHEMA_MAP_KEYWORDS = new Set(['properties', 'patternProperties', 'dependentSchemas', '$defs', 'definitions']);
|
|
13
|
+
/** OpenAPI-only keywords that mean nothing to a validator. */
|
|
14
|
+
const DROPPED_KEYWORDS = new Set(['discriminator', 'xml', 'externalDocs', 'example', 'deprecated']);
|
|
15
|
+
export class ResponseValidator {
|
|
16
|
+
ajv = new Ajv2020({ strict: false, allErrors: false, logger: false, validateSchema: false });
|
|
17
|
+
ids = new WeakMap();
|
|
18
|
+
failed = new WeakSet();
|
|
19
|
+
nextId = 0;
|
|
20
|
+
openapi30;
|
|
21
|
+
/** @param openapi the spec's `openapi` version; 3.0 schemas get converted. */
|
|
22
|
+
constructor(openapi = '3.1.0') {
|
|
23
|
+
addFormats(this.ajv);
|
|
24
|
+
this.openapi30 = openapi.startsWith('3.0');
|
|
25
|
+
}
|
|
26
|
+
/** Validate a value; throws if the schema itself can't be compiled (e.g. a non-JS regex). */
|
|
27
|
+
validate(schema, value) {
|
|
28
|
+
if (this.failed.has(schema))
|
|
29
|
+
throw new Error('schema could not be compiled');
|
|
30
|
+
let fn;
|
|
31
|
+
try {
|
|
32
|
+
fn = this.ajv.getSchema(this.register(schema));
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
this.failed.add(schema);
|
|
36
|
+
throw err;
|
|
37
|
+
}
|
|
38
|
+
if (!fn)
|
|
39
|
+
throw new Error('schema could not be compiled');
|
|
40
|
+
return fn(value) ? { valid: true } : { valid: false, errors: this.ajv.errorsText(fn.errors) };
|
|
41
|
+
}
|
|
42
|
+
// Every object-like schema is registered with Ajv once, under an id, and referenced
|
|
43
|
+
// from everywhere else. Shared and circular parts of big specs then compile once.
|
|
44
|
+
register(node) {
|
|
45
|
+
let id = this.ids.get(node);
|
|
46
|
+
if (!id) {
|
|
47
|
+
id = `mock:s${this.nextId++}`;
|
|
48
|
+
this.ids.set(node, id); // Before converting, so cycles find it.
|
|
49
|
+
this.ajv.addSchema({ $id: id, ...convertObject(node, this.openapi30, (child) => this.child(child)) });
|
|
50
|
+
}
|
|
51
|
+
return id;
|
|
52
|
+
}
|
|
53
|
+
child(node) {
|
|
54
|
+
if (!isObject(node))
|
|
55
|
+
return node;
|
|
56
|
+
return isComposite(node) ? { $ref: this.register(node) } : convertObject(node, this.openapi30, (c) => this.child(c));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/** Rewrite a dereferenced (possibly cyclic) OpenAPI schema as one self-contained JSON Schema. */
|
|
60
|
+
export function toJsonSchema(root, openapi30) {
|
|
61
|
+
// Pass 1: count how often each schema object is reached; shared ones become $defs.
|
|
62
|
+
const refs = new Map();
|
|
63
|
+
const count = (node) => {
|
|
64
|
+
if (!isObject(node))
|
|
65
|
+
return;
|
|
66
|
+
const n = (refs.get(node) ?? 0) + 1;
|
|
67
|
+
refs.set(node, n);
|
|
68
|
+
if (n === 1)
|
|
69
|
+
forEachSubschema(node, count);
|
|
70
|
+
};
|
|
71
|
+
count(root);
|
|
72
|
+
const defs = {};
|
|
73
|
+
const names = new Map();
|
|
74
|
+
const convert = (node) => {
|
|
75
|
+
if (!isObject(node))
|
|
76
|
+
return node;
|
|
77
|
+
if ((refs.get(node) ?? 0) > 1) {
|
|
78
|
+
let name = names.get(node);
|
|
79
|
+
if (!name) {
|
|
80
|
+
name = `s${names.size}`;
|
|
81
|
+
names.set(node, name);
|
|
82
|
+
defs[name] = convertObject(node, openapi30, convert);
|
|
83
|
+
}
|
|
84
|
+
return { $ref: `#/$defs/${name}` };
|
|
85
|
+
}
|
|
86
|
+
return convertObject(node, openapi30, convert);
|
|
87
|
+
};
|
|
88
|
+
const schema = convertObject(root, openapi30, convert);
|
|
89
|
+
return Object.keys(defs).length ? { ...schema, $defs: { ...schema.$defs, ...defs } } : schema;
|
|
90
|
+
}
|
|
91
|
+
/** Convert one schema object's own keywords; `child` converts nested schemas. */
|
|
92
|
+
function convertObject(node, openapi30, child) {
|
|
93
|
+
const out = {};
|
|
94
|
+
for (const [k, v] of Object.entries(node)) {
|
|
95
|
+
if (k.startsWith('x-') || DROPPED_KEYWORDS.has(k) || (openapi30 && k === 'nullable'))
|
|
96
|
+
continue;
|
|
97
|
+
if (SCHEMA_KEYWORDS.has(k))
|
|
98
|
+
out[k] = typeof v === 'boolean' ? v : child(v);
|
|
99
|
+
else if (SCHEMA_ARRAY_KEYWORDS.has(k) && Array.isArray(v))
|
|
100
|
+
out[k] = v.map(child);
|
|
101
|
+
else if (SCHEMA_MAP_KEYWORDS.has(k) && isObject(v))
|
|
102
|
+
out[k] = Object.fromEntries(Object.entries(v).map(([p, s]) => [p, child(s)]));
|
|
103
|
+
else if (k !== '$id' && k !== '$schema')
|
|
104
|
+
out[k] = v;
|
|
105
|
+
}
|
|
106
|
+
// OpenAPI semantics: a discriminator picks the branch, so branches that only differ
|
|
107
|
+
// by the discriminator value don't count as overlapping.
|
|
108
|
+
const disc = node.oneOf || node.anyOf ? discriminatorOf(node) : undefined;
|
|
109
|
+
if (disc) {
|
|
110
|
+
delete out.oneOf;
|
|
111
|
+
delete out.anyOf;
|
|
112
|
+
const p = disc.property;
|
|
113
|
+
out.allOf = [
|
|
114
|
+
...(out.allOf ?? []),
|
|
115
|
+
{ type: 'object', required: [p], properties: { [p]: { enum: disc.mapping.map((m) => m.value) } } },
|
|
116
|
+
...disc.mapping.map((m) => ({ if: { properties: { [p]: { const: m.value } } }, then: child(m.schema) })),
|
|
117
|
+
];
|
|
118
|
+
}
|
|
119
|
+
// Responses never contain writeOnly properties, so they can't be required there.
|
|
120
|
+
if (isObject(node.properties)) {
|
|
121
|
+
const writeOnly = Object.keys(node.properties).filter((p) => node.properties[p]?.writeOnly);
|
|
122
|
+
for (const p of writeOnly)
|
|
123
|
+
delete out.properties[p];
|
|
124
|
+
if (Array.isArray(out.required))
|
|
125
|
+
out.required = out.required.filter((r) => !writeOnly.includes(r));
|
|
126
|
+
}
|
|
127
|
+
if (openapi30) {
|
|
128
|
+
for (const [bound, exclusive] of [['minimum', 'exclusiveMinimum'], ['maximum', 'exclusiveMaximum']]) {
|
|
129
|
+
if (out[exclusive] === true) {
|
|
130
|
+
out[exclusive] = out[bound];
|
|
131
|
+
delete out[bound];
|
|
132
|
+
}
|
|
133
|
+
else if (out[exclusive] === false) {
|
|
134
|
+
delete out[exclusive];
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (node.nullable === true) {
|
|
138
|
+
if (Array.isArray(out.enum) && !out.enum.includes(null))
|
|
139
|
+
out.enum = [...out.enum, null];
|
|
140
|
+
if (typeof out.type === 'string')
|
|
141
|
+
out.type = [out.type, 'null'];
|
|
142
|
+
else if (!out.type)
|
|
143
|
+
return { anyOf: [out, { type: 'null' }] };
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return out;
|
|
147
|
+
}
|
|
148
|
+
function isComposite(node) {
|
|
149
|
+
return !!(node.properties || node.allOf || node.anyOf || node.oneOf || node.items || node.additionalProperties);
|
|
150
|
+
}
|
|
151
|
+
function forEachSubschema(node, fn) {
|
|
152
|
+
for (const [k, v] of Object.entries(node)) {
|
|
153
|
+
if (SCHEMA_KEYWORDS.has(k))
|
|
154
|
+
fn(v);
|
|
155
|
+
else if (SCHEMA_ARRAY_KEYWORDS.has(k) && Array.isArray(v))
|
|
156
|
+
v.forEach(fn);
|
|
157
|
+
else if (SCHEMA_MAP_KEYWORDS.has(k) && isObject(v))
|
|
158
|
+
Object.values(v).forEach(fn);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
function isObject(v) {
|
|
162
|
+
return !!v && typeof v === 'object' && !Array.isArray(v);
|
|
163
|
+
}
|
|
164
|
+
//# sourceMappingURL=validate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"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","sourcesContent":["import { Ajv2020, type ValidateFunction } from 'ajv/dist/2020.js';\nimport addFormatsModule from 'ajv-formats';\nimport type { Schema } from './model.js';\nimport { discriminatorOf } from './schema.js';\n\n// Validates generated responses against their schemas. OpenAPI schemas aren't quite\n// JSON Schema (3.0 has `nullable` and boolean exclusive bounds), and dereferenced\n// specs contain object cycles Ajv can't compile, so each schema is first rewritten\n// into plain JSON Schema with shared and circular parts moved into `$defs`.\n\nconst addFormats = addFormatsModule as unknown as (ajv: Ajv2020) => void;\n\n/** Keywords whose values are schemas, maps of schemas, or arrays of schemas. */\nconst SCHEMA_KEYWORDS = new Set(['additionalProperties', 'items', 'not', 'if', 'then', 'else', 'contains', 'propertyNames', 'unevaluatedProperties', 'unevaluatedItems']);\nconst SCHEMA_ARRAY_KEYWORDS = new Set(['allOf', 'anyOf', 'oneOf', 'prefixItems']);\nconst SCHEMA_MAP_KEYWORDS = new Set(['properties', 'patternProperties', 'dependentSchemas', '$defs', 'definitions']);\n/** OpenAPI-only keywords that mean nothing to a validator. */\nconst DROPPED_KEYWORDS = new Set(['discriminator', 'xml', 'externalDocs', 'example', 'deprecated']);\n\nexport interface ValidationResult {\n valid: boolean;\n /** Human-readable errors when invalid. */\n errors?: string;\n}\n\nexport class ResponseValidator {\n private ajv = new Ajv2020({ strict: false, allErrors: false, logger: false, validateSchema: false });\n private ids = new WeakMap<Schema, string>();\n private failed = new WeakSet<Schema>();\n private nextId = 0;\n private readonly openapi30: boolean;\n\n /** @param openapi the spec's `openapi` version; 3.0 schemas get converted. */\n constructor(openapi = '3.1.0') {\n addFormats(this.ajv);\n this.openapi30 = openapi.startsWith('3.0');\n }\n\n /** Validate a value; throws if the schema itself can't be compiled (e.g. a non-JS regex). */\n validate(schema: Schema, value: unknown): ValidationResult {\n if (this.failed.has(schema)) throw new Error('schema could not be compiled');\n let fn: ValidateFunction | undefined;\n try {\n fn = this.ajv.getSchema(this.register(schema));\n } catch (err) {\n this.failed.add(schema);\n throw err;\n }\n if (!fn) throw new Error('schema could not be compiled');\n return fn(value) ? { valid: true } : { valid: false, errors: this.ajv.errorsText(fn.errors) };\n }\n\n // Every object-like schema is registered with Ajv once, under an id, and referenced\n // from everywhere else. Shared and circular parts of big specs then compile once.\n private register(node: Schema): string {\n let id = this.ids.get(node);\n if (!id) {\n id = `mock:s${this.nextId++}`;\n this.ids.set(node, id); // Before converting, so cycles find it.\n this.ajv.addSchema({ $id: id, ...convertObject(node, this.openapi30, (child) => this.child(child)) });\n }\n return id;\n }\n\n private child(node: unknown): unknown {\n if (!isObject(node)) return node;\n return isComposite(node) ? { $ref: this.register(node) } : convertObject(node, this.openapi30, (c) => this.child(c));\n }\n}\n\n/** Rewrite a dereferenced (possibly cyclic) OpenAPI schema as one self-contained JSON Schema. */\nexport function toJsonSchema(root: Schema, openapi30: boolean): Schema {\n // Pass 1: count how often each schema object is reached; shared ones become $defs.\n const refs = new Map<object, number>();\n const count = (node: unknown) => {\n if (!isObject(node)) return;\n const n = (refs.get(node) ?? 0) + 1;\n refs.set(node, n);\n if (n === 1) forEachSubschema(node, count);\n };\n count(root);\n\n const defs: Record<string, Schema> = {};\n const names = new Map<object, string>();\n const convert = (node: unknown): unknown => {\n if (!isObject(node)) return node;\n if ((refs.get(node) ?? 0) > 1) {\n let name = names.get(node);\n if (!name) {\n name = `s${names.size}`;\n names.set(node, name);\n defs[name] = convertObject(node, openapi30, convert);\n }\n return { $ref: `#/$defs/${name}` };\n }\n return convertObject(node, openapi30, convert);\n };\n\n const schema = convertObject(root, openapi30, convert);\n return Object.keys(defs).length ? { ...schema, $defs: { ...schema.$defs, ...defs } } : schema;\n}\n\n/** Convert one schema object's own keywords; `child` converts nested schemas. */\nfunction convertObject(node: Schema, openapi30: boolean, child: (s: unknown) => unknown): Schema {\n const out: Schema = {};\n for (const [k, v] of Object.entries(node)) {\n if (k.startsWith('x-') || DROPPED_KEYWORDS.has(k) || (openapi30 && k === 'nullable')) continue;\n if (SCHEMA_KEYWORDS.has(k)) out[k] = typeof v === 'boolean' ? v : child(v);\n else if (SCHEMA_ARRAY_KEYWORDS.has(k) && Array.isArray(v)) out[k] = v.map(child);\n else if (SCHEMA_MAP_KEYWORDS.has(k) && isObject(v)) out[k] = Object.fromEntries(Object.entries(v).map(([p, s]) => [p, child(s)]));\n else if (k !== '$id' && k !== '$schema') out[k] = v;\n }\n\n // OpenAPI semantics: a discriminator picks the branch, so branches that only differ\n // by the discriminator value don't count as overlapping.\n const disc = node.oneOf || node.anyOf ? discriminatorOf(node) : undefined;\n if (disc) {\n delete out.oneOf;\n delete out.anyOf;\n const p = disc.property;\n out.allOf = [\n ...(out.allOf ?? []),\n { type: 'object', required: [p], properties: { [p]: { enum: disc.mapping.map((m) => m.value) } } },\n ...disc.mapping.map((m) => ({ if: { properties: { [p]: { const: m.value } } }, then: child(m.schema) })),\n ];\n }\n\n // Responses never contain writeOnly properties, so they can't be required there.\n if (isObject(node.properties)) {\n const writeOnly = Object.keys(node.properties).filter((p) => (node.properties[p] as Schema)?.writeOnly);\n for (const p of writeOnly) delete out.properties[p];\n if (Array.isArray(out.required)) out.required = out.required.filter((r: string) => !writeOnly.includes(r));\n }\n\n if (openapi30) {\n for (const [bound, exclusive] of [['minimum', 'exclusiveMinimum'], ['maximum', 'exclusiveMaximum']] as const) {\n if (out[exclusive] === true) {\n out[exclusive] = out[bound];\n delete out[bound];\n } else if (out[exclusive] === false) {\n delete out[exclusive];\n }\n }\n if (node.nullable === true) {\n if (Array.isArray(out.enum) && !out.enum.includes(null)) out.enum = [...out.enum, null];\n if (typeof out.type === 'string') out.type = [out.type, 'null'];\n else if (!out.type) return { anyOf: [out, { type: 'null' }] };\n }\n }\n return out;\n}\n\nfunction isComposite(node: Schema): boolean {\n return !!(node.properties || node.allOf || node.anyOf || node.oneOf || node.items || node.additionalProperties);\n}\n\nfunction forEachSubschema(node: Schema, fn: (s: unknown) => void) {\n for (const [k, v] of Object.entries(node)) {\n if (SCHEMA_KEYWORDS.has(k)) fn(v);\n else if (SCHEMA_ARRAY_KEYWORDS.has(k) && Array.isArray(v)) v.forEach(fn);\n else if (SCHEMA_MAP_KEYWORDS.has(k) && isObject(v)) Object.values(v).forEach(fn);\n }\n}\n\nfunction isObject(v: unknown): v is Schema {\n return !!v && typeof v === 'object' && !Array.isArray(v);\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,70 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wylan24/mockgen",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0-alpha.2",
|
|
4
|
+
"description": "Turn an OpenAPI spec into a running mock server with realistic, deterministic fake data — one command.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"mock",
|
|
7
|
+
"openapi",
|
|
8
|
+
"mock-server",
|
|
9
|
+
"api",
|
|
10
|
+
"faker",
|
|
11
|
+
"testing"
|
|
12
|
+
],
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"author": "wylan24",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/wylan24/mockgen.git"
|
|
18
|
+
},
|
|
19
|
+
"homepage": "https://github.com/wylan24/mockgen#readme",
|
|
20
|
+
"bugs": "https://github.com/wylan24/mockgen/issues",
|
|
21
|
+
"type": "module",
|
|
22
|
+
"bin": {
|
|
23
|
+
"mockgen": "dist/cli.js"
|
|
24
|
+
},
|
|
25
|
+
"exports": {
|
|
26
|
+
".": {
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"default": "./dist/index.js"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"main": "./dist/index.js",
|
|
32
|
+
"types": "./dist/index.d.ts",
|
|
33
|
+
"files": [
|
|
34
|
+
"dist"
|
|
35
|
+
],
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=22.19"
|
|
38
|
+
},
|
|
39
|
+
"scripts": {
|
|
40
|
+
"clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
|
|
41
|
+
"build": "npm run clean && tsc -p tsconfig.build.json",
|
|
42
|
+
"typecheck": "tsc --noEmit",
|
|
43
|
+
"test": "vitest run",
|
|
44
|
+
"test:watch": "vitest",
|
|
45
|
+
"serve:example": "npm run build && node dist/cli.js serve examples/petstore.yaml",
|
|
46
|
+
"prepublishOnly": "npm run build",
|
|
47
|
+
"corpus:fetch": "tsx scripts/corpus-fetch.ts",
|
|
48
|
+
"corpus": "tsx scripts/corpus.ts",
|
|
49
|
+
"corpus:explain": "tsx scripts/corpus-explain.ts",
|
|
50
|
+
"smoke": "npm run build && tsx scripts/smoke-pack.ts"
|
|
51
|
+
},
|
|
52
|
+
"dependencies": {
|
|
53
|
+
"@apidevtools/json-schema-ref-parser": "^16.0.5",
|
|
54
|
+
"@faker-js/faker": "10.6.0",
|
|
55
|
+
"ajv": "8.20.0",
|
|
56
|
+
"ajv-formats": "3.0.1",
|
|
57
|
+
"randexp": "0.5.3",
|
|
58
|
+
"yaml": "^2.9.1"
|
|
59
|
+
},
|
|
60
|
+
"devDependencies": {
|
|
61
|
+
"@types/node": "^22.20.5",
|
|
62
|
+
"tsx": "^4.23.15",
|
|
63
|
+
"typescript": "^7.0.2",
|
|
64
|
+
"vitest": "^5.0.5"
|
|
65
|
+
},
|
|
66
|
+
"publishConfig": {
|
|
67
|
+
"access": "public",
|
|
68
|
+
"provenance": true
|
|
69
|
+
}
|
|
70
|
+
}
|