toolcraft-openapi 0.0.14 → 0.0.16

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.
@@ -123,6 +123,7 @@ export interface GeneratedParam {
123
123
  description?: string;
124
124
  shortFlag?: string;
125
125
  scope?: readonly [GeneratedParamScope, ...GeneratedParamScope[]];
126
+ global?: boolean;
126
127
  optional: boolean;
127
128
  definition: GeneratedParamDefinition;
128
129
  }
@@ -187,6 +188,7 @@ interface RenderSchemaOptionsInput {
187
188
  description?: string;
188
189
  shortFlag?: string;
189
190
  scope?: readonly [GeneratedParamScope, ...GeneratedParamScope[]];
191
+ global?: boolean;
190
192
  }
191
193
  export type GeneratedPreflightBlock = {
192
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
@@ -963,15 +969,16 @@ function renderParamLines(params) {
963
969
  return params.map((param) => ` ${renderObjectKey(param.paramName)}: ${renderParamSchema(param)},`);
964
970
  }
965
971
  function renderParamSchema(param) {
966
- 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);
967
973
  return param.optional ? `S.Optional(${schema})` : schema;
968
974
  }
969
- function renderDefinition(definition, description, shortFlag, scope) {
975
+ function renderDefinition(definition, description, shortFlag, scope, global) {
970
976
  const options = renderSchemaOptions({
971
977
  definition,
972
978
  description,
973
979
  shortFlag,
974
- scope
980
+ scope,
981
+ global
975
982
  });
976
983
  const renderer = DEFINITION_RENDERERS[definition.kind];
977
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
@@ -0,0 +1,2 @@
1
+ export { mockFetch } from "./mock/fetch.js";
2
+ export type { MockFetchHandle, MockFetchOptions, MockFetchFixtures, MockFixtureEntry, OnUnmocked, RequestRecord } from "./mock/fetch.js";
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
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@poe-code/design-system",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toolcraft-openapi",
3
- "version": "0.0.14",
3
+ "version": "0.0.16",
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/*.test.ts",
16
- "test:unit": "cd ../.. && vitest run packages/toolcraft-openapi/src/*.test.ts",
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
  },
@@ -26,11 +30,11 @@
26
30
  "dependencies": {
27
31
  "@clack/core": "^1.0.0",
28
32
  "@clack/prompts": "^1.0.0",
29
- "@poe-code/design-system": "^0.0.1",
33
+ "@poe-code/design-system": "^0.0.2",
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.14",
37
+ "toolcraft": "^0.0.16",
34
38
  "yaml": "^2.8.2"
35
39
  },
36
40
  "engines": {