@askrjs/cli 0.0.2 → 0.0.3
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 +9 -7
- package/dist/add.js +554 -1
- package/dist/add.js.map +1 -0
- package/dist/cli.js +43 -14
- package/dist/cli.js.map +1 -1
- package/dist/discovery-Difb7Y4G.js +325 -0
- package/dist/discovery-Difb7Y4G.js.map +1 -0
- package/dist/generate.js +396 -0
- package/dist/generate.js.map +1 -0
- package/dist/openapi.js +5 -4
- package/dist/openapi.js.map +1 -1
- package/dist/planner-VAj7qlxr.js +287 -0
- package/dist/planner-VAj7qlxr.js.map +1 -0
- package/dist/range-YUs9eimn.js +127 -0
- package/dist/range-YUs9eimn.js.map +1 -0
- package/dist/registry-CyrRHRG5.js +211 -0
- package/dist/registry-CyrRHRG5.js.map +1 -0
- package/dist/skills/askr-dashboard-charts/SKILL.md +61 -44
- package/dist/skills/askr-dashboard-charts/agents/openai.yaml +2 -2
- package/dist/specification-DXnDOC-0.js +51 -0
- package/dist/specification-DXnDOC-0.js.map +1 -0
- package/dist/templates/full-stack/AGENTS.md +0 -1
- package/dist/templates/full-stack/README.md +0 -1
- package/dist/templates/full-stack/index.html +0 -1
- package/dist/templates/full-stack/package.json +4 -2
- package/dist/templates/full-stack/server.ts +5 -9
- package/dist/templates/full-stack/src/action-authorizations.ts +2 -2
- package/dist/templates/full-stack/src/actions/create-message.ts +5 -5
- package/dist/templates/full-stack/src/i18n.ts +4 -4
- package/dist/templates/full-stack/src/main.tsx +4 -5
- package/dist/templates/full-stack/src/pages/home.tsx +5 -5
- package/dist/templates/full-stack/src/pages/layout.tsx +15 -5
- package/dist/templates/full-stack/src/pages/not-found.tsx +5 -2
- package/dist/templates/full-stack/src/routes.tsx +11 -12
- package/dist/templates/full-stack/src/schemas.ts +1 -2
- package/dist/templates/full-stack/src/server/action-registry.ts +6 -10
- package/dist/templates/full-stack/src/server/actions/create-message.ts +4 -4
- package/dist/templates/full-stack/src/server/app.ts +67 -57
- package/dist/templates/full-stack/src/server/dependencies.ts +10 -10
- package/dist/templates/full-stack/src/server/entry-server.ts +3 -3
- package/dist/templates/full-stack/src/telemetry.ts +1 -2
- package/dist/templates/full-stack/src/vite-server.d.ts +2 -3
- package/dist/templates/full-stack/tests/actions/create-message.test.ts +7 -8
- package/dist/templates/full-stack/tsconfig.json +0 -1
- package/dist/templates/full-stack/vite.config.ts +5 -6
- package/dist/templates/spa/AGENTS.md +1 -1
- package/dist/templates/spa/package.json +1 -1
- package/dist/templates/spa/src/adapters/operations-client.ts +7 -2
- package/dist/templates/spa/src/pages/app/admin-home.tsx +21 -3
- package/dist/templates/spa/src/styles.css +1 -1
- package/dist/templates/ssg/tests/ssg-config.test.ts +3 -3
- package/dist/templates/ssr/server.ts +4 -10
- package/dist/templates/ssr/src/entry-server.tsx +6 -4
- package/dist/update.js +284 -2
- package/dist/update.js.map +1 -0
- package/dist/writer-D8qe_7ud.js +228 -0
- package/dist/writer-D8qe_7ud.js.map +1 -0
- package/package.json +11 -15
- package/skills/askr-dashboard-charts/SKILL.md +61 -44
- package/skills/askr-dashboard-charts/agents/openai.yaml +2 -2
- package/templates/full-stack/AGENTS.md +0 -1
- package/templates/full-stack/README.md +0 -1
- package/templates/full-stack/index.html +0 -1
- package/templates/full-stack/package.json +4 -2
- package/templates/full-stack/server.ts +5 -9
- package/templates/full-stack/src/action-authorizations.ts +2 -2
- package/templates/full-stack/src/actions/create-message.ts +5 -5
- package/templates/full-stack/src/i18n.ts +4 -4
- package/templates/full-stack/src/main.tsx +4 -5
- package/templates/full-stack/src/pages/home.tsx +5 -5
- package/templates/full-stack/src/pages/layout.tsx +15 -5
- package/templates/full-stack/src/pages/not-found.tsx +5 -2
- package/templates/full-stack/src/routes.tsx +11 -12
- package/templates/full-stack/src/schemas.ts +1 -2
- package/templates/full-stack/src/server/action-registry.ts +6 -10
- package/templates/full-stack/src/server/actions/create-message.ts +4 -4
- package/templates/full-stack/src/server/app.ts +67 -57
- package/templates/full-stack/src/server/dependencies.ts +10 -10
- package/templates/full-stack/src/server/entry-server.ts +3 -3
- package/templates/full-stack/src/telemetry.ts +1 -2
- package/templates/full-stack/src/vite-server.d.ts +2 -3
- package/templates/full-stack/tests/actions/create-message.test.ts +7 -8
- package/templates/full-stack/tsconfig.json +0 -1
- package/templates/full-stack/vite.config.ts +5 -6
- package/templates/spa/AGENTS.md +1 -1
- package/templates/spa/package.json +1 -1
- package/templates/spa/src/adapters/operations-client.ts +7 -2
- package/templates/spa/src/pages/app/admin-home.tsx +21 -3
- package/templates/spa/src/styles.css +1 -1
- package/templates/ssg/tests/ssg-config.test.ts +3 -3
- package/templates/ssr/server.ts +4 -10
- package/templates/ssr/src/entry-server.tsx +6 -4
- package/dist/add-DqhqRzBQ.js +0 -555
- package/dist/add-DqhqRzBQ.js.map +0 -1
- package/dist/update-DyW1na5V.js +0 -1140
- package/dist/update-DyW1na5V.js.map +0 -1
package/dist/generate.js
ADDED
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
import { mkdir, mkdtemp, readFile, readdir, rename, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
4
|
+
import { load } from "js-yaml";
|
|
5
|
+
//#region src/generate/generator.ts
|
|
6
|
+
const OWNED = [
|
|
7
|
+
".askr-generated.json",
|
|
8
|
+
"schemas.ts",
|
|
9
|
+
"operations.ts",
|
|
10
|
+
"api.ts",
|
|
11
|
+
"index.ts"
|
|
12
|
+
];
|
|
13
|
+
const METHODS = [
|
|
14
|
+
"get",
|
|
15
|
+
"post",
|
|
16
|
+
"put",
|
|
17
|
+
"patch",
|
|
18
|
+
"delete",
|
|
19
|
+
"head",
|
|
20
|
+
"options"
|
|
21
|
+
];
|
|
22
|
+
const identifier = /^[A-Za-z_$][\w$]*$/;
|
|
23
|
+
const pointer = (...parts) => `#/${parts.map((p) => String(p).replace(/~/g, "~0").replace(/\//g, "~1")).join("/")}`;
|
|
24
|
+
var GenerationError = class extends Error {};
|
|
25
|
+
const fail = (message, at, operationId) => {
|
|
26
|
+
throw new GenerationError(`${operationId ? `${operationId}: ` : ""}${message} at ${at}`);
|
|
27
|
+
};
|
|
28
|
+
const pascal = (name) => {
|
|
29
|
+
const result = name.replace(/(^|[^A-Za-z0-9]+)([A-Za-z0-9])/g, (_, _s, c) => c.toUpperCase()).replace(/[^A-Za-z0-9_$]/g, "");
|
|
30
|
+
return /^\d/.test(result) ? `_${result}` : result;
|
|
31
|
+
};
|
|
32
|
+
function schemaType(schema, at, direction = "response") {
|
|
33
|
+
if (schema.$ref) {
|
|
34
|
+
const name = decodeURIComponent(String(schema.$ref).split("/").at(-1));
|
|
35
|
+
return pascal(name);
|
|
36
|
+
}
|
|
37
|
+
if (schema.const !== void 0) return JSON.stringify(schema.const);
|
|
38
|
+
if (schema.enum) return schema.enum.map((value) => JSON.stringify(value)).join(" | ") || "never";
|
|
39
|
+
if (schema.oneOf || schema.anyOf) return (schema.oneOf ?? schema.anyOf).map((item, i) => schemaType(item, `${at}/${schema.oneOf ? "oneOf" : "anyOf"}/${i}`, direction)).join(" | ");
|
|
40
|
+
if (schema.allOf) return schema.allOf.map((item, i) => schemaType(item, `${at}/allOf/${i}`, direction)).join(" & ");
|
|
41
|
+
let type = "never";
|
|
42
|
+
if (schema.type === "string") type = "string";
|
|
43
|
+
else if (schema.type === "number" || schema.type === "integer") type = "number";
|
|
44
|
+
else if (schema.type === "boolean") type = "boolean";
|
|
45
|
+
else if (schema.type === "null") type = "null";
|
|
46
|
+
else if (schema.type === "array" || schema.items) type = `Array<${schemaType(schema.items ?? {}, `${at}/items`, direction)}>`;
|
|
47
|
+
else if (schema.type === "object" || schema.properties || schema.additionalProperties !== void 0) {
|
|
48
|
+
const required = new Set(schema.required ?? []);
|
|
49
|
+
const properties = Object.entries(schema.properties ?? {}).filter(([, value]) => !(direction === "request" ? value.readOnly : value.writeOnly)).map(([key, value]) => ` ${JSON.stringify(key)}${required.has(key) ? "" : "?"}: ${schemaType(value, `${at}/properties/${key}`, direction)};`);
|
|
50
|
+
if (schema.additionalProperties && typeof schema.additionalProperties === "object") properties.push(` [key: string]: ${schemaType(schema.additionalProperties, `${at}/additionalProperties`, direction)};`);
|
|
51
|
+
type = `{\n${properties.join("\n")}\n}`;
|
|
52
|
+
} else if (!schema.type) fail("Unsupported schema without a type", at);
|
|
53
|
+
else fail(`Unsupported schema type ${schema.type}`, at);
|
|
54
|
+
return schema.nullable || Array.isArray(schema.type) && schema.type.includes("null") ? `${type} | null` : type;
|
|
55
|
+
}
|
|
56
|
+
function responseSchema(operation, at, success) {
|
|
57
|
+
return Object.entries(operation.responses ?? {}).filter(([status]) => status === "default" ? !success : success === /^2\d\d$/.test(status)).sort(([a], [b]) => a.localeCompare(b, void 0, { numeric: true }));
|
|
58
|
+
}
|
|
59
|
+
function pickContent(content, at, direction) {
|
|
60
|
+
const entries = Object.entries(content ?? {}).sort(([a], [b]) => a.localeCompare(b));
|
|
61
|
+
if (!entries.length) return {
|
|
62
|
+
type: "undefined",
|
|
63
|
+
codec: "empty()",
|
|
64
|
+
media: []
|
|
65
|
+
};
|
|
66
|
+
const types = entries.map(([mediaType, value]) => ({
|
|
67
|
+
mediaType,
|
|
68
|
+
type: schemaType(value.schema ?? {}, `${at}/content/${mediaType.replace(/~/g, "~0").replace(/\//g, "~1")}/schema`, direction)
|
|
69
|
+
}));
|
|
70
|
+
const union = [...new Set(types.map((v) => v.type))].join(" | ");
|
|
71
|
+
return {
|
|
72
|
+
type: union,
|
|
73
|
+
codec: types.length === 1 ? codecFor(types[0].mediaType, union) : `content({ ${types.map((v) => `${JSON.stringify(v.mediaType)}: ${codecFor(v.mediaType, v.type)}`).join(", ")} })`,
|
|
74
|
+
media: types.map((v) => v.mediaType)
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function codecFor(mediaType, type) {
|
|
78
|
+
if (mediaType === "application/json" || mediaType.endsWith("+json")) return `json<${type}>()`;
|
|
79
|
+
if (mediaType.startsWith("text/")) return "text()";
|
|
80
|
+
if (mediaType === "application/x-www-form-urlencoded") return "urlEncoded()";
|
|
81
|
+
if (mediaType === "multipart/form-data") return "multipart()";
|
|
82
|
+
if (mediaType === "application/octet-stream") return "arrayBuffer()";
|
|
83
|
+
fail(`Unsupported media type ${mediaType}`, "#/content");
|
|
84
|
+
}
|
|
85
|
+
const pointerPart = (value) => decodeURIComponent(value).replace(/~1/g, "/").replace(/~0/g, "~");
|
|
86
|
+
const pointerValue = (document, fragment) => {
|
|
87
|
+
let value = document;
|
|
88
|
+
for (const part of fragment.replace(/^#?\/?/, "").split("/").filter(Boolean)) {
|
|
89
|
+
if (!value || typeof value !== "object") throw new GenerationError(`Reference target is missing at #/${part}`);
|
|
90
|
+
value = value[pointerPart(part)];
|
|
91
|
+
}
|
|
92
|
+
if (value === void 0) throw new GenerationError(`Reference target is missing at ${fragment || "#"}`);
|
|
93
|
+
return value;
|
|
94
|
+
};
|
|
95
|
+
function parseOpenApiDocument(contents, source) {
|
|
96
|
+
let parsed;
|
|
97
|
+
try {
|
|
98
|
+
parsed = JSON.parse(contents);
|
|
99
|
+
} catch {
|
|
100
|
+
try {
|
|
101
|
+
parsed = load(contents);
|
|
102
|
+
} catch (error) {
|
|
103
|
+
throw new GenerationError(`Unable to parse OpenAPI document ${source}: ${error instanceof Error ? error.message : String(error)}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new GenerationError(`OpenAPI document must be an object: ${source}`);
|
|
107
|
+
return parsed;
|
|
108
|
+
}
|
|
109
|
+
async function readSource(uri) {
|
|
110
|
+
if (uri.startsWith("http://") || uri.startsWith("https://")) {
|
|
111
|
+
const response = await fetch(uri);
|
|
112
|
+
if (!response.ok) throw new GenerationError(`Unable to fetch OpenAPI reference ${uri}: ${response.status} ${response.statusText}`);
|
|
113
|
+
return {
|
|
114
|
+
uri,
|
|
115
|
+
document: parseOpenApiDocument(await response.text(), uri)
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
const path = fileURLToPath(uri);
|
|
119
|
+
return {
|
|
120
|
+
uri,
|
|
121
|
+
document: parseOpenApiDocument(await readFile(path, "utf8"), path)
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
var OpenApiBundler = class {
|
|
125
|
+
root;
|
|
126
|
+
rootUri;
|
|
127
|
+
sources = /* @__PURE__ */ new Map();
|
|
128
|
+
aliases = /* @__PURE__ */ new Map();
|
|
129
|
+
names = /* @__PURE__ */ new Map();
|
|
130
|
+
constructor(root, rootUri) {
|
|
131
|
+
this.root = root;
|
|
132
|
+
this.rootUri = rootUri;
|
|
133
|
+
}
|
|
134
|
+
async bundle() {
|
|
135
|
+
if (!this.root.components) this.root.components = {};
|
|
136
|
+
if (!this.root.components.schemas) this.root.components.schemas = {};
|
|
137
|
+
await this.expandObject(this.root, this.rootUri, this.root, /* @__PURE__ */ new Set());
|
|
138
|
+
return this.root;
|
|
139
|
+
}
|
|
140
|
+
async source(uri) {
|
|
141
|
+
let pending = this.sources.get(uri);
|
|
142
|
+
if (!pending) {
|
|
143
|
+
pending = readSource(uri);
|
|
144
|
+
this.sources.set(uri, pending);
|
|
145
|
+
}
|
|
146
|
+
return pending;
|
|
147
|
+
}
|
|
148
|
+
componentName(uri, fragment) {
|
|
149
|
+
const canonical = `${uri}#${fragment}`;
|
|
150
|
+
const existing = this.aliases.get(canonical);
|
|
151
|
+
if (existing) return existing;
|
|
152
|
+
const raw = fragment.split("/").filter(Boolean).at(-1) ?? basename(new URL(uri).pathname).replace(/\.[^.]+$/, "");
|
|
153
|
+
const base = pascal(pointerPart(raw)) || "ExternalSchema";
|
|
154
|
+
let name = base;
|
|
155
|
+
let suffix = 2;
|
|
156
|
+
while (this.names.has(name) && this.names.get(name) !== canonical) name = `${base}${suffix++}`;
|
|
157
|
+
this.aliases.set(canonical, name);
|
|
158
|
+
this.names.set(name, canonical);
|
|
159
|
+
return name;
|
|
160
|
+
}
|
|
161
|
+
async expandObject(value, uri, document, stack) {
|
|
162
|
+
for (const [key, child] of Object.entries(value)) value[key] = await this.expand(child, uri, document, stack);
|
|
163
|
+
}
|
|
164
|
+
async expand(value, uri, document, stack) {
|
|
165
|
+
if (Array.isArray(value)) return Promise.all(value.map((item) => this.expand(item, uri, document, stack)));
|
|
166
|
+
if (!value || typeof value !== "object") return value;
|
|
167
|
+
const object = value;
|
|
168
|
+
if (typeof object.$ref === "string") {
|
|
169
|
+
const resolved = await this.resolveRef(object.$ref, uri, document, stack);
|
|
170
|
+
const siblingEntries = Object.entries(object).filter(([key]) => key !== "$ref");
|
|
171
|
+
if (!siblingEntries.length) return resolved;
|
|
172
|
+
const result = resolved && typeof resolved === "object" && !Array.isArray(resolved) ? { ...resolved } : {};
|
|
173
|
+
for (const [key, child] of siblingEntries) result[key] = await this.expand(child, uri, document, stack);
|
|
174
|
+
return result;
|
|
175
|
+
}
|
|
176
|
+
const result = {};
|
|
177
|
+
for (const [key, child] of Object.entries(object)) result[key] = await this.expand(child, uri, document, stack);
|
|
178
|
+
return result;
|
|
179
|
+
}
|
|
180
|
+
async resolveRef(ref, baseUri, document, stack) {
|
|
181
|
+
const hash = ref.indexOf("#");
|
|
182
|
+
const targetUri = hash === -1 ? new URL(ref, baseUri).href : new URL(ref.slice(0, hash) || baseUri, baseUri).href;
|
|
183
|
+
const fragment = hash === -1 ? "" : ref.slice(hash + 1);
|
|
184
|
+
const canonical = `${targetUri}#${fragment}`;
|
|
185
|
+
const targetSource = targetUri === this.rootUri ? {
|
|
186
|
+
uri: targetUri,
|
|
187
|
+
document
|
|
188
|
+
} : await this.source(targetUri);
|
|
189
|
+
const target = pointerValue(targetSource.document, fragment);
|
|
190
|
+
if (targetUri === this.rootUri && fragment.startsWith("/components/schemas/")) return { $ref: `#${fragment}` };
|
|
191
|
+
if (fragment.startsWith("/components/schemas/")) {
|
|
192
|
+
const name = this.componentName(targetUri, fragment);
|
|
193
|
+
if (stack.has(canonical)) return { $ref: `#/components/schemas/${name}` };
|
|
194
|
+
if (!(name in this.root.components.schemas)) {
|
|
195
|
+
this.root.components.schemas[name] = {};
|
|
196
|
+
const next = new Set(stack).add(canonical);
|
|
197
|
+
this.root.components.schemas[name] = await this.expand(target, targetSource.uri, targetSource.document, next);
|
|
198
|
+
}
|
|
199
|
+
return { $ref: `#/components/schemas/${name}` };
|
|
200
|
+
}
|
|
201
|
+
if (stack.has(canonical)) throw new GenerationError(`Circular non-schema reference at ${ref}`);
|
|
202
|
+
return this.expand(target, targetSource.uri, targetSource.document, new Set(stack).add(canonical));
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
async function loadOpenApi(input) {
|
|
206
|
+
const source = await readSource(/^https?:\/\//.test(input) ? input : pathToFileURL(resolve(input)).href);
|
|
207
|
+
return new OpenApiBundler(source.document, source.uri).bundle();
|
|
208
|
+
}
|
|
209
|
+
function generateFiles(document) {
|
|
210
|
+
const version = String(document.openapi ?? "");
|
|
211
|
+
if (!/^3\.(0|1)\.\d+$/.test(version)) fail(`Unsupported OpenAPI version ${version || "missing"}`, "#/openapi");
|
|
212
|
+
if (document.webhooks) fail("Webhooks are unsupported", "#/webhooks");
|
|
213
|
+
const schemas = document.components?.schemas ?? {};
|
|
214
|
+
const names = /* @__PURE__ */ new Map();
|
|
215
|
+
for (const name of Object.keys(schemas).sort()) {
|
|
216
|
+
const safe = pascal(name);
|
|
217
|
+
if (!safe || names.has(safe)) fail(`Component name collision for ${name}`, pointer("components", "schemas", name));
|
|
218
|
+
names.set(safe, name);
|
|
219
|
+
}
|
|
220
|
+
const schemaLines = [...names].map(([safe, original]) => `export type ${safe} = ${schemaType(schemas[original], pointer("components", "schemas", original))};`);
|
|
221
|
+
const operationTypes = [];
|
|
222
|
+
const descriptors = [];
|
|
223
|
+
const seen = /* @__PURE__ */ new Set();
|
|
224
|
+
for (const path of Object.keys(document.paths ?? {}).sort()) for (const method of METHODS) {
|
|
225
|
+
const operation = document.paths[path]?.[method];
|
|
226
|
+
if (!operation) continue;
|
|
227
|
+
const at = pointer("paths", path, method);
|
|
228
|
+
const id = operation.operationId;
|
|
229
|
+
if (!id || !identifier.test(id)) fail("A unique JavaScript-identifier operationId is required", `${at}/operationId`, id);
|
|
230
|
+
if (seen.has(id)) fail("Duplicate operationId", `${at}/operationId`, id);
|
|
231
|
+
seen.add(id);
|
|
232
|
+
if (operation.callbacks) fail("Callbacks are unsupported", `${at}/callbacks`, id);
|
|
233
|
+
const parameters = [...document.paths[path].parameters ?? [], ...operation.parameters ?? []];
|
|
234
|
+
const byLocation = {
|
|
235
|
+
path: [],
|
|
236
|
+
query: [],
|
|
237
|
+
header: []
|
|
238
|
+
};
|
|
239
|
+
for (const parameter of parameters) {
|
|
240
|
+
if (parameter.$ref) fail("Parameter references must be bundled", `${at}/parameters`, id);
|
|
241
|
+
if (parameter.in === "cookie") fail("Cookie parameters are unsupported", `${at}/parameters`, id);
|
|
242
|
+
if (!byLocation[parameter.in]) fail(`Unsupported parameter location ${parameter.in}`, `${at}/parameters`, id);
|
|
243
|
+
byLocation[parameter.in].push(parameter);
|
|
244
|
+
}
|
|
245
|
+
const prefix = pascal(id);
|
|
246
|
+
const renderParameters = (location) => {
|
|
247
|
+
const list = byLocation[location].sort((a, b) => a.name.localeCompare(b.name));
|
|
248
|
+
if (!list.length) return void 0;
|
|
249
|
+
const name = `${prefix}${pascal(location)}`;
|
|
250
|
+
operationTypes.push(`export type ${name} = {\n${list.map((p) => ` ${JSON.stringify(p.name)}${p.required || location === "path" ? "" : "?"}: ${schemaType(p.schema ?? {}, `${at}/parameters`, "request")};`).join("\n")}\n};`);
|
|
251
|
+
const defaultStyle = location === "query" ? "form" : "simple";
|
|
252
|
+
return {
|
|
253
|
+
name,
|
|
254
|
+
spec: `{ ${list.map((p) => {
|
|
255
|
+
const style = p.style ?? defaultStyle;
|
|
256
|
+
const explode = p.explode ?? style === "form";
|
|
257
|
+
return `${JSON.stringify(p.name)}: { style: ${JSON.stringify(style)}, explode: ${explode} }`;
|
|
258
|
+
}).join(", ")} }`
|
|
259
|
+
};
|
|
260
|
+
};
|
|
261
|
+
const params = renderParameters("path"), query = renderParameters("query"), headers = renderParameters("header");
|
|
262
|
+
const body = operation.requestBody ? pickContent(operation.requestBody.content, `${at}/requestBody`, "request") : void 0;
|
|
263
|
+
if (body) operationTypes.push(`export type ${prefix}Body = ${body.type};`);
|
|
264
|
+
const successes = responseSchema(operation, at, true);
|
|
265
|
+
if (!successes.length) fail("At least one 2xx response is required", `${at}/responses`, id);
|
|
266
|
+
const errors = responseSchema(operation, at, false);
|
|
267
|
+
const chain = [`${method === "delete" ? "del" : method}(${JSON.stringify(path)})`];
|
|
268
|
+
if (params) chain.push(`params<${params.name}>(${params.spec})`);
|
|
269
|
+
if (query) chain.push(`query<${query.name}>(${query.spec})`);
|
|
270
|
+
if (headers) chain.push(`headers<${headers.name}>(${headers.spec})`);
|
|
271
|
+
if (body) chain.push(`body(${body.codec})`);
|
|
272
|
+
for (const [status, response] of successes) {
|
|
273
|
+
if (response.links) fail("Response links are unsupported", `${at}/responses/${status}/links`, id);
|
|
274
|
+
const value = pickContent(response.content, `${at}/responses/${status}`, "response");
|
|
275
|
+
const typeName = `${prefix}Response${status}`;
|
|
276
|
+
operationTypes.push(`export type ${typeName} = ${value.type};`);
|
|
277
|
+
chain.push(`returns(${status === "200" ? "" : `${status}, `}${value.codec})`);
|
|
278
|
+
}
|
|
279
|
+
if (errors.length) {
|
|
280
|
+
const rendered = errors.map(([status, response]) => {
|
|
281
|
+
if (response.links) fail("Response links are unsupported", `${at}/responses/${status}/links`, id);
|
|
282
|
+
const value = pickContent(response.content, `${at}/responses/${status}`, "response");
|
|
283
|
+
const typeName = `${prefix}Error${pascal(status)}`;
|
|
284
|
+
operationTypes.push(`export type ${typeName} = ${value.type};`);
|
|
285
|
+
return `${JSON.stringify(status)}: ${value.codec}`;
|
|
286
|
+
});
|
|
287
|
+
chain.push(`errors({ ${rendered.join(", ")} })`);
|
|
288
|
+
}
|
|
289
|
+
if (operation.security ?? document.security) chain.push(`security(${JSON.stringify(operation.security ?? document.security)})`);
|
|
290
|
+
descriptors.push(` ${id}: ${chain.join("\n .")},`);
|
|
291
|
+
}
|
|
292
|
+
const imports = [...new Set(descriptors.join(" ").match(/\b(get|post|put|patch|del|head|options|json|text|urlEncoded|multipart|arrayBuffer|empty|content)\b/g) ?? [])].sort();
|
|
293
|
+
const files = {
|
|
294
|
+
"schemas.ts": `${schemaLines.join("\n\n")}\n`,
|
|
295
|
+
"operations.ts": `${names.size ? `import type { ${[...names.keys()].join(", ")} } from "./schemas";\n\n` : ""}${operationTypes.join("\n\n")}\n`,
|
|
296
|
+
"api.ts": `import { ${[
|
|
297
|
+
"defineApi",
|
|
298
|
+
"createClient",
|
|
299
|
+
...imports
|
|
300
|
+
].join(", ")} } from "@askrjs/fetch";\nimport type { ClientOptions } from "@askrjs/fetch";\nimport type { ${operationTypes.map((line) => line.match(/^export type (\w+)/)?.[1]).filter(Boolean).join(", ")} } from "./operations";\n\nexport const api = defineApi({\n${descriptors.join("\n")}\n}, ${JSON.stringify({
|
|
301
|
+
servers: (document.servers ?? []).map((s) => s.url),
|
|
302
|
+
securitySchemes: document.components?.securitySchemes ?? {}
|
|
303
|
+
}, null, 2)});\n\nexport const createApiClient = (options?: ClientOptions) => createClient(api, options);\n`,
|
|
304
|
+
"index.ts": `export * from "./schemas";\nexport * from "./operations";\nexport { api, createApiClient } from "./api";\n`,
|
|
305
|
+
".askr-generated.json": ""
|
|
306
|
+
};
|
|
307
|
+
files[".askr-generated.json"] = `${JSON.stringify({
|
|
308
|
+
version: 1,
|
|
309
|
+
files: [...OWNED]
|
|
310
|
+
}, null, 2)}\n`;
|
|
311
|
+
return files;
|
|
312
|
+
}
|
|
313
|
+
async function existingFiles(directory) {
|
|
314
|
+
try {
|
|
315
|
+
return (await readdir(directory)).sort();
|
|
316
|
+
} catch {
|
|
317
|
+
return [];
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
async function writeGenerated(directory, files, check) {
|
|
321
|
+
const output = resolve(directory);
|
|
322
|
+
const entries = await existingFiles(output);
|
|
323
|
+
if (check) {
|
|
324
|
+
if (!entries.length) throw new GenerationError(`Generated directory is missing: ${output}`);
|
|
325
|
+
const expected = Object.keys(files).sort();
|
|
326
|
+
if (JSON.stringify(entries) !== JSON.stringify(expected)) throw new GenerationError(`Generated file ownership differs in ${output}`);
|
|
327
|
+
for (const name of expected) if (await readFile(join(output, name), "utf8") !== files[name]) throw new GenerationError(`Generated file is stale: ${name}`);
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
if (entries.length && !entries.includes(".askr-generated.json")) throw new GenerationError(`Refusing to overwrite non-generated directory: ${output}`);
|
|
331
|
+
const stage = await mkdtemp(join(dirname(output), `.${basename(output)}-stage-`));
|
|
332
|
+
for (const [name, content] of Object.entries(files)) await writeFile(join(stage, name), content);
|
|
333
|
+
const backup = `${output}.backup-${process.pid}`;
|
|
334
|
+
let moved = false;
|
|
335
|
+
try {
|
|
336
|
+
if (entries.length) {
|
|
337
|
+
await rename(output, backup);
|
|
338
|
+
moved = true;
|
|
339
|
+
}
|
|
340
|
+
await rename(stage, output);
|
|
341
|
+
if (moved) await rm(backup, {
|
|
342
|
+
recursive: true,
|
|
343
|
+
force: true
|
|
344
|
+
});
|
|
345
|
+
} catch (error) {
|
|
346
|
+
await rm(output, {
|
|
347
|
+
recursive: true,
|
|
348
|
+
force: true
|
|
349
|
+
});
|
|
350
|
+
if (moved) await rename(backup, output);
|
|
351
|
+
throw error;
|
|
352
|
+
} finally {
|
|
353
|
+
await rm(stage, {
|
|
354
|
+
recursive: true,
|
|
355
|
+
force: true
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
async function generate(input, output, check = false) {
|
|
360
|
+
const files = generateFiles(await loadOpenApi(input));
|
|
361
|
+
await mkdir(dirname(resolve(output)), { recursive: true });
|
|
362
|
+
await writeGenerated(output, files, check);
|
|
363
|
+
}
|
|
364
|
+
//#endregion
|
|
365
|
+
//#region src/bin/generate.ts
|
|
366
|
+
async function runGenerateCli(args, io = console) {
|
|
367
|
+
let output;
|
|
368
|
+
let check = false;
|
|
369
|
+
const inputs = [];
|
|
370
|
+
for (let i = 0; i < args.length; i++) {
|
|
371
|
+
const arg = args[i];
|
|
372
|
+
if (arg === "--check") check = true;
|
|
373
|
+
else if (arg === "-o" || arg === "--output") output = args[++i];
|
|
374
|
+
else if (arg.startsWith("--output=")) output = arg.slice(9);
|
|
375
|
+
else if (arg.startsWith("-")) {
|
|
376
|
+
io.error(`Unknown option: ${arg}`);
|
|
377
|
+
return 1;
|
|
378
|
+
} else inputs.push(arg);
|
|
379
|
+
}
|
|
380
|
+
if (inputs.length !== 1 || !output) {
|
|
381
|
+
io.error("Usage: askr generate <input> -o <output-directory> [--check]");
|
|
382
|
+
return 1;
|
|
383
|
+
}
|
|
384
|
+
try {
|
|
385
|
+
await generate(inputs[0], output, check);
|
|
386
|
+
io.log(check ? "Generated client is current." : `Generated client in ${output}.`);
|
|
387
|
+
return 0;
|
|
388
|
+
} catch (error) {
|
|
389
|
+
io.error(error instanceof Error ? error.message : String(error));
|
|
390
|
+
return 1;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
//#endregion
|
|
394
|
+
export { runGenerateCli };
|
|
395
|
+
|
|
396
|
+
//# sourceMappingURL=generate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate.js","names":[],"sources":["../src/generate/generator.ts","../src/bin/generate.ts"],"sourcesContent":["import { mkdir, mkdtemp, readFile, readdir, rename, rm, writeFile } from \"node:fs/promises\";\nimport { basename, dirname, join, resolve } from \"node:path\";\nimport { fileURLToPath, pathToFileURL } from \"node:url\";\nimport { load } from \"js-yaml\";\n\ntype Json = Record<string, any>;\nconst OWNED = [\n \".askr-generated.json\",\n \"schemas.ts\",\n \"operations.ts\",\n \"api.ts\",\n \"index.ts\",\n] as const;\nconst METHODS = [\"get\", \"post\", \"put\", \"patch\", \"delete\", \"head\", \"options\"] as const;\nconst identifier = /^[A-Za-z_$][\\w$]*$/;\nconst pointer = (...parts: (string | number)[]) =>\n `#/${parts.map((p) => String(p).replace(/~/g, \"~0\").replace(/\\//g, \"~1\")).join(\"/\")}`;\nexport class GenerationError extends Error {}\nconst fail = (message: string, at: string, operationId?: string): never => {\n throw new GenerationError(`${operationId ? `${operationId}: ` : \"\"}${message} at ${at}`);\n};\nconst pascal = (name: string) => {\n const result = name\n .replace(/(^|[^A-Za-z0-9]+)([A-Za-z0-9])/g, (_, _s, c) => c.toUpperCase())\n .replace(/[^A-Za-z0-9_$]/g, \"\");\n return /^\\d/.test(result) ? `_${result}` : result;\n};\n\nfunction schemaType(\n schema: Json,\n at: string,\n direction: \"request\" | \"response\" = \"response\",\n): string {\n if (schema.$ref) {\n const name = decodeURIComponent(String(schema.$ref).split(\"/\").at(-1)!);\n return pascal(name);\n }\n if (schema.const !== undefined) return JSON.stringify(schema.const);\n if (schema.enum)\n return schema.enum.map((value: unknown) => JSON.stringify(value)).join(\" | \") || \"never\";\n if (schema.oneOf || schema.anyOf)\n return (schema.oneOf ?? schema.anyOf)\n .map((item: Json, i: number) =>\n schemaType(item, `${at}/${schema.oneOf ? \"oneOf\" : \"anyOf\"}/${i}`, direction),\n )\n .join(\" | \");\n if (schema.allOf)\n return schema.allOf\n .map((item: Json, i: number) => schemaType(item, `${at}/allOf/${i}`, direction))\n .join(\" & \");\n let type = \"never\";\n if (schema.type === \"string\") type = \"string\";\n else if (schema.type === \"number\" || schema.type === \"integer\") type = \"number\";\n else if (schema.type === \"boolean\") type = \"boolean\";\n else if (schema.type === \"null\") type = \"null\";\n else if (schema.type === \"array\" || schema.items)\n type = `Array<${schemaType(schema.items ?? {}, `${at}/items`, direction)}>`;\n else if (\n schema.type === \"object\" ||\n schema.properties ||\n schema.additionalProperties !== undefined\n ) {\n const required = new Set(schema.required ?? []);\n const properties = Object.entries(schema.properties ?? {})\n .filter(([, value]: any) => !(direction === \"request\" ? value.readOnly : value.writeOnly))\n .map(\n ([key, value]: [string, any]) =>\n ` ${JSON.stringify(key)}${required.has(key) ? \"\" : \"?\"}: ${schemaType(value, `${at}/properties/${key}`, direction)};`,\n );\n if (schema.additionalProperties && typeof schema.additionalProperties === \"object\")\n properties.push(\n ` [key: string]: ${schemaType(schema.additionalProperties, `${at}/additionalProperties`, direction)};`,\n );\n type = `{\\n${properties.join(\"\\n\")}\\n}`;\n } else if (!schema.type) fail(\"Unsupported schema without a type\", at);\n else fail(`Unsupported schema type ${schema.type}`, at);\n return schema.nullable || (Array.isArray(schema.type) && schema.type.includes(\"null\"))\n ? `${type} | null`\n : type;\n}\nfunction responseSchema(operation: Json, at: string, success: boolean) {\n const entries = Object.entries<Json>(operation.responses ?? {})\n .filter(([status]) => (status === \"default\" ? !success : success === /^2\\d\\d$/.test(status)))\n .sort(([a], [b]) => a.localeCompare(b, undefined, { numeric: true }));\n return entries;\n}\nfunction pickContent(content: Json | undefined, at: string, direction: \"request\" | \"response\") {\n const entries = Object.entries<Json>(content ?? {}).sort(([a], [b]) => a.localeCompare(b));\n if (!entries.length) return { type: \"undefined\", codec: \"empty()\", media: [] as string[] };\n const types = entries.map(([mediaType, value]) => ({\n mediaType,\n type: schemaType(\n value.schema ?? {},\n `${at}/content/${mediaType.replace(/~/g, \"~0\").replace(/\\//g, \"~1\")}/schema`,\n direction,\n ),\n }));\n const union = [...new Set(types.map((v) => v.type))].join(\" | \");\n const codec =\n types.length === 1\n ? codecFor(types[0]!.mediaType, union)\n : `content({ ${types.map((v) => `${JSON.stringify(v.mediaType)}: ${codecFor(v.mediaType, v.type)}`).join(\", \")} })`;\n return { type: union, codec, media: types.map((v) => v.mediaType) };\n}\nfunction codecFor(mediaType: string, type: string) {\n if (mediaType === \"application/json\" || mediaType.endsWith(\"+json\")) return `json<${type}>()`;\n if (mediaType.startsWith(\"text/\")) return \"text()\";\n if (mediaType === \"application/x-www-form-urlencoded\") return \"urlEncoded()\";\n if (mediaType === \"multipart/form-data\") return \"multipart()\";\n if (mediaType === \"application/octet-stream\") return \"arrayBuffer()\";\n fail(`Unsupported media type ${mediaType}`, \"#/content\");\n}\n\ntype SourceDocument = { uri: string; document: Json };\n\nconst pointerPart = (value: string): string =>\n decodeURIComponent(value).replace(/~1/g, \"/\").replace(/~0/g, \"~\");\nconst pointerValue = (document: Json, fragment: string): unknown => {\n let value: unknown = document;\n for (const part of fragment\n .replace(/^#?\\/?/, \"\")\n .split(\"/\")\n .filter(Boolean)) {\n if (!value || typeof value !== \"object\")\n throw new GenerationError(`Reference target is missing at #/${part}`);\n value = (value as Record<string, unknown>)[pointerPart(part)];\n }\n if (value === undefined)\n throw new GenerationError(`Reference target is missing at ${fragment || \"#\"}`);\n return value;\n};\n\nfunction parseOpenApiDocument(contents: string, source: string): Json {\n let parsed: unknown;\n try {\n parsed = JSON.parse(contents);\n } catch {\n try {\n parsed = load(contents);\n } catch (error) {\n throw new GenerationError(\n `Unable to parse OpenAPI document ${source}: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n if (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed))\n throw new GenerationError(`OpenAPI document must be an object: ${source}`);\n return parsed as Json;\n}\n\nasync function readSource(uri: string): Promise<SourceDocument> {\n if (uri.startsWith(\"http://\") || uri.startsWith(\"https://\")) {\n const response = await fetch(uri);\n if (!response.ok)\n throw new GenerationError(\n `Unable to fetch OpenAPI reference ${uri}: ${response.status} ${response.statusText}`,\n );\n return { uri, document: parseOpenApiDocument(await response.text(), uri) };\n }\n const path = fileURLToPath(uri);\n return { uri, document: parseOpenApiDocument(await readFile(path, \"utf8\"), path) };\n}\n\nclass OpenApiBundler {\n private readonly sources = new Map<string, Promise<SourceDocument>>();\n private readonly aliases = new Map<string, string>();\n private readonly names = new Map<string, string>();\n\n constructor(\n private readonly root: Json,\n private readonly rootUri: string,\n ) {}\n\n async bundle(): Promise<Json> {\n if (!this.root.components) this.root.components = {};\n if (!this.root.components.schemas) this.root.components.schemas = {};\n await this.expandObject(this.root, this.rootUri, this.root, new Set());\n return this.root;\n }\n\n private async source(uri: string): Promise<SourceDocument> {\n let pending = this.sources.get(uri);\n if (!pending) {\n pending = readSource(uri);\n this.sources.set(uri, pending);\n }\n return pending;\n }\n\n private componentName(uri: string, fragment: string): string {\n const canonical = `${uri}#${fragment}`;\n const existing = this.aliases.get(canonical);\n if (existing) return existing;\n const raw =\n fragment.split(\"/\").filter(Boolean).at(-1) ??\n basename(new URL(uri).pathname).replace(/\\.[^.]+$/, \"\");\n const base = pascal(pointerPart(raw)) || \"ExternalSchema\";\n let name = base;\n let suffix = 2;\n while (this.names.has(name) && this.names.get(name) !== canonical) name = `${base}${suffix++}`;\n this.aliases.set(canonical, name);\n this.names.set(name, canonical);\n return name;\n }\n\n private async expandObject(\n value: Json,\n uri: string,\n document: Json,\n stack: Set<string>,\n ): Promise<void> {\n for (const [key, child] of Object.entries(value))\n value[key] = await this.expand(child, uri, document, stack);\n }\n\n private async expand(\n value: unknown,\n uri: string,\n document: Json,\n stack: Set<string>,\n ): Promise<unknown> {\n if (Array.isArray(value))\n return Promise.all(value.map((item) => this.expand(item, uri, document, stack)));\n if (!value || typeof value !== \"object\") return value;\n const object = value as Json;\n if (typeof object.$ref === \"string\") {\n const resolved = await this.resolveRef(object.$ref, uri, document, stack);\n const siblingEntries = Object.entries(object).filter(([key]) => key !== \"$ref\");\n if (!siblingEntries.length) return resolved;\n const result =\n resolved && typeof resolved === \"object\" && !Array.isArray(resolved)\n ? { ...(resolved as Json) }\n : {};\n for (const [key, child] of siblingEntries)\n result[key] = await this.expand(child, uri, document, stack);\n return result;\n }\n const result: Json = {};\n for (const [key, child] of Object.entries(object))\n result[key] = await this.expand(child, uri, document, stack);\n return result;\n }\n\n private async resolveRef(\n ref: string,\n baseUri: string,\n document: Json,\n stack: Set<string>,\n ): Promise<unknown> {\n const hash = ref.indexOf(\"#\");\n const targetUri =\n hash === -1\n ? new URL(ref, baseUri).href\n : new URL(ref.slice(0, hash) || baseUri, baseUri).href;\n const fragment = hash === -1 ? \"\" : ref.slice(hash + 1);\n const canonical = `${targetUri}#${fragment}`;\n const targetSource =\n targetUri === this.rootUri ? { uri: targetUri, document } : await this.source(targetUri);\n const target = pointerValue(targetSource.document, fragment);\n const isRootSchema = targetUri === this.rootUri && fragment.startsWith(\"/components/schemas/\");\n if (isRootSchema) return { $ref: `#${fragment}` };\n if (fragment.startsWith(\"/components/schemas/\")) {\n const name = this.componentName(targetUri, fragment);\n if (stack.has(canonical)) return { $ref: `#/components/schemas/${name}` };\n if (!(name in this.root.components.schemas)) {\n this.root.components.schemas[name] = {};\n const next = new Set(stack).add(canonical);\n this.root.components.schemas[name] = (await this.expand(\n target,\n targetSource.uri,\n targetSource.document,\n next,\n )) as Json;\n }\n return { $ref: `#/components/schemas/${name}` };\n }\n if (stack.has(canonical)) throw new GenerationError(`Circular non-schema reference at ${ref}`);\n return this.expand(\n target,\n targetSource.uri,\n targetSource.document,\n new Set(stack).add(canonical),\n );\n }\n}\n\nexport async function loadOpenApi(input: string): Promise<Json> {\n const uri = /^https?:\\/\\//.test(input) ? input : pathToFileURL(resolve(input)).href;\n const source = await readSource(uri);\n return new OpenApiBundler(source.document, source.uri).bundle();\n}\nexport function generateFiles(document: Json): Record<(typeof OWNED)[number], string> {\n const version = String(document.openapi ?? \"\");\n if (!/^3\\.(0|1)\\.\\d+$/.test(version))\n fail(`Unsupported OpenAPI version ${version || \"missing\"}`, \"#/openapi\");\n if (document.webhooks) fail(\"Webhooks are unsupported\", \"#/webhooks\");\n const schemas = document.components?.schemas ?? {};\n const names = new Map<string, string>();\n for (const name of Object.keys(schemas).sort()) {\n const safe = pascal(name);\n if (!safe || names.has(safe))\n fail(`Component name collision for ${name}`, pointer(\"components\", \"schemas\", name));\n names.set(safe, name);\n }\n const schemaLines = [...names].map(\n ([safe, original]) =>\n `export type ${safe} = ${schemaType(schemas[original], pointer(\"components\", \"schemas\", original))};`,\n );\n const operationTypes: string[] = [];\n const descriptors: string[] = [];\n const seen = new Set<string>();\n for (const path of Object.keys(document.paths ?? {}).sort())\n for (const method of METHODS) {\n const operation = document.paths[path]?.[method];\n if (!operation) continue;\n const at = pointer(\"paths\", path, method);\n const id = operation.operationId;\n if (!id || !identifier.test(id))\n fail(\"A unique JavaScript-identifier operationId is required\", `${at}/operationId`, id);\n if (seen.has(id)) fail(\"Duplicate operationId\", `${at}/operationId`, id);\n seen.add(id);\n if (operation.callbacks) fail(\"Callbacks are unsupported\", `${at}/callbacks`, id);\n const parameters = [\n ...(document.paths[path].parameters ?? []),\n ...(operation.parameters ?? []),\n ];\n const byLocation: Record<string, Json[]> = { path: [], query: [], header: [] };\n for (const parameter of parameters) {\n if (parameter.$ref) fail(\"Parameter references must be bundled\", `${at}/parameters`, id);\n if (parameter.in === \"cookie\")\n fail(\"Cookie parameters are unsupported\", `${at}/parameters`, id);\n if (!byLocation[parameter.in])\n fail(`Unsupported parameter location ${parameter.in}`, `${at}/parameters`, id);\n byLocation[parameter.in]!.push(parameter);\n }\n const prefix = pascal(id);\n const renderParameters = (location: string) => {\n const list = byLocation[location]!.sort((a, b) => a.name.localeCompare(b.name));\n if (!list.length) return undefined;\n const name = `${prefix}${pascal(location)}`;\n operationTypes.push(\n `export type ${name} = {\\n${list.map((p) => ` ${JSON.stringify(p.name)}${p.required || location === \"path\" ? \"\" : \"?\"}: ${schemaType(p.schema ?? {}, `${at}/parameters`, \"request\")};`).join(\"\\n\")}\\n};`,\n );\n const defaultStyle = location === \"query\" ? \"form\" : \"simple\";\n const spec = `{ ${list\n .map((p) => {\n const style = p.style ?? defaultStyle;\n const explode = p.explode ?? style === \"form\";\n return `${JSON.stringify(p.name)}: { style: ${JSON.stringify(style)}, explode: ${explode} }`;\n })\n .join(\", \")} }`;\n return { name, spec };\n };\n const params = renderParameters(\"path\"),\n query = renderParameters(\"query\"),\n headers = renderParameters(\"header\");\n const body = operation.requestBody\n ? pickContent(operation.requestBody.content, `${at}/requestBody`, \"request\")\n : undefined;\n if (body) operationTypes.push(`export type ${prefix}Body = ${body.type};`);\n const successes = responseSchema(operation, at, true);\n if (!successes.length) fail(\"At least one 2xx response is required\", `${at}/responses`, id);\n const errors = responseSchema(operation, at, false);\n const chain: string[] = [`${method === \"delete\" ? \"del\" : method}(${JSON.stringify(path)})`];\n if (params) chain.push(`params<${params.name}>(${params.spec})`);\n if (query) chain.push(`query<${query.name}>(${query.spec})`);\n if (headers) chain.push(`headers<${headers.name}>(${headers.spec})`);\n if (body) chain.push(`body(${body.codec})`);\n for (const [status, response] of successes) {\n if (response.links)\n fail(\"Response links are unsupported\", `${at}/responses/${status}/links`, id);\n const value = pickContent(response.content, `${at}/responses/${status}`, \"response\");\n const typeName = `${prefix}Response${status}`;\n operationTypes.push(`export type ${typeName} = ${value.type};`);\n chain.push(`returns(${status === \"200\" ? \"\" : `${status}, `}${value.codec})`);\n }\n if (errors.length) {\n const rendered = errors.map(([status, response]) => {\n if (response.links)\n fail(\"Response links are unsupported\", `${at}/responses/${status}/links`, id);\n const value = pickContent(response.content, `${at}/responses/${status}`, \"response\");\n const typeName = `${prefix}Error${pascal(status)}`;\n operationTypes.push(`export type ${typeName} = ${value.type};`);\n return `${JSON.stringify(status)}: ${value.codec}`;\n });\n chain.push(`errors({ ${rendered.join(\", \")} })`);\n }\n if (operation.security ?? document.security)\n chain.push(`security(${JSON.stringify(operation.security ?? document.security)})`);\n descriptors.push(` ${id}: ${chain.join(\"\\n .\")},`);\n }\n const imports = [\n ...new Set(\n descriptors\n .join(\" \")\n .match(\n /\\b(get|post|put|patch|del|head|options|json|text|urlEncoded|multipart|arrayBuffer|empty|content)\\b/g,\n ) ?? [],\n ),\n ].sort();\n const files = {\n \"schemas.ts\": `${schemaLines.join(\"\\n\\n\")}\\n`,\n \"operations.ts\": `${names.size ? `import type { ${[...names.keys()].join(\", \")} } from \"./schemas\";\\n\\n` : \"\"}${operationTypes.join(\"\\n\\n\")}\\n`,\n \"api.ts\": `import { ${[\"defineApi\", \"createClient\", ...imports].join(\", \")} } from \"@askrjs/fetch\";\\nimport type { ClientOptions } from \"@askrjs/fetch\";\\nimport type { ${operationTypes\n .map((line) => line.match(/^export type (\\w+)/)?.[1])\n .filter(Boolean)\n .join(\n \", \",\n )} } from \"./operations\";\\n\\nexport const api = defineApi({\\n${descriptors.join(\"\\n\")}\\n}, ${JSON.stringify({ servers: (document.servers ?? []).map((s: Json) => s.url), securitySchemes: document.components?.securitySchemes ?? {} }, null, 2)});\\n\\nexport const createApiClient = (options?: ClientOptions) => createClient(api, options);\\n`,\n \"index.ts\": `export * from \"./schemas\";\\nexport * from \"./operations\";\\nexport { api, createApiClient } from \"./api\";\\n`,\n \".askr-generated.json\": \"\",\n } satisfies Record<(typeof OWNED)[number], string>;\n files[\".askr-generated.json\"] = `${JSON.stringify({ version: 1, files: [...OWNED] }, null, 2)}\\n`;\n return files;\n}\nasync function existingFiles(directory: string) {\n try {\n return (await readdir(directory)).sort();\n } catch {\n return [];\n }\n}\nexport async function writeGenerated(\n directory: string,\n files: Record<string, string>,\n check: boolean,\n): Promise<void> {\n const output = resolve(directory);\n const entries = await existingFiles(output);\n if (check) {\n if (!entries.length) throw new GenerationError(`Generated directory is missing: ${output}`);\n const expected = Object.keys(files).sort();\n if (JSON.stringify(entries) !== JSON.stringify(expected))\n throw new GenerationError(`Generated file ownership differs in ${output}`);\n for (const name of expected)\n if ((await readFile(join(output, name), \"utf8\")) !== files[name])\n throw new GenerationError(`Generated file is stale: ${name}`);\n return;\n }\n if (entries.length && !entries.includes(\".askr-generated.json\"))\n throw new GenerationError(`Refusing to overwrite non-generated directory: ${output}`);\n const stage = await mkdtemp(join(dirname(output), `.${basename(output)}-stage-`));\n for (const [name, content] of Object.entries(files)) await writeFile(join(stage, name), content);\n const backup = `${output}.backup-${process.pid}`;\n let moved = false;\n try {\n if (entries.length) {\n await rename(output, backup);\n moved = true;\n }\n await rename(stage, output);\n if (moved) await rm(backup, { recursive: true, force: true });\n } catch (error) {\n await rm(output, { recursive: true, force: true });\n if (moved) await rename(backup, output);\n throw error;\n } finally {\n await rm(stage, { recursive: true, force: true });\n }\n}\nexport async function generate(input: string, output: string, check = false) {\n const document = await loadOpenApi(input);\n const files = generateFiles(document);\n await mkdir(dirname(resolve(output)), { recursive: true });\n await writeGenerated(output, files, check);\n}\n","import { generate } from \"../generate/generator\";\ntype Io = Pick<Console, \"error\" | \"log\">;\nexport async function runGenerateCli(args: string[], io: Io = console): Promise<number> {\n let output: string | undefined;\n let check = false;\n const inputs: string[] = [];\n for (let i = 0; i < args.length; i++) {\n const arg = args[i]!;\n if (arg === \"--check\") check = true;\n else if (arg === \"-o\" || arg === \"--output\") output = args[++i];\n else if (arg.startsWith(\"--output=\")) output = arg.slice(9);\n else if (arg.startsWith(\"-\")) {\n io.error(`Unknown option: ${arg}`);\n return 1;\n } else inputs.push(arg);\n }\n if (inputs.length !== 1 || !output) {\n io.error(\"Usage: askr generate <input> -o <output-directory> [--check]\");\n return 1;\n }\n try {\n await generate(inputs[0]!, output, check);\n io.log(check ? \"Generated client is current.\" : `Generated client in ${output}.`);\n return 0;\n } catch (error) {\n io.error(error instanceof Error ? error.message : String(error));\n return 1;\n }\n}\n"],"mappings":";;;;;AAMA,MAAM,QAAQ;CACZ;CACA;CACA;CACA;CACA;AACF;AACA,MAAM,UAAU;CAAC;CAAO;CAAQ;CAAO;CAAS;CAAU;CAAQ;AAAS;AAC3E,MAAM,aAAa;AACnB,MAAM,WAAW,GAAG,UAClB,KAAK,MAAM,KAAK,MAAM,OAAO,CAAC,CAAC,CAAC,QAAQ,MAAM,IAAI,CAAC,CAAC,QAAQ,OAAO,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG;AACpF,IAAa,kBAAb,cAAqC,MAAM,CAAC;AAC5C,MAAM,QAAQ,SAAiB,IAAY,gBAAgC;CACzE,MAAM,IAAI,gBAAgB,GAAG,cAAc,GAAG,YAAY,MAAM,KAAK,QAAQ,MAAM,IAAI;AACzF;AACA,MAAM,UAAU,SAAiB;CAC/B,MAAM,SAAS,KACZ,QAAQ,oCAAoC,GAAG,IAAI,MAAM,EAAE,YAAY,CAAC,CAAC,CACzE,QAAQ,mBAAmB,EAAE;CAChC,OAAO,MAAM,KAAK,MAAM,IAAI,IAAI,WAAW;AAC7C;AAEA,SAAS,WACP,QACA,IACA,YAAoC,YAC5B;CACR,IAAI,OAAO,MAAM;EACf,MAAM,OAAO,mBAAmB,OAAO,OAAO,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,EAAE,CAAE;EACtE,OAAO,OAAO,IAAI;CACpB;CACA,IAAI,OAAO,UAAU,KAAA,GAAW,OAAO,KAAK,UAAU,OAAO,KAAK;CAClE,IAAI,OAAO,MACT,OAAO,OAAO,KAAK,KAAK,UAAmB,KAAK,UAAU,KAAK,CAAC,CAAC,CAAC,KAAK,KAAK,KAAK;CACnF,IAAI,OAAO,SAAS,OAAO,OACzB,QAAQ,OAAO,SAAS,OAAO,MAAA,CAC5B,KAAK,MAAY,MAChB,WAAW,MAAM,GAAG,GAAG,GAAG,OAAO,QAAQ,UAAU,QAAQ,GAAG,KAAK,SAAS,CAC9E,CAAC,CACA,KAAK,KAAK;CACf,IAAI,OAAO,OACT,OAAO,OAAO,MACX,KAAK,MAAY,MAAc,WAAW,MAAM,GAAG,GAAG,SAAS,KAAK,SAAS,CAAC,CAAC,CAC/E,KAAK,KAAK;CACf,IAAI,OAAO;CACX,IAAI,OAAO,SAAS,UAAU,OAAO;MAChC,IAAI,OAAO,SAAS,YAAY,OAAO,SAAS,WAAW,OAAO;MAClE,IAAI,OAAO,SAAS,WAAW,OAAO;MACtC,IAAI,OAAO,SAAS,QAAQ,OAAO;MACnC,IAAI,OAAO,SAAS,WAAW,OAAO,OACzC,OAAO,SAAS,WAAW,OAAO,SAAS,CAAC,GAAG,GAAG,GAAG,SAAS,SAAS,EAAE;MACtE,IACH,OAAO,SAAS,YAChB,OAAO,cACP,OAAO,yBAAyB,KAAA,GAChC;EACA,MAAM,WAAW,IAAI,IAAI,OAAO,YAAY,CAAC,CAAC;EAC9C,MAAM,aAAa,OAAO,QAAQ,OAAO,cAAc,CAAC,CAAC,CAAC,CACvD,QAAQ,GAAG,WAAgB,EAAE,cAAc,YAAY,MAAM,WAAW,MAAM,UAAU,CAAC,CACzF,KACE,CAAC,KAAK,WACL,KAAK,KAAK,UAAU,GAAG,IAAI,SAAS,IAAI,GAAG,IAAI,KAAK,IAAI,IAAI,WAAW,OAAO,GAAG,GAAG,cAAc,OAAO,SAAS,EAAE,EACxH;EACF,IAAI,OAAO,wBAAwB,OAAO,OAAO,yBAAyB,UACxE,WAAW,KACT,oBAAoB,WAAW,OAAO,sBAAsB,GAAG,GAAG,wBAAwB,SAAS,EAAE,EACvG;EACF,OAAO,MAAM,WAAW,KAAK,IAAI,EAAE;CACrC,OAAO,IAAI,CAAC,OAAO,MAAM,KAAK,qCAAqC,EAAE;MAChE,KAAK,2BAA2B,OAAO,QAAQ,EAAE;CACtD,OAAO,OAAO,YAAa,MAAM,QAAQ,OAAO,IAAI,KAAK,OAAO,KAAK,SAAS,MAAM,IAChF,GAAG,KAAK,WACR;AACN;AACA,SAAS,eAAe,WAAiB,IAAY,SAAkB;CAIrE,OAHgB,OAAO,QAAc,UAAU,aAAa,CAAC,CAAC,CAAC,CAC5D,QAAQ,CAAC,YAAa,WAAW,YAAY,CAAC,UAAU,YAAY,UAAU,KAAK,MAAM,CAAE,CAAC,CAC5F,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,cAAc,GAAG,KAAA,GAAW,EAAE,SAAS,KAAK,CAAC,CACxD;AACf;AACA,SAAS,YAAY,SAA2B,IAAY,WAAmC;CAC7F,MAAM,UAAU,OAAO,QAAc,WAAW,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;CACzF,IAAI,CAAC,QAAQ,QAAQ,OAAO;EAAE,MAAM;EAAa,OAAO;EAAW,OAAO,CAAC;CAAc;CACzF,MAAM,QAAQ,QAAQ,KAAK,CAAC,WAAW,YAAY;EACjD;EACA,MAAM,WACJ,MAAM,UAAU,CAAC,GACjB,GAAG,GAAG,WAAW,UAAU,QAAQ,MAAM,IAAI,CAAC,CAAC,QAAQ,OAAO,IAAI,EAAE,UACpE,SACF;CACF,EAAE;CACF,MAAM,QAAQ,CAAC,GAAG,IAAI,IAAI,MAAM,KAAK,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,KAAK;CAK/D,OAAO;EAAE,MAAM;EAAO,OAHpB,MAAM,WAAW,IACb,SAAS,MAAM,EAAE,CAAE,WAAW,KAAK,IACnC,aAAa,MAAM,KAAK,MAAM,GAAG,KAAK,UAAU,EAAE,SAAS,EAAE,IAAI,SAAS,EAAE,WAAW,EAAE,IAAI,GAAG,CAAC,CAAC,KAAK,IAAI,EAAE;EACtF,OAAO,MAAM,KAAK,MAAM,EAAE,SAAS;CAAE;AACpE;AACA,SAAS,SAAS,WAAmB,MAAc;CACjD,IAAI,cAAc,sBAAsB,UAAU,SAAS,OAAO,GAAG,OAAO,QAAQ,KAAK;CACzF,IAAI,UAAU,WAAW,OAAO,GAAG,OAAO;CAC1C,IAAI,cAAc,qCAAqC,OAAO;CAC9D,IAAI,cAAc,uBAAuB,OAAO;CAChD,IAAI,cAAc,4BAA4B,OAAO;CACrD,KAAK,0BAA0B,aAAa,WAAW;AACzD;AAIA,MAAM,eAAe,UACnB,mBAAmB,KAAK,CAAC,CAAC,QAAQ,OAAO,GAAG,CAAC,CAAC,QAAQ,OAAO,GAAG;AAClE,MAAM,gBAAgB,UAAgB,aAA8B;CAClE,IAAI,QAAiB;CACrB,KAAK,MAAM,QAAQ,SAChB,QAAQ,UAAU,EAAE,CAAC,CACrB,MAAM,GAAG,CAAC,CACV,OAAO,OAAO,GAAG;EAClB,IAAI,CAAC,SAAS,OAAO,UAAU,UAC7B,MAAM,IAAI,gBAAgB,oCAAoC,MAAM;EACtE,QAAS,MAAkC,YAAY,IAAI;CAC7D;CACA,IAAI,UAAU,KAAA,GACZ,MAAM,IAAI,gBAAgB,kCAAkC,YAAY,KAAK;CAC/E,OAAO;AACT;AAEA,SAAS,qBAAqB,UAAkB,QAAsB;CACpE,IAAI;CACJ,IAAI;EACF,SAAS,KAAK,MAAM,QAAQ;CAC9B,QAAQ;EACN,IAAI;GACF,SAAS,KAAK,QAAQ;EACxB,SAAS,OAAO;GACd,MAAM,IAAI,gBACR,oCAAoC,OAAO,IAAI,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,GACtG;EACF;CACF;CACA,IAAI,CAAC,UAAU,OAAO,WAAW,YAAY,MAAM,QAAQ,MAAM,GAC/D,MAAM,IAAI,gBAAgB,uCAAuC,QAAQ;CAC3E,OAAO;AACT;AAEA,eAAe,WAAW,KAAsC;CAC9D,IAAI,IAAI,WAAW,SAAS,KAAK,IAAI,WAAW,UAAU,GAAG;EAC3D,MAAM,WAAW,MAAM,MAAM,GAAG;EAChC,IAAI,CAAC,SAAS,IACZ,MAAM,IAAI,gBACR,qCAAqC,IAAI,IAAI,SAAS,OAAO,GAAG,SAAS,YAC3E;EACF,OAAO;GAAE;GAAK,UAAU,qBAAqB,MAAM,SAAS,KAAK,GAAG,GAAG;EAAE;CAC3E;CACA,MAAM,OAAO,cAAc,GAAG;CAC9B,OAAO;EAAE;EAAK,UAAU,qBAAqB,MAAM,SAAS,MAAM,MAAM,GAAG,IAAI;CAAE;AACnF;AAEA,IAAM,iBAAN,MAAqB;CAMA;CACA;CANnB,0BAA2B,IAAI,IAAqC;CACpE,0BAA2B,IAAI,IAAoB;CACnD,wBAAyB,IAAI,IAAoB;CAEjD,YACE,MACA,SACA;EAFiB,KAAA,OAAA;EACA,KAAA,UAAA;CAChB;CAEH,MAAM,SAAwB;EAC5B,IAAI,CAAC,KAAK,KAAK,YAAY,KAAK,KAAK,aAAa,CAAC;EACnD,IAAI,CAAC,KAAK,KAAK,WAAW,SAAS,KAAK,KAAK,WAAW,UAAU,CAAC;EACnE,MAAM,KAAK,aAAa,KAAK,MAAM,KAAK,SAAS,KAAK,sBAAM,IAAI,IAAI,CAAC;EACrE,OAAO,KAAK;CACd;CAEA,MAAc,OAAO,KAAsC;EACzD,IAAI,UAAU,KAAK,QAAQ,IAAI,GAAG;EAClC,IAAI,CAAC,SAAS;GACZ,UAAU,WAAW,GAAG;GACxB,KAAK,QAAQ,IAAI,KAAK,OAAO;EAC/B;EACA,OAAO;CACT;CAEA,cAAsB,KAAa,UAA0B;EAC3D,MAAM,YAAY,GAAG,IAAI,GAAG;EAC5B,MAAM,WAAW,KAAK,QAAQ,IAAI,SAAS;EAC3C,IAAI,UAAU,OAAO;EACrB,MAAM,MACJ,SAAS,MAAM,GAAG,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,GAAG,EAAE,KACzC,SAAS,IAAI,IAAI,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC,QAAQ,YAAY,EAAE;EACxD,MAAM,OAAO,OAAO,YAAY,GAAG,CAAC,KAAK;EACzC,IAAI,OAAO;EACX,IAAI,SAAS;EACb,OAAO,KAAK,MAAM,IAAI,IAAI,KAAK,KAAK,MAAM,IAAI,IAAI,MAAM,WAAW,OAAO,GAAG,OAAO;EACpF,KAAK,QAAQ,IAAI,WAAW,IAAI;EAChC,KAAK,MAAM,IAAI,MAAM,SAAS;EAC9B,OAAO;CACT;CAEA,MAAc,aACZ,OACA,KACA,UACA,OACe;EACf,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,KAAK,GAC7C,MAAM,OAAO,MAAM,KAAK,OAAO,OAAO,KAAK,UAAU,KAAK;CAC9D;CAEA,MAAc,OACZ,OACA,KACA,UACA,OACkB;EAClB,IAAI,MAAM,QAAQ,KAAK,GACrB,OAAO,QAAQ,IAAI,MAAM,KAAK,SAAS,KAAK,OAAO,MAAM,KAAK,UAAU,KAAK,CAAC,CAAC;EACjF,IAAI,CAAC,SAAS,OAAO,UAAU,UAAU,OAAO;EAChD,MAAM,SAAS;EACf,IAAI,OAAO,OAAO,SAAS,UAAU;GACnC,MAAM,WAAW,MAAM,KAAK,WAAW,OAAO,MAAM,KAAK,UAAU,KAAK;GACxE,MAAM,iBAAiB,OAAO,QAAQ,MAAM,CAAC,CAAC,QAAQ,CAAC,SAAS,QAAQ,MAAM;GAC9E,IAAI,CAAC,eAAe,QAAQ,OAAO;GACnC,MAAM,SACJ,YAAY,OAAO,aAAa,YAAY,CAAC,MAAM,QAAQ,QAAQ,IAC/D,EAAE,GAAI,SAAkB,IACxB,CAAC;GACP,KAAK,MAAM,CAAC,KAAK,UAAU,gBACzB,OAAO,OAAO,MAAM,KAAK,OAAO,OAAO,KAAK,UAAU,KAAK;GAC7D,OAAO;EACT;EACA,MAAM,SAAe,CAAC;EACtB,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,MAAM,GAC9C,OAAO,OAAO,MAAM,KAAK,OAAO,OAAO,KAAK,UAAU,KAAK;EAC7D,OAAO;CACT;CAEA,MAAc,WACZ,KACA,SACA,UACA,OACkB;EAClB,MAAM,OAAO,IAAI,QAAQ,GAAG;EAC5B,MAAM,YACJ,SAAS,KACL,IAAI,IAAI,KAAK,OAAO,CAAC,CAAC,OACtB,IAAI,IAAI,IAAI,MAAM,GAAG,IAAI,KAAK,SAAS,OAAO,CAAC,CAAC;EACtD,MAAM,WAAW,SAAS,KAAK,KAAK,IAAI,MAAM,OAAO,CAAC;EACtD,MAAM,YAAY,GAAG,UAAU,GAAG;EAClC,MAAM,eACJ,cAAc,KAAK,UAAU;GAAE,KAAK;GAAW;EAAS,IAAI,MAAM,KAAK,OAAO,SAAS;EACzF,MAAM,SAAS,aAAa,aAAa,UAAU,QAAQ;EAE3D,IADqB,cAAc,KAAK,WAAW,SAAS,WAAW,sBAAsB,GAC3E,OAAO,EAAE,MAAM,IAAI,WAAW;EAChD,IAAI,SAAS,WAAW,sBAAsB,GAAG;GAC/C,MAAM,OAAO,KAAK,cAAc,WAAW,QAAQ;GACnD,IAAI,MAAM,IAAI,SAAS,GAAG,OAAO,EAAE,MAAM,wBAAwB,OAAO;GACxE,IAAI,EAAE,QAAQ,KAAK,KAAK,WAAW,UAAU;IAC3C,KAAK,KAAK,WAAW,QAAQ,QAAQ,CAAC;IACtC,MAAM,OAAO,IAAI,IAAI,KAAK,CAAC,CAAC,IAAI,SAAS;IACzC,KAAK,KAAK,WAAW,QAAQ,QAAS,MAAM,KAAK,OAC/C,QACA,aAAa,KACb,aAAa,UACb,IACF;GACF;GACA,OAAO,EAAE,MAAM,wBAAwB,OAAO;EAChD;EACA,IAAI,MAAM,IAAI,SAAS,GAAG,MAAM,IAAI,gBAAgB,oCAAoC,KAAK;EAC7F,OAAO,KAAK,OACV,QACA,aAAa,KACb,aAAa,UACb,IAAI,IAAI,KAAK,CAAC,CAAC,IAAI,SAAS,CAC9B;CACF;AACF;AAEA,eAAsB,YAAY,OAA8B;CAE9D,MAAM,SAAS,MAAM,WADT,eAAe,KAAK,KAAK,IAAI,QAAQ,cAAc,QAAQ,KAAK,CAAC,CAAC,CAAC,IAC5C;CACnC,OAAO,IAAI,eAAe,OAAO,UAAU,OAAO,GAAG,CAAC,CAAC,OAAO;AAChE;AACA,SAAgB,cAAc,UAAwD;CACpF,MAAM,UAAU,OAAO,SAAS,WAAW,EAAE;CAC7C,IAAI,CAAC,kBAAkB,KAAK,OAAO,GACjC,KAAK,+BAA+B,WAAW,aAAa,WAAW;CACzE,IAAI,SAAS,UAAU,KAAK,4BAA4B,YAAY;CACpE,MAAM,UAAU,SAAS,YAAY,WAAW,CAAC;CACjD,MAAM,wBAAQ,IAAI,IAAoB;CACtC,KAAK,MAAM,QAAQ,OAAO,KAAK,OAAO,CAAC,CAAC,KAAK,GAAG;EAC9C,MAAM,OAAO,OAAO,IAAI;EACxB,IAAI,CAAC,QAAQ,MAAM,IAAI,IAAI,GACzB,KAAK,gCAAgC,QAAQ,QAAQ,cAAc,WAAW,IAAI,CAAC;EACrF,MAAM,IAAI,MAAM,IAAI;CACtB;CACA,MAAM,cAAc,CAAC,GAAG,KAAK,CAAC,CAAC,KAC5B,CAAC,MAAM,cACN,eAAe,KAAK,KAAK,WAAW,QAAQ,WAAW,QAAQ,cAAc,WAAW,QAAQ,CAAC,EAAE,EACvG;CACA,MAAM,iBAA2B,CAAC;CAClC,MAAM,cAAwB,CAAC;CAC/B,MAAM,uBAAO,IAAI,IAAY;CAC7B,KAAK,MAAM,QAAQ,OAAO,KAAK,SAAS,SAAS,CAAC,CAAC,CAAC,CAAC,KAAK,GACxD,KAAK,MAAM,UAAU,SAAS;EAC5B,MAAM,YAAY,SAAS,MAAM,KAAK,GAAG;EACzC,IAAI,CAAC,WAAW;EAChB,MAAM,KAAK,QAAQ,SAAS,MAAM,MAAM;EACxC,MAAM,KAAK,UAAU;EACrB,IAAI,CAAC,MAAM,CAAC,WAAW,KAAK,EAAE,GAC5B,KAAK,0DAA0D,GAAG,GAAG,eAAe,EAAE;EACxF,IAAI,KAAK,IAAI,EAAE,GAAG,KAAK,yBAAyB,GAAG,GAAG,eAAe,EAAE;EACvE,KAAK,IAAI,EAAE;EACX,IAAI,UAAU,WAAW,KAAK,6BAA6B,GAAG,GAAG,aAAa,EAAE;EAChF,MAAM,aAAa,CACjB,GAAI,SAAS,MAAM,KAAK,CAAC,cAAc,CAAC,GACxC,GAAI,UAAU,cAAc,CAAC,CAC/B;EACA,MAAM,aAAqC;GAAE,MAAM,CAAC;GAAG,OAAO,CAAC;GAAG,QAAQ,CAAC;EAAE;EAC7E,KAAK,MAAM,aAAa,YAAY;GAClC,IAAI,UAAU,MAAM,KAAK,wCAAwC,GAAG,GAAG,cAAc,EAAE;GACvF,IAAI,UAAU,OAAO,UACnB,KAAK,qCAAqC,GAAG,GAAG,cAAc,EAAE;GAClE,IAAI,CAAC,WAAW,UAAU,KACxB,KAAK,kCAAkC,UAAU,MAAM,GAAG,GAAG,cAAc,EAAE;GAC/E,WAAW,UAAU,GAAG,CAAE,KAAK,SAAS;EAC1C;EACA,MAAM,SAAS,OAAO,EAAE;EACxB,MAAM,oBAAoB,aAAqB;GAC7C,MAAM,OAAO,WAAW,SAAS,CAAE,MAAM,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;GAC9E,IAAI,CAAC,KAAK,QAAQ,OAAO,KAAA;GACzB,MAAM,OAAO,GAAG,SAAS,OAAO,QAAQ;GACxC,eAAe,KACb,eAAe,KAAK,QAAQ,KAAK,KAAK,MAAM,KAAK,KAAK,UAAU,EAAE,IAAI,IAAI,EAAE,YAAY,aAAa,SAAS,KAAK,IAAI,IAAI,WAAW,EAAE,UAAU,CAAC,GAAG,GAAG,GAAG,cAAc,SAAS,EAAE,EAAE,CAAC,CAAC,KAAK,IAAI,EAAE,KACtM;GACA,MAAM,eAAe,aAAa,UAAU,SAAS;GAQrD,OAAO;IAAE;IAAM,MAAA,KAPG,KACf,KAAK,MAAM;KACV,MAAM,QAAQ,EAAE,SAAS;KACzB,MAAM,UAAU,EAAE,WAAW,UAAU;KACvC,OAAO,GAAG,KAAK,UAAU,EAAE,IAAI,EAAE,aAAa,KAAK,UAAU,KAAK,EAAE,aAAa,QAAQ;IAC3F,CAAC,CAAC,CACD,KAAK,IAAI,EAAE;GACM;EACtB;EACA,MAAM,SAAS,iBAAiB,MAAM,GACpC,QAAQ,iBAAiB,OAAO,GAChC,UAAU,iBAAiB,QAAQ;EACrC,MAAM,OAAO,UAAU,cACnB,YAAY,UAAU,YAAY,SAAS,GAAG,GAAG,eAAe,SAAS,IACzE,KAAA;EACJ,IAAI,MAAM,eAAe,KAAK,eAAe,OAAO,SAAS,KAAK,KAAK,EAAE;EACzE,MAAM,YAAY,eAAe,WAAW,IAAI,IAAI;EACpD,IAAI,CAAC,UAAU,QAAQ,KAAK,yCAAyC,GAAG,GAAG,aAAa,EAAE;EAC1F,MAAM,SAAS,eAAe,WAAW,IAAI,KAAK;EAClD,MAAM,QAAkB,CAAC,GAAG,WAAW,WAAW,QAAQ,OAAO,GAAG,KAAK,UAAU,IAAI,EAAE,EAAE;EAC3F,IAAI,QAAQ,MAAM,KAAK,UAAU,OAAO,KAAK,IAAI,OAAO,KAAK,EAAE;EAC/D,IAAI,OAAO,MAAM,KAAK,SAAS,MAAM,KAAK,IAAI,MAAM,KAAK,EAAE;EAC3D,IAAI,SAAS,MAAM,KAAK,WAAW,QAAQ,KAAK,IAAI,QAAQ,KAAK,EAAE;EACnE,IAAI,MAAM,MAAM,KAAK,QAAQ,KAAK,MAAM,EAAE;EAC1C,KAAK,MAAM,CAAC,QAAQ,aAAa,WAAW;GAC1C,IAAI,SAAS,OACX,KAAK,kCAAkC,GAAG,GAAG,aAAa,OAAO,SAAS,EAAE;GAC9E,MAAM,QAAQ,YAAY,SAAS,SAAS,GAAG,GAAG,aAAa,UAAU,UAAU;GACnF,MAAM,WAAW,GAAG,OAAO,UAAU;GACrC,eAAe,KAAK,eAAe,SAAS,KAAK,MAAM,KAAK,EAAE;GAC9D,MAAM,KAAK,WAAW,WAAW,QAAQ,KAAK,GAAG,OAAO,MAAM,MAAM,MAAM,EAAE;EAC9E;EACA,IAAI,OAAO,QAAQ;GACjB,MAAM,WAAW,OAAO,KAAK,CAAC,QAAQ,cAAc;IAClD,IAAI,SAAS,OACX,KAAK,kCAAkC,GAAG,GAAG,aAAa,OAAO,SAAS,EAAE;IAC9E,MAAM,QAAQ,YAAY,SAAS,SAAS,GAAG,GAAG,aAAa,UAAU,UAAU;IACnF,MAAM,WAAW,GAAG,OAAO,OAAO,OAAO,MAAM;IAC/C,eAAe,KAAK,eAAe,SAAS,KAAK,MAAM,KAAK,EAAE;IAC9D,OAAO,GAAG,KAAK,UAAU,MAAM,EAAE,IAAI,MAAM;GAC7C,CAAC;GACD,MAAM,KAAK,YAAY,SAAS,KAAK,IAAI,EAAE,IAAI;EACjD;EACA,IAAI,UAAU,YAAY,SAAS,UACjC,MAAM,KAAK,YAAY,KAAK,UAAU,UAAU,YAAY,SAAS,QAAQ,EAAE,EAAE;EACnF,YAAY,KAAK,KAAK,GAAG,IAAI,MAAM,KAAK,SAAS,EAAE,EAAE;CACvD;CACF,MAAM,UAAU,CACd,GAAG,IAAI,IACL,YACG,KAAK,GAAG,CAAC,CACT,MACC,qGACF,KAAK,CAAC,CACV,CACF,CAAC,CAAC,KAAK;CACP,MAAM,QAAQ;EACZ,cAAc,GAAG,YAAY,KAAK,MAAM,EAAE;EAC1C,iBAAiB,GAAG,MAAM,OAAO,iBAAiB,CAAC,GAAG,MAAM,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,4BAA4B,KAAK,eAAe,KAAK,MAAM,EAAE;EAC5I,UAAU,YAAY;GAAC;GAAa;GAAgB,GAAG;EAAO,CAAC,CAAC,KAAK,IAAI,EAAE,+FAA+F,eACvK,KAAK,SAAS,KAAK,MAAM,oBAAoB,CAAC,GAAG,EAAE,CAAC,CACpD,OAAO,OAAO,CAAC,CACf,KACC,IACF,EAAE,6DAA6D,YAAY,KAAK,IAAI,EAAE,OAAO,KAAK,UAAU;GAAE,UAAU,SAAS,WAAW,CAAC,EAAA,CAAG,KAAK,MAAY,EAAE,GAAG;GAAG,iBAAiB,SAAS,YAAY,mBAAmB,CAAC;EAAE,GAAG,MAAM,CAAC,EAAE;EACnP,YAAY;EACZ,wBAAwB;CAC1B;CACA,MAAM,0BAA0B,GAAG,KAAK,UAAU;EAAE,SAAS;EAAG,OAAO,CAAC,GAAG,KAAK;CAAE,GAAG,MAAM,CAAC,EAAE;CAC9F,OAAO;AACT;AACA,eAAe,cAAc,WAAmB;CAC9C,IAAI;EACF,QAAQ,MAAM,QAAQ,SAAS,EAAA,CAAG,KAAK;CACzC,QAAQ;EACN,OAAO,CAAC;CACV;AACF;AACA,eAAsB,eACpB,WACA,OACA,OACe;CACf,MAAM,SAAS,QAAQ,SAAS;CAChC,MAAM,UAAU,MAAM,cAAc,MAAM;CAC1C,IAAI,OAAO;EACT,IAAI,CAAC,QAAQ,QAAQ,MAAM,IAAI,gBAAgB,mCAAmC,QAAQ;EAC1F,MAAM,WAAW,OAAO,KAAK,KAAK,CAAC,CAAC,KAAK;EACzC,IAAI,KAAK,UAAU,OAAO,MAAM,KAAK,UAAU,QAAQ,GACrD,MAAM,IAAI,gBAAgB,uCAAuC,QAAQ;EAC3E,KAAK,MAAM,QAAQ,UACjB,IAAK,MAAM,SAAS,KAAK,QAAQ,IAAI,GAAG,MAAM,MAAO,MAAM,OACzD,MAAM,IAAI,gBAAgB,4BAA4B,MAAM;EAChE;CACF;CACA,IAAI,QAAQ,UAAU,CAAC,QAAQ,SAAS,sBAAsB,GAC5D,MAAM,IAAI,gBAAgB,kDAAkD,QAAQ;CACtF,MAAM,QAAQ,MAAM,QAAQ,KAAK,QAAQ,MAAM,GAAG,IAAI,SAAS,MAAM,EAAE,QAAQ,CAAC;CAChF,KAAK,MAAM,CAAC,MAAM,YAAY,OAAO,QAAQ,KAAK,GAAG,MAAM,UAAU,KAAK,OAAO,IAAI,GAAG,OAAO;CAC/F,MAAM,SAAS,GAAG,OAAO,UAAU,QAAQ;CAC3C,IAAI,QAAQ;CACZ,IAAI;EACF,IAAI,QAAQ,QAAQ;GAClB,MAAM,OAAO,QAAQ,MAAM;GAC3B,QAAQ;EACV;EACA,MAAM,OAAO,OAAO,MAAM;EAC1B,IAAI,OAAO,MAAM,GAAG,QAAQ;GAAE,WAAW;GAAM,OAAO;EAAK,CAAC;CAC9D,SAAS,OAAO;EACd,MAAM,GAAG,QAAQ;GAAE,WAAW;GAAM,OAAO;EAAK,CAAC;EACjD,IAAI,OAAO,MAAM,OAAO,QAAQ,MAAM;EACtC,MAAM;CACR,UAAU;EACR,MAAM,GAAG,OAAO;GAAE,WAAW;GAAM,OAAO;EAAK,CAAC;CAClD;AACF;AACA,eAAsB,SAAS,OAAe,QAAgB,QAAQ,OAAO;CAE3E,MAAM,QAAQ,cAAc,MADL,YAAY,KAAK,CACJ;CACpC,MAAM,MAAM,QAAQ,QAAQ,MAAM,CAAC,GAAG,EAAE,WAAW,KAAK,CAAC;CACzD,MAAM,eAAe,QAAQ,OAAO,KAAK;AAC3C;;;AC/cA,eAAsB,eAAe,MAAgB,KAAS,SAA0B;CACtF,IAAI;CACJ,IAAI,QAAQ;CACZ,MAAM,SAAmB,CAAC;CAC1B,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;EACpC,MAAM,MAAM,KAAK;EACjB,IAAI,QAAQ,WAAW,QAAQ;OAC1B,IAAI,QAAQ,QAAQ,QAAQ,YAAY,SAAS,KAAK,EAAE;OACxD,IAAI,IAAI,WAAW,WAAW,GAAG,SAAS,IAAI,MAAM,CAAC;OACrD,IAAI,IAAI,WAAW,GAAG,GAAG;GAC5B,GAAG,MAAM,mBAAmB,KAAK;GACjC,OAAO;EACT,OAAO,OAAO,KAAK,GAAG;CACxB;CACA,IAAI,OAAO,WAAW,KAAK,CAAC,QAAQ;EAClC,GAAG,MAAM,8DAA8D;EACvE,OAAO;CACT;CACA,IAAI;EACF,MAAM,SAAS,OAAO,IAAK,QAAQ,KAAK;EACxC,GAAG,IAAI,QAAQ,iCAAiC,uBAAuB,OAAO,EAAE;EAChF,OAAO;CACT,SAAS,OAAO;EACd,GAAG,MAAM,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;EAC/D,OAAO;CACT;AACF"}
|
package/dist/openapi.js
CHANGED
|
@@ -3,8 +3,8 @@ import { t as isDirectExecution } from "./is-direct-execution-Cdlr-ZUl.js";
|
|
|
3
3
|
import fs from "node:fs/promises";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { pathToFileURL } from "node:url";
|
|
6
|
+
import { dump } from "js-yaml";
|
|
6
7
|
import { tsImport } from "tsx/esm/api";
|
|
7
|
-
import { stringify } from "yaml";
|
|
8
8
|
//#region src/bin/openapi.ts
|
|
9
9
|
const helpText = `askr openapi - Generate an OpenAPI YAML artifact
|
|
10
10
|
|
|
@@ -56,9 +56,10 @@ function exporterFrom(moduleValue) {
|
|
|
56
56
|
return exported;
|
|
57
57
|
}
|
|
58
58
|
function serializeOpenApi(document) {
|
|
59
|
-
return `${
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
return `${dump(document, {
|
|
60
|
+
lineWidth: -1,
|
|
61
|
+
noCompatMode: true,
|
|
62
|
+
noRefs: true
|
|
62
63
|
}).replace(/\n+$/, "")}\n`;
|
|
63
64
|
}
|
|
64
65
|
async function generate(entry, deps) {
|
package/dist/openapi.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openapi.js","names":[],"sources":["../src/bin/openapi.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport { tsImport } from \"tsx/esm/api\";\nimport {
|
|
1
|
+
{"version":3,"file":"openapi.js","names":[],"sources":["../src/bin/openapi.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport fs from \"node:fs/promises\";\nimport { dump } from \"js-yaml\";\nimport path from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport { tsImport } from \"tsx/esm/api\";\nimport { isDirectExecution } from \"./is-direct-execution\";\n\ntype CliIo = Pick<Console, \"error\" | \"log\">;\n\ninterface ParsedArgs {\n entry: string;\n output: string;\n check: boolean;\n help: boolean;\n}\n\ninterface DocumentExporter {\n toOpenApiDocument(): unknown;\n}\n\ninterface OpenApiDeps {\n cwd(): string;\n importModule(filePath: string): Promise<unknown>;\n readFile(filePath: string): Promise<string>;\n mkdir(directory: string): Promise<void>;\n writeFile(filePath: string, contents: string): Promise<void>;\n rename(from: string, to: string): Promise<void>;\n rm(filePath: string): Promise<void>;\n temporarySuffix(): string;\n}\n\nconst helpText = `askr openapi - Generate an OpenAPI YAML artifact\n\nUsage:\n askr openapi [--entry <path>] [--output <path>] [--check]\n\nOptions:\n --entry <path> TypeScript API module (default: ./src/api.ts)\n --output <path> YAML artifact (default: ./openapi.yml)\n --check Fail when the artifact is missing or byte-stale; never write\n --help, -h Show help\n`;\n\nconst defaultDeps: OpenApiDeps = {\n cwd: () => process.cwd(),\n importModule: (filePath) => tsImport(pathToFileURL(filePath).href, import.meta.url),\n readFile: (filePath) => fs.readFile(filePath, \"utf8\"),\n mkdir: (directory) => fs.mkdir(directory, { recursive: true }).then(() => undefined),\n writeFile: (filePath, contents) => fs.writeFile(filePath, contents, \"utf8\"),\n rename: (from, to) => fs.rename(from, to),\n rm: (filePath) => fs.rm(filePath, { force: true }),\n temporarySuffix: () => `${process.pid}.${Date.now()}`,\n};\n\nexport function parseOpenApiArgs(args: readonly string[]): ParsedArgs {\n const parsed: ParsedArgs = {\n entry: \"./src/api.ts\",\n output: \"./openapi.yml\",\n check: false,\n help: false,\n };\n for (let index = 0; index < args.length; index += 1) {\n const value = args[index];\n if (value === \"--entry\" || value === \"--output\") {\n const next = args[index + 1];\n if (!next) throw new Error(`${value} requires a path`);\n if (value === \"--entry\") parsed.entry = next;\n else parsed.output = next;\n index += 1;\n } else if (value === \"--check\") parsed.check = true;\n else if (value === \"--help\" || value === \"-h\") parsed.help = true;\n else throw new Error(`Unknown option: ${value}`);\n }\n return parsed;\n}\n\nfunction exporterFrom(moduleValue: unknown): DocumentExporter {\n if (!moduleValue || typeof moduleValue !== \"object\") {\n throw new Error(\"API module must have a default export with toOpenApiDocument()\");\n }\n let exported = (moduleValue as { default?: unknown }).default;\n for (\n let depth = 0;\n depth < 3 &&\n exported &&\n typeof exported === \"object\" &&\n typeof (exported as Partial<DocumentExporter>).toOpenApiDocument !== \"function\" &&\n \"default\" in exported;\n depth += 1\n ) {\n exported = (exported as { default?: unknown }).default;\n }\n if (\n !exported ||\n typeof exported !== \"object\" ||\n typeof (exported as Partial<DocumentExporter>).toOpenApiDocument !== \"function\"\n ) {\n throw new Error(\"API module must have a default export with toOpenApiDocument()\");\n }\n return exported as DocumentExporter;\n}\n\nexport function serializeOpenApi(document: unknown): string {\n const yaml = dump(document, { lineWidth: -1, noCompatMode: true, noRefs: true });\n return `${yaml.replace(/\\n+$/, \"\")}\\n`;\n}\n\nasync function generate(entry: string, deps: OpenApiDeps): Promise<string> {\n const moduleValue = await deps.importModule(entry);\n const document = exporterFrom(moduleValue).toOpenApiDocument();\n if (!document || typeof document !== \"object\" || Array.isArray(document)) {\n throw new Error(\"toOpenApiDocument() must return an OpenAPI document object\");\n }\n return serializeOpenApi(document);\n}\n\nasync function atomicWrite(output: string, contents: string, deps: OpenApiDeps): Promise<void> {\n await deps.mkdir(path.dirname(output));\n const temporary = `${output}.${deps.temporarySuffix()}.tmp`;\n try {\n await deps.writeFile(temporary, contents);\n await deps.rename(temporary, output);\n } catch (error) {\n await deps.rm(temporary);\n throw error;\n }\n}\n\nexport async function runOpenApiCli(\n args: string[] = process.argv.slice(2),\n io: CliIo = console,\n overrides: Partial<OpenApiDeps> = {},\n): Promise<number> {\n const deps = { ...defaultDeps, ...overrides };\n try {\n const parsed = parseOpenApiArgs(args);\n if (parsed.help) {\n io.log(helpText.trimEnd());\n return 0;\n }\n const entry = path.resolve(deps.cwd(), parsed.entry);\n const output = path.resolve(deps.cwd(), parsed.output);\n const expected = await generate(entry, deps);\n if (parsed.check) {\n let actual: string;\n try {\n actual = await deps.readFile(output);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n io.error(`OpenAPI artifact is missing: ${output}`);\n return 1;\n }\n throw error;\n }\n if (actual !== expected) {\n io.error(`OpenAPI artifact is stale: ${output}`);\n return 1;\n }\n io.log(`OpenAPI artifact is current: ${output}`);\n return 0;\n }\n await atomicWrite(output, expected, deps);\n io.log(`Generated OpenAPI artifact: ${output}`);\n return 0;\n } catch (error) {\n io.error(error instanceof Error ? error.message : String(error));\n return 1;\n }\n}\n\nasync function main(): Promise<void> {\n process.exit(await runOpenApiCli(process.argv.slice(2)));\n}\n\nif (isDirectExecution(import.meta.url)) void main();\n"],"mappings":"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"}
|