hono-takibi 0.9.99994 → 0.9.99995
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 +3 -5
- package/dist/config/index.d.ts +0 -8
- package/dist/config/index.js +23 -3
- package/dist/core/docs/index.js +1 -1
- package/dist/core/hooks/index.js +1 -1
- package/dist/core/rpc/index.js +3 -2
- package/dist/core/type/index.js +2 -1
- package/dist/{docs-380Wcu6a.js → docs-DbknIWmx.js} +2 -1
- package/dist/generator/zod-openapi-hono/openapi/index.js +1 -1
- package/dist/guard-BSZ8ezEv.js +83 -0
- package/dist/{hooks-CkiatAe9.js → hooks-CCw7jm8g.js} +5 -4
- package/dist/index.js +1 -1
- package/dist/{openapi-8ZYAMj_e.js → openapi-BlTDQFFF.js} +35 -111
- package/dist/{rpc-CepeggWU.js → rpc-BzPo2tZf.js} +1 -1
- package/dist/{shared-n3ZMLfl9.js → shared-B-TUm7Fa.js} +53 -81
- package/dist/{guard-zvkMUVYD.js → utils-B5PbMfWs.js} +72 -82
- package/dist/vite-plugin/index.js +117 -22
- package/package.json +7 -3
package/README.md
CHANGED
|
@@ -200,7 +200,6 @@ export default app
|
|
|
200
200
|
```ts
|
|
201
201
|
export default defineConfig({
|
|
202
202
|
input: 'openapi.yaml',
|
|
203
|
-
output: './src/index.ts',
|
|
204
203
|
template: { define: true },
|
|
205
204
|
})
|
|
206
205
|
```
|
|
@@ -221,7 +220,7 @@ export const getUsersIdRoute = defineOpenAPIRoute({
|
|
|
221
220
|
})
|
|
222
221
|
```
|
|
223
222
|
|
|
224
|
-
|
|
223
|
+
The app entry defaults to `src/index.ts` (override with `output`, which must be an `index.ts` path such as `./server/index.ts`). Route files always go to `routes/` next to the app entry, and component schemas to `components/index.ts` (override with `components.output`). When `output` is omitted, `components.output` also anchors the layout: `components: { output: './server/components/index.ts' }` puts the app entry at `server/index.ts` and routes at `server/routes/`.
|
|
225
224
|
|
|
226
225
|
## Client Library Integrations
|
|
227
226
|
|
|
@@ -305,7 +304,7 @@ export default defineConfig({
|
|
|
305
304
|
|
|
306
305
|
## Full Config Reference
|
|
307
306
|
|
|
308
|
-
Some options are mutually exclusive: `output` ↔ `routes`, `components.output` ↔ per-type components, `template.define` ↔ `routeHandler`.
|
|
307
|
+
Some options are mutually exclusive: `output` ↔ `routes`, `template.define` ↔ `routes`, `components.output` ↔ per-type components, `template.define` ↔ `routeHandler`.
|
|
309
308
|
|
|
310
309
|
```ts
|
|
311
310
|
import { defineConfig } from 'hono-takibi/config'
|
|
@@ -313,7 +312,7 @@ import { defineConfig } from 'hono-takibi/config'
|
|
|
313
312
|
export default defineConfig({
|
|
314
313
|
input: 'openapi.yaml',
|
|
315
314
|
|
|
316
|
-
output: './src/routes.ts', // single-file mode; with template.define, the app entry (
|
|
315
|
+
output: './src/routes.ts', // single-file mode; with template.define, the app entry (an index.ts path, default ./src/index.ts)
|
|
317
316
|
basePath: '/api',
|
|
318
317
|
readonly: true,
|
|
319
318
|
// format: {}, // oxfmt FormatConfig
|
|
@@ -322,7 +321,6 @@ export default defineConfig({
|
|
|
322
321
|
test: true,
|
|
323
322
|
routeHandler: false, // true: RouteHandler exports
|
|
324
323
|
define: false, // true: defineOpenAPIRoute output
|
|
325
|
-
// output: './src/routes', // define mode dir (default ./src/routes)
|
|
326
324
|
pathAlias: '@/',
|
|
327
325
|
testFramework: 'vitest', // "vitest" | "vite-plus" | "bun"
|
|
328
326
|
},
|
package/dist/config/index.d.ts
CHANGED
|
@@ -10,7 +10,6 @@ declare const ConfigSchema: z.ZodReadonly<z.ZodObject<{
|
|
|
10
10
|
format: z.ZodExactOptional<z.ZodCustom<FormatConfig, FormatConfig>>;
|
|
11
11
|
template: z.ZodExactOptional<z.ZodDiscriminatedUnion<[z.ZodReadonly<z.ZodObject<{
|
|
12
12
|
define: z.ZodLiteral<true>;
|
|
13
|
-
output: z.ZodDefault<z.ZodString>;
|
|
14
13
|
test: z.ZodDefault<z.ZodBoolean>;
|
|
15
14
|
pathAlias: z.ZodExactOptional<z.ZodString>;
|
|
16
15
|
testFramework: z.ZodExactOptional<z.ZodDefault<z.ZodEnum<{
|
|
@@ -21,7 +20,6 @@ declare const ConfigSchema: z.ZodReadonly<z.ZodObject<{
|
|
|
21
20
|
}, z.core.$strip>>, z.ZodReadonly<z.ZodObject<{
|
|
22
21
|
define: z.ZodDefault<z.ZodOptional<z.ZodLiteral<false>>>;
|
|
23
22
|
routeHandler: z.ZodDefault<z.ZodBoolean>;
|
|
24
|
-
output: z.ZodExactOptional<z.ZodString>;
|
|
25
23
|
test: z.ZodDefault<z.ZodBoolean>;
|
|
26
24
|
pathAlias: z.ZodExactOptional<z.ZodString>;
|
|
27
25
|
testFramework: z.ZodExactOptional<z.ZodDefault<z.ZodEnum<{
|
|
@@ -330,7 +328,6 @@ declare function parseConfig(config: unknown): {
|
|
|
330
328
|
format?: FormatConfig;
|
|
331
329
|
template?: Readonly<{
|
|
332
330
|
define: true;
|
|
333
|
-
output: string;
|
|
334
331
|
test: boolean;
|
|
335
332
|
pathAlias?: string;
|
|
336
333
|
testFramework?: "vitest" | "vite-plus" | "bun";
|
|
@@ -338,7 +335,6 @@ declare function parseConfig(config: unknown): {
|
|
|
338
335
|
define: false;
|
|
339
336
|
routeHandler: boolean;
|
|
340
337
|
test: boolean;
|
|
341
|
-
output?: string;
|
|
342
338
|
pathAlias?: string;
|
|
343
339
|
testFramework?: "vitest" | "vite-plus" | "bun";
|
|
344
340
|
}>;
|
|
@@ -625,7 +621,6 @@ declare function readConfig(): Promise<{
|
|
|
625
621
|
format?: FormatConfig;
|
|
626
622
|
template?: Readonly<{
|
|
627
623
|
define: true;
|
|
628
|
-
output: string;
|
|
629
624
|
test: boolean;
|
|
630
625
|
pathAlias?: string;
|
|
631
626
|
testFramework?: "vitest" | "vite-plus" | "bun";
|
|
@@ -633,7 +628,6 @@ declare function readConfig(): Promise<{
|
|
|
633
628
|
define: false;
|
|
634
629
|
routeHandler: boolean;
|
|
635
630
|
test: boolean;
|
|
636
|
-
output?: string;
|
|
637
631
|
pathAlias?: string;
|
|
638
632
|
testFramework?: "vitest" | "vite-plus" | "bun";
|
|
639
633
|
}>;
|
|
@@ -902,14 +896,12 @@ declare function defineConfig(config: z.input<typeof ConfigSchema>): Readonly<{
|
|
|
902
896
|
format?: FormatConfig;
|
|
903
897
|
template?: Readonly<{
|
|
904
898
|
define: true;
|
|
905
|
-
output?: string | undefined;
|
|
906
899
|
test?: boolean | undefined;
|
|
907
900
|
pathAlias?: string;
|
|
908
901
|
testFramework?: "vitest" | "vite-plus" | "bun" | undefined;
|
|
909
902
|
}> | Readonly<{
|
|
910
903
|
define?: false | undefined;
|
|
911
904
|
routeHandler?: boolean | undefined;
|
|
912
|
-
output?: string;
|
|
913
905
|
test?: boolean | undefined;
|
|
914
906
|
pathAlias?: string;
|
|
915
907
|
testFramework?: "vitest" | "vite-plus" | "bun" | undefined;
|
package/dist/config/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { i as deriveAppEntry } from "../utils-B5PbMfWs.js";
|
|
1
2
|
import { existsSync } from "node:fs";
|
|
2
3
|
import { resolve } from "node:path";
|
|
3
4
|
import { pathToFileURL } from "node:url";
|
|
@@ -64,7 +65,6 @@ const ConfigSchema = z.object({
|
|
|
64
65
|
format: z.custom(() => true).exactOptional(),
|
|
65
66
|
template: z.discriminatedUnion("define", [z.object({
|
|
66
67
|
define: z.literal(true),
|
|
67
|
-
output: z.string().regex(/^(?!.*\.ts$).+/, { error: "template.output must be a directory, not a .ts file" }).default("src/routes"),
|
|
68
68
|
test: z.boolean().default(false),
|
|
69
69
|
pathAlias: z.string().exactOptional(),
|
|
70
70
|
testFramework: z.enum([
|
|
@@ -75,7 +75,6 @@ const ConfigSchema = z.object({
|
|
|
75
75
|
}).readonly(), z.object({
|
|
76
76
|
define: z.literal(false).optional().default(false),
|
|
77
77
|
routeHandler: z.boolean().default(false),
|
|
78
|
-
output: z.string().regex(/^(?!.*\.ts$).+/, { error: "template.output must be a directory, not a .ts file" }).exactOptional(),
|
|
79
78
|
test: z.boolean().default(false),
|
|
80
79
|
pathAlias: z.string().exactOptional(),
|
|
81
80
|
testFramework: z.enum([
|
|
@@ -174,7 +173,28 @@ const ConfigSchema = z.object({
|
|
|
174
173
|
entry: z.string().exactOptional(),
|
|
175
174
|
baseUrl: z.string().exactOptional()
|
|
176
175
|
}).readonly()]).exactOptional()
|
|
177
|
-
}).readonly().refine((v) => !(v.output && v.routes), { message: "output and routes are mutually exclusive. Use output for single-file mode, or routes for separate route output." }).refine((v) => !(v.template?.define && !v.output), { message: "template.define
|
|
176
|
+
}).readonly().refine((v) => !(v.output && v.routes), { message: "output and routes are mutually exclusive. Use output for single-file mode, or routes for separate route output." }).refine((v) => !(v.template?.define === true && v.routes), { message: "template.define and routes are mutually exclusive. define derives routes/ next to the app entry (output, default src/index.ts)." }).refine((v) => !(v.template?.define === true && v.output !== void 0 && !(v.output === "index.ts" || v.output.endsWith("/index.ts"))), { message: "with template.define, output is the app entry and must be an index.ts file (e.g. ./src/index.ts), or omitted to default to src/index.ts. Other names collide with the derived routes/ directory." }).refine((v) => !(v.template?.define === true && v.components !== void 0 && [
|
|
177
|
+
"schemas",
|
|
178
|
+
"responses",
|
|
179
|
+
"parameters",
|
|
180
|
+
"examples",
|
|
181
|
+
"requestBodies",
|
|
182
|
+
"headers",
|
|
183
|
+
"securitySchemes",
|
|
184
|
+
"links",
|
|
185
|
+
"callbacks",
|
|
186
|
+
"pathItems",
|
|
187
|
+
"mediaTypes"
|
|
188
|
+
].some((k) => v.components?.[k] !== void 0)), { message: "with template.define, per-type component outputs (components.schemas, components.responses, ...) are not supported. Use components.output for a single components file." }).refine((v) => {
|
|
189
|
+
if (v.template?.define !== true || v.components?.output === void 0) return true;
|
|
190
|
+
const normalize = (p) => p.replace(/^\.\//, "");
|
|
191
|
+
const componentsOutput = normalize(v.components.output);
|
|
192
|
+
const appEntry = normalize(v.output ?? deriveAppEntry(componentsOutput));
|
|
193
|
+
if (!appEntry.endsWith("index.ts")) return true;
|
|
194
|
+
const baseDir = appEntry === "index.ts" ? "" : appEntry.slice(0, -9);
|
|
195
|
+
const routesDir = baseDir === "" ? "routes" : `${baseDir}/routes`;
|
|
196
|
+
return componentsOutput !== appEntry && !componentsOutput.startsWith(`${routesDir}/`);
|
|
197
|
+
}, { message: "with template.define, components.output must not point at the app entry or inside the derived routes/ directory (it would be overwritten). Choose another path, e.g. src/components/index.ts." });
|
|
178
198
|
function parseConfig(config) {
|
|
179
199
|
const result = ConfigSchema.safeParse(config);
|
|
180
200
|
if (!result.success) {
|
package/dist/core/docs/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as docs } from "../../docs-
|
|
1
|
+
import { t as docs } from "../../docs-DbknIWmx.js";
|
|
2
2
|
export { docs };
|
package/dist/core/hooks/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as hooks } from "../../hooks-
|
|
1
|
+
import { t as hooks } from "../../hooks-CCw7jm8g.js";
|
|
2
2
|
export { hooks };
|
package/dist/core/rpc/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { f as methodPath, u as makeInferRequestType } from "../../utils-B5PbMfWs.js";
|
|
1
2
|
import { t as emit } from "../../emit-CFR63U4L.js";
|
|
2
|
-
import {
|
|
3
|
-
import { a as parsePathItem, i as operationHasArgs, o as resolveSplitOutDir, r as makeOperationDeps, t as formatPath } from "../../rpc-
|
|
3
|
+
import { c as isOperationLike, f as isRecord, o as isOpenAPIPaths } from "../../guard-BSZ8ezEv.js";
|
|
4
|
+
import { a as parsePathItem, i as operationHasArgs, o as resolveSplitOutDir, r as makeOperationDeps, t as formatPath } from "../../rpc-BzPo2tZf.js";
|
|
4
5
|
import path from "node:path";
|
|
5
6
|
//#region src/core/rpc/index.ts
|
|
6
7
|
/**
|
package/dist/core/type/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { d as makeSafeKey, g as statusCodeToNumber } from "../../utils-B5PbMfWs.js";
|
|
1
2
|
import { t as emit } from "../../emit-CFR63U4L.js";
|
|
2
|
-
import {
|
|
3
|
+
import { g as isSchemaArray, i as isMediaWithSchema, l as isParameter, m as isRequestBody, n as isHttpMethod, s as isOperation, u as isParameterArray, x as isStringRef } from "../../guard-BSZ8ezEv.js";
|
|
3
4
|
import path from "node:path";
|
|
4
5
|
//#region src/core/type/index.ts
|
|
5
6
|
/**
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { s as escapeHtml } from "./utils-B5PbMfWs.js";
|
|
1
2
|
import { a as writeFile, t as mkdir } from "./fsp-BXry-Hx5.js";
|
|
2
|
-
import {
|
|
3
|
+
import { a as isOAuthFlowValue, b as isSecurityScheme, f as isRecord, h as isResponses, m as isRequestBody, p as isRefObject, r as isMedia, y as isSecurityArray } from "./guard-BSZ8ezEv.js";
|
|
3
4
|
import path from "node:path";
|
|
4
5
|
import { STATUS_CODES } from "node:http";
|
|
5
6
|
//#region src/generator/docs/index.ts
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as zodOpenAPIHono } from "../../../openapi-
|
|
1
|
+
import { t as zodOpenAPIHono } from "../../../openapi-BlTDQFFF.js";
|
|
2
2
|
export { zodOpenAPIHono };
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
//#region src/guard/index.ts
|
|
2
|
+
function isRecord(v) {
|
|
3
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
4
|
+
}
|
|
5
|
+
function isHttpMethod(method) {
|
|
6
|
+
return method === "get" || method === "put" || method === "post" || method === "delete" || method === "patch" || method === "options" || method === "head" || method === "trace";
|
|
7
|
+
}
|
|
8
|
+
function isValidIdent(str) {
|
|
9
|
+
return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(str);
|
|
10
|
+
}
|
|
11
|
+
function isOpenAPIPaths(v) {
|
|
12
|
+
if (typeof v !== "object" || v === null || Array.isArray(v)) return false;
|
|
13
|
+
return Object.values(v).every((entry) => typeof entry === "object" && entry !== null && !Array.isArray(entry));
|
|
14
|
+
}
|
|
15
|
+
function isRefObject(v) {
|
|
16
|
+
return typeof v === "object" && v !== null && !Array.isArray(v) && "$ref" in v && typeof v.$ref === "string";
|
|
17
|
+
}
|
|
18
|
+
function isStringRef(v) {
|
|
19
|
+
return "$ref" in v && typeof v.$ref === "string";
|
|
20
|
+
}
|
|
21
|
+
function isParameterObject(v) {
|
|
22
|
+
if (typeof v !== "object" || v === null || Array.isArray(v)) return false;
|
|
23
|
+
if (!("name" in v) || typeof v.name !== "string") return false;
|
|
24
|
+
if (!("in" in v)) return false;
|
|
25
|
+
const pos = v.in;
|
|
26
|
+
return pos === "path" || pos === "query" || pos === "header" || pos === "cookie";
|
|
27
|
+
}
|
|
28
|
+
function isParameter(v) {
|
|
29
|
+
return typeof v === "object" && v !== null && "name" in v && "in" in v && ("schema" in v || "content" in v);
|
|
30
|
+
}
|
|
31
|
+
function isParameterArray(v) {
|
|
32
|
+
return Array.isArray(v);
|
|
33
|
+
}
|
|
34
|
+
function isOperationLike(v) {
|
|
35
|
+
return typeof v === "object" && v !== null && !Array.isArray(v) && "responses" in v;
|
|
36
|
+
}
|
|
37
|
+
function isOperation(v) {
|
|
38
|
+
return typeof v === "object" && v !== null && "responses" in v;
|
|
39
|
+
}
|
|
40
|
+
function isSchemaProperty(v) {
|
|
41
|
+
return typeof v === "object" && v !== null && !Array.isArray(v) && "schema" in v;
|
|
42
|
+
}
|
|
43
|
+
function isSchemaArray(v) {
|
|
44
|
+
return Array.isArray(v);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* JSON Schema 2020-12 §4.3.2 / §10.3.1.2 type guard for schema-vs-boolean
|
|
48
|
+
* discrimination. `items`, `additionalProperties`, `unevaluatedItems`, and
|
|
49
|
+
* `unevaluatedProperties` may be either an object schema or a boolean schema
|
|
50
|
+
* (`true` = pass-through, `false` = reject). This narrows the union to the
|
|
51
|
+
* object form so callers can recurse without `as` casts.
|
|
52
|
+
*/
|
|
53
|
+
function isSchemaObject(v) {
|
|
54
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
55
|
+
}
|
|
56
|
+
function isMediaWithSchema(v) {
|
|
57
|
+
return typeof v === "object" && v !== null && "schema" in v;
|
|
58
|
+
}
|
|
59
|
+
function isMedia(v) {
|
|
60
|
+
return typeof v === "object" && v !== null && "schema" in v;
|
|
61
|
+
}
|
|
62
|
+
function isRequestBody(v) {
|
|
63
|
+
return typeof v === "object" && v !== null && ("content" in v || "required" in v || "description" in v);
|
|
64
|
+
}
|
|
65
|
+
function isContentBody(v) {
|
|
66
|
+
return typeof v === "object" && v !== null && !("$ref" in v);
|
|
67
|
+
}
|
|
68
|
+
function isSecurityScheme(v) {
|
|
69
|
+
return typeof v === "object" && v !== null && !("$ref" in v);
|
|
70
|
+
}
|
|
71
|
+
function isSecurityArray(v) {
|
|
72
|
+
return Array.isArray(v);
|
|
73
|
+
}
|
|
74
|
+
function isResponses(v) {
|
|
75
|
+
if (typeof v !== "object" || v === null || Array.isArray(v)) return false;
|
|
76
|
+
return "$ref" in v || "description" in v || "content" in v || "headers" in v || "links" in v;
|
|
77
|
+
}
|
|
78
|
+
function isOAuthFlowValue(v) {
|
|
79
|
+
if (typeof v !== "object" || v === null || Array.isArray(v)) return false;
|
|
80
|
+
return "authorizationUrl" in v || "tokenUrl" in v || "scopes" in v;
|
|
81
|
+
}
|
|
82
|
+
//#endregion
|
|
83
|
+
export { isValidIdent as S, isSchemaObject as _, isOAuthFlowValue as a, isSecurityScheme as b, isOperationLike as c, isParameterObject as d, isRecord as f, isSchemaArray as g, isResponses as h, isMediaWithSchema as i, isParameter as l, isRequestBody as m, isHttpMethod as n, isOpenAPIPaths as o, isRefObject as p, isMedia as r, isOperation as s, isContentBody as t, isParameterArray as u, isSchemaProperty as v, isStringRef as x, isSecurityArray as y };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { _ as toIdentifierPascalCase, f as methodPath, n as capitalize } from "./utils-B5PbMfWs.js";
|
|
1
2
|
import { t as emit } from "./emit-CFR63U4L.js";
|
|
2
|
-
import {
|
|
3
|
-
import { a as parsePathItem, i as operationHasArgs, n as hasNoContentResponse, o as resolveSplitOutDir, r as makeOperationDeps, t as formatPath } from "./rpc-
|
|
3
|
+
import { c as isOperationLike, f as isRecord, o as isOpenAPIPaths } from "./guard-BSZ8ezEv.js";
|
|
4
|
+
import { a as parsePathItem, i as operationHasArgs, n as hasNoContentResponse, o as resolveSplitOutDir, r as makeOperationDeps, t as formatPath } from "./rpc-BzPo2tZf.js";
|
|
4
5
|
import path from "node:path";
|
|
5
6
|
//#region src/helper/query.ts
|
|
6
7
|
function makeHookName(method, pathStr, prefix) {
|
|
@@ -313,7 +314,7 @@ function makeSWRHeader(importPath, clientName, hasQuery, hasMutation, hasAnyArgs
|
|
|
313
314
|
lines.push("import useSWRMutation from'swr/mutation'");
|
|
314
315
|
lines.push("import type{SWRMutationConfiguration}from'swr/mutation'");
|
|
315
316
|
}
|
|
316
|
-
const honoTypeImports = ["ClientRequestOptions", ...hasAnyArgs ? ["InferRequestType"] : []];
|
|
317
|
+
const honoTypeImports = ["ClientRequestOptions", ...hasAnyArgs || hasInfiniteQuery ? ["InferRequestType"] : []];
|
|
317
318
|
lines.push(`import type{${honoTypeImports.join(",")}}from'hono/client'`);
|
|
318
319
|
lines.push("import{parseResponse}from'hono/client'");
|
|
319
320
|
lines.push(`import{${clientName}}from'${importPath}'`);
|
|
@@ -515,7 +516,7 @@ function makeHeader(importPath, clientName, hasQuery, hasMutation, hasAnyArgs, c
|
|
|
515
516
|
...hasMutation ? [config.useMutationOptionsType] : []
|
|
516
517
|
];
|
|
517
518
|
const needsVueImports = config.isVueQuery && hasQueryWithArgs;
|
|
518
|
-
const honoTypeImports = ["ClientRequestOptions", ...hasAnyArgs ? ["InferRequestType"] : []];
|
|
519
|
+
const honoTypeImports = ["ClientRequestOptions", ...hasAnyArgs || hasInfiniteQuery ? ["InferRequestType"] : []];
|
|
519
520
|
return `${[
|
|
520
521
|
...queryImports.length > 0 ? [`import{${queryImports.join(",")}}from'${config.packageName}'`] : [],
|
|
521
522
|
...typeImports.length > 0 ? [`import type{${typeImports.join(",")}}from'${config.packageName}'`] : [],
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { readConfig } from "./config/index.js";
|
|
3
3
|
import { r as setFormatOptions } from "./emit-CFR63U4L.js";
|
|
4
|
-
import { n as parseOpenAPI, r as takibi, t as makeJob } from "./shared-
|
|
4
|
+
import { n as parseOpenAPI, r as takibi, t as makeJob } from "./shared-B-TUm7Fa.js";
|
|
5
5
|
import { existsSync } from "node:fs";
|
|
6
6
|
import { resolve } from "node:path";
|
|
7
7
|
//#region src/cli/index.ts
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { _ as toIdentifierPascalCase, a as ensureSuffix, c as escapeRegexLiteral, d as makeSafeKey, f as methodPath, h as requestParamsArray, m as renderNamedImport, o as error, p as normalizeTypes, r as cyclicNodes, t as baseError, v as uncapitalize, y as zodToOpenAPISchema } from "./utils-B5PbMfWs.js";
|
|
2
|
+
import { _ as isSchemaObject, f as isRecord, g as isSchemaArray, l as isParameter, p as isRefObject, r as isMedia, s as isOperation } from "./guard-BSZ8ezEv.js";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import ts from "typescript";
|
|
4
5
|
//#region src/helper/code.ts
|
|
@@ -1634,108 +1635,33 @@ function getChildren(node) {
|
|
|
1634
1635
|
return semanticChildren;
|
|
1635
1636
|
}
|
|
1636
1637
|
function collectIdentifiers(node) {
|
|
1638
|
+
const identifiers = [];
|
|
1637
1639
|
const visit = (n) => {
|
|
1638
|
-
|
|
1639
|
-
const
|
|
1640
|
-
return [...current, ...children];
|
|
1640
|
+
if (ts.isIdentifier(n)) identifiers.push(n.text);
|
|
1641
|
+
for (const child of getChildren(n)) visit(child);
|
|
1641
1642
|
};
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
function extractIdentifiers(code, varNames) {
|
|
1645
|
-
const allIdentifiers = collectIdentifiers(makeSourceFile(code));
|
|
1646
|
-
return [...new Set(allIdentifiers.filter((id) => varNames.has(id)))];
|
|
1647
|
-
}
|
|
1648
|
-
function createInitialState() {
|
|
1649
|
-
return {
|
|
1650
|
-
indices: /* @__PURE__ */ new Map(),
|
|
1651
|
-
lowLinks: /* @__PURE__ */ new Map(),
|
|
1652
|
-
onStack: /* @__PURE__ */ new Set(),
|
|
1653
|
-
stack: [],
|
|
1654
|
-
sccs: [],
|
|
1655
|
-
index: 0
|
|
1656
|
-
};
|
|
1657
|
-
}
|
|
1658
|
-
function popStackUntil(stack, onStack, name) {
|
|
1659
|
-
const idx = stack.lastIndexOf(name);
|
|
1660
|
-
if (idx === -1) return {
|
|
1661
|
-
scc: [],
|
|
1662
|
-
newStack: stack,
|
|
1663
|
-
newOnStack: onStack
|
|
1664
|
-
};
|
|
1665
|
-
const scc = stack.slice(idx);
|
|
1666
|
-
return {
|
|
1667
|
-
scc,
|
|
1668
|
-
newStack: stack.slice(0, idx),
|
|
1669
|
-
newOnStack: new Set([...onStack].filter((n) => !scc.includes(n)))
|
|
1670
|
-
};
|
|
1671
|
-
}
|
|
1672
|
-
function tarjanConnect(name, deps, var2name, state) {
|
|
1673
|
-
const currentIndex = state.index;
|
|
1674
|
-
const indices = new Map(state.indices).set(name, currentIndex);
|
|
1675
|
-
const lowLinks = new Map(state.lowLinks).set(name, currentIndex);
|
|
1676
|
-
const stack = [...state.stack, name];
|
|
1677
|
-
const onStack = /* @__PURE__ */ new Set([...state.onStack, name]);
|
|
1678
|
-
const initialState = {
|
|
1679
|
-
...state,
|
|
1680
|
-
indices,
|
|
1681
|
-
lowLinks,
|
|
1682
|
-
stack,
|
|
1683
|
-
onStack,
|
|
1684
|
-
index: currentIndex + 1
|
|
1685
|
-
};
|
|
1686
|
-
const afterDeps = (deps.get(name) ?? []).reduce((s, depVar) => {
|
|
1687
|
-
const depName = var2name.get(depVar);
|
|
1688
|
-
if (depName === void 0) return s;
|
|
1689
|
-
if (!s.indices.has(depName)) {
|
|
1690
|
-
const afterConnect = tarjanConnect(depName, deps, var2name, s);
|
|
1691
|
-
const newLowLink = Math.min(afterConnect.lowLinks.get(name) ?? 0, afterConnect.lowLinks.get(depName) ?? 0);
|
|
1692
|
-
const updatedLowLinks = new Map(afterConnect.lowLinks).set(name, newLowLink);
|
|
1693
|
-
return {
|
|
1694
|
-
...afterConnect,
|
|
1695
|
-
lowLinks: updatedLowLinks
|
|
1696
|
-
};
|
|
1697
|
-
}
|
|
1698
|
-
if (s.onStack.has(depName)) {
|
|
1699
|
-
const newLowLink = Math.min(s.lowLinks.get(name) ?? 0, s.indices.get(depName) ?? 0);
|
|
1700
|
-
const updatedLowLinks = new Map(s.lowLinks).set(name, newLowLink);
|
|
1701
|
-
return {
|
|
1702
|
-
...s,
|
|
1703
|
-
lowLinks: updatedLowLinks
|
|
1704
|
-
};
|
|
1705
|
-
}
|
|
1706
|
-
return s;
|
|
1707
|
-
}, initialState);
|
|
1708
|
-
if (afterDeps.lowLinks.get(name) === afterDeps.indices.get(name)) {
|
|
1709
|
-
const { scc, newStack, newOnStack } = popStackUntil(afterDeps.stack, afterDeps.onStack, name);
|
|
1710
|
-
return {
|
|
1711
|
-
...afterDeps,
|
|
1712
|
-
stack: newStack,
|
|
1713
|
-
onStack: newOnStack,
|
|
1714
|
-
sccs: [...afterDeps.sccs, scc]
|
|
1715
|
-
};
|
|
1716
|
-
}
|
|
1717
|
-
return afterDeps;
|
|
1643
|
+
visit(node);
|
|
1644
|
+
return identifiers;
|
|
1718
1645
|
}
|
|
1719
1646
|
function findCyclicSchemas(names, deps) {
|
|
1720
|
-
const name2var = new Map(names.map((n) => [n, toIdentifierPascalCase(ensureSuffix(n, "Schema"))]));
|
|
1721
1647
|
const var2name = new Map(names.map((n) => [toIdentifierPascalCase(ensureSuffix(n, "Schema")), n]));
|
|
1722
|
-
|
|
1723
|
-
return new Set(finalState.sccs.flatMap((scc) => {
|
|
1724
|
-
if (scc.length > 1) return [...scc];
|
|
1725
|
-
const single = scc[0];
|
|
1726
|
-
if (!single) return [];
|
|
1727
|
-
const selfVar = name2var.get(single);
|
|
1728
|
-
return selfVar && (deps.get(single) ?? []).includes(selfVar) ? [single] : [];
|
|
1729
|
-
}));
|
|
1648
|
+
return cyclicNodes(new Map(names.map((n) => [n, (deps.get(n) ?? []).map((v) => var2name.get(v)).filter((x) => x !== void 0)])));
|
|
1730
1649
|
}
|
|
1731
1650
|
function analyzeCircularSchemas(schemas, schemaNames, readonly) {
|
|
1732
1651
|
const varNameSet = new Set(schemaNames.map((n) => toIdentifierPascalCase(ensureSuffix(n, "Schema"))));
|
|
1733
1652
|
const varNameToName = new Map(schemaNames.map((n) => [toIdentifierPascalCase(ensureSuffix(n, "Schema")), n]));
|
|
1734
1653
|
const zSchemaMap = new Map(schemaNames.map((n) => [n, zodToOpenAPI(schemas[n], void 0, readonly === true ? { readonly: true } : void 0)]));
|
|
1654
|
+
const batchedSource = makeSourceFile(schemaNames.map((n) => `const ${toIdentifierPascalCase(ensureSuffix(n, "Schema"))} = ${zSchemaMap.get(n)}`).join("\n"));
|
|
1655
|
+
const initializerIdentifiers = new Map(batchedSource.statements.flatMap((statement) => {
|
|
1656
|
+
if (!ts.isVariableStatement(statement)) return [];
|
|
1657
|
+
const declaration = statement.declarationList.declarations[0];
|
|
1658
|
+
if (!(declaration && ts.isIdentifier(declaration.name) && declaration.initializer)) return [];
|
|
1659
|
+
return [[declaration.name.text, collectIdentifiers(declaration.initializer)]];
|
|
1660
|
+
}));
|
|
1735
1661
|
const depsMap = new Map(schemaNames.map((n) => {
|
|
1736
|
-
const code = zSchemaMap.get(n) ?? "";
|
|
1737
1662
|
const selfVar = toIdentifierPascalCase(ensureSuffix(n, "Schema"));
|
|
1738
|
-
|
|
1663
|
+
const identifiers = initializerIdentifiers.get(selfVar) ?? [];
|
|
1664
|
+
return [n, [...new Set(identifiers.filter((id) => varNameSet.has(id)))].filter((v) => v !== selfVar)];
|
|
1739
1665
|
}));
|
|
1740
1666
|
const cyclicSchemas = findCyclicSchemas(schemaNames, depsMap);
|
|
1741
1667
|
const extendedCyclicSchemas = /* @__PURE__ */ new Set([...cyclicSchemas, ...[...cyclicSchemas].flatMap((n) => (depsMap.get(n) ?? []).map((v) => varNameToName.get(v)).filter((x) => x !== void 0))]);
|
|
@@ -1799,27 +1725,24 @@ function topoSort(decls) {
|
|
|
1799
1725
|
const makeKey = (kind, name) => `${kind}:${name}`;
|
|
1800
1726
|
const map = new Map(decls.map((d) => [makeKey(d.kind, d.name), d]));
|
|
1801
1727
|
const findByName = (name) => map.get(makeKey("variable", name)) ?? map.get(makeKey("type", name)) ?? map.get(makeKey("interface", name));
|
|
1802
|
-
const
|
|
1803
|
-
|
|
1728
|
+
const sorted = [];
|
|
1729
|
+
const perm = /* @__PURE__ */ new Set();
|
|
1730
|
+
const temp = /* @__PURE__ */ new Set();
|
|
1731
|
+
const visit = (key) => {
|
|
1732
|
+
if (perm.has(key) || temp.has(key)) return;
|
|
1804
1733
|
const decl = map.get(key);
|
|
1805
|
-
if (!decl) return
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
temp: new Set([...afterRefs.temp].filter((t) => t !== key))
|
|
1815
|
-
};
|
|
1816
|
-
};
|
|
1817
|
-
const initial = {
|
|
1818
|
-
sorted: [],
|
|
1819
|
-
perm: /* @__PURE__ */ new Set(),
|
|
1820
|
-
temp: /* @__PURE__ */ new Set()
|
|
1734
|
+
if (!decl) return;
|
|
1735
|
+
temp.add(key);
|
|
1736
|
+
for (const ref of decl.refs) {
|
|
1737
|
+
const found = findByName(ref);
|
|
1738
|
+
if (found) visit(makeKey(found.kind, found.name));
|
|
1739
|
+
}
|
|
1740
|
+
temp.delete(key);
|
|
1741
|
+
perm.add(key);
|
|
1742
|
+
sorted.push(decl);
|
|
1821
1743
|
};
|
|
1822
|
-
|
|
1744
|
+
for (const decl of decls) visit(makeKey(decl.kind, decl.name));
|
|
1745
|
+
return sorted;
|
|
1823
1746
|
}
|
|
1824
1747
|
function ast(code) {
|
|
1825
1748
|
const decls = parseStatements(makeSourceFile(code));
|
|
@@ -2365,6 +2288,7 @@ function componentsCode(components, options) {
|
|
|
2365
2288
|
function routeCode(openapi, readonly) {
|
|
2366
2289
|
const routeEntries = (openapi, readonly) => {
|
|
2367
2290
|
const makeEntry = (path, method, operation, readonly) => {
|
|
2291
|
+
const request = makeRequest(operation.parameters, operation.requestBody, readonly);
|
|
2368
2292
|
const properties = [
|
|
2369
2293
|
`method:${JSON.stringify(method)}`,
|
|
2370
2294
|
`path:${JSON.stringify(path)}`,
|
|
@@ -2373,7 +2297,7 @@ function routeCode(openapi, readonly) {
|
|
|
2373
2297
|
operation.description ? `description:${JSON.stringify(operation.description)}` : void 0,
|
|
2374
2298
|
operation.externalDocs ? `externalDocs:${JSON.stringify(operation.externalDocs)}` : void 0,
|
|
2375
2299
|
operation.operationId ? `operationId:${JSON.stringify(operation.operationId)}` : void 0,
|
|
2376
|
-
|
|
2300
|
+
request ? `request:${request}` : void 0,
|
|
2377
2301
|
operation.responses ? `responses:${makeOperationResponses(operation.responses, readonly)}` : void 0,
|
|
2378
2302
|
operation.callbacks ? `callbacks:{${makeCallbacks(operation.callbacks, readonly)}}` : void 0,
|
|
2379
2303
|
operation.deprecated ? `deprecated:${JSON.stringify(operation.deprecated)}` : void 0,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { S as isValidIdent, c as isOperationLike, d as isParameterObject, f as isRecord, p as isRefObject, v as isSchemaProperty } from "./guard-
|
|
1
|
+
import { S as isValidIdent, c as isOperationLike, d as isParameterObject, f as isRecord, p as isRefObject, v as isSchemaProperty } from "./guard-BSZ8ezEv.js";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
//#region src/helper/rpc.ts
|
|
4
4
|
function makeEscaped(s) {
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { _ as toIdentifierPascalCase, a as ensureSuffix, c as escapeRegexLiteral, f as methodPath, g as statusCodeToNumber, i as deriveAppEntry, l as makeBarrel, m as renderNamedImport, r as cyclicNodes, v as uncapitalize, y as zodToOpenAPISchema } from "./utils-B5PbMfWs.js";
|
|
1
2
|
import { n as fmt, t as emit } from "./emit-CFR63U4L.js";
|
|
2
3
|
import { a as writeFile, i as unlink, n as readFile, r as readdir, t as mkdir } from "./fsp-BXry-Hx5.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { t as hooks } from "./hooks-
|
|
6
|
-
import { t as docs } from "./docs-
|
|
4
|
+
import { S as makeModuleSpec, _ as makeRef, a as responsesCode, b as makeExportConst, c as headersCode, d as ast, f as zodToOpenAPI, g as makePathItem, h as makeOperationResponses, i as schemasCode, l as makeSplitSchemaFile, m as makeCallbacks, n as routeCode, o as requestBodiesCode, p as makeCallback, r as componentsCode, s as parametersCode, t as zodOpenAPIHono, u as analyzeCircularSchemas, v as makeRequest, x as makeImports, y as makeConst } from "./openapi-BlTDQFFF.js";
|
|
5
|
+
import { b as isSecurityScheme, i as isMediaWithSchema, l as isParameter, n as isHttpMethod, p as isRefObject, s as isOperation, t as isContentBody, y as isSecurityArray } from "./guard-BSZ8ezEv.js";
|
|
6
|
+
import { t as hooks } from "./hooks-CCw7jm8g.js";
|
|
7
|
+
import { t as docs } from "./docs-DbknIWmx.js";
|
|
7
8
|
import { rpc } from "./core/rpc/index.js";
|
|
8
9
|
import { type } from "./core/type/index.js";
|
|
9
10
|
import path from "node:path";
|
|
@@ -652,7 +653,9 @@ function schemaToFaker(schema, propertyName, options = {}) {
|
|
|
652
653
|
if (schema.format && FORMAT_TO_FAKER[schema.format]) return FORMAT_TO_FAKER[schema.format];
|
|
653
654
|
if (propertyName && PROPERTY_NAME_TO_FAKER[propertyName]) {
|
|
654
655
|
const hint = PROPERTY_NAME_TO_FAKER[propertyName];
|
|
655
|
-
|
|
656
|
+
const isNumberHint = hint.includes("faker.number.");
|
|
657
|
+
const hasStringConstraint = schema.pattern !== void 0 || schema.minLength !== void 0 || schema.maxLength !== void 0;
|
|
658
|
+
if (!(schema.type === "string" && (isNumberHint || hasStringConstraint))) return hint;
|
|
656
659
|
}
|
|
657
660
|
if (schema.type && typeof schema.type === "string" && TYPE_TO_FAKER[schema.type]) {
|
|
658
661
|
if (schema.type === "string") {
|
|
@@ -708,34 +711,7 @@ function topologicalSort(refs, schemas) {
|
|
|
708
711
|
return result;
|
|
709
712
|
}
|
|
710
713
|
function detectCircularSchemas$1(schemas) {
|
|
711
|
-
|
|
712
|
-
for (const name of Object.keys(schemas)) {
|
|
713
|
-
const schema = schemas[name];
|
|
714
|
-
if (!schema) continue;
|
|
715
|
-
for (const dep of collectRefs(schema)) {
|
|
716
|
-
if (dep === name) {
|
|
717
|
-
circular.add(name);
|
|
718
|
-
break;
|
|
719
|
-
}
|
|
720
|
-
const visited = /* @__PURE__ */ new Set();
|
|
721
|
-
const stack = [dep];
|
|
722
|
-
while (stack.length > 0) {
|
|
723
|
-
const current = stack[stack.length - 1];
|
|
724
|
-
stack.length -= 1;
|
|
725
|
-
if (current === void 0) break;
|
|
726
|
-
if (current === name) {
|
|
727
|
-
circular.add(name);
|
|
728
|
-
break;
|
|
729
|
-
}
|
|
730
|
-
if (visited.has(current)) continue;
|
|
731
|
-
visited.add(current);
|
|
732
|
-
const s = schemas[current];
|
|
733
|
-
if (s) for (const r of collectRefs(s)) stack.push(r);
|
|
734
|
-
}
|
|
735
|
-
if (circular.has(name)) break;
|
|
736
|
-
}
|
|
737
|
-
}
|
|
738
|
-
return circular;
|
|
714
|
+
return cyclicNodes(new Map(Object.entries(schemas).map(([name, schema]) => [name, [...collectRefs(schema)]])));
|
|
739
715
|
}
|
|
740
716
|
function collectSchemaRefs$1(node, refs) {
|
|
741
717
|
if (Array.isArray(node)) {
|
|
@@ -1014,22 +990,24 @@ function makeMock(openapi, basePath, options = {}) {
|
|
|
1014
990
|
const routes = routeCode(filteredOpenapi, readonlyOption);
|
|
1015
991
|
const appSetup = routeMetas.map(({ routeId }) => `.openapi(${routeId}Route, ${routeId}RouteHandler)`).join("");
|
|
1016
992
|
const handlersJoined = handlers.join("\n\n");
|
|
993
|
+
const mockFunctionsJoined = mockFunctions.join("\n\n");
|
|
994
|
+
const bigIntSerializer = `${mockFunctionsJoined}\n${handlersJoined}`.includes("faker.number.bigInt(") ? `if(typeof BigInt.prototype.toJSON!=='function'){Object.defineProperty(BigInt.prototype,'toJSON',{value(this:bigint){return this.toString()},writable:true,configurable:true})}` : "";
|
|
1017
995
|
const needsCookieImport = handlersJoined.includes("getCookie(c,");
|
|
1018
996
|
const imports = `import { OpenAPIHono, createRoute, z, type RouteHandler } from '@hono/zod-openapi'
|
|
1019
997
|
${locale ? `import { faker } from '@faker-js/faker/locale/${locale}'` : `import { faker } from '@faker-js/faker'`}${needsCookieImport ? `\nimport { getCookie } from 'hono/cookie'` : ""}`;
|
|
1020
998
|
const delayMiddleware = delayMiddlewareCode(delay);
|
|
1021
|
-
const appCode = `const app = new OpenAPIHono()${basePath !== "/" ? `.basePath('${basePath}')` : ""}${delayMiddleware}
|
|
1022
|
-
|
|
1023
|
-
export const api = app${appSetup}
|
|
1024
|
-
|
|
1025
|
-
export default app`;
|
|
1026
999
|
return [
|
|
1027
1000
|
imports,
|
|
1001
|
+
bigIntSerializer,
|
|
1028
1002
|
components,
|
|
1029
1003
|
routes,
|
|
1030
|
-
|
|
1004
|
+
mockFunctionsJoined,
|
|
1031
1005
|
handlersJoined,
|
|
1032
|
-
|
|
1006
|
+
`const app = new OpenAPIHono()${basePath !== "/" ? `.basePath('${basePath}')` : ""}${delayMiddleware}
|
|
1007
|
+
|
|
1008
|
+
export const api = app${appSetup}
|
|
1009
|
+
|
|
1010
|
+
export default app`
|
|
1033
1011
|
].filter((s) => s.length > 0).join("\n\n");
|
|
1034
1012
|
}
|
|
1035
1013
|
//#endregion
|
|
@@ -1302,20 +1280,8 @@ function shallowRefs(schema) {
|
|
|
1302
1280
|
...compositeRefs
|
|
1303
1281
|
];
|
|
1304
1282
|
}
|
|
1305
|
-
function reachesSelf(start, target, schemas, visited = /* @__PURE__ */ new Set()) {
|
|
1306
|
-
if (start === target) return true;
|
|
1307
|
-
if (visited.has(start)) return false;
|
|
1308
|
-
visited.add(start);
|
|
1309
|
-
const schema = schemas[start];
|
|
1310
|
-
if (!schema) return false;
|
|
1311
|
-
return shallowRefs(schema).some((dep) => reachesSelf(dep, target, schemas, visited));
|
|
1312
|
-
}
|
|
1313
1283
|
function detectCircularSchemas(schemas) {
|
|
1314
|
-
return new
|
|
1315
|
-
const schema = schemas[name];
|
|
1316
|
-
if (!schema) return false;
|
|
1317
|
-
return shallowRefs(schema).some((dep) => reachesSelf(dep, name, schemas));
|
|
1318
|
-
}));
|
|
1284
|
+
return cyclicNodes(new Map(Object.entries(schemas).map(([name, schema]) => [name, shallowRefs(schema)])));
|
|
1319
1285
|
}
|
|
1320
1286
|
function topologicalOrder(usedSchemaNames, schemas) {
|
|
1321
1287
|
const visit = (name, visited, visiting) => {
|
|
@@ -1333,10 +1299,10 @@ function topologicalOrder(usedSchemaNames, schemas) {
|
|
|
1333
1299
|
const visited = /* @__PURE__ */ new Set();
|
|
1334
1300
|
return [...usedSchemaNames].flatMap((name) => visit(name, visited, /* @__PURE__ */ new Set()));
|
|
1335
1301
|
}
|
|
1336
|
-
function makeMockFunctions(spec, usedSchemaNames) {
|
|
1302
|
+
function makeMockFunctions(spec, usedSchemaNames, circularSchemas) {
|
|
1337
1303
|
if (!spec.components?.schemas || usedSchemaNames.size === 0) return "";
|
|
1338
1304
|
const schemas = spec.components.schemas;
|
|
1339
|
-
const circular = detectCircularSchemas(schemas);
|
|
1305
|
+
const circular = circularSchemas ?? detectCircularSchemas(schemas);
|
|
1340
1306
|
return topologicalOrder(usedSchemaNames, schemas).map((name) => {
|
|
1341
1307
|
const returnType = circular.has(name) ? ": any" : "";
|
|
1342
1308
|
return `function mock${name.replace(/\./g, "")}()${returnType} {\n return ${schemaToFaker(schemas[name])}\n}`;
|
|
@@ -1442,11 +1408,17 @@ function getPathFirstSegment(path) {
|
|
|
1442
1408
|
const sanitized = (path.replace(/^\/+/, "").split("/")[0] ?? "").replace(/\{([^}]+)\}/g, "$1").replace(/[^0-9A-Za-z._-]/g, "_").replace(/^[._-]+|[._-]+$/g, "").replace(/__+/g, "_").replace(/[-._](\w)/g, (_, c) => c.toUpperCase());
|
|
1443
1409
|
return sanitized === "" ? "__root" : sanitized;
|
|
1444
1410
|
}
|
|
1445
|
-
function
|
|
1411
|
+
function makeHandlerTestContext(spec) {
|
|
1412
|
+
return {
|
|
1413
|
+
testCases: extractTestCases(spec),
|
|
1414
|
+
circularSchemas: detectCircularSchemas(spec.components?.schemas ?? {})
|
|
1415
|
+
};
|
|
1416
|
+
}
|
|
1417
|
+
function makeHandlerTestCode(spec, handlerPath, _routeNames, importFrom, basePath = "/", testFramework = "vitest", context = makeHandlerTestContext(spec)) {
|
|
1446
1418
|
const handlerFileName = path.basename(handlerPath, ".ts");
|
|
1447
|
-
const relevantCases =
|
|
1419
|
+
const relevantCases = context.testCases.filter((testCase) => getPathFirstSegment(testCase.path) === handlerFileName);
|
|
1448
1420
|
if (relevantCases.length === 0) return "";
|
|
1449
|
-
const mockFunctions = makeMockFunctions(spec, new Set(relevantCases.flatMap((testCase) => testCase.usedSchemaRefs)));
|
|
1421
|
+
const mockFunctions = makeMockFunctions(spec, new Set(relevantCases.flatMap((testCase) => testCase.usedSchemaRefs)), context.circularSchemas);
|
|
1450
1422
|
const testCasesCode = relevantCases.map((testCase) => makeTestCase(testCase, basePath, spec.components?.schemas)).join("");
|
|
1451
1423
|
const body = `${mockFunctions ? `${mockFunctions}\n\n` : ""}describe(${quoteSingle(handlerFileName.charAt(0).toUpperCase() + handlerFileName.slice(1))},()=>{${testCasesCode}})\n`;
|
|
1452
1424
|
const fakerImport = body.includes("faker.") ? `\nimport{faker}from'@faker-js/faker'` : "";
|
|
@@ -2059,11 +2031,11 @@ function makeHandlerFileName(path) {
|
|
|
2059
2031
|
function makeTestFileName(fileName) {
|
|
2060
2032
|
return `${path.basename(fileName, ".ts")}.test.ts`;
|
|
2061
2033
|
}
|
|
2062
|
-
function makePaths(output, pathAlias, routeImport
|
|
2034
|
+
function makePaths(output, pathAlias, routeImport) {
|
|
2063
2035
|
const isDot = output === "." || output === "./";
|
|
2064
2036
|
const isIndexFile = !isDot && output.endsWith("/index.ts");
|
|
2065
2037
|
const baseDir = isDot ? "." : isIndexFile ? output.match(/^(.*)\/[^/]+\/index\.ts$/)?.[1] ?? "." : output.match(/^(.*)\/[^/]+\.ts$/)?.[1] ?? ".";
|
|
2066
|
-
const handlerPath =
|
|
2038
|
+
const handlerPath = baseDir === "." ? "handlers" : `${baseDir}/handlers`;
|
|
2067
2039
|
const routeModuleName = isIndexFile ? output.match(/([^/]+)\/index\.ts$/)?.[1] ?? "index" : output.endsWith(".ts") ? path.basename(output, ".ts") : "index";
|
|
2068
2040
|
const aliasPrefix = pathAlias?.endsWith("/") ? pathAlias.slice(0, -1) : pathAlias;
|
|
2069
2041
|
return {
|
|
@@ -2161,10 +2133,11 @@ async function removeStaleFiles(handlerPath, generatedFileNames) {
|
|
|
2161
2133
|
* @param test - Whether to generate corresponding test files.
|
|
2162
2134
|
* @returns A `Result` indicating success or error with message.
|
|
2163
2135
|
*/
|
|
2164
|
-
async function zodOpenAPIHonoHandler(openapi, output, test = false, pathAlias, routeImport, routeHandler = false, basePath = "/", testFramework = "vitest"
|
|
2136
|
+
async function zodOpenAPIHonoHandler(openapi, output, test = false, pathAlias, routeImport, routeHandler = false, basePath = "/", testFramework = "vitest") {
|
|
2165
2137
|
const paths = openapi.paths;
|
|
2166
2138
|
const handlers = makeMergedHandlers(Object.entries(paths).flatMap(([path, pathItem]) => Object.entries(pathItem).filter((entry) => isHttpMethod(entry[0]) && isOperation(entry[1])).map(([method]) => routeHandler ? makeStubHandlerInfo(path, method) : makeInlineStubHandlerInfo(path, method))));
|
|
2167
|
-
const { handlerPath, importFrom, testImportFrom } = makePaths(output, pathAlias, routeImport
|
|
2139
|
+
const { handlerPath, importFrom, testImportFrom } = makePaths(output, pathAlias, routeImport);
|
|
2140
|
+
const handlerTestContext = test ? makeHandlerTestContext(openapi) : void 0;
|
|
2168
2141
|
const mkdirResult = await mkdir(handlerPath);
|
|
2169
2142
|
if (!mkdirResult.ok) return {
|
|
2170
2143
|
ok: false,
|
|
@@ -2189,8 +2162,8 @@ async function zodOpenAPIHonoHandler(openapi, output, test = false, pathAlias, r
|
|
|
2189
2162
|
ok: false,
|
|
2190
2163
|
error: writeResult.error
|
|
2191
2164
|
};
|
|
2192
|
-
if (
|
|
2193
|
-
const testContent = makeHandlerTestCode(openapi, `${handlerPath}/${handler.fileName}`, [...handler.routeNames], testImportFrom, basePath, testFramework);
|
|
2165
|
+
if (handlerTestContext) {
|
|
2166
|
+
const testContent = makeHandlerTestCode(openapi, `${handlerPath}/${handler.fileName}`, [...handler.routeNames], testImportFrom, basePath, testFramework, handlerTestContext);
|
|
2194
2167
|
if (testContent) {
|
|
2195
2168
|
const testFmtResult = await fmt(testContent);
|
|
2196
2169
|
const testCode = testFmtResult.ok ? testFmtResult.value : testContent;
|
|
@@ -2258,7 +2231,7 @@ async function zodOpenAPIHonoHandler(openapi, output, test = false, pathAlias, r
|
|
|
2258
2231
|
* @param componentsOutput - The components module path schemas are imported from.
|
|
2259
2232
|
* @returns A `Result` indicating success or error with message.
|
|
2260
2233
|
*/
|
|
2261
|
-
async function defineOpenAPIRouteHandler(openapi, output, componentsOutput, test = false, pathAlias, basePath = "/", testFramework = "vitest", readonly
|
|
2234
|
+
async function defineOpenAPIRouteHandler(openapi, output, componentsOutput, test = false, pathAlias, basePath = "/", testFramework = "vitest", readonly) {
|
|
2262
2235
|
const handlers = defineEntries(openapi, readonly).reduce((acc, entry) => {
|
|
2263
2236
|
const fileName = makeHandlerFileName(entry.path);
|
|
2264
2237
|
const prev = acc.get(fileName);
|
|
@@ -2270,7 +2243,7 @@ async function defineOpenAPIRouteHandler(openapi, output, componentsOutput, test
|
|
|
2270
2243
|
});
|
|
2271
2244
|
}, /* @__PURE__ */ new Map());
|
|
2272
2245
|
const baseDir = path.dirname(output);
|
|
2273
|
-
const handlerPath =
|
|
2246
|
+
const handlerPath = baseDir === "." ? "routes" : `${baseDir}/routes`;
|
|
2274
2247
|
const aliasPrefix = pathAlias?.endsWith("/") ? pathAlias.slice(0, -1) : pathAlias;
|
|
2275
2248
|
const testImportFrom = aliasPrefix ?? makeModuleSpec(`${handlerPath}/handler.ts`, { output });
|
|
2276
2249
|
const componentsModulePath = componentsOutput.endsWith("/index.ts") ? path.dirname(componentsOutput) : componentsOutput.replace(/\.ts$/, "");
|
|
@@ -2291,6 +2264,7 @@ async function defineOpenAPIRouteHandler(openapi, output, componentsOutput, test
|
|
|
2291
2264
|
output: componentsOutput,
|
|
2292
2265
|
...componentsImport ? { import: componentsImport } : {}
|
|
2293
2266
|
}]));
|
|
2267
|
+
const handlerTestContext = test ? makeHandlerTestContext(openapi) : void 0;
|
|
2294
2268
|
const mkdirResult = await mkdir(handlerPath);
|
|
2295
2269
|
if (!mkdirResult.ok) return {
|
|
2296
2270
|
ok: false,
|
|
@@ -2316,8 +2290,8 @@ async function defineOpenAPIRouteHandler(openapi, output, componentsOutput, test
|
|
|
2316
2290
|
ok: false,
|
|
2317
2291
|
error: writeResult.error
|
|
2318
2292
|
};
|
|
2319
|
-
if (
|
|
2320
|
-
const testContent = makeHandlerTestCode(openapi, `${handlerPath}/${handler.fileName}`, [...handler.routeNames], testImportFrom, basePath, testFramework);
|
|
2293
|
+
if (handlerTestContext) {
|
|
2294
|
+
const testContent = makeHandlerTestCode(openapi, `${handlerPath}/${handler.fileName}`, [...handler.routeNames], testImportFrom, basePath, testFramework, handlerTestContext);
|
|
2321
2295
|
if (testContent) {
|
|
2322
2296
|
const testFmtResult = await fmt(testContent);
|
|
2323
2297
|
const testCode = testFmtResult.ok ? testFmtResult.value : testContent;
|
|
@@ -2449,12 +2423,13 @@ function app(openapi, output, basePath, pathAlias, routeImport, routeHandler = f
|
|
|
2449
2423
|
}
|
|
2450
2424
|
//#endregion
|
|
2451
2425
|
//#region src/core/template/define.ts
|
|
2452
|
-
async function defineTemplate(openAPI, output, componentsOutput, test, basePath, pathAlias, routeImport,
|
|
2426
|
+
async function defineTemplate(openAPI, output, componentsOutput, test, basePath, pathAlias, routeImport, testFramework = "vitest", readonly) {
|
|
2453
2427
|
const target = output.endsWith(".ts") ? output : path.join(output, "index.ts");
|
|
2454
|
-
const baseDir = path.dirname(
|
|
2428
|
+
const baseDir = path.dirname(target);
|
|
2429
|
+
const handlerDir = baseDir === "." ? "routes" : `${baseDir}/routes`;
|
|
2455
2430
|
const aliasPrefix = pathAlias?.endsWith("/") ? pathAlias.slice(0, -1) : pathAlias;
|
|
2456
|
-
const handlerImport = aliasPrefix ? `${aliasPrefix}
|
|
2457
|
-
const [appFmtResult, handlersResult] = await Promise.all([fmt(app(openAPI, output, basePath, pathAlias, routeImport, false, true, handlerImport)), defineOpenAPIRouteHandler(openAPI,
|
|
2431
|
+
const handlerImport = aliasPrefix ? `${aliasPrefix}/routes` : makeModuleSpec(target, { output: handlerDir });
|
|
2432
|
+
const [appFmtResult, handlersResult] = await Promise.all([fmt(app(openAPI, output, basePath, pathAlias, routeImport, false, true, handlerImport)), defineOpenAPIRouteHandler(openAPI, target, componentsOutput, test, pathAlias, basePath, testFramework, readonly)]);
|
|
2458
2433
|
if (!appFmtResult.ok) return {
|
|
2459
2434
|
ok: false,
|
|
2460
2435
|
error: appFmtResult.error
|
|
@@ -2482,12 +2457,10 @@ async function defineTemplate(openAPI, output, componentsOutput, test, basePath,
|
|
|
2482
2457
|
}
|
|
2483
2458
|
//#endregion
|
|
2484
2459
|
//#region src/core/template/index.ts
|
|
2485
|
-
async function template(openAPI, output, test, basePath, pathAlias, routeImport, routeHandler, testFramework = "vitest"
|
|
2460
|
+
async function template(openAPI, output, test, basePath, pathAlias, routeImport, routeHandler, testFramework = "vitest") {
|
|
2486
2461
|
const dir = output.endsWith("/index.ts") ? path.dirname(path.dirname(output)) : path.dirname(output);
|
|
2487
2462
|
const target = path.join(dir, "index.ts");
|
|
2488
|
-
const
|
|
2489
|
-
const handlerImport = handlerDir ? aliasPrefix ? `${aliasPrefix}/${path.relative(dir, handlerDir).replaceAll("\\", "/")}` : makeModuleSpec(path.join(dir, "index.ts"), { output: handlerDir }) : void 0;
|
|
2490
|
-
const [appFmtResult, stubHandlersResult] = await Promise.all([fmt(app(openAPI, output, basePath, pathAlias, routeImport, routeHandler, false, handlerImport)), zodOpenAPIHonoHandler(openAPI, output, test, pathAlias, routeImport, routeHandler, basePath, testFramework, handlerDir)]);
|
|
2463
|
+
const [appFmtResult, stubHandlersResult] = await Promise.all([fmt(app(openAPI, output, basePath, pathAlias, routeImport, routeHandler, false)), zodOpenAPIHonoHandler(openAPI, output, test, pathAlias, routeImport, routeHandler, basePath, testFramework)]);
|
|
2491
2464
|
if (!appFmtResult.ok) return {
|
|
2492
2465
|
ok: false,
|
|
2493
2466
|
error: appFmtResult.error
|
|
@@ -2694,8 +2667,7 @@ async function parseOpenAPI(input) {
|
|
|
2694
2667
|
//#region src/shared/index.ts
|
|
2695
2668
|
function makeJob(openAPI, config) {
|
|
2696
2669
|
const defineOn = config.template?.define === true;
|
|
2697
|
-
const appOutput = config.output ?? config.routes?.output;
|
|
2698
|
-
const defineHandlerDir = defineOn ? config.template?.output : void 0;
|
|
2670
|
+
const appOutput = config.output ?? (defineOn ? config.components?.output ? deriveAppEntry(config.components.output) : "src/index.ts" : config.routes?.output);
|
|
2699
2671
|
const componentsOutput = config.components?.output ?? (defineOn && appOutput ? `${path.dirname(appOutput)}/components/index.ts` : void 0);
|
|
2700
2672
|
const componentKinds = [
|
|
2701
2673
|
"schemas",
|
|
@@ -2891,16 +2863,16 @@ function makeJob(openAPI, config) {
|
|
|
2891
2863
|
split: false,
|
|
2892
2864
|
run: (output) => docs(openAPI, output, config.docs?.entry, config.basePath, config.docs?.curl, config.docs?.baseUrl)
|
|
2893
2865
|
} : void 0,
|
|
2894
|
-
config.template && defineOn && appOutput && componentsOutput
|
|
2866
|
+
config.template && defineOn && appOutput && componentsOutput ? {
|
|
2895
2867
|
name: "template",
|
|
2896
2868
|
output: appOutput,
|
|
2897
2869
|
split: false,
|
|
2898
|
-
run: (output) => defineTemplate(openAPI, output, componentsOutput, config.template?.test ?? false, config.basePath, config.template?.pathAlias, config.routes?.import,
|
|
2870
|
+
run: (output) => defineTemplate(openAPI, output, componentsOutput, config.template?.test ?? false, config.basePath, config.template?.pathAlias, config.routes?.import, config.template?.testFramework, config.readonly)
|
|
2899
2871
|
} : config.template && !defineOn && appOutput ? {
|
|
2900
2872
|
name: "template",
|
|
2901
2873
|
output: appOutput,
|
|
2902
2874
|
split: false,
|
|
2903
|
-
run: (output) => template(openAPI, output, config.template?.test ?? false, config.basePath, config.template?.pathAlias, config.routes?.import, config.template?.define === false ? config.template.routeHandler : false, config.template?.testFramework
|
|
2875
|
+
run: (output) => template(openAPI, output, config.template?.test ?? false, config.basePath, config.template?.pathAlias, config.routes?.import, config.template?.define === false ? config.template.routeHandler : false, config.template?.testFramework)
|
|
2904
2876
|
} : void 0
|
|
2905
2877
|
].filter((job) => job !== void 0);
|
|
2906
2878
|
}
|
|
@@ -234,6 +234,27 @@ function makeBarrel(value) {
|
|
|
234
234
|
return `${Object.keys(value).sort().map((k) => `export * from './${k.charAt(0).toLowerCase() + k.slice(1)}'`).join("\n")}\n`;
|
|
235
235
|
}
|
|
236
236
|
/**
|
|
237
|
+
* Derives the define-mode app entry from a components output path.
|
|
238
|
+
*
|
|
239
|
+
* The path is read as `<anchor>/<module>` where module is either a flat
|
|
240
|
+
* `.ts` file or a `<dir>/index.ts` pair; the app entry is `<anchor>/index.ts`.
|
|
241
|
+
*
|
|
242
|
+
* @param componentsOutput - The components output path (a `.ts` file).
|
|
243
|
+
* @returns The derived app entry path.
|
|
244
|
+
*
|
|
245
|
+
* @example
|
|
246
|
+
* ```ts
|
|
247
|
+
* deriveAppEntry('./server/components/index.ts') // → './server/index.ts'
|
|
248
|
+
* deriveAppEntry('server/components.ts') // → 'server/index.ts'
|
|
249
|
+
* deriveAppEntry('components/index.ts') // → 'index.ts'
|
|
250
|
+
* ```
|
|
251
|
+
*/
|
|
252
|
+
function deriveAppEntry(componentsOutput) {
|
|
253
|
+
const container = componentsOutput.endsWith("/index.ts") ? componentsOutput.slice(0, -9) : componentsOutput;
|
|
254
|
+
const anchor = container.includes("/") ? container.slice(0, container.lastIndexOf("/")) : "";
|
|
255
|
+
return anchor === "" || anchor === "." ? "index.ts" : `${anchor}/index.ts`;
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
237
258
|
* Formats an error message argument using the Zod v4 unified `error` parameter.
|
|
238
259
|
*
|
|
239
260
|
* @param message - The error message string
|
|
@@ -272,87 +293,56 @@ function makeInferRequestType(clientName, pathResult, method) {
|
|
|
272
293
|
const { runtimePath, typeofPrefix, bracketSuffix, hasBracket } = pathResult;
|
|
273
294
|
return hasBracket ? `InferRequestType<typeof ${clientName}${typeofPrefix}${bracketSuffix}['$${method}']>` : `InferRequestType<typeof ${clientName}${runtimePath}.$${method}>`;
|
|
274
295
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
* (`true` = pass-through, `false` = reject). This narrows the union to the
|
|
326
|
-
* object form so callers can recurse without `as` casts.
|
|
327
|
-
*/
|
|
328
|
-
function isSchemaObject(v) {
|
|
329
|
-
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
330
|
-
}
|
|
331
|
-
function isMediaWithSchema(v) {
|
|
332
|
-
return typeof v === "object" && v !== null && "schema" in v;
|
|
333
|
-
}
|
|
334
|
-
function isMedia(v) {
|
|
335
|
-
return typeof v === "object" && v !== null && "schema" in v;
|
|
336
|
-
}
|
|
337
|
-
function isRequestBody(v) {
|
|
338
|
-
return typeof v === "object" && v !== null && ("content" in v || "required" in v || "description" in v);
|
|
339
|
-
}
|
|
340
|
-
function isContentBody(v) {
|
|
341
|
-
return typeof v === "object" && v !== null && !("$ref" in v);
|
|
342
|
-
}
|
|
343
|
-
function isSecurityScheme(v) {
|
|
344
|
-
return typeof v === "object" && v !== null && !("$ref" in v);
|
|
345
|
-
}
|
|
346
|
-
function isSecurityArray(v) {
|
|
347
|
-
return Array.isArray(v);
|
|
348
|
-
}
|
|
349
|
-
function isResponses(v) {
|
|
350
|
-
if (typeof v !== "object" || v === null || Array.isArray(v)) return false;
|
|
351
|
-
return "$ref" in v || "description" in v || "content" in v || "headers" in v || "links" in v;
|
|
352
|
-
}
|
|
353
|
-
function isOAuthFlowValue(v) {
|
|
354
|
-
if (typeof v !== "object" || v === null || Array.isArray(v)) return false;
|
|
355
|
-
return "authorizationUrl" in v || "tokenUrl" in v || "scopes" in v;
|
|
296
|
+
function cyclicNodes(deps) {
|
|
297
|
+
const indices = /* @__PURE__ */ new Map();
|
|
298
|
+
const lowLinks = /* @__PURE__ */ new Map();
|
|
299
|
+
const onStack = /* @__PURE__ */ new Set();
|
|
300
|
+
const stack = [];
|
|
301
|
+
const result = /* @__PURE__ */ new Set();
|
|
302
|
+
const open = (node) => {
|
|
303
|
+
const index = indices.size;
|
|
304
|
+
indices.set(node, index);
|
|
305
|
+
lowLinks.set(node, index);
|
|
306
|
+
stack.push(node);
|
|
307
|
+
onStack.add(node);
|
|
308
|
+
};
|
|
309
|
+
const close = (node) => {
|
|
310
|
+
if (lowLinks.get(node) !== indices.get(node)) return;
|
|
311
|
+
const sccStart = stack.lastIndexOf(node);
|
|
312
|
+
const scc = stack.slice(sccStart);
|
|
313
|
+
stack.length = sccStart;
|
|
314
|
+
for (const member of scc) onStack.delete(member);
|
|
315
|
+
if (scc.length > 1 || (deps.get(node) ?? []).includes(node)) for (const member of scc) result.add(member);
|
|
316
|
+
};
|
|
317
|
+
const connect = (root) => {
|
|
318
|
+
open(root);
|
|
319
|
+
const frames = [{
|
|
320
|
+
node: root,
|
|
321
|
+
depIndex: 0
|
|
322
|
+
}];
|
|
323
|
+
while (frames.length > 0) {
|
|
324
|
+
const frame = frames[frames.length - 1];
|
|
325
|
+
if (frame === void 0) return;
|
|
326
|
+
const dep = (deps.get(frame.node) ?? [])[frame.depIndex];
|
|
327
|
+
if (dep === void 0) {
|
|
328
|
+
close(frame.node);
|
|
329
|
+
frames.length -= 1;
|
|
330
|
+
const parent = frames[frames.length - 1];
|
|
331
|
+
if (parent) lowLinks.set(parent.node, Math.min(lowLinks.get(parent.node) ?? 0, lowLinks.get(frame.node) ?? 0));
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
frame.depIndex += 1;
|
|
335
|
+
if (!indices.has(dep)) {
|
|
336
|
+
open(dep);
|
|
337
|
+
frames.push({
|
|
338
|
+
node: dep,
|
|
339
|
+
depIndex: 0
|
|
340
|
+
});
|
|
341
|
+
} else if (onStack.has(dep)) lowLinks.set(frame.node, Math.min(lowLinks.get(frame.node) ?? 0, indices.get(dep) ?? 0));
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
for (const node of deps.keys()) if (!indices.has(node)) connect(node);
|
|
345
|
+
return result;
|
|
356
346
|
}
|
|
357
347
|
//#endregion
|
|
358
|
-
export {
|
|
348
|
+
export { toIdentifierPascalCase as _, ensureSuffix as a, escapeRegexLiteral as c, makeSafeKey as d, methodPath as f, statusCodeToNumber as g, requestParamsArray as h, deriveAppEntry as i, makeBarrel as l, renderNamedImport as m, capitalize as n, error as o, normalizeTypes as p, cyclicNodes as r, escapeHtml as s, baseError as t, makeInferRequestType as u, uncapitalize as v, zodToOpenAPISchema as y };
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { parseConfig } from "../config/index.js";
|
|
2
2
|
import { r as setFormatOptions } from "../emit-CFR63U4L.js";
|
|
3
|
-
import { f as isRecord } from "../guard-
|
|
4
|
-
import { n as parseOpenAPI, t as makeJob } from "../shared-
|
|
3
|
+
import { f as isRecord } from "../guard-BSZ8ezEv.js";
|
|
4
|
+
import { n as parseOpenAPI, t as makeJob } from "../shared-B-TUm7Fa.js";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import fsp from "node:fs/promises";
|
|
7
|
+
import crypto from "node:crypto";
|
|
7
8
|
//#region src/vite-plugin/index.ts
|
|
8
9
|
async function readConfigurationWithHotReload(server) {
|
|
9
10
|
const absoluteConfigPath = path.resolve(process.cwd(), "hono-takibi.config.ts");
|
|
@@ -53,28 +54,97 @@ function debounce(delayMilliseconds, callback) {
|
|
|
53
54
|
};
|
|
54
55
|
return wrappedFunction;
|
|
55
56
|
}
|
|
57
|
+
function isWatchedInputFile(filePath) {
|
|
58
|
+
return filePath.endsWith(".yaml") || filePath.endsWith(".json") || filePath.endsWith(".tsp");
|
|
59
|
+
}
|
|
60
|
+
async function listWatchedInputFiles(directory) {
|
|
61
|
+
const entries = await fsp.readdir(directory, { withFileTypes: true }).catch(() => []);
|
|
62
|
+
return (await Promise.all(entries.map((entry) => {
|
|
63
|
+
const entryPath = path.join(directory, entry.name);
|
|
64
|
+
if (entry.isDirectory()) return entry.name === "node_modules" || entry.name.startsWith(".") ? Promise.resolve([]) : listWatchedInputFiles(entryPath);
|
|
65
|
+
return Promise.resolve(entry.isFile() && isWatchedInputFile(entryPath) ? [entryPath] : []);
|
|
66
|
+
}))).flat();
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Hashes the contents of all watched input files under the input directory.
|
|
70
|
+
*
|
|
71
|
+
* Returns null when the set cannot be read reliably, so callers treat
|
|
72
|
+
* "unknown" as "changed" and regenerate.
|
|
73
|
+
*/
|
|
74
|
+
async function hashWatchedInputs(directory) {
|
|
75
|
+
const files = [...await listWatchedInputFiles(directory)].sort();
|
|
76
|
+
if (files.length === 0) return null;
|
|
77
|
+
const contents = await Promise.all(files.map(async (file) => ({
|
|
78
|
+
file,
|
|
79
|
+
content: await fsp.readFile(file, "utf-8").catch(() => null)
|
|
80
|
+
})));
|
|
81
|
+
if (contents.some((entry) => entry.content === null)) return null;
|
|
82
|
+
const hash = crypto.createHash("sha256");
|
|
83
|
+
for (const entry of contents) {
|
|
84
|
+
hash.update(entry.file);
|
|
85
|
+
hash.update("\0");
|
|
86
|
+
hash.update(entry.content ?? "");
|
|
87
|
+
hash.update("\0");
|
|
88
|
+
}
|
|
89
|
+
return hash.digest("hex");
|
|
90
|
+
}
|
|
91
|
+
async function listOutputFiles(target) {
|
|
92
|
+
const stats = await fsp.stat(target).catch(() => null);
|
|
93
|
+
if (!stats) return [];
|
|
94
|
+
if (stats.isFile()) return [{
|
|
95
|
+
file: target,
|
|
96
|
+
mtimeMs: stats.mtimeMs
|
|
97
|
+
}];
|
|
98
|
+
if (!stats.isDirectory()) return [];
|
|
99
|
+
const entries = await fsp.readdir(target, { withFileTypes: true }).catch(() => []);
|
|
100
|
+
return (await Promise.all(entries.map((entry) => listOutputFiles(path.join(target, entry.name))))).flat();
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Snapshots mtimes of all files under the job output paths.
|
|
104
|
+
*
|
|
105
|
+
* `writeFile` skips identical content, so an unchanged mtime means the file
|
|
106
|
+
* was not rewritten. For `.ts` outputs the sibling split directory
|
|
107
|
+
* (`dir/name/` derived from `dir/name.ts`) is included as well.
|
|
108
|
+
*/
|
|
109
|
+
async function snapshotOutputs(outputPaths) {
|
|
110
|
+
const targets = [...new Set(outputPaths.flatMap((outputPath) => [outputPath, ...outputPath.endsWith(".ts") ? [path.join(path.dirname(outputPath), path.basename(outputPath, ".ts"))] : []]))];
|
|
111
|
+
const collected = await Promise.all(targets.map((target) => listOutputFiles(target)));
|
|
112
|
+
return new Map(collected.flat().map((entry) => [entry.file, entry.mtimeMs]));
|
|
113
|
+
}
|
|
114
|
+
function sameOutputSnapshot(before, after) {
|
|
115
|
+
return before.size === after.size && [...before].every(([file, mtimeMs]) => after.get(file) === mtimeMs);
|
|
116
|
+
}
|
|
56
117
|
/**
|
|
57
118
|
* Runs all code generation tasks in parallel based on the provided configuration.
|
|
58
119
|
*
|
|
59
120
|
* @param config - Parsed configuration object
|
|
60
|
-
* @returns Promise resolving to object containing log messages
|
|
121
|
+
* @returns Promise resolving to object containing log messages and whether any output file changed
|
|
61
122
|
*/
|
|
62
123
|
async function runAllGenerationTasks(config) {
|
|
63
124
|
if (config.format) setFormatOptions(config.format);
|
|
64
125
|
const openAPIResult = await parseOpenAPI(config.input);
|
|
65
|
-
if (!openAPIResult.ok) return {
|
|
126
|
+
if (!openAPIResult.ok) return {
|
|
127
|
+
logs: [`❌ parseOpenAPI: ${openAPIResult.error}`],
|
|
128
|
+
changed: false
|
|
129
|
+
};
|
|
66
130
|
const openAPI = openAPIResult.value;
|
|
67
131
|
const cleanupSplitOutput = async (absOutput) => {
|
|
68
132
|
if (!(await fsp.stat(absOutput).catch(() => null))?.isDirectory()) return;
|
|
69
133
|
const entries = await fsp.readdir(absOutput, { withFileTypes: true }).catch(() => []);
|
|
70
134
|
await Promise.all(entries.filter((entry) => entry.isFile() && entry.name.endsWith(".ts")).map((entry) => fsp.unlink(path.join(absOutput, entry.name)).catch(() => void 0)));
|
|
71
135
|
};
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
136
|
+
const jobs = makeJob(openAPI, config);
|
|
137
|
+
const outputPaths = jobs.map((job) => path.resolve(process.cwd(), job.output));
|
|
138
|
+
const beforeSnapshot = await snapshotOutputs(outputPaths);
|
|
139
|
+
return {
|
|
140
|
+
logs: await Promise.all(jobs.map(async (job) => {
|
|
141
|
+
const absOutput = path.resolve(process.cwd(), job.output);
|
|
142
|
+
if (job.split) await cleanupSplitOutput(absOutput);
|
|
143
|
+
const result = await job.run(absOutput);
|
|
144
|
+
return result.ok ? `✅ ${job.name}${job.split ? "(split)" : ""} -> ${absOutput}` : `❌ ${job.name}: ${result.error}`;
|
|
145
|
+
})),
|
|
146
|
+
changed: !sameOutputSnapshot(beforeSnapshot, await snapshotOutputs(outputPaths))
|
|
147
|
+
};
|
|
78
148
|
}
|
|
79
149
|
/**
|
|
80
150
|
* Adds glob patterns to the Vite file watcher.
|
|
@@ -97,6 +167,9 @@ function addInputGlobsToWatcher(server, absoluteInputPath) {
|
|
|
97
167
|
server.watcher.add(watchPatterns);
|
|
98
168
|
return inputDirectory;
|
|
99
169
|
}
|
|
170
|
+
async function allOutputsExist(config) {
|
|
171
|
+
return (await Promise.all(extractOutputPaths(config).map((outputPath) => fsp.stat(outputPath).catch(() => null)))).every((stat) => stat !== null);
|
|
172
|
+
}
|
|
100
173
|
function extractOutputPaths(config) {
|
|
101
174
|
return [
|
|
102
175
|
config.output,
|
|
@@ -131,18 +204,36 @@ function honoTakibiVite() {
|
|
|
131
204
|
const pluginState = {
|
|
132
205
|
current: null,
|
|
133
206
|
previous: null,
|
|
134
|
-
inputDirectory: null
|
|
207
|
+
inputDirectory: null,
|
|
208
|
+
lastInputHash: null,
|
|
209
|
+
runQueue: Promise.resolve()
|
|
135
210
|
};
|
|
136
211
|
const absoluteConfigFilePath = path.resolve(process.cwd(), "hono-takibi.config.ts");
|
|
137
212
|
const runGeneration = async () => {
|
|
138
|
-
if (!pluginState.current) return;
|
|
213
|
+
if (!pluginState.current) return false;
|
|
139
214
|
console.log("🔥 hono-takibi");
|
|
140
|
-
const { logs } = await runAllGenerationTasks(pluginState.current);
|
|
215
|
+
const { logs, changed } = await runAllGenerationTasks(pluginState.current);
|
|
141
216
|
for (const logMessage of logs) console.log(logMessage);
|
|
217
|
+
return changed;
|
|
142
218
|
};
|
|
143
219
|
const runGenerationAndReload = async (server) => {
|
|
144
|
-
await runGeneration();
|
|
145
|
-
if (server) server.ws.send({ type: "full-reload" });
|
|
220
|
+
const changed = await runGeneration();
|
|
221
|
+
if (server && changed) server.ws.send({ type: "full-reload" });
|
|
222
|
+
};
|
|
223
|
+
const runIfInputsChanged = async (server) => {
|
|
224
|
+
if (!pluginState.inputDirectory || !pluginState.current) return;
|
|
225
|
+
const inputHash = await hashWatchedInputs(pluginState.inputDirectory);
|
|
226
|
+
if (inputHash !== null && inputHash === pluginState.lastInputHash && await allOutputsExist(pluginState.current)) {
|
|
227
|
+
console.log("⏭️ input unchanged - skipped regeneration");
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
pluginState.lastInputHash = inputHash;
|
|
231
|
+
await runGenerationAndReload(server);
|
|
232
|
+
};
|
|
233
|
+
const enqueueRun = (task) => {
|
|
234
|
+
const queued = pluginState.runQueue.then(task).catch((error) => console.error("❌ run error:", error));
|
|
235
|
+
pluginState.runQueue = queued;
|
|
236
|
+
return queued;
|
|
146
237
|
};
|
|
147
238
|
const handleConfigurationChange = async (server) => {
|
|
148
239
|
const nextConfiguration = await readConfigurationWithHotReload(server);
|
|
@@ -177,7 +268,9 @@ function honoTakibiVite() {
|
|
|
177
268
|
}
|
|
178
269
|
pluginState.previous = pluginState.current;
|
|
179
270
|
pluginState.current = nextConfiguration.value;
|
|
180
|
-
|
|
271
|
+
const inputDirectory = addInputGlobsToWatcher(server, path.resolve(process.cwd(), pluginState.current.input));
|
|
272
|
+
pluginState.inputDirectory = inputDirectory;
|
|
273
|
+
pluginState.lastInputHash = await hashWatchedInputs(inputDirectory);
|
|
181
274
|
await runGenerationAndReload(server);
|
|
182
275
|
};
|
|
183
276
|
return {
|
|
@@ -185,7 +278,7 @@ function honoTakibiVite() {
|
|
|
185
278
|
handleHotUpdate(context) {
|
|
186
279
|
if (path.resolve(context.file) === path.resolve(process.cwd(), "hono-takibi.config.ts")) {
|
|
187
280
|
console.log("config changed (hot-update)");
|
|
188
|
-
|
|
281
|
+
enqueueRun(() => handleConfigurationChange(context.server));
|
|
189
282
|
return [];
|
|
190
283
|
}
|
|
191
284
|
},
|
|
@@ -198,19 +291,21 @@ function honoTakibiVite() {
|
|
|
198
291
|
return;
|
|
199
292
|
}
|
|
200
293
|
pluginState.current = initialConfiguration.value;
|
|
201
|
-
|
|
294
|
+
const inputDirectory = addInputGlobsToWatcher(server, path.resolve(process.cwd(), pluginState.current.input));
|
|
295
|
+
pluginState.inputDirectory = inputDirectory;
|
|
296
|
+
pluginState.lastInputHash = await hashWatchedInputs(inputDirectory);
|
|
202
297
|
server.watcher.add(absoluteConfigFilePath);
|
|
203
|
-
const debouncedRunGeneration = debounce(200, () => void
|
|
298
|
+
const debouncedRunGeneration = debounce(200, () => void enqueueRun(() => runIfInputsChanged(server)));
|
|
204
299
|
server.watcher.on("all", async (_eventType, filePath) => {
|
|
205
300
|
const absoluteChangedPath = path.resolve(filePath);
|
|
206
301
|
if (absoluteChangedPath === absoluteConfigFilePath) {
|
|
207
302
|
console.log("config changed (watch)");
|
|
208
|
-
await handleConfigurationChange(server);
|
|
303
|
+
await enqueueRun(() => handleConfigurationChange(server));
|
|
209
304
|
return;
|
|
210
305
|
}
|
|
211
|
-
if (pluginState.inputDirectory && absoluteChangedPath.startsWith(pluginState.inputDirectory) && (absoluteChangedPath
|
|
306
|
+
if (pluginState.inputDirectory && absoluteChangedPath.startsWith(pluginState.inputDirectory) && isWatchedInputFile(absoluteChangedPath)) debouncedRunGeneration();
|
|
212
307
|
});
|
|
213
|
-
await runGenerationAndReload(server);
|
|
308
|
+
await enqueueRun(() => runGenerationAndReload(server));
|
|
214
309
|
})().catch((e) => console.error("❌ watch error:", e));
|
|
215
310
|
}
|
|
216
311
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hono-takibi",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.99995",
|
|
4
4
|
"description": "Hono Takibi is a code generator from OpenAPI to @hono/zod-openapi",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"hono",
|
|
@@ -57,17 +57,21 @@
|
|
|
57
57
|
"@apidevtools/swagger-parser": "^12.1.0",
|
|
58
58
|
"@typespec/compiler": "^1.13.0",
|
|
59
59
|
"@typespec/openapi3": "^1.13.0",
|
|
60
|
-
"oxfmt": "^0.
|
|
60
|
+
"oxfmt": "^0.57.0",
|
|
61
61
|
"ts-morph": "^28.0.0",
|
|
62
62
|
"typescript": "^6.0.3",
|
|
63
63
|
"zod": "^4.4.3"
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
66
|
+
"@faker-js/faker": "^10.5.0",
|
|
67
|
+
"@hono/zod-openapi": "^1.4.0",
|
|
66
68
|
"@types/node": "^26.0.1",
|
|
67
69
|
"@typespec/http": "^1.13.0",
|
|
68
70
|
"@typespec/rest": "^0.83.0",
|
|
69
71
|
"@typespec/versioning": "^0.83.0",
|
|
70
|
-
"
|
|
72
|
+
"hono": "^4.12.27",
|
|
73
|
+
"tsdown": "0.22.3",
|
|
74
|
+
"zod": "^4.0.0"
|
|
71
75
|
},
|
|
72
76
|
"scripts": {
|
|
73
77
|
"build": "tsdown"
|