toolcraft-openapi 0.0.38 → 0.0.40
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 +67 -0
- package/dist/bin/generate.d.ts +9 -0
- package/dist/bin/generate.js +122 -6
- package/dist/config.d.ts +53 -0
- package/dist/config.js +266 -0
- package/dist/define-client.js +4 -0
- package/dist/diagnose.d.ts +4 -0
- package/dist/diagnose.js +93 -0
- package/dist/diagnostics.d.ts +19 -0
- package/dist/diagnostics.js +19 -0
- package/dist/generate.d.ts +13 -1
- package/dist/generate.js +165 -12
- package/dist/http.d.ts +30 -25
- package/dist/http.js +184 -42
- package/dist/index.d.ts +7 -2
- package/dist/index.js +5 -2
- package/dist/runtime.d.ts +7 -0
- package/dist/runtime.js +30 -5
- package/node_modules/@poe-code/frontmatter/README.md +35 -0
- package/node_modules/@poe-code/frontmatter/dist/fences.d.ts +8 -0
- package/node_modules/@poe-code/frontmatter/dist/fences.js +72 -0
- package/node_modules/@poe-code/frontmatter/dist/index.d.ts +3 -0
- package/node_modules/@poe-code/frontmatter/dist/index.js +3 -0
- package/node_modules/@poe-code/frontmatter/dist/parse.d.ts +20 -0
- package/node_modules/@poe-code/frontmatter/dist/parse.js +137 -0
- package/node_modules/@poe-code/frontmatter/dist/stringify.d.ts +1 -0
- package/node_modules/@poe-code/frontmatter/dist/stringify.js +35 -0
- package/node_modules/@poe-code/frontmatter/package.json +25 -0
- package/node_modules/toolcraft-design/README.md +12 -0
- package/node_modules/toolcraft-design/dist/prompts/index.d.ts +39 -14
- package/node_modules/toolcraft-design/dist/prompts/index.js +10 -6
- package/node_modules/toolcraft-design/dist/prompts/interactive/cancel-symbol.d.ts +2 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/cancel-symbol.js +4 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/confirm.d.ts +9 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/confirm.js +47 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/core.d.ts +55 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/core.js +274 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/glyphs.d.ts +20 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/glyphs.js +53 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/index.d.ts +6 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/index.js +6 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/keys.d.ts +2 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/keys.js +28 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/multiselect.d.ts +13 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/multiselect.js +131 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/pagination.d.ts +10 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/pagination.js +52 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/password.d.ts +10 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/password.js +55 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/select.d.ts +18 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/select.js +89 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/test-helpers.d.ts +21 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/test-helpers.js +32 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/text.d.ts +12 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/text.js +60 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/wrap.d.ts +4 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/wrap.js +18 -0
- package/node_modules/toolcraft-design/dist/prompts/primitives/cancel.d.ts +1 -1
- package/node_modules/toolcraft-design/dist/prompts/primitives/cancel.js +1 -1
- package/node_modules/toolcraft-design/dist/terminal-markdown/parser/frontmatter.js +20 -453
- package/node_modules/toolcraft-design/package.json +6 -3
- package/package.json +7 -7
package/dist/http.js
CHANGED
|
@@ -1,26 +1,11 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
1
3
|
import { text as designText } from "toolcraft-design";
|
|
2
|
-
import { UserError } from "toolcraft";
|
|
4
|
+
import { HttpError, UserError, createHttpError } from "toolcraft";
|
|
3
5
|
import { classifyNetworkError } from "./network-error.js";
|
|
4
6
|
import { redactHeaders, redactHeaderValue, redactSensitiveQueryValues } from "./redaction.js";
|
|
7
|
+
export { HttpError };
|
|
5
8
|
const TRANSCRIPT_BODY_BYTE_LIMIT = 4 * 1024;
|
|
6
|
-
export class HttpError extends Error {
|
|
7
|
-
status;
|
|
8
|
-
statusText;
|
|
9
|
-
request;
|
|
10
|
-
response;
|
|
11
|
-
get body() {
|
|
12
|
-
return this.response.body;
|
|
13
|
-
}
|
|
14
|
-
constructor(args) {
|
|
15
|
-
super(args.message ??
|
|
16
|
-
`${args.request.method} ${args.request.url} → ${args.response.status} ${args.response.statusText}`);
|
|
17
|
-
this.name = "HttpError";
|
|
18
|
-
this.status = args.response.status;
|
|
19
|
-
this.statusText = args.response.statusText;
|
|
20
|
-
this.request = args.request;
|
|
21
|
-
this.response = args.response;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
9
|
export async function requestJson(options) {
|
|
25
10
|
const token = options.auth === "none" ? undefined : await options.tokenSource.getToken();
|
|
26
11
|
const method = options.method.toUpperCase();
|
|
@@ -37,7 +22,12 @@ export async function requestJson(options) {
|
|
|
37
22
|
: JSON.stringify(options.body)
|
|
38
23
|
: undefined;
|
|
39
24
|
const url = buildRequestUrl(options);
|
|
40
|
-
const
|
|
25
|
+
const idempotencyHeader = options.idempotency?.enabled === true && method !== "GET"
|
|
26
|
+
? {
|
|
27
|
+
[options.idempotency.header]: options.idempotency.key ?? options.idempotency.createKey?.() ?? randomUUID()
|
|
28
|
+
}
|
|
29
|
+
: {};
|
|
30
|
+
const headers = createHeaders(token, hasBody, { ...options.headers, ...idempotencyHeader }, options.accept, options.bodyMode, options.contentType);
|
|
41
31
|
const writeStdout = options.writeStdout ?? process.stdout.write.bind(process.stdout);
|
|
42
32
|
const writeStderr = options.writeStderr ?? process.stderr.write.bind(process.stderr);
|
|
43
33
|
const requestLine = `${method} ${redactSensitiveQueryValues(url)}`;
|
|
@@ -48,18 +38,12 @@ export async function requestJson(options) {
|
|
|
48
38
|
if (options.verbose) {
|
|
49
39
|
writeStderr(formatTranscriptLines(formatVerboseRequestTranscript(method, url, headers, options.body)));
|
|
50
40
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
signal: options.signal
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
catch (error) {
|
|
61
|
-
throw classifyNetworkError(error, url) ?? error;
|
|
62
|
-
}
|
|
41
|
+
const response = await fetchWithRetries(options, url, {
|
|
42
|
+
method,
|
|
43
|
+
headers,
|
|
44
|
+
body: serializedBody,
|
|
45
|
+
signal: options.signal
|
|
46
|
+
});
|
|
63
47
|
const contentType = response.headers.get("content-type");
|
|
64
48
|
const request = createHttpErrorRequest(method, url, headers, options.body);
|
|
65
49
|
const responseHeaders = redactHeaders(serializeHeaders(response.headers));
|
|
@@ -69,7 +53,7 @@ export async function requestJson(options) {
|
|
|
69
53
|
if (options.verbose) {
|
|
70
54
|
writeStderr(formatTranscriptLines(formatVerboseResponseTranscript(response, responseHeaders)));
|
|
71
55
|
}
|
|
72
|
-
return undefined;
|
|
56
|
+
return formatRawResponseResult(undefined, response, options.rawResponse);
|
|
73
57
|
}
|
|
74
58
|
const body = {
|
|
75
59
|
contentType: contentType ?? options.accept ?? "application/octet-stream",
|
|
@@ -80,7 +64,7 @@ export async function requestJson(options) {
|
|
|
80
64
|
if (options.verbose) {
|
|
81
65
|
writeStderr(formatTranscriptLines(formatVerboseResponseTranscript(response, responseHeaders, body)));
|
|
82
66
|
}
|
|
83
|
-
return body;
|
|
67
|
+
return formatRawResponseResult(body, response, options.rawResponse);
|
|
84
68
|
}
|
|
85
69
|
const text = await response.text();
|
|
86
70
|
if (response.ok) {
|
|
@@ -88,19 +72,19 @@ export async function requestJson(options) {
|
|
|
88
72
|
if (options.verbose) {
|
|
89
73
|
writeStderr(formatTranscriptLines(formatVerboseResponseTranscript(response, responseHeaders)));
|
|
90
74
|
}
|
|
91
|
-
return undefined;
|
|
75
|
+
return formatRawResponseResult(undefined, response, options.rawResponse);
|
|
92
76
|
}
|
|
93
77
|
if (options.responseMode === "text") {
|
|
94
78
|
if (options.verbose) {
|
|
95
79
|
writeStderr(formatTranscriptLines(formatVerboseResponseTranscript(response, responseHeaders, text)));
|
|
96
80
|
}
|
|
97
|
-
return text;
|
|
81
|
+
return formatRawResponseResult(text, response, options.rawResponse);
|
|
98
82
|
}
|
|
99
83
|
if (!isJsonContentType(contentType)) {
|
|
100
84
|
if (options.verbose) {
|
|
101
85
|
writeStderr(formatTranscriptLines(formatVerboseResponseTranscript(response, responseHeaders, text)));
|
|
102
86
|
}
|
|
103
|
-
throw
|
|
87
|
+
throw createHttpError({
|
|
104
88
|
request,
|
|
105
89
|
response: {
|
|
106
90
|
status: response.status,
|
|
@@ -119,7 +103,7 @@ export async function requestJson(options) {
|
|
|
119
103
|
if (options.verbose) {
|
|
120
104
|
writeStderr(formatTranscriptLines(formatVerboseResponseTranscript(response, responseHeaders, text)));
|
|
121
105
|
}
|
|
122
|
-
throw
|
|
106
|
+
throw createHttpError({
|
|
123
107
|
request,
|
|
124
108
|
response: {
|
|
125
109
|
status: response.status,
|
|
@@ -133,7 +117,7 @@ export async function requestJson(options) {
|
|
|
133
117
|
if (options.verbose) {
|
|
134
118
|
writeStderr(formatTranscriptLines(formatVerboseResponseTranscript(response, responseHeaders, body)));
|
|
135
119
|
}
|
|
136
|
-
return body;
|
|
120
|
+
return formatRawResponseResult(body, response, options.rawResponse);
|
|
137
121
|
}
|
|
138
122
|
if (response.status === 401 && options.auth === "required") {
|
|
139
123
|
await options.tokenSource.invalidate?.(token).catch(() => undefined);
|
|
@@ -142,16 +126,128 @@ export async function requestJson(options) {
|
|
|
142
126
|
if (options.verbose) {
|
|
143
127
|
writeStderr(formatTranscriptLines(formatVerboseResponseTranscript(response, responseHeaders, body)));
|
|
144
128
|
}
|
|
145
|
-
throw
|
|
129
|
+
throw createHttpError({
|
|
146
130
|
request,
|
|
147
131
|
response: {
|
|
148
132
|
status: response.status,
|
|
149
133
|
statusText: response.statusText,
|
|
150
134
|
headers: responseHeaders,
|
|
151
135
|
body
|
|
152
|
-
}
|
|
136
|
+
},
|
|
137
|
+
code: extractErrorCode(body),
|
|
138
|
+
requestId: response.headers.get("x-request-id") ?? response.headers.get("x-requestid") ?? undefined
|
|
153
139
|
});
|
|
154
140
|
}
|
|
141
|
+
async function fetchWithRetries(options, url, init) {
|
|
142
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
143
|
+
const retries = options.retries;
|
|
144
|
+
const maxRetries = retries?.max ?? 0;
|
|
145
|
+
let attempt = 0;
|
|
146
|
+
while (true) {
|
|
147
|
+
try {
|
|
148
|
+
const response = await fetchImpl(url, init);
|
|
149
|
+
if (retries === undefined ||
|
|
150
|
+
attempt >= maxRetries ||
|
|
151
|
+
!shouldRetryStatus(response.status, retries.retryOn)) {
|
|
152
|
+
return response;
|
|
153
|
+
}
|
|
154
|
+
await sleepBeforeRetry(response, retries, attempt);
|
|
155
|
+
attempt += 1;
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
const classified = classifyNetworkError(error, url) ?? error;
|
|
159
|
+
if (attempt >= maxRetries || retries === undefined) {
|
|
160
|
+
throw classified;
|
|
161
|
+
}
|
|
162
|
+
await sleepBeforeRetry(undefined, retries, attempt);
|
|
163
|
+
attempt += 1;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
function shouldRetryStatus(status, retryOn) {
|
|
168
|
+
return retryOn?.includes(status) === true;
|
|
169
|
+
}
|
|
170
|
+
async function sleepBeforeRetry(response, retries, attempt) {
|
|
171
|
+
const retryAfter = response?.headers.get("retry-after");
|
|
172
|
+
const delay = parseRetryAfter(retryAfter) ?? calculateBackoffDelay(attempt, retries.random);
|
|
173
|
+
await (retries.sleep ?? defaultSleep)(delay);
|
|
174
|
+
}
|
|
175
|
+
function parseRetryAfter(value) {
|
|
176
|
+
if (value === null || value === undefined) {
|
|
177
|
+
return undefined;
|
|
178
|
+
}
|
|
179
|
+
const seconds = Number(value);
|
|
180
|
+
if (Number.isFinite(seconds) && seconds >= 0) {
|
|
181
|
+
return seconds * 1000;
|
|
182
|
+
}
|
|
183
|
+
const timestamp = Date.parse(value);
|
|
184
|
+
if (!Number.isNaN(timestamp)) {
|
|
185
|
+
return Math.max(0, timestamp - Date.now());
|
|
186
|
+
}
|
|
187
|
+
return undefined;
|
|
188
|
+
}
|
|
189
|
+
function calculateBackoffDelay(attempt, random) {
|
|
190
|
+
const base = 100 * 2 ** attempt;
|
|
191
|
+
return Math.floor(base * (random ?? Math.random)());
|
|
192
|
+
}
|
|
193
|
+
function defaultSleep(ms) {
|
|
194
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
195
|
+
}
|
|
196
|
+
function formatRawResponseResult(data, response, rawResponse) {
|
|
197
|
+
return rawResponse === true ? { data, response } : data;
|
|
198
|
+
}
|
|
199
|
+
export async function prepareMultipartFileInputs(requestShape, options) {
|
|
200
|
+
if (options.bodyMode !== "multipart" ||
|
|
201
|
+
options.multipartBinaryFields === undefined ||
|
|
202
|
+
options.multipartBinaryFields.length === 0 ||
|
|
203
|
+
options.fs === undefined ||
|
|
204
|
+
options.env === undefined ||
|
|
205
|
+
requestShape.body === undefined) {
|
|
206
|
+
return requestShape;
|
|
207
|
+
}
|
|
208
|
+
if (requestShape.body === null ||
|
|
209
|
+
typeof requestShape.body !== "object" ||
|
|
210
|
+
Array.isArray(requestShape.body)) {
|
|
211
|
+
return requestShape;
|
|
212
|
+
}
|
|
213
|
+
const body = { ...requestShape.body };
|
|
214
|
+
for (const field of options.multipartBinaryFields) {
|
|
215
|
+
const value = body[field];
|
|
216
|
+
if (typeof value !== "string") {
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
const filePath = resolveUserPath(value, options.env);
|
|
220
|
+
if (!(await options.fs.exists(filePath))) {
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
body[field] = {
|
|
224
|
+
data: await options.fs.readFile(filePath, "base64"),
|
|
225
|
+
filename: path.basename(filePath) || field
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
return {
|
|
229
|
+
...requestShape,
|
|
230
|
+
body
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
export async function writeBinaryResponseOutput(result, outputPath, runtime) {
|
|
234
|
+
if (outputPath === undefined) {
|
|
235
|
+
return result;
|
|
236
|
+
}
|
|
237
|
+
if (runtime.fs === undefined || runtime.env === undefined) {
|
|
238
|
+
throw new UserError("Cannot write binary output without a Toolcraft file runtime.");
|
|
239
|
+
}
|
|
240
|
+
if (!isBinaryHttpResponse(result)) {
|
|
241
|
+
throw new UserError("Cannot write an empty binary response to an output path.");
|
|
242
|
+
}
|
|
243
|
+
const resolvedOutputPath = resolveUserPath(String(outputPath), runtime.env);
|
|
244
|
+
await runtime.fs.writeFile(resolvedOutputPath, result.data, { encoding: "base64" });
|
|
245
|
+
return {
|
|
246
|
+
output: resolvedOutputPath,
|
|
247
|
+
byteLength: result.byteLength,
|
|
248
|
+
contentType: result.contentType
|
|
249
|
+
};
|
|
250
|
+
}
|
|
155
251
|
function buildRequestUrl(options) {
|
|
156
252
|
const resolvedPath = substitutePathParams(options.path, options.pathParams);
|
|
157
253
|
const baseUrl = new URL(options.baseUrl);
|
|
@@ -223,6 +319,22 @@ function decodeBase64Body(body) {
|
|
|
223
319
|
}
|
|
224
320
|
return Uint8Array.from(Buffer.from(body, "base64")).buffer;
|
|
225
321
|
}
|
|
322
|
+
function decodeMultipartBinaryValue(value, fallbackFilename) {
|
|
323
|
+
if (typeof value === "string") {
|
|
324
|
+
return {
|
|
325
|
+
data: value,
|
|
326
|
+
filename: fallbackFilename
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
if (value !== null &&
|
|
330
|
+
typeof value === "object" &&
|
|
331
|
+
!Array.isArray(value) &&
|
|
332
|
+
typeof value.data === "string" &&
|
|
333
|
+
typeof value.filename === "string") {
|
|
334
|
+
return value;
|
|
335
|
+
}
|
|
336
|
+
throw new UserError("Multipart binary request fields must be base64 strings or resolved file inputs.");
|
|
337
|
+
}
|
|
226
338
|
function serializeMultipartBody(body, binaryFields = []) {
|
|
227
339
|
if (body === null || typeof body !== "object" || Array.isArray(body)) {
|
|
228
340
|
throw new UserError("Multipart request bodies must be objects.");
|
|
@@ -233,7 +345,8 @@ function serializeMultipartBody(body, binaryFields = []) {
|
|
|
233
345
|
if (value === undefined)
|
|
234
346
|
continue;
|
|
235
347
|
if (binary.has(key)) {
|
|
236
|
-
|
|
348
|
+
const file = decodeMultipartBinaryValue(value, key);
|
|
349
|
+
form.append(key, new Blob([decodeBase64Body(file.data)]), file.filename);
|
|
237
350
|
}
|
|
238
351
|
else if (Array.isArray(value)) {
|
|
239
352
|
for (const item of value)
|
|
@@ -245,6 +358,20 @@ function serializeMultipartBody(body, binaryFields = []) {
|
|
|
245
358
|
}
|
|
246
359
|
return form;
|
|
247
360
|
}
|
|
361
|
+
function resolveUserPath(userPath, env) {
|
|
362
|
+
if (path.isAbsolute(userPath)) {
|
|
363
|
+
return path.normalize(userPath);
|
|
364
|
+
}
|
|
365
|
+
return path.resolve(env.get("INIT_CWD") ?? process.cwd(), userPath);
|
|
366
|
+
}
|
|
367
|
+
function isBinaryHttpResponse(value) {
|
|
368
|
+
return (value !== null &&
|
|
369
|
+
typeof value === "object" &&
|
|
370
|
+
value.encoding === "base64" &&
|
|
371
|
+
typeof value.data === "string" &&
|
|
372
|
+
typeof value.byteLength === "number" &&
|
|
373
|
+
typeof value.contentType === "string");
|
|
374
|
+
}
|
|
248
375
|
function isQueryObject(value) {
|
|
249
376
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
250
377
|
}
|
|
@@ -358,6 +485,21 @@ function parseResponseBody(text, contentType) {
|
|
|
358
485
|
}
|
|
359
486
|
return JSON.parse(text);
|
|
360
487
|
}
|
|
488
|
+
function extractErrorCode(body) {
|
|
489
|
+
if (body === null || typeof body !== "object" || Array.isArray(body)) {
|
|
490
|
+
return undefined;
|
|
491
|
+
}
|
|
492
|
+
const directCode = body.code;
|
|
493
|
+
if (typeof directCode === "string") {
|
|
494
|
+
return directCode;
|
|
495
|
+
}
|
|
496
|
+
const error = body.error;
|
|
497
|
+
if (error !== null && typeof error === "object" && !Array.isArray(error)) {
|
|
498
|
+
const nestedCode = error.code;
|
|
499
|
+
return typeof nestedCode === "string" ? nestedCode : undefined;
|
|
500
|
+
}
|
|
501
|
+
return undefined;
|
|
502
|
+
}
|
|
361
503
|
function isJsonContentType(contentType) {
|
|
362
504
|
if (contentType === null) {
|
|
363
505
|
return false;
|
package/dist/index.d.ts
CHANGED
|
@@ -2,16 +2,21 @@ export { defineApiCommand } from "./api-command.js";
|
|
|
2
2
|
export { defineClient } from "./define-client.js";
|
|
3
3
|
export { generate } from "./generate.js";
|
|
4
4
|
export type { GenerateOptions, GeneratedFile, OpenApiDocument } from "./generate.js";
|
|
5
|
+
export { SUPPORTED_TOOLCRAFT_EDITION, mergeToolcraftConfig, readToolcraftConfig, validateToolcraftConfig } from "./config.js";
|
|
6
|
+
export type { ToolcraftConfig, ToolcraftMethodConfig, ToolcraftResourceConfig } from "./config.js";
|
|
7
|
+
export { diagnose } from "./diagnose.js";
|
|
8
|
+
export { DIAGNOSTIC_CODES, formatDiagnostic, formatDiagnostics } from "./diagnostics.js";
|
|
9
|
+
export type { Diagnostic, DiagnosticSeverity } from "./diagnostics.js";
|
|
5
10
|
export { inspectOpenApiDocument } from "./inspect.js";
|
|
6
11
|
export type { OpenApiInspectionOperation, OpenApiInspectionReport } from "./inspect.js";
|
|
7
12
|
export { inspectOpenApiSource } from "./inspect-source.js";
|
|
8
13
|
export type { InspectOpenApiSourceOptions, OpenApiInspectionSource } from "./inspect-source.js";
|
|
9
14
|
export { renderOpenApiInspection } from "./render-inspection.js";
|
|
10
|
-
export { commandsFromSpec, defineClientFromSpec } from "./runtime.js";
|
|
15
|
+
export { commandsFromSpec, defineClientFromSpec, resolveOpenApiBaseUrl } from "./runtime.js";
|
|
11
16
|
export type { CommandsFromSpecOptions, DefineClientFromSpecOptions, OpenApiDocumentSource } from "./runtime.js";
|
|
12
17
|
export type { DefineClientOptions, DefinedClient, OpenApiClientServices } from "./define-client.js";
|
|
13
18
|
export type { AuthProvider, CommandContributor, TokenSource } from "./auth/types.js";
|
|
14
19
|
export { bearerTokenAuth } from "./auth/bearer-token-auth.js";
|
|
15
20
|
export type { BearerTokenAuthOptions } from "./auth/bearer-token-auth.js";
|
|
16
|
-
export { HttpError, requestJson } from "./http.js";
|
|
21
|
+
export { HttpError, prepareMultipartFileInputs, requestJson, writeBinaryResponseOutput } from "./http.js";
|
|
17
22
|
export type { BinaryHttpResponse, HttpErrorRequest, HttpErrorResponse, HttpRequestOptions, QueryValue } from "./http.js";
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
export { defineApiCommand } from "./api-command.js";
|
|
2
2
|
export { defineClient } from "./define-client.js";
|
|
3
3
|
export { generate } from "./generate.js";
|
|
4
|
+
export { SUPPORTED_TOOLCRAFT_EDITION, mergeToolcraftConfig, readToolcraftConfig, validateToolcraftConfig } from "./config.js";
|
|
5
|
+
export { diagnose } from "./diagnose.js";
|
|
6
|
+
export { DIAGNOSTIC_CODES, formatDiagnostic, formatDiagnostics } from "./diagnostics.js";
|
|
4
7
|
export { inspectOpenApiDocument } from "./inspect.js";
|
|
5
8
|
export { inspectOpenApiSource } from "./inspect-source.js";
|
|
6
9
|
export { renderOpenApiInspection } from "./render-inspection.js";
|
|
7
|
-
export { commandsFromSpec, defineClientFromSpec } from "./runtime.js";
|
|
10
|
+
export { commandsFromSpec, defineClientFromSpec, resolveOpenApiBaseUrl } from "./runtime.js";
|
|
8
11
|
export { bearerTokenAuth } from "./auth/bearer-token-auth.js";
|
|
9
|
-
export { HttpError, requestJson } from "./http.js";
|
|
12
|
+
export { HttpError, prepareMultipartFileInputs, requestJson, writeBinaryResponseOutput } from "./http.js";
|
package/dist/runtime.d.ts
CHANGED
|
@@ -9,5 +9,12 @@ export interface CommandsFromSpecOptions {
|
|
|
9
9
|
fs?: OpenApiSourceFileSystem;
|
|
10
10
|
}
|
|
11
11
|
export type DefineClientFromSpecOptions<TServices extends object = Record<string, never>> = Omit<DefineClientOptions<TServices>, "commands"> & CommandsFromSpecOptions;
|
|
12
|
+
export interface ResolveOpenApiBaseUrlOptions {
|
|
13
|
+
document: OpenApiDocument;
|
|
14
|
+
environments?: Record<string, string>;
|
|
15
|
+
environment?: string;
|
|
16
|
+
env?: Record<string, string | undefined>;
|
|
17
|
+
}
|
|
12
18
|
export declare function commandsFromSpec(source: OpenApiDocumentSource, options?: CommandsFromSpecOptions): Promise<CommandNode<OpenApiClientServices>[]>;
|
|
13
19
|
export declare function defineClientFromSpec<TServices extends object = Record<string, never>>(spec: OpenApiDocumentSource, options: DefineClientFromSpecOptions<TServices>): Promise<DefinedClient<TServices>>;
|
|
20
|
+
export declare function resolveOpenApiBaseUrl(options: ResolveOpenApiBaseUrlOptions): string | undefined;
|
package/dist/runtime.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
|
-
import { defineCommand, defineGroup, S } from "toolcraft";
|
|
2
|
+
import { UserError, defineCommand, defineGroup, S } from "toolcraft";
|
|
3
3
|
import { defineClient } from "./define-client.js";
|
|
4
4
|
import { collectSchemaOptionEntries, collectGeneratedCommands } from "./generate.js";
|
|
5
5
|
import { groupByNoun } from "./group-by-noun.js";
|
|
6
|
-
import { requestJson } from "./http.js";
|
|
6
|
+
import { prepareMultipartFileInputs, requestJson, writeBinaryResponseOutput } from "./http.js";
|
|
7
7
|
import { buildRequestShape, executePreflightBlocks } from "./interpreter.js";
|
|
8
8
|
import { parseOpenApiDocument, readOpenApiSourceText } from "./spec-source.js";
|
|
9
9
|
const RUNTIME_COMMAND_SCOPE = ["cli", "mcp", "sdk"];
|
|
@@ -31,6 +31,24 @@ async function resolveDocument(source, options) {
|
|
|
31
31
|
});
|
|
32
32
|
return parseOpenApiDocument(sourceText, source);
|
|
33
33
|
}
|
|
34
|
+
export function resolveOpenApiBaseUrl(options) {
|
|
35
|
+
const environments = options.environments;
|
|
36
|
+
if (environments !== undefined && Object.keys(environments).length > 0) {
|
|
37
|
+
const selectedName = options.environment ?? options.env?.TOOLCRAFT_OPENAPI_ENV ?? Object.keys(environments)[0];
|
|
38
|
+
const selectedUrl = selectedName === undefined ? undefined : environments[selectedName];
|
|
39
|
+
if (selectedUrl !== undefined) {
|
|
40
|
+
return normalizeBaseUrl(selectedUrl);
|
|
41
|
+
}
|
|
42
|
+
if (selectedName !== undefined) {
|
|
43
|
+
throw new UserError(`Unknown OpenAPI environment ${JSON.stringify(selectedName)}. Available: ${Object.keys(environments).join(", ")}.`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const server = options.document.servers?.[0]?.url;
|
|
47
|
+
return server === undefined ? undefined : normalizeBaseUrl(server);
|
|
48
|
+
}
|
|
49
|
+
function normalizeBaseUrl(value) {
|
|
50
|
+
return new URL(value).toString().replace(/\/$/, "");
|
|
51
|
+
}
|
|
34
52
|
function createRuntimeGroups(commands) {
|
|
35
53
|
return groupByNoun(commands).map(({ noun, commands: nounCommands }) => defineGroup({
|
|
36
54
|
name: noun,
|
|
@@ -49,10 +67,16 @@ function createRuntimeCommand(command) {
|
|
|
49
67
|
});
|
|
50
68
|
}
|
|
51
69
|
function createRuntimeHandler(command) {
|
|
52
|
-
return async ({ params, baseUrl, tokenSource, fetch }) => {
|
|
70
|
+
return async ({ params, baseUrl, tokenSource, fetch, fs, env }) => {
|
|
53
71
|
const resolvedValues = executePreflightBlocks(command.preflightBlocks, params);
|
|
54
72
|
const requestShape = buildRequestShape(command.requestFields, command.sectionRenders, command.optionalSections, params, resolvedValues);
|
|
55
|
-
|
|
73
|
+
const preparedRequestShape = await prepareMultipartFileInputs(requestShape, {
|
|
74
|
+
bodyMode: command.bodyMode,
|
|
75
|
+
multipartBinaryFields: command.multipartBinaryFields,
|
|
76
|
+
fs,
|
|
77
|
+
env
|
|
78
|
+
});
|
|
79
|
+
const result = await requestJson({
|
|
56
80
|
baseUrl: command.baseUrl ?? baseUrl,
|
|
57
81
|
path: command.path,
|
|
58
82
|
method: command.method,
|
|
@@ -66,8 +90,9 @@ function createRuntimeHandler(command) {
|
|
|
66
90
|
fetch,
|
|
67
91
|
dryRun: params.dryRun,
|
|
68
92
|
verbose: params.verbose,
|
|
69
|
-
...
|
|
93
|
+
...preparedRequestShape
|
|
70
94
|
});
|
|
95
|
+
return writeBinaryResponseOutput(result, command.responseMode === "binary" ? params.output : undefined, { fs, env });
|
|
71
96
|
};
|
|
72
97
|
}
|
|
73
98
|
function createRuntimeParamSchema(param) {
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# @poe-code/frontmatter
|
|
2
|
+
|
|
3
|
+
Shared YAML frontmatter parsing for poe-code packages.
|
|
4
|
+
|
|
5
|
+
## API
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import {
|
|
9
|
+
FrontmatterParseError,
|
|
10
|
+
parseFrontmatter,
|
|
11
|
+
parseFrontmatterDocument,
|
|
12
|
+
stringifyFrontmatter
|
|
13
|
+
} from "@poe-code/frontmatter";
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
- `parseFrontmatter(source)` returns `{ frontmatter, body }`.
|
|
17
|
+
- `parseFrontmatterDocument(source)` returns `{ frontmatter, body, errors, lineCounter }` for callers that need YAML diagnostics.
|
|
18
|
+
- `stringifyFrontmatter(frontmatter, body)` writes `---` fences, YAML, and the body.
|
|
19
|
+
- `FrontmatterParseError` is thrown for malformed frontmatter, invalid YAML, non-object frontmatter, and stringify failures.
|
|
20
|
+
|
|
21
|
+
When no leading frontmatter block exists, parsing returns `{ frontmatter: {}, body: source }`.
|
|
22
|
+
The returned `body` is sliced from the original input and is otherwise byte-for-byte unchanged.
|
|
23
|
+
YAML mappings that contain `__proto__` keep it as an own property without changing the object's prototype.
|
|
24
|
+
|
|
25
|
+
Duplicate mapping keys use YAML's last-wins behavior by default, matching the
|
|
26
|
+
legacy frontmatter readers. Callers that need strict YAML mappings can pass
|
|
27
|
+
`{ uniqueKeys: true }` to `parseFrontmatter` or `parseFrontmatterDocument`.
|
|
28
|
+
|
|
29
|
+
## Environment
|
|
30
|
+
|
|
31
|
+
This package reads no environment variables.
|
|
32
|
+
|
|
33
|
+
## Config
|
|
34
|
+
|
|
35
|
+
This package has no configuration options.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
export function splitFrontmatterBlock(source) {
|
|
2
|
+
const content = source.startsWith("\uFEFF") ? source.slice(1) : source;
|
|
3
|
+
const opening = readOpeningFence(content);
|
|
4
|
+
if (opening === undefined) {
|
|
5
|
+
return { body: source };
|
|
6
|
+
}
|
|
7
|
+
const closing = findClosingFence(content, opening.next);
|
|
8
|
+
if (closing === undefined) {
|
|
9
|
+
throw new Error("Missing YAML frontmatter end delimiter (---).");
|
|
10
|
+
}
|
|
11
|
+
return {
|
|
12
|
+
raw: content.slice(opening.next, closing.index),
|
|
13
|
+
body: content.slice(closing.end + closing.lineBreakLength)
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function readOpeningFence(source) {
|
|
17
|
+
if (!source.startsWith("---")) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
const lineEnd = findLineEnd(source, 0);
|
|
21
|
+
if (lineEnd.lineBreakLength === 0 || source.slice(0, lineEnd.index) !== "---") {
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
return { next: lineEnd.index + lineEnd.lineBreakLength };
|
|
25
|
+
}
|
|
26
|
+
function findClosingFence(source, start) {
|
|
27
|
+
let lineStart = start;
|
|
28
|
+
while (lineStart <= source.length) {
|
|
29
|
+
const lineEnd = findLineEnd(source, lineStart);
|
|
30
|
+
if (isClosingFenceLine(source.slice(lineStart, lineEnd.index))) {
|
|
31
|
+
return {
|
|
32
|
+
index: lineStart,
|
|
33
|
+
end: lineEnd.index,
|
|
34
|
+
lineBreakLength: lineEnd.lineBreakLength
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
if (lineEnd.lineBreakLength === 0) {
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
lineStart = lineEnd.index + lineEnd.lineBreakLength;
|
|
41
|
+
}
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
function isClosingFenceLine(line) {
|
|
45
|
+
if (!line.startsWith("---")) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
for (let index = 3; index < line.length; index += 1) {
|
|
49
|
+
const character = line[index];
|
|
50
|
+
if (character !== " " && character !== "\t") {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
function findLineEnd(source, start) {
|
|
57
|
+
let index = start;
|
|
58
|
+
while (index < source.length) {
|
|
59
|
+
const character = source[index];
|
|
60
|
+
if (character === "\n") {
|
|
61
|
+
return { index, lineBreakLength: 1 };
|
|
62
|
+
}
|
|
63
|
+
if (character === "\r") {
|
|
64
|
+
return {
|
|
65
|
+
index,
|
|
66
|
+
lineBreakLength: source[index + 1] === "\n" ? 2 : 1
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
index += 1;
|
|
70
|
+
}
|
|
71
|
+
return { index: source.length, lineBreakLength: 0 };
|
|
72
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { splitFrontmatterBlock, type FrontmatterBlock, type SplitFrontmatterResult } from "./fences.js";
|
|
2
|
+
export { FrontmatterParseError, parseFrontmatter, parseFrontmatterDocument, type ParsedFrontmatter, type ParsedFrontmatterDocument, type ParseFrontmatterOptions } from "./parse.js";
|
|
3
|
+
export { stringifyFrontmatter } from "./stringify.js";
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { LineCounter } from "yaml";
|
|
2
|
+
export interface ParsedFrontmatter {
|
|
3
|
+
frontmatter: Record<string, unknown>;
|
|
4
|
+
body: string;
|
|
5
|
+
}
|
|
6
|
+
export interface ParsedFrontmatterDocument extends ParsedFrontmatter {
|
|
7
|
+
errors: readonly {
|
|
8
|
+
message: string;
|
|
9
|
+
pos?: [number, number];
|
|
10
|
+
}[];
|
|
11
|
+
lineCounter: LineCounter;
|
|
12
|
+
}
|
|
13
|
+
export interface ParseFrontmatterOptions {
|
|
14
|
+
uniqueKeys?: boolean;
|
|
15
|
+
}
|
|
16
|
+
export declare class FrontmatterParseError extends Error {
|
|
17
|
+
constructor(message: string);
|
|
18
|
+
}
|
|
19
|
+
export declare function parseFrontmatter(source: string, options?: ParseFrontmatterOptions): ParsedFrontmatter;
|
|
20
|
+
export declare function parseFrontmatterDocument(source: string, options?: ParseFrontmatterOptions): ParsedFrontmatterDocument;
|