toolcraft-openapi 0.0.13 → 0.0.15
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/dist/generate.d.ts +6 -1
- package/dist/generate.js +50 -15
- package/dist/http.js +2 -1
- package/dist/mock/fetch.d.ts +35 -0
- package/dist/mock/fetch.js +498 -0
- package/dist/mock.compile-check.d.ts +1 -0
- package/dist/mock.compile-check.js +15 -0
- package/dist/mock.d.ts +2 -0
- package/dist/mock.js +1 -0
- package/dist/runtime.js +6 -5
- package/package.json +8 -4
package/dist/generate.d.ts
CHANGED
|
@@ -2,6 +2,9 @@ import { type HttpMethod } from "./naming.js";
|
|
|
2
2
|
type OpenApiOperation = OpenApiOperationObject | OpenApiReferenceObject;
|
|
3
3
|
type OpenApiOperationMap = Partial<Record<HttpMethod, OpenApiOperation>>;
|
|
4
4
|
type OpenApiParameterLocation = "path" | "query" | "header" | "cookie";
|
|
5
|
+
type OpenApiScalarType = "string" | "number" | "integer" | "boolean";
|
|
6
|
+
type OpenApiSchemaType = OpenApiScalarType | "object" | "array";
|
|
7
|
+
type OpenApiJsonSchemaType = OpenApiSchemaType | "null";
|
|
5
8
|
export type GeneratedRequestLocation = Exclude<GeneratedParam["location"], "transport">;
|
|
6
9
|
export interface OpenApiDocument {
|
|
7
10
|
openapi?: string;
|
|
@@ -57,7 +60,7 @@ export interface OpenApiMediaTypeObject {
|
|
|
57
60
|
schema?: OpenApiSchemaObject | OpenApiReferenceObject;
|
|
58
61
|
}
|
|
59
62
|
export interface OpenApiSchemaObject {
|
|
60
|
-
type?:
|
|
63
|
+
type?: OpenApiJsonSchemaType | readonly OpenApiJsonSchemaType[];
|
|
61
64
|
additionalProperties?: boolean | OpenApiSchemaObject | OpenApiReferenceObject;
|
|
62
65
|
allOf?: Array<OpenApiSchemaObject | OpenApiReferenceObject>;
|
|
63
66
|
anyOf?: Array<OpenApiSchemaObject | OpenApiReferenceObject>;
|
|
@@ -120,6 +123,7 @@ export interface GeneratedParam {
|
|
|
120
123
|
description?: string;
|
|
121
124
|
shortFlag?: string;
|
|
122
125
|
scope?: readonly [GeneratedParamScope, ...GeneratedParamScope[]];
|
|
126
|
+
global?: boolean;
|
|
123
127
|
optional: boolean;
|
|
124
128
|
definition: GeneratedParamDefinition;
|
|
125
129
|
}
|
|
@@ -184,6 +188,7 @@ interface RenderSchemaOptionsInput {
|
|
|
184
188
|
description?: string;
|
|
185
189
|
shortFlag?: string;
|
|
186
190
|
scope?: readonly [GeneratedParamScope, ...GeneratedParamScope[]];
|
|
191
|
+
global?: boolean;
|
|
187
192
|
}
|
|
188
193
|
export type GeneratedPreflightBlock = {
|
|
189
194
|
kind: "scalar-null";
|
package/dist/generate.js
CHANGED
|
@@ -25,6 +25,7 @@ const TRANSPORT_PARAMS = [
|
|
|
25
25
|
location: "transport",
|
|
26
26
|
description: "Print the HTTP request and exit without sending it.",
|
|
27
27
|
scope: ["cli", "sdk"],
|
|
28
|
+
global: true,
|
|
28
29
|
optional: true,
|
|
29
30
|
definition: { kind: "boolean" }
|
|
30
31
|
},
|
|
@@ -35,6 +36,7 @@ const TRANSPORT_PARAMS = [
|
|
|
35
36
|
description: "Log the request line to stderr.",
|
|
36
37
|
shortFlag: "v",
|
|
37
38
|
scope: ["cli", "sdk"],
|
|
39
|
+
global: true,
|
|
38
40
|
optional: true,
|
|
39
41
|
definition: { kind: "boolean" }
|
|
40
42
|
}
|
|
@@ -56,6 +58,10 @@ const SCHEMA_OPTION_SOURCES = [
|
|
|
56
58
|
key: "scope",
|
|
57
59
|
get: (param) => param.scope
|
|
58
60
|
},
|
|
61
|
+
{
|
|
62
|
+
key: "global",
|
|
63
|
+
get: (param) => (param.global === true ? true : undefined)
|
|
64
|
+
},
|
|
59
65
|
{
|
|
60
66
|
key: "minimum",
|
|
61
67
|
get: (param) => param.definition.minimum
|
|
@@ -324,17 +330,11 @@ function assertSupportedSuccessResponseSchema(document, schema, operationId, con
|
|
|
324
330
|
}
|
|
325
331
|
}
|
|
326
332
|
function expectSupportedSuccessResponseSchema(document, schema, operationId, context) {
|
|
327
|
-
const resolvedSchema = resolveSchema(document, schema, operationId, context);
|
|
333
|
+
const resolvedSchema = normalizeNullableSchema(document, resolveSchema(document, schema, operationId, context), operationId, context);
|
|
328
334
|
const compositionKeyword = getCompositionKeyword(resolvedSchema);
|
|
329
335
|
if (compositionKeyword === undefined) {
|
|
330
336
|
return resolvedSchema;
|
|
331
337
|
}
|
|
332
|
-
const nullableAnyOfSchema = compositionKeyword === "anyOf"
|
|
333
|
-
? resolveNullableAnyOfSchema(document, resolvedSchema, operationId, context)
|
|
334
|
-
: undefined;
|
|
335
|
-
if (nullableAnyOfSchema !== undefined) {
|
|
336
|
-
return nullableAnyOfSchema;
|
|
337
|
-
}
|
|
338
338
|
throw new UserError(`Operation ${JSON.stringify(operationId)} uses unsupported ${context}. JSON Schema composition keyword ${JSON.stringify(compositionKeyword)} is not supported in v1.`);
|
|
339
339
|
}
|
|
340
340
|
function createGeneratedParameter(document, parameter, operationId) {
|
|
@@ -578,6 +578,9 @@ function createCollectedRequestBodyParams(assemblies, bodyOptional, requestBodyD
|
|
|
578
578
|
};
|
|
579
579
|
}
|
|
580
580
|
function createParamDefinition(document, schema, operationId, context) {
|
|
581
|
+
if (Array.isArray(schema.type)) {
|
|
582
|
+
throw new UserError(`Operation ${JSON.stringify(operationId)} uses unsupported ${context}. JSON Schema type arrays with multiple non-null types are not supported in v1.`);
|
|
583
|
+
}
|
|
581
584
|
if (schema.type === "array") {
|
|
582
585
|
const itemSchema = expectArrayItemsSchema(document, schema, operationId, context);
|
|
583
586
|
return {
|
|
@@ -589,9 +592,9 @@ function createParamDefinition(document, schema, operationId, context) {
|
|
|
589
592
|
...(schema.nullable === true ? { nullable: true } : {})
|
|
590
593
|
};
|
|
591
594
|
}
|
|
592
|
-
const scalarDefinition =
|
|
593
|
-
?
|
|
594
|
-
:
|
|
595
|
+
const scalarDefinition = isOpenApiScalarType(schema.type)
|
|
596
|
+
? SCHEMA_TYPE_TO_KIND[schema.type]
|
|
597
|
+
: undefined;
|
|
595
598
|
const enumValues = normalizeEnumValues(schema.enum, operationId, context, schema.nullable === true, schema.type);
|
|
596
599
|
if (enumValues !== undefined) {
|
|
597
600
|
return {
|
|
@@ -747,6 +750,9 @@ function expectOperation(document, operation, method, path, refChain = []) {
|
|
|
747
750
|
function isEnumPrimitiveValue(value) {
|
|
748
751
|
return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
749
752
|
}
|
|
753
|
+
function isOpenApiScalarType(type) {
|
|
754
|
+
return typeof type === "string" && type in SCHEMA_TYPE_TO_KIND;
|
|
755
|
+
}
|
|
750
756
|
function expectRequestBody(document, requestBody, operationId, context, refChain = []) {
|
|
751
757
|
if (!isReferenceObject(requestBody)) {
|
|
752
758
|
return requestBody;
|
|
@@ -763,7 +769,7 @@ function expectResponse(document, response, operationId, statusCode, refChain =
|
|
|
763
769
|
return expectResponse(document, resolveLocalReference(document, response.$ref, operationId, context), operationId, statusCode, [...refChain, response.$ref]);
|
|
764
770
|
}
|
|
765
771
|
function expectSchema(document, schema, operationId, context, refChain = []) {
|
|
766
|
-
const resolvedSchema = resolveSchema(document, schema, operationId, context, refChain);
|
|
772
|
+
const resolvedSchema = normalizeNullableSchema(document, resolveSchema(document, schema, operationId, context, refChain), operationId, context);
|
|
767
773
|
const compositionKeyword = getCompositionKeyword(resolvedSchema);
|
|
768
774
|
if (compositionKeyword !== undefined) {
|
|
769
775
|
throw new UserError(`Operation ${JSON.stringify(operationId)} uses unsupported ${context}. JSON Schema composition keyword ${JSON.stringify(compositionKeyword)} is not supported in v1.`);
|
|
@@ -780,6 +786,28 @@ function resolveSchema(document, schema, operationId, context, refChain = []) {
|
|
|
780
786
|
}
|
|
781
787
|
return schema;
|
|
782
788
|
}
|
|
789
|
+
function normalizeNullableSchema(document, schema, operationId, context) {
|
|
790
|
+
const typeNormalizedSchema = normalizeNullableTypeArray(schema);
|
|
791
|
+
if (getCompositionKeyword(typeNormalizedSchema) !== "anyOf") {
|
|
792
|
+
return typeNormalizedSchema;
|
|
793
|
+
}
|
|
794
|
+
return (resolveNullableAnyOfSchema(document, typeNormalizedSchema, operationId, context) ??
|
|
795
|
+
typeNormalizedSchema);
|
|
796
|
+
}
|
|
797
|
+
function normalizeNullableTypeArray(schema) {
|
|
798
|
+
if (!Array.isArray(schema.type)) {
|
|
799
|
+
return schema;
|
|
800
|
+
}
|
|
801
|
+
const nonNullTypes = schema.type.filter((type) => type !== "null");
|
|
802
|
+
if (nonNullTypes.length === schema.type.length) {
|
|
803
|
+
return schema;
|
|
804
|
+
}
|
|
805
|
+
return {
|
|
806
|
+
...schema,
|
|
807
|
+
type: nonNullTypes.length === 1 ? nonNullTypes[0] : nonNullTypes,
|
|
808
|
+
nullable: true
|
|
809
|
+
};
|
|
810
|
+
}
|
|
783
811
|
function getCompositionKeyword(schema) {
|
|
784
812
|
for (const keyword of ["allOf", "anyOf", "oneOf"]) {
|
|
785
813
|
if (schema[keyword] !== undefined) {
|
|
@@ -793,7 +821,7 @@ function resolveNullableAnyOfSchema(document, schema, operationId, context) {
|
|
|
793
821
|
if (variants === undefined || variants.length !== 2) {
|
|
794
822
|
return undefined;
|
|
795
823
|
}
|
|
796
|
-
const resolvedVariants = variants.map((variant, index) => resolveSchema(document, variant, operationId, `${context} anyOf variant ${index}`));
|
|
824
|
+
const resolvedVariants = variants.map((variant, index) => normalizeNullableSchema(document, resolveSchema(document, variant, operationId, `${context} anyOf variant ${index}`), operationId, `${context} anyOf variant ${index}`));
|
|
797
825
|
const nullVariantIndex = resolvedVariants.findIndex(isExplicitNullSchema);
|
|
798
826
|
if (nullVariantIndex === -1) {
|
|
799
827
|
return undefined;
|
|
@@ -802,8 +830,14 @@ function resolveNullableAnyOfSchema(document, schema, operationId, context) {
|
|
|
802
830
|
if (nonNullVariant === undefined || getCompositionKeyword(nonNullVariant) !== undefined) {
|
|
803
831
|
return undefined;
|
|
804
832
|
}
|
|
833
|
+
const { anyOf: _anyOf, nullable: _nullable, ...wrapperSchema } = schema;
|
|
834
|
+
void _anyOf;
|
|
835
|
+
void _nullable;
|
|
805
836
|
return {
|
|
837
|
+
...wrapperSchema,
|
|
806
838
|
...nonNullVariant,
|
|
839
|
+
description: nonNullVariant.description ?? schema.description,
|
|
840
|
+
default: nonNullVariant.default ?? schema.default,
|
|
807
841
|
nullable: true
|
|
808
842
|
};
|
|
809
843
|
}
|
|
@@ -935,15 +969,16 @@ function renderParamLines(params) {
|
|
|
935
969
|
return params.map((param) => ` ${renderObjectKey(param.paramName)}: ${renderParamSchema(param)},`);
|
|
936
970
|
}
|
|
937
971
|
function renderParamSchema(param) {
|
|
938
|
-
const schema = renderDefinition(param.definition, param.description, param.shortFlag, param.scope);
|
|
972
|
+
const schema = renderDefinition(param.definition, param.description, param.shortFlag, param.scope, param.global);
|
|
939
973
|
return param.optional ? `S.Optional(${schema})` : schema;
|
|
940
974
|
}
|
|
941
|
-
function renderDefinition(definition, description, shortFlag, scope) {
|
|
975
|
+
function renderDefinition(definition, description, shortFlag, scope, global) {
|
|
942
976
|
const options = renderSchemaOptions({
|
|
943
977
|
definition,
|
|
944
978
|
description,
|
|
945
979
|
shortFlag,
|
|
946
|
-
scope
|
|
980
|
+
scope,
|
|
981
|
+
global
|
|
947
982
|
});
|
|
948
983
|
const renderer = DEFINITION_RENDERERS[definition.kind];
|
|
949
984
|
return renderer(definition, options);
|
package/dist/http.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { text as designText } from "@poe-code/design-system";
|
|
1
2
|
import { UserError } from "toolcraft";
|
|
2
3
|
export class HttpError extends Error {
|
|
3
4
|
status;
|
|
@@ -20,7 +21,7 @@ export async function requestJson(options) {
|
|
|
20
21
|
const writeStderr = options.writeStderr ?? process.stderr.write.bind(process.stderr);
|
|
21
22
|
const requestLine = `${method} ${url}`;
|
|
22
23
|
if (options.verbose) {
|
|
23
|
-
writeStderr(`${requestLine}\n`);
|
|
24
|
+
writeStderr(`${designText.muted(requestLine)}\n`);
|
|
24
25
|
}
|
|
25
26
|
if (options.dryRun) {
|
|
26
27
|
writeStdout(formatDryRunOutput(requestLine, headers, options.body));
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { OpenApiDocument } from "../generate.js";
|
|
2
|
+
import { type OpenApiSourceFileSystem } from "../spec-source.js";
|
|
3
|
+
export type OnUnmocked = "throw" | "reply404";
|
|
4
|
+
export interface MockFixtureEntry {
|
|
5
|
+
status?: number;
|
|
6
|
+
headers?: Record<string, string>;
|
|
7
|
+
body?: unknown;
|
|
8
|
+
}
|
|
9
|
+
export type MockFetchFixtures = Record<string, MockFixtureEntry> | string;
|
|
10
|
+
export interface MockFetchFileSystem extends OpenApiSourceFileSystem {
|
|
11
|
+
readdir?(directory: string): Promise<string[]>;
|
|
12
|
+
}
|
|
13
|
+
export interface MockFetchOptions {
|
|
14
|
+
spec: OpenApiDocument | string | URL;
|
|
15
|
+
fixtures?: MockFetchFixtures;
|
|
16
|
+
onUnmocked?: OnUnmocked;
|
|
17
|
+
cwd?: string;
|
|
18
|
+
fs?: MockFetchFileSystem;
|
|
19
|
+
fetch?: typeof globalThis.fetch;
|
|
20
|
+
}
|
|
21
|
+
export interface RequestRecord {
|
|
22
|
+
method: string;
|
|
23
|
+
path: string;
|
|
24
|
+
operationId: string;
|
|
25
|
+
headers: Record<string, string>;
|
|
26
|
+
body: unknown;
|
|
27
|
+
at: Date;
|
|
28
|
+
}
|
|
29
|
+
export interface MockFetchHandle {
|
|
30
|
+
fetch: typeof globalThis.fetch;
|
|
31
|
+
requests: RequestRecord[];
|
|
32
|
+
reset(): void;
|
|
33
|
+
}
|
|
34
|
+
export declare function mockFetch(options: MockFetchOptions): Promise<MockFetchHandle>;
|
|
35
|
+
export type { OpenApiSourceFileSystem };
|
|
@@ -0,0 +1,498 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { UserError } from "toolcraft";
|
|
4
|
+
import { parseOpenApiDocument, readOpenApiSourceText } from "../spec-source.js";
|
|
5
|
+
const HTTP_METHOD_NAMES = ["get", "post", "put", "patch", "delete"];
|
|
6
|
+
export async function mockFetch(options) {
|
|
7
|
+
const document = await resolveSpec(options);
|
|
8
|
+
const operations = compileOperations(document);
|
|
9
|
+
const operationIds = new Set(operations.map((op) => op.operationId));
|
|
10
|
+
const fixtureLoader = await createFixtureLoader(options.fixtures, options.cwd, options.fs, operationIds);
|
|
11
|
+
const onUnmocked = options.onUnmocked ?? "throw";
|
|
12
|
+
const requests = [];
|
|
13
|
+
const fetchImpl = async (input, init) => {
|
|
14
|
+
const requestUrl = parseRequestUrl(input);
|
|
15
|
+
const method = (init?.method ?? getRequestMethod(input) ?? "GET").toUpperCase();
|
|
16
|
+
const headers = collectHeaders(input, init);
|
|
17
|
+
const bodyText = await readRequestBody(input, init);
|
|
18
|
+
const parsedBody = parseJsonOrUndefined(bodyText);
|
|
19
|
+
const matchingPath = operations.filter((op) => op.pathRegex.test(requestUrl.pathname));
|
|
20
|
+
if (matchingPath.length === 0) {
|
|
21
|
+
throw new MockFetchError(`mockFetch: no operation in the spec matches ${method} ${requestUrl.pathname}.`);
|
|
22
|
+
}
|
|
23
|
+
const operation = matchingPath.find((op) => op.method === method);
|
|
24
|
+
if (operation === undefined) {
|
|
25
|
+
const allowed = matchingPath.map((op) => op.method).join(", ");
|
|
26
|
+
throw new MockFetchError(`mockFetch: method ${method} not declared for ${requestUrl.pathname} (spec allows ${allowed}).`);
|
|
27
|
+
}
|
|
28
|
+
requests.push({
|
|
29
|
+
method,
|
|
30
|
+
path: requestUrl.pathname,
|
|
31
|
+
operationId: operation.operationId,
|
|
32
|
+
headers,
|
|
33
|
+
body: parsedBody,
|
|
34
|
+
at: new Date()
|
|
35
|
+
});
|
|
36
|
+
if (operation.requestBodySchema !== undefined && parsedBody !== undefined) {
|
|
37
|
+
const errors = validateAgainstSchema(parsedBody, operation.requestBodySchema, document, "$");
|
|
38
|
+
if (errors.length > 0) {
|
|
39
|
+
return jsonResponse(422, { errors });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
else if (operation.requestBodyRequired && parsedBody === undefined) {
|
|
43
|
+
return jsonResponse(422, { errors: ["$: request body is required"] });
|
|
44
|
+
}
|
|
45
|
+
const fixture = await fixtureLoader(operation.operationId);
|
|
46
|
+
if (fixture !== undefined) {
|
|
47
|
+
const status = fixture.status ?? operation.defaultStatus;
|
|
48
|
+
const responseSchema = operation.responseSchemas.get(status);
|
|
49
|
+
if (responseSchema !== undefined && fixture.body !== undefined) {
|
|
50
|
+
const errors = validateAgainstSchema(fixture.body, responseSchema, document, "$response");
|
|
51
|
+
if (errors.length > 0) {
|
|
52
|
+
throw new MockFetchError(`mockFetch: response fixture for ${JSON.stringify(operation.operationId)} ` +
|
|
53
|
+
`violates the spec response schema for status ${status}:\n ${errors.join("\n ")}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return buildResponse(fixture, operation.defaultStatus);
|
|
57
|
+
}
|
|
58
|
+
if (operation.defaultExample !== undefined) {
|
|
59
|
+
return buildResponse({ body: operation.defaultExample }, operation.defaultStatus);
|
|
60
|
+
}
|
|
61
|
+
if (onUnmocked === "reply404") {
|
|
62
|
+
return jsonResponse(404, { error: `unmocked: ${operation.operationId}` });
|
|
63
|
+
}
|
|
64
|
+
throw new MockFetchError(`mockFetch: unmocked operation ${JSON.stringify(operation.operationId)}. ` +
|
|
65
|
+
`Add a fixture, an OpenAPI example on the success response, or pass { onUnmocked: "reply404" }.`);
|
|
66
|
+
};
|
|
67
|
+
return {
|
|
68
|
+
fetch: fetchImpl,
|
|
69
|
+
requests,
|
|
70
|
+
reset() {
|
|
71
|
+
requests.length = 0;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
class MockFetchError extends Error {
|
|
76
|
+
constructor(message) {
|
|
77
|
+
super(message);
|
|
78
|
+
this.name = "MockFetchError";
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async function resolveSpec(options) {
|
|
82
|
+
const { spec } = options;
|
|
83
|
+
if (typeof spec !== "string" && !(spec instanceof URL)) {
|
|
84
|
+
return spec;
|
|
85
|
+
}
|
|
86
|
+
const sourceText = await readOpenApiSourceText(spec, {
|
|
87
|
+
cwd: options.cwd ?? process.cwd(),
|
|
88
|
+
fetch: options.fetch ?? globalThis.fetch,
|
|
89
|
+
fs: options.fs ?? fs
|
|
90
|
+
});
|
|
91
|
+
return parseOpenApiDocument(sourceText, spec);
|
|
92
|
+
}
|
|
93
|
+
function compileOperations(document) {
|
|
94
|
+
const paths = document.paths;
|
|
95
|
+
if (paths === undefined) {
|
|
96
|
+
throw new UserError('mockFetch: OpenAPI document must define a top-level "paths" object.');
|
|
97
|
+
}
|
|
98
|
+
const compiled = [];
|
|
99
|
+
for (const [pathTemplate, pathItem] of Object.entries(paths)) {
|
|
100
|
+
if (pathItem === undefined) {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
for (const method of HTTP_METHOD_NAMES) {
|
|
104
|
+
const operation = pathItem[method];
|
|
105
|
+
if (operation === undefined) {
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
const resolvedOperation = resolveOperation(operation, document);
|
|
109
|
+
const operationId = resolvedOperation.operationId ?? `${method.toUpperCase()} ${pathTemplate}`;
|
|
110
|
+
const { defaultStatus, defaultExample, responseSchemas } = pickResponseMetadata(resolvedOperation, document);
|
|
111
|
+
const { schema: requestBodySchema, required: requestBodyRequired } = pickRequestBody(resolvedOperation, document);
|
|
112
|
+
compiled.push({
|
|
113
|
+
method: method.toUpperCase(),
|
|
114
|
+
pathTemplate,
|
|
115
|
+
pathRegex: pathTemplateToRegex(pathTemplate),
|
|
116
|
+
pathSpecificity: countPathPlaceholders(pathTemplate),
|
|
117
|
+
operationId,
|
|
118
|
+
operation: resolvedOperation,
|
|
119
|
+
defaultStatus,
|
|
120
|
+
defaultExample,
|
|
121
|
+
requestBodySchema,
|
|
122
|
+
requestBodyRequired,
|
|
123
|
+
responseSchemas
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// Literal paths win over templated paths when both match the same pathname.
|
|
128
|
+
// Sort ascending by placeholder count so concrete operations are matched first.
|
|
129
|
+
compiled.sort((a, b) => a.pathSpecificity - b.pathSpecificity);
|
|
130
|
+
return compiled;
|
|
131
|
+
}
|
|
132
|
+
function countPathPlaceholders(template) {
|
|
133
|
+
return (template.match(/\{[^}]+\}/g) ?? []).length;
|
|
134
|
+
}
|
|
135
|
+
function pathTemplateToRegex(template) {
|
|
136
|
+
// Escape regex metacharacters except for "{...}" placeholders, which become non-slash captures.
|
|
137
|
+
const pattern = template.replace(/[.*+?^${}()|[\]\\]/g, (match) => match === "{" || match === "}" ? match : `\\${match}`);
|
|
138
|
+
const withParams = pattern.replace(/\{[^}]+\}/g, "[^/]+");
|
|
139
|
+
return new RegExp(`^${withParams}$`);
|
|
140
|
+
}
|
|
141
|
+
function resolveOperation(operation, document) {
|
|
142
|
+
if (isReference(operation)) {
|
|
143
|
+
return resolveReference(operation, document);
|
|
144
|
+
}
|
|
145
|
+
return operation;
|
|
146
|
+
}
|
|
147
|
+
function pickResponseMetadata(operation, document) {
|
|
148
|
+
const responses = operation.responses ?? {};
|
|
149
|
+
const responseSchemas = new Map();
|
|
150
|
+
for (const [code, response] of Object.entries(responses)) {
|
|
151
|
+
const status = parseInt(code, 10);
|
|
152
|
+
if (!Number.isFinite(status) || response === undefined) {
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
const resolved = isReference(response)
|
|
156
|
+
? resolveReference(response, document)
|
|
157
|
+
: response;
|
|
158
|
+
const schema = extractResponseSchema(resolved, document);
|
|
159
|
+
if (schema !== undefined) {
|
|
160
|
+
responseSchemas.set(status, schema);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
const successCodes = Object.keys(responses)
|
|
164
|
+
.map((code) => parseInt(code, 10))
|
|
165
|
+
.filter((code) => Number.isFinite(code) && code >= 200 && code < 300)
|
|
166
|
+
.sort((a, b) => a - b);
|
|
167
|
+
if (successCodes.length === 0) {
|
|
168
|
+
return { defaultStatus: 200, defaultExample: undefined, responseSchemas };
|
|
169
|
+
}
|
|
170
|
+
const status = successCodes[0];
|
|
171
|
+
const response = responses[String(status)];
|
|
172
|
+
const resolvedResponse = response !== undefined && isReference(response)
|
|
173
|
+
? resolveReference(response, document)
|
|
174
|
+
: response;
|
|
175
|
+
return {
|
|
176
|
+
defaultStatus: status,
|
|
177
|
+
defaultExample: extractExample(resolvedResponse, document),
|
|
178
|
+
responseSchemas
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
function extractResponseSchema(response, document) {
|
|
182
|
+
if (response === undefined) {
|
|
183
|
+
return undefined;
|
|
184
|
+
}
|
|
185
|
+
const media = pickJsonMediaType(response.content);
|
|
186
|
+
if (media === undefined || media.schema === undefined) {
|
|
187
|
+
return undefined;
|
|
188
|
+
}
|
|
189
|
+
return isReference(media.schema)
|
|
190
|
+
? resolveReference(media.schema, document)
|
|
191
|
+
: media.schema;
|
|
192
|
+
}
|
|
193
|
+
function pickRequestBody(operation, document) {
|
|
194
|
+
const raw = operation.requestBody;
|
|
195
|
+
if (raw === undefined) {
|
|
196
|
+
return { schema: undefined, required: false };
|
|
197
|
+
}
|
|
198
|
+
const resolved = isReference(raw)
|
|
199
|
+
? resolveReference(raw, document)
|
|
200
|
+
: raw;
|
|
201
|
+
const media = pickJsonMediaType(resolved.content);
|
|
202
|
+
if (media === undefined) {
|
|
203
|
+
return { schema: undefined, required: resolved.required === true };
|
|
204
|
+
}
|
|
205
|
+
if (media.schema === undefined) {
|
|
206
|
+
return { schema: undefined, required: resolved.required === true };
|
|
207
|
+
}
|
|
208
|
+
const schema = isReference(media.schema)
|
|
209
|
+
? resolveReference(media.schema, document)
|
|
210
|
+
: media.schema;
|
|
211
|
+
return { schema, required: resolved.required === true };
|
|
212
|
+
}
|
|
213
|
+
function extractExample(response, document) {
|
|
214
|
+
if (response === undefined) {
|
|
215
|
+
return undefined;
|
|
216
|
+
}
|
|
217
|
+
const media = pickJsonMediaType(response.content);
|
|
218
|
+
if (media === undefined) {
|
|
219
|
+
return undefined;
|
|
220
|
+
}
|
|
221
|
+
if (media.example !== undefined) {
|
|
222
|
+
return media.example;
|
|
223
|
+
}
|
|
224
|
+
if (media.examples !== undefined) {
|
|
225
|
+
for (const value of Object.values(media.examples)) {
|
|
226
|
+
if (value !== undefined && "value" in value) {
|
|
227
|
+
return value.value;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
if (media.schema !== undefined) {
|
|
232
|
+
const schema = isReference(media.schema)
|
|
233
|
+
? resolveReference(media.schema, document)
|
|
234
|
+
: media.schema;
|
|
235
|
+
const schemaExample = schema.example;
|
|
236
|
+
if (schemaExample !== undefined) {
|
|
237
|
+
return schemaExample;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return undefined;
|
|
241
|
+
}
|
|
242
|
+
function pickJsonMediaType(content) {
|
|
243
|
+
if (content === undefined) {
|
|
244
|
+
return undefined;
|
|
245
|
+
}
|
|
246
|
+
for (const [type, media] of Object.entries(content)) {
|
|
247
|
+
if (media !== undefined && /application\/json|\+json/i.test(type)) {
|
|
248
|
+
return media;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return undefined;
|
|
252
|
+
}
|
|
253
|
+
function isReference(value) {
|
|
254
|
+
return (typeof value === "object" &&
|
|
255
|
+
value !== null &&
|
|
256
|
+
"$ref" in value &&
|
|
257
|
+
typeof value.$ref === "string");
|
|
258
|
+
}
|
|
259
|
+
function resolveReference(reference, document) {
|
|
260
|
+
const ref = reference.$ref;
|
|
261
|
+
if (!ref.startsWith("#/")) {
|
|
262
|
+
throw new UserError(`mockFetch: only local $ref values are supported, got ${JSON.stringify(ref)}.`);
|
|
263
|
+
}
|
|
264
|
+
const segments = ref
|
|
265
|
+
.slice(2)
|
|
266
|
+
.split("/")
|
|
267
|
+
.map((segment) => segment.replace(/~1/g, "/").replace(/~0/g, "~"));
|
|
268
|
+
let current = document;
|
|
269
|
+
for (const segment of segments) {
|
|
270
|
+
if (current === null || typeof current !== "object") {
|
|
271
|
+
throw new UserError(`mockFetch: failed to resolve $ref ${JSON.stringify(ref)}.`);
|
|
272
|
+
}
|
|
273
|
+
current = current[segment];
|
|
274
|
+
}
|
|
275
|
+
if (current === undefined) {
|
|
276
|
+
throw new UserError(`mockFetch: failed to resolve $ref ${JSON.stringify(ref)}.`);
|
|
277
|
+
}
|
|
278
|
+
return current;
|
|
279
|
+
}
|
|
280
|
+
function validateAgainstSchema(value, schema, document, pointer) {
|
|
281
|
+
const resolved = isReference(schema)
|
|
282
|
+
? resolveReference(schema, document)
|
|
283
|
+
: schema;
|
|
284
|
+
if (resolved.anyOf !== undefined) {
|
|
285
|
+
return resolved.anyOf.some((branch) => validateAgainstSchema(value, branch, document, pointer).length === 0)
|
|
286
|
+
? []
|
|
287
|
+
: [`${pointer}: did not match any anyOf branch`];
|
|
288
|
+
}
|
|
289
|
+
if (resolved.oneOf !== undefined) {
|
|
290
|
+
const matches = resolved.oneOf.filter((branch) => validateAgainstSchema(value, branch, document, pointer).length === 0);
|
|
291
|
+
return matches.length === 1 ? [] : [`${pointer}: matched ${matches.length} oneOf branches`];
|
|
292
|
+
}
|
|
293
|
+
const errors = [];
|
|
294
|
+
if (resolved.allOf !== undefined) {
|
|
295
|
+
for (const branch of resolved.allOf) {
|
|
296
|
+
errors.push(...validateAgainstSchema(value, branch, document, pointer));
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
const types = normalizeTypes(resolved);
|
|
300
|
+
if (types.length > 0 && !types.some((type) => matchesPrimitiveType(value, type))) {
|
|
301
|
+
errors.push(`${pointer}: expected ${types.join(" or ")}, got ${describeValue(value)}`);
|
|
302
|
+
return errors;
|
|
303
|
+
}
|
|
304
|
+
if (types.includes("object") && typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
305
|
+
for (const required of resolved.required ?? []) {
|
|
306
|
+
if (!(required in value)) {
|
|
307
|
+
errors.push(`${pointer}/${required}: required`);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
if (resolved.properties !== undefined) {
|
|
311
|
+
for (const [key, propValue] of Object.entries(value)) {
|
|
312
|
+
const propSchema = resolved.properties[key];
|
|
313
|
+
if (propSchema !== undefined) {
|
|
314
|
+
errors.push(...validateAgainstSchema(propValue, propSchema, document, `${pointer}/${key}`));
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
if (types.includes("array") && Array.isArray(value) && resolved.items !== undefined) {
|
|
320
|
+
for (let i = 0; i < value.length; i++) {
|
|
321
|
+
errors.push(...validateAgainstSchema(value[i], resolved.items, document, `${pointer}/${i}`));
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
if (resolved.enum !== undefined && !resolved.enum.includes(value)) {
|
|
325
|
+
errors.push(`${pointer}: not in enum`);
|
|
326
|
+
}
|
|
327
|
+
return errors;
|
|
328
|
+
}
|
|
329
|
+
function normalizeTypes(schema) {
|
|
330
|
+
const type = schema.type;
|
|
331
|
+
if (type === undefined) {
|
|
332
|
+
return [];
|
|
333
|
+
}
|
|
334
|
+
const list = Array.isArray(type) ? type.slice() : [type];
|
|
335
|
+
if (schema.nullable === true && !list.includes("null")) {
|
|
336
|
+
list.push("null");
|
|
337
|
+
}
|
|
338
|
+
return list;
|
|
339
|
+
}
|
|
340
|
+
function matchesPrimitiveType(value, type) {
|
|
341
|
+
switch (type) {
|
|
342
|
+
case "object":
|
|
343
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
344
|
+
case "array":
|
|
345
|
+
return Array.isArray(value);
|
|
346
|
+
case "string":
|
|
347
|
+
return typeof value === "string";
|
|
348
|
+
case "number":
|
|
349
|
+
return typeof value === "number" && !Number.isNaN(value);
|
|
350
|
+
case "integer":
|
|
351
|
+
return typeof value === "number" && Number.isInteger(value);
|
|
352
|
+
case "boolean":
|
|
353
|
+
return typeof value === "boolean";
|
|
354
|
+
case "null":
|
|
355
|
+
return value === null;
|
|
356
|
+
default:
|
|
357
|
+
return true;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
function describeValue(value) {
|
|
361
|
+
if (value === null)
|
|
362
|
+
return "null";
|
|
363
|
+
if (Array.isArray(value))
|
|
364
|
+
return "array";
|
|
365
|
+
return typeof value;
|
|
366
|
+
}
|
|
367
|
+
function parseRequestUrl(input) {
|
|
368
|
+
if (input instanceof URL) {
|
|
369
|
+
return input;
|
|
370
|
+
}
|
|
371
|
+
if (typeof input === "string") {
|
|
372
|
+
return new URL(input);
|
|
373
|
+
}
|
|
374
|
+
return new URL(input.url);
|
|
375
|
+
}
|
|
376
|
+
function getRequestMethod(input) {
|
|
377
|
+
if (typeof input === "string" || input instanceof URL) {
|
|
378
|
+
return undefined;
|
|
379
|
+
}
|
|
380
|
+
return input.method;
|
|
381
|
+
}
|
|
382
|
+
function collectHeaders(input, init) {
|
|
383
|
+
const headers = {};
|
|
384
|
+
const initHeaders = init?.headers;
|
|
385
|
+
const requestHeaders = typeof input !== "string" && !(input instanceof URL) ? input.headers : undefined;
|
|
386
|
+
appendHeaders(headers, requestHeaders);
|
|
387
|
+
appendHeaders(headers, initHeaders);
|
|
388
|
+
return headers;
|
|
389
|
+
}
|
|
390
|
+
function appendHeaders(target, source) {
|
|
391
|
+
if (source === undefined) {
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
if (source instanceof Headers) {
|
|
395
|
+
source.forEach((value, key) => {
|
|
396
|
+
target[key.toLowerCase()] = value;
|
|
397
|
+
});
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
if (Array.isArray(source)) {
|
|
401
|
+
for (const [key, value] of source) {
|
|
402
|
+
target[key.toLowerCase()] = value;
|
|
403
|
+
}
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
for (const [key, value] of Object.entries(source)) {
|
|
407
|
+
target[key.toLowerCase()] = String(value);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
async function readRequestBody(input, init) {
|
|
411
|
+
if (init?.body !== undefined && init.body !== null) {
|
|
412
|
+
return typeof init.body === "string" ? init.body : await new Response(init.body).text();
|
|
413
|
+
}
|
|
414
|
+
if (typeof input !== "string" && !(input instanceof URL)) {
|
|
415
|
+
return await input.clone().text();
|
|
416
|
+
}
|
|
417
|
+
return undefined;
|
|
418
|
+
}
|
|
419
|
+
function parseJsonOrUndefined(text) {
|
|
420
|
+
if (text === undefined || text.length === 0) {
|
|
421
|
+
return undefined;
|
|
422
|
+
}
|
|
423
|
+
try {
|
|
424
|
+
return JSON.parse(text);
|
|
425
|
+
}
|
|
426
|
+
catch {
|
|
427
|
+
return text;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
function buildResponse(fixture, defaultStatus) {
|
|
431
|
+
const status = fixture.status ?? defaultStatus;
|
|
432
|
+
const headers = new Headers(fixture.headers ?? { "content-type": "application/json" });
|
|
433
|
+
const body = fixture.body === undefined ? null : fixture.body === null ? null : JSON.stringify(fixture.body);
|
|
434
|
+
return new Response(body, { status, headers });
|
|
435
|
+
}
|
|
436
|
+
function jsonResponse(status, body) {
|
|
437
|
+
return new Response(JSON.stringify(body), {
|
|
438
|
+
status,
|
|
439
|
+
headers: { "content-type": "application/json" }
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
async function createFixtureLoader(fixtures, cwd, injectedFs, operationIds) {
|
|
443
|
+
if (fixtures === undefined) {
|
|
444
|
+
return async () => undefined;
|
|
445
|
+
}
|
|
446
|
+
if (typeof fixtures !== "string") {
|
|
447
|
+
rejectUnknownFixtureKeys(Object.keys(fixtures), operationIds, "fixture key");
|
|
448
|
+
const map = fixtures;
|
|
449
|
+
return async (operationId) => map[operationId];
|
|
450
|
+
}
|
|
451
|
+
const directory = path.resolve(cwd ?? process.cwd(), fixtures);
|
|
452
|
+
const fileSystem = injectedFs ?? fs;
|
|
453
|
+
const readdir = fileSystem.readdir?.bind(fileSystem);
|
|
454
|
+
if (readdir === undefined) {
|
|
455
|
+
throw new UserError("mockFetch: directory fixtures require an fs implementation that exposes readdir.");
|
|
456
|
+
}
|
|
457
|
+
let entries;
|
|
458
|
+
try {
|
|
459
|
+
entries = await readdir(directory);
|
|
460
|
+
}
|
|
461
|
+
catch (error) {
|
|
462
|
+
if (isNotFoundError(error)) {
|
|
463
|
+
return async () => undefined;
|
|
464
|
+
}
|
|
465
|
+
throw error;
|
|
466
|
+
}
|
|
467
|
+
const fixtureFiles = entries.filter((entry) => entry.endsWith(".json"));
|
|
468
|
+
const operationIdsFromFiles = fixtureFiles.map((entry) => entry.slice(0, -".json".length));
|
|
469
|
+
rejectUnknownFixtureKeys(operationIdsFromFiles, operationIds, "fixture file");
|
|
470
|
+
const cache = new Map();
|
|
471
|
+
for (const entry of fixtureFiles) {
|
|
472
|
+
const operationId = entry.slice(0, -".json".length);
|
|
473
|
+
const filePath = path.join(directory, entry);
|
|
474
|
+
const contents = await fileSystem.readFile(filePath, "utf8");
|
|
475
|
+
cache.set(operationId, JSON.parse(contents));
|
|
476
|
+
}
|
|
477
|
+
return async (operationId) => cache.get(operationId);
|
|
478
|
+
}
|
|
479
|
+
function rejectUnknownFixtureKeys(candidates, operationIds, label) {
|
|
480
|
+
const unknown = candidates.filter((key) => !operationIds.has(key));
|
|
481
|
+
if (unknown.length === 0) {
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
const sorted = [...operationIds].sort();
|
|
485
|
+
throw new UserError(`mockFetch: ${unknown.length === 1 ? label : `${label}s`} ${formatList(unknown)} ` +
|
|
486
|
+
`${unknown.length === 1 ? "is" : "are"} not declared in the spec. ` +
|
|
487
|
+
`Known operationIds: ${sorted.length === 0 ? "(none)" : formatList(sorted)}.`);
|
|
488
|
+
}
|
|
489
|
+
function formatList(values) {
|
|
490
|
+
return values.map((value) => JSON.stringify(value)).join(", ");
|
|
491
|
+
}
|
|
492
|
+
function isNotFoundError(error) {
|
|
493
|
+
if (typeof error !== "object" || error === null) {
|
|
494
|
+
return false;
|
|
495
|
+
}
|
|
496
|
+
const code = error.code;
|
|
497
|
+
return code === "ENOENT" || code === "ENOTDIR";
|
|
498
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { mockFetch } from "./mock.js";
|
|
2
|
+
const ignoredHandlePromise = mockFetch({
|
|
3
|
+
spec: { openapi: "3.0.0", info: { title: "T", version: "0" }, paths: {} },
|
|
4
|
+
fixtures: { whoami: { body: { handle: "x" } } },
|
|
5
|
+
onUnmocked: "throw"
|
|
6
|
+
});
|
|
7
|
+
void ignoredHandlePromise.then((handle) => {
|
|
8
|
+
void handle.fetch;
|
|
9
|
+
void handle.requests;
|
|
10
|
+
void handle.reset;
|
|
11
|
+
});
|
|
12
|
+
const ignoredOptions = { spec: "./openapi.json" };
|
|
13
|
+
void ignoredOptions;
|
|
14
|
+
const ignoredFixture = { status: 200, body: { ok: true } };
|
|
15
|
+
void ignoredFixture;
|
package/dist/mock.d.ts
ADDED
package/dist/mock.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { mockFetch } from "./mock/fetch.js";
|
package/dist/runtime.js
CHANGED
|
@@ -66,11 +66,11 @@ function createRuntimeHandler(command) {
|
|
|
66
66
|
};
|
|
67
67
|
}
|
|
68
68
|
function createRuntimeParamSchema(param) {
|
|
69
|
-
const definition = createRuntimeDefinition(param.definition, param.description, param.shortFlag, param.scope);
|
|
69
|
+
const definition = createRuntimeDefinition(param.definition, param.description, param.shortFlag, param.scope, param.global);
|
|
70
70
|
return param.optional ? S.Optional(definition) : definition;
|
|
71
71
|
}
|
|
72
|
-
function createRuntimeDefinition(definition, description, shortFlag, scope) {
|
|
73
|
-
const options = createRuntimeSchemaOptions(definition, description, shortFlag, scope);
|
|
72
|
+
function createRuntimeDefinition(definition, description, shortFlag, scope, global) {
|
|
73
|
+
const options = createRuntimeSchemaOptions(definition, description, shortFlag, scope, global);
|
|
74
74
|
return RUNTIME_DEFINITION_BUILDERS[definition.kind](definition, options);
|
|
75
75
|
}
|
|
76
76
|
const RUNTIME_DEFINITION_BUILDERS = {
|
|
@@ -83,12 +83,13 @@ const RUNTIME_DEFINITION_BUILDERS = {
|
|
|
83
83
|
number: (_definition, options) => options === undefined ? S.Number() : S.Number(options),
|
|
84
84
|
string: (_definition, options) => options === undefined ? S.String() : S.String(options)
|
|
85
85
|
};
|
|
86
|
-
function createRuntimeSchemaOptions(definition, description, shortFlag, scope) {
|
|
86
|
+
function createRuntimeSchemaOptions(definition, description, shortFlag, scope, global) {
|
|
87
87
|
const options = Object.fromEntries(collectSchemaOptionEntries({
|
|
88
88
|
definition,
|
|
89
89
|
description,
|
|
90
90
|
shortFlag,
|
|
91
|
-
scope
|
|
91
|
+
scope,
|
|
92
|
+
global
|
|
92
93
|
}).map(({ key, value }) => [key, Array.isArray(value) ? [...value] : value]));
|
|
93
94
|
return Object.keys(options).length === 0 ? undefined : options;
|
|
94
95
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "toolcraft-openapi",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.15",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -8,12 +8,16 @@
|
|
|
8
8
|
".": {
|
|
9
9
|
"types": "./dist/index.d.ts",
|
|
10
10
|
"import": "./dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"./mock": {
|
|
13
|
+
"types": "./dist/mock.d.ts",
|
|
14
|
+
"import": "./dist/mock.js"
|
|
11
15
|
}
|
|
12
16
|
},
|
|
13
17
|
"scripts": {
|
|
14
18
|
"build": "rm -rf dist && tsc",
|
|
15
|
-
"test": "cd ../.. && vitest run packages/toolcraft-openapi/src
|
|
16
|
-
"test:unit": "cd ../.. && vitest run packages/toolcraft-openapi/src
|
|
19
|
+
"test": "cd ../.. && vitest run packages/toolcraft-openapi/src",
|
|
20
|
+
"test:unit": "cd ../.. && vitest run packages/toolcraft-openapi/src",
|
|
17
21
|
"prepack": "node ../../scripts/manage-bundled-workspace-deps.mjs prepare . @poe-code/design-system auth-store",
|
|
18
22
|
"postpack": "node ../../scripts/manage-bundled-workspace-deps.mjs cleanup . @poe-code/design-system auth-store"
|
|
19
23
|
},
|
|
@@ -30,7 +34,7 @@
|
|
|
30
34
|
"auth-store": "^0.0.1",
|
|
31
35
|
"chalk": "^5.6.2",
|
|
32
36
|
"console-table-printer": "^2.15.0",
|
|
33
|
-
"toolcraft": "^0.0.
|
|
37
|
+
"toolcraft": "^0.0.15",
|
|
34
38
|
"yaml": "^2.8.2"
|
|
35
39
|
},
|
|
36
40
|
"engines": {
|