hono-takibi 0.9.995 → 0.9.996

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 CHANGED
@@ -196,7 +196,7 @@ export default defineConfig({
196
196
  template: {
197
197
  test: true,
198
198
  pathAlias: '@/',
199
- framework: 'bun', // "vitest" (default) | "bun"
199
+ testFramework: 'bun', // "vitest" (default) | "vite-plus" | "bun"
200
200
  },
201
201
  },
202
202
  })
@@ -206,7 +206,7 @@ This generates:
206
206
 
207
207
  - `src/index.ts` - App entry point with route registrations
208
208
  - `src/handlers/*.ts` - Handler stubs for each resource
209
- - `src/handlers/*.test.ts` - Test files with `@faker-js/faker` mock data (imports from `vitest` or `bun:test`)
209
+ - `src/handlers/*.test.ts` - Test files with `@faker-js/faker` mock data (imports from `vitest`, `vite-plus/test`, or `bun:test`)
210
210
 
211
211
  Re-running after updating your OpenAPI spec is safe — your hand-written handler logic and test customizations are preserved. Only new routes are added as stubs.
212
212
 
@@ -300,7 +300,7 @@ export default defineConfig({
300
300
  test: {
301
301
  output: './src/test.ts',
302
302
  import: '../index',
303
- framework: 'bun', // "vitest" (default) | "bun"
303
+ testFramework: 'bun', // "vitest" (default) | "vite-plus" | "bun"
304
304
  },
305
305
  })
306
306
  ```
@@ -387,7 +387,7 @@ export default defineConfig({
387
387
  test: true, // Generate test files
388
388
  routeHandler: false, // false: inline .openapi() (default), true: RouteHandler exports
389
389
  pathAlias: '@/', // TypeScript path alias for imports
390
- framework: 'vitest', // "vitest" (default) | "bun" — test import source
390
+ testFramework: 'vitest', // "vitest" (default) | "vite-plus" | "bun" — test import source
391
391
  },
392
392
 
393
393
  // Export options (OpenAPI Components Object)
@@ -528,7 +528,7 @@ export default defineConfig({
528
528
  test: {
529
529
  output: './src/test.ts',
530
530
  import: '../index', // Import path for the app instance
531
- framework: 'vitest', // "vitest" (default) | "bun" — test import source
531
+ testFramework: 'vitest', // "vitest" (default) | "vite-plus" | "bun" — test import source
532
532
  },
533
533
 
534
534
  // Mock server generation
@@ -13,7 +13,7 @@ declare const ConfigSchema: z.ZodReadonly<z.ZodPipe<z.ZodObject<{
13
13
  test: z.ZodDefault<z.ZodBoolean>;
14
14
  routeHandler: z.ZodDefault<z.ZodBoolean>;
15
15
  pathAlias: z.ZodExactOptional<z.ZodString>;
16
- framework: z.ZodExactOptional<z.ZodDefault<z.ZodEnum<{
16
+ testFramework: z.ZodExactOptional<z.ZodDefault<z.ZodEnum<{
17
17
  vitest: "vitest";
18
18
  "vite-plus": "vite-plus";
19
19
  bun: "bun";
@@ -144,7 +144,7 @@ declare const ConfigSchema: z.ZodReadonly<z.ZodPipe<z.ZodObject<{
144
144
  test: z.ZodExactOptional<z.ZodObject<{
145
145
  output: z.ZodString;
146
146
  import: z.ZodString;
147
- framework: z.ZodExactOptional<z.ZodDefault<z.ZodEnum<{
147
+ testFramework: z.ZodExactOptional<z.ZodDefault<z.ZodEnum<{
148
148
  vitest: "vitest";
149
149
  "vite-plus": "vite-plus";
150
150
  bun: "bun";
@@ -167,7 +167,7 @@ declare const ConfigSchema: z.ZodReadonly<z.ZodPipe<z.ZodObject<{
167
167
  test: boolean;
168
168
  routeHandler: boolean;
169
169
  pathAlias?: string;
170
- framework?: "vitest" | "vite-plus" | "bun";
170
+ testFramework?: "vitest" | "vite-plus" | "bun";
171
171
  };
172
172
  exportSchemas?: boolean;
173
173
  exportSchemasTypes?: boolean;
@@ -297,7 +297,7 @@ declare const ConfigSchema: z.ZodReadonly<z.ZodPipe<z.ZodObject<{
297
297
  test?: {
298
298
  output: string;
299
299
  import: string;
300
- framework?: "vitest" | "vite-plus" | "bun";
300
+ testFramework?: "vitest" | "vite-plus" | "bun";
301
301
  };
302
302
  mock?: {
303
303
  output: string;
@@ -319,7 +319,7 @@ declare const ConfigSchema: z.ZodReadonly<z.ZodPipe<z.ZodObject<{
319
319
  test: boolean;
320
320
  routeHandler: boolean;
321
321
  pathAlias?: string;
322
- framework?: "vitest" | "vite-plus" | "bun";
322
+ testFramework?: "vitest" | "vite-plus" | "bun";
323
323
  };
324
324
  exportSchemas?: boolean;
325
325
  exportSchemasTypes?: boolean;
@@ -446,7 +446,7 @@ declare const ConfigSchema: z.ZodReadonly<z.ZodPipe<z.ZodObject<{
446
446
  test?: {
447
447
  output: string;
448
448
  import: string;
449
- framework?: "vitest" | "vite-plus" | "bun";
449
+ testFramework?: "vitest" | "vite-plus" | "bun";
450
450
  };
451
451
  mock?: {
452
452
  output: string;
@@ -480,9 +480,6 @@ declare function readConfig(): Promise<{
480
480
  readonly ok: false;
481
481
  readonly error: string;
482
482
  }>;
483
- /**
484
- * Helper to define a config with full type completion.
485
- */
486
483
  declare function defineConfig(config: ConfigInput): Readonly<{
487
484
  input: `${string}.yaml` | `${string}.json` | `${string}.tsp`;
488
485
  basePath?: string;
@@ -494,7 +491,7 @@ declare function defineConfig(config: ConfigInput): Readonly<{
494
491
  test?: boolean | undefined;
495
492
  routeHandler?: boolean | undefined;
496
493
  pathAlias?: string;
497
- framework?: "vitest" | "vite-plus" | "bun" | undefined;
494
+ testFramework?: "vitest" | "vite-plus" | "bun" | undefined;
498
495
  };
499
496
  exportSchemas?: boolean;
500
497
  exportSchemasTypes?: boolean;
@@ -621,7 +618,7 @@ declare function defineConfig(config: ConfigInput): Readonly<{
621
618
  test?: {
622
619
  output: string;
623
620
  import: string;
624
- framework?: "vitest" | "vite-plus" | "bun" | undefined;
621
+ testFramework?: "vitest" | "vite-plus" | "bun" | undefined;
625
622
  };
626
623
  mock?: {
627
624
  output: string;
@@ -16,7 +16,7 @@ const ConfigSchema = z.object({
16
16
  test: z.boolean().default(false),
17
17
  routeHandler: z.boolean().default(false),
18
18
  pathAlias: z.string().exactOptional(),
19
- framework: z.enum([
19
+ testFramework: z.enum([
20
20
  "vitest",
21
21
  "vite-plus",
22
22
  "bun"
@@ -147,7 +147,7 @@ const ConfigSchema = z.object({
147
147
  test: z.object({
148
148
  output: z.string(),
149
149
  import: z.string(),
150
- framework: z.enum([
150
+ testFramework: z.enum([
151
151
  "vitest",
152
152
  "vite-plus",
153
153
  "bun"
@@ -250,9 +250,6 @@ async function readConfig() {
250
250
  };
251
251
  }
252
252
  }
253
- /**
254
- * Helper to define a config with full type completion.
255
- */
256
253
  function defineConfig(config) {
257
254
  return config;
258
255
  }
@@ -1,2 +1,2 @@
1
- import { n as makeDocs, t as docs } from "../../docs-Da_MOdAe.js";
1
+ import { n as makeDocs, t as docs } from "../../docs-BnLvHynR.js";
2
2
  export { docs, makeDocs };
@@ -1,7 +1,7 @@
1
- import { c as isOperationLike, f as isRecord, o as isOpenAPIPaths } from "../../guard-BabA4f3q.js";
2
- import { a as makeInferRequestType, s as methodPath } from "../../utils-B9bUHU9P.js";
3
- import { t as core } from "../../core-BlUaJE2n.js";
4
- import { a as parsePathItem, i as operationHasArgs, o as resolveSplitOutDir, r as makeOperationDeps, t as formatPath } from "../../hono-rpc-BOLdCVD_.js";
1
+ import { a as makeInferRequestType, s as methodPath } from "../../utils-BqOCY-3W.js";
2
+ import { c as isOperationLike, f as isRecord, o as isOpenAPIPaths } from "../../guard-BzaK5_qz.js";
3
+ import { t as core } from "../../core-CaXcQRYE.js";
4
+ import { a as parsePathItem, i as operationHasArgs, o as resolveSplitOutDir, r as makeOperationDeps, t as formatPath } from "../../hono-rpc-CvPU8S0M.js";
5
5
  import path from "node:path";
6
6
  //#region src/core/rpc/index.ts
7
7
  const makeOperationCode = (pathStr, method, item, deps, useParseResponse, hasBasePath) => {
@@ -1,4 +1,4 @@
1
- import { t as makeQueryHooks } from "../../query-CArUJxE4.js";
1
+ import { t as makeQueryHooks } from "../../query-BVXAozkk.js";
2
2
  //#region src/core/svelte-query/index.ts
3
3
  /**
4
4
  * Generates Svelte Query hooks from OpenAPI specification.
@@ -1,4 +1,4 @@
1
- import { t as makeQueryHooks } from "../../query-CArUJxE4.js";
1
+ import { t as makeQueryHooks } from "../../query-BVXAozkk.js";
2
2
  //#region src/core/swr/index.ts
3
3
  /**
4
4
  * Generates SWR hooks from OpenAPI specification.
@@ -1,4 +1,4 @@
1
- import { t as makeQueryHooks } from "../../query-CArUJxE4.js";
1
+ import { t as makeQueryHooks } from "../../query-BVXAozkk.js";
2
2
  //#region src/core/tanstack-query/index.ts
3
3
  /**
4
4
  * Generates TanStack Query hooks from OpenAPI specification.
@@ -1,6 +1,6 @@
1
- import { b as isStringRef, g as isSchemaArray, i as isMediaWithSchema, l as isParameter, m as isRequestBody, n as isHttpMethod, s as isOperation, u as isParameterArray } from "../../guard-BabA4f3q.js";
2
- import { o as makeSafeKey } from "../../utils-B9bUHU9P.js";
3
- import { t as core } from "../../core-BlUaJE2n.js";
1
+ import { o as makeSafeKey } from "../../utils-BqOCY-3W.js";
2
+ import { b as isStringRef, g as isSchemaArray, i as isMediaWithSchema, l as isParameter, m as isRequestBody, n as isHttpMethod, s as isOperation, u as isParameterArray } from "../../guard-BzaK5_qz.js";
3
+ import { t as core } from "../../core-CaXcQRYE.js";
4
4
  import path from "node:path";
5
5
  //#region src/core/type/index.ts
6
6
  /**
@@ -1,4 +1,4 @@
1
- import { t as makeQueryHooks } from "../../query-CArUJxE4.js";
1
+ import { t as makeQueryHooks } from "../../query-BVXAozkk.js";
2
2
  //#region src/core/vue-query/index.ts
3
3
  /**
4
4
  * Generates Vue Query hooks from OpenAPI specification.
@@ -61,7 +61,6 @@ async function fmt(input) {
61
61
  }
62
62
  //#endregion
63
63
  //#region src/helper/core.ts
64
- /** Formats code, creates directory, and writes the file. */
65
64
  async function core(code, dir, output) {
66
65
  const [fmtResult, mkdirResult] = await Promise.all([fmt(code), mkdir(dir)]);
67
66
  if (!fmtResult.ok) return {
@@ -1,4 +1,4 @@
1
- import { a as isOAuthFlowValue, f as isRecord, h as isResponses, m as isRequestBody, p as isRefObject, r as isMedia, v as isSecurityArray, y as isSecurityScheme } from "./guard-BabA4f3q.js";
1
+ import { a as isOAuthFlowValue, f as isRecord, h as isResponses, m as isRequestBody, p as isRefObject, r as isMedia, v as isSecurityArray, y as isSecurityScheme } from "./guard-BzaK5_qz.js";
2
2
  import { a as writeFile, t as mkdir } from "./fsp-Bv1yR6UV.js";
3
3
  import path from "node:path";
4
4
  import { STATUS_CODES } from "node:http";
@@ -1,2 +1,2 @@
1
- import { t as zodOpenAPIHono } from "../../../openapi-BZ9b1y3X.js";
1
+ import { t as zodOpenAPIHono } from "../../../openapi-Bq_Z1BrA.js";
2
2
  export { zodOpenAPIHono };
@@ -1,4 +1,4 @@
1
- import { _ as isSchemaProperty, c as isOperationLike, d as isParameterObject, f as isRecord, p as isRefObject, x as isValidIdent } from "./guard-BabA4f3q.js";
1
+ import { _ as isSchemaProperty, c as isOperationLike, d as isParameterObject, f as isRecord, p as isRefObject, x as isValidIdent } from "./guard-BzaK5_qz.js";
2
2
  import path from "node:path";
3
3
  //#region src/helper/hono-rpc.ts
4
4
  /**
@@ -30,9 +30,8 @@ function formatPath(p, hasBasePath) {
30
30
  hasBracket: false
31
31
  };
32
32
  }
33
- const hasTrailingSlash = p !== "/" && p.endsWith("/");
34
33
  const segs = p.replace(/^\/+/, "").split("/").filter(Boolean);
35
- if (hasTrailingSlash) segs.push("index");
34
+ if (p !== "/" && p.endsWith("/")) segs.push("index");
36
35
  const honoSegs = segs.map((seg) => seg.replace(/\{([^}]+)\}/g, ":$1"));
37
36
  const firstBracketIdx = honoSegs.findIndex((seg) => !isValidIdent(seg));
38
37
  const hasBracket = firstBracketIdx !== -1;
@@ -68,7 +67,6 @@ function makeResolveParameter(componentsParameters) {
68
67
  function makeToParameterLikes(resolveParam) {
69
68
  return (arr) => Array.isArray(arr) ? arr.map((x) => resolveParam(x)).filter((param) => param !== void 0) : [];
70
69
  }
71
- const NO_CONTENT_STATUS_CODES = [204, 205];
72
70
  /**
73
71
  * Check if operation has No Content response (204 or 205).
74
72
  */
@@ -77,10 +75,13 @@ function hasNoContentResponse(op) {
77
75
  if (!isRecord(responses)) return false;
78
76
  return Object.keys(responses).some((status) => {
79
77
  const code = Number.parseInt(status, 10);
80
- return !Number.isNaN(code) && NO_CONTENT_STATUS_CODES.includes(code);
78
+ return !Number.isNaN(code) && [204, 205].includes(code);
81
79
  });
82
80
  }
83
81
  /**
82
+ * All body info grouped by type.
83
+ */
84
+ /**
84
85
  * Extract requestBody name from $ref.
85
86
  */
86
87
  function refRequestBodyName(refLike) {
@@ -108,9 +109,9 @@ function pickAllBodyInfoFromContent(content) {
108
109
  */
109
110
  function makePickAllBodyInfo(componentsRequestBodies) {
110
111
  return (op) => {
111
- const rb = op.requestBody;
112
- if (!isRecord(rb)) return void 0;
113
- const refName = refRequestBodyName(rb);
112
+ const requestBody = op.requestBody;
113
+ if (!isRecord(requestBody)) return void 0;
114
+ const refName = refRequestBodyName(requestBody);
114
115
  if (refName) {
115
116
  const resolved = componentsRequestBodies[refName];
116
117
  if (isRecord(resolved) && isRecord(resolved.content)) return pickAllBodyInfoFromContent(resolved.content);
@@ -120,7 +121,7 @@ function makePickAllBodyInfo(componentsRequestBodies) {
120
121
  };
121
122
  return;
122
123
  }
123
- return pickAllBodyInfoFromContent(rb.content);
124
+ return pickAllBodyInfoFromContent(requestBody.content);
124
125
  };
125
126
  }
126
127
  /**
@@ -163,9 +164,7 @@ function makeOperationDeps(clientName, componentsParameters, componentsRequestBo
163
164
  * Check if operation has arguments (parameters or body).
164
165
  */
165
166
  function operationHasArgs(item, op, deps) {
166
- const pathLevelParams = deps.toParameterLikes(item.parameters);
167
- const opParams = deps.toParameterLikes(op.parameters);
168
- const allParams = [...pathLevelParams, ...opParams];
167
+ const allParams = [...deps.toParameterLikes(item.parameters), ...deps.toParameterLikes(op.parameters)];
169
168
  const hasParams = allParams.filter((p) => p.in === "path").length > 0 || allParams.filter((p) => p.in === "query").length > 0 || allParams.filter((p) => p.in === "header").length > 0 || allParams.filter((p) => p.in === "cookie").length > 0;
170
169
  const allBodyInfo = deps.pickAllBodyInfo(op);
171
170
  const hasBody = allBodyInfo !== void 0 && (allBodyInfo.form.length > 0 || allBodyInfo.json.length > 0);
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import { readConfig } from "./config/index.js";
3
- import { r as setFormatOptions } from "./core-BlUaJE2n.js";
4
- import { _ as examples, a as takibi, c as securitySchemes, d as requestBodies, f as pathItems, g as headers, h as links, i as template, l as schemas, m as mediaTypes, n as webhooks, o as route, p as parameters, r as test, s as mock, t as parseOpenAPI, u as responses, v as callbacks } from "./openapi-D2QO8Njt.js";
5
- import { t as docs } from "./docs-Da_MOdAe.js";
3
+ import { r as setFormatOptions } from "./core-CaXcQRYE.js";
4
+ import { _ as examples, a as takibi, c as securitySchemes, d as requestBodies, f as pathItems, g as headers, h as links, i as template, l as schemas, m as mediaTypes, n as webhooks, o as route, p as parameters, r as test, s as mock, t as parseOpenAPI, u as responses, v as callbacks } from "./openapi-CSP6nmmf.js";
5
+ import { t as docs } from "./docs-BnLvHynR.js";
6
6
  import { rpc } from "./core/rpc/index.js";
7
7
  import { svelteQuery } from "./core/svelte-query/index.js";
8
8
  import { swr } from "./core/swr/index.js";
@@ -133,13 +133,13 @@ async function honoTakibi() {
133
133
  config["tanstack-query"] ? tanstackQuery(openAPI, config["tanstack-query"].output, config["tanstack-query"].import, config["tanstack-query"].split ?? false, config["tanstack-query"].client ?? "client") : Promise.resolve(void 0),
134
134
  config["svelte-query"] ? svelteQuery(openAPI, config["svelte-query"].output, config["svelte-query"].import, config["svelte-query"].split ?? false, config["svelte-query"].client ?? "client") : Promise.resolve(void 0),
135
135
  config["vue-query"] ? vueQuery(openAPI, config["vue-query"].output, config["vue-query"].import, config["vue-query"].split ?? false, config["vue-query"].client ?? "client") : Promise.resolve(void 0),
136
- config.test ? test(openAPI, config.test.output, config.test.import, config.basePath ?? "/", config.test.framework) : Promise.resolve(void 0),
136
+ config.test ? test(openAPI, config.test.output, config.test.import, config.basePath ?? "/", config.test.testFramework) : Promise.resolve(void 0),
137
137
  config.mock ? mock(openAPI, config.mock.output, config.basePath ?? "/", config["zod-openapi"]?.readonly) : Promise.resolve(void 0),
138
138
  config.docs ? docs(openAPI, config.docs.output, config.docs.entry, config.basePath ?? "/", config.docs.curl, config.docs.baseUrl) : Promise.resolve(void 0),
139
139
  (() => {
140
140
  if (!config["zod-openapi"]?.template) return Promise.resolve(void 0);
141
141
  if (!(config["zod-openapi"]?.output ?? config["zod-openapi"]?.routes?.output)) return Promise.resolve(void 0);
142
- return template(openAPI, config["zod-openapi"]?.output ?? config["zod-openapi"]?.routes?.output ?? "src/routes/index.ts", config["zod-openapi"]?.template.test, config.basePath ?? "/", config["zod-openapi"]?.template.pathAlias, config["zod-openapi"]?.routes?.import, config["zod-openapi"]?.template.routeHandler, config["zod-openapi"]?.template.framework);
142
+ return template(openAPI, config["zod-openapi"]?.output ?? config["zod-openapi"]?.routes?.output ?? "src/routes/index.ts", config["zod-openapi"]?.template.test, config.basePath ?? "/", config["zod-openapi"]?.template.pathAlias, config["zod-openapi"]?.routes?.import, config["zod-openapi"]?.template.routeHandler, config["zod-openapi"]?.template.testFramework);
143
143
  })()
144
144
  ]);
145
145
  const values = [];
@@ -1,28 +1,219 @@
1
- import { f as isRecord, g as isSchemaArray, l as isParameter, p as isRefObject, r as isMedia, s as isOperation } from "./guard-BabA4f3q.js";
2
- import { c as normalizeTypes, d as toIdentifierPascalCase, f as uncapitalize, l as renderNamedImport, n as ensureSuffix, o as makeSafeKey, p as zodToOpenAPISchema, r as error, s as methodPath, u as requestParamsArray } from "./utils-B9bUHU9P.js";
1
+ import { c as normalizeTypes, d as toIdentifierPascalCase, f as uncapitalize, l as renderNamedImport, n as ensureSuffix, o as makeSafeKey, p as zodToOpenAPISchema, r as error, s as methodPath, u as requestParamsArray } from "./utils-BqOCY-3W.js";
2
+ import { f as isRecord, g as isSchemaArray, l as isParameter, p as isRefObject, r as isMedia, s as isOperation } from "./guard-BzaK5_qz.js";
3
3
  import path from "node:path";
4
4
  import ts from "typescript";
5
- //#region src/helper/coerce.ts
6
- const constraintPattern = /^(?:\.(?:min|max|gt|lt|positive|negative|nonnegative|nonpositive|multipleOf)\([^)]*\))*/;
7
- function pipeCoerce(coerceBase, typeBase, baseSchema) {
8
- const afterType = baseSchema.slice(typeBase.length);
9
- const match = afterType.match(constraintPattern);
10
- const constraints = match ? match[0] : "";
11
- return `${coerceBase}.pipe(${typeBase}${constraints})${afterType.slice(constraints.length)}`;
12
- }
13
- function applyNumberCoerce(baseSchema, schemaType, format) {
14
- if (schemaType === "number") {
15
- if (format === "float" || format === "float32") return pipeCoerce("z.coerce.number()", "z.float32()", baseSchema);
16
- if (format === "float64") return pipeCoerce("z.coerce.number()", "z.float64()", baseSchema);
17
- return baseSchema.replace("z.number()", "z.coerce.number()");
18
- }
19
- if (schemaType === "integer") {
20
- if (format === "int32") return pipeCoerce("z.coerce.number()", "z.int32()", baseSchema);
21
- if (format === "int64") return pipeCoerce("z.coerce.bigint()", "z.int64()", baseSchema);
22
- if (format === "bigint") return baseSchema.replace("z.bigint()", "z.coerce.bigint()");
23
- return pipeCoerce("z.coerce.number()", "z.int()", baseSchema);
5
+ //#region src/helper/code.ts
6
+ /**
7
+ * Builds a relative module specifier from `fromFile` to a configured output.
8
+ *
9
+ * Computes the relative path from the directory of `fromFile` to the `target.output`,
10
+ * stripping `.ts` extension and `/index` suffix for clean ES module import paths.
11
+ *
12
+ * @param fromFile - The absolute path of the source file that will import the target.
13
+ * @param target - Configuration for the target module.
14
+ * @param target.output - The absolute path to the output file or directory.
15
+ * @param target.split - When true, treats output as a directory path rather than a file.
16
+ * @returns A relative module specifier (e.g., `'../schemas'`, `'./user'`, `'.'`).
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * // File to directory (strips /index)
21
+ * makeModuleSpec('/src/routes/index.ts', { output: '/src/schemas/index.ts' })
22
+ * // '../schemas'
23
+ *
24
+ * // File to file (strips .ts extension)
25
+ * makeModuleSpec('/src/routes/user.ts', { output: '/src/schemas/user.ts' })
26
+ * // → '../schemas/user'
27
+ *
28
+ * // Split mode: same directory
29
+ * makeModuleSpec('/src/routes/index.ts', { output: '/src/routes', split: true })
30
+ * // → '.'
31
+ *
32
+ * // Ensures dot-relative prefix
33
+ * makeModuleSpec('/src/index.ts', { output: '/src/schemas.ts' })
34
+ * // → './schemas'
35
+ * ```
36
+ */
37
+ function makeModuleSpec(fromFile, target) {
38
+ const stripped = path.relative(path.dirname(fromFile), target.output).replace(/\\/g, "/").replace(/\.ts$/, "").replace(/\/index$/, "");
39
+ return stripped === "" ? "." : stripped.startsWith(".") ? stripped : `./${stripped}`;
40
+ }
41
+ /**
42
+ * Generates a const declaration prefix with optional export.
43
+ *
44
+ * Combines the text and suffix, converts to PascalCase, and prepends
45
+ * `export const ` or `const ` based on the `exportVariable` flag.
46
+ *
47
+ * @param exportVariable - Whether to add the `export` keyword.
48
+ * @param text - The base name for the constant (will be converted to PascalCase).
49
+ * @param suffix - The suffix to append to the name (added before PascalCase conversion).
50
+ * @returns A string like `'export const UserSchema='` or `'const UserSchema='`.
51
+ *
52
+ * @example
53
+ * ```ts
54
+ * makeConst(true, 'User', 'Schema')
55
+ * // → 'export const UserSchema='
56
+ *
57
+ * makeConst(false, 'post', 'Response')
58
+ * // → 'const PostResponse='
59
+ *
60
+ * // Handles kebab-case input
61
+ * makeConst(true, 'user-profile', 'Schema')
62
+ * // → 'export const UserProfileSchema='
63
+ * ```
64
+ */
65
+ function makeConst(exportVariable, text, suffix) {
66
+ return `${exportVariable ? "export const " : "const "}${toIdentifierPascalCase(ensureSuffix(text, suffix))}=`;
67
+ }
68
+ /**
69
+ * Generates a string of export const statements for the given value.
70
+ *
71
+ * Iterates over the keys of `value`, converts each key to PascalCase with the suffix,
72
+ * and serializes the value as JSON. Multiple entries are separated by double newlines.
73
+ *
74
+ * @param value - Object containing values to export (keys become constant names).
75
+ * @param suffix - Suffix to append to each export name (before PascalCase conversion).
76
+ * @param readonly - Whether to add `as const` assertion for TypeScript readonly inference.
77
+ * @returns A string of TypeScript export const statements separated by `\n\n`.
78
+ *
79
+ * @example
80
+ * ```ts
81
+ * // Single export
82
+ * makeExportConst({ user: { id: 1 } }, 'Example')
83
+ * // → 'export const UserExample={"id":1}'
84
+ *
85
+ * // With as const assertion
86
+ * makeExportConst({ user: { id: 1 } }, 'Example', true)
87
+ * // → 'export const UserExample={"id":1} as const'
88
+ *
89
+ * // Multiple exports
90
+ * makeExportConst({ user: { id: 1 }, post: { title: 'Hello' } }, 'Data')
91
+ * // → 'export const UserData={"id":1}\n\nexport const PostData={"title":"Hello"}'
92
+ * ```
93
+ */
94
+ function makeExportConst(value, suffix, readonly) {
95
+ const asConst = readonly ? " as const" : "";
96
+ return Object.keys(value).map((key) => `export const ${toIdentifierPascalCase(ensureSuffix(key, suffix))}=${JSON.stringify(value[key])}${asConst}`).join("\n\n");
97
+ }
98
+ /**
99
+ * Universal import generator for OpenAPI-based Hono route files.
100
+ *
101
+ * Analyzes the provided code to auto-detect required imports and generates
102
+ * import statements for `@hono/zod-openapi` and OpenAPI component references.
103
+ *
104
+ * **Import Detection Logic:**
105
+ * - Detects `z.` usage to import `z` from `@hono/zod-openapi`
106
+ * - Detects `createRoute(` usage to import `createRoute`
107
+ * - Scans for OpenAPI component references by suffix pattern (e.g., `*Schema`, `*Response`)
108
+ * - Excludes locally defined exports from import generation
109
+ *
110
+ * **OpenAPI Component Patterns** (ordered per OpenAPI 3.0 spec):
111
+ * | Suffix | Component Type | Example |
112
+ * |--------|---------------|---------|
113
+ * | `*Schema` | schemas | `UserSchema` |
114
+ * | `*ParamsSchema` | parameters | `IdParamsSchema` |
115
+ * | `*SecurityScheme` | securitySchemes | `BearerSecurityScheme` |
116
+ * | `*RequestBody` | requestBodies | `CreateUserRequestBody` |
117
+ * | `*Response` | responses | `UserResponse` |
118
+ * | `*HeaderSchema` | headers | `AuthHeaderSchema` |
119
+ * | `*Example` | examples | `UserExample` |
120
+ * | `*Link` | links | `GetUserLink` |
121
+ * | `*Callback` | callbacks | `WebhookCallback` |
122
+ *
123
+ * @param code - The TypeScript code to analyze and prepend imports to.
124
+ * @param fromFile - The absolute path of the file where code will be written.
125
+ * @param components - Configuration mapping OpenAPI component types to output locations.
126
+ * @param split - When true, uses `'..'` as fallback prefix; otherwise `'.'`.
127
+ * @returns The code with generated import statements prepended.
128
+ *
129
+ * @see {@link https://swagger.io/docs/specification/v3_0/components/|OpenAPI Components}
130
+ *
131
+ * @example
132
+ * ```ts
133
+ * // Basic usage with schema reference
134
+ * const code = 'const route = createRoute({ request: { body: UserSchema } })'
135
+ * makeImports(code, '/src/routes/user.ts', { schemas: { output: '/src/schemas.ts' } })
136
+ * // → "import{createRoute}from'@hono/zod-openapi'\nimport{UserSchema}from'../schemas'\n\n..."
137
+ *
138
+ * // With z usage
139
+ * const code2 = 'z.string()'
140
+ * makeImports(code2, '/src/routes/user.ts', undefined)
141
+ * // → "import{z}from'@hono/zod-openapi'\n\n..."
142
+ * ```
143
+ */
144
+ /** Valid JavaScript identifier pattern (e.g., `UserSchema`, `_private`, `$special`) */
145
+ const JS_IDENT = "[A-Za-z_$][A-Za-z0-9_$]*";
146
+ /**
147
+ * Regex patterns for OpenAPI component types.
148
+ * Ordered per OpenAPI 3.0 specification.
149
+ * Note: (?<!Params)(?<!Header) excludes ParamsSchema/HeaderSchema from generic Schema matches
150
+ *
151
+ * Hoisted to module scope to avoid re-creating RegExp objects on every call.
152
+ */
153
+ const IMPORT_PATTERNS = [
154
+ {
155
+ pattern: new RegExp(`\\b(${JS_IDENT}(?<!Params)(?<!Header)(?<!MediaType)Schema)\\b`, "g"),
156
+ key: "schemas"
157
+ },
158
+ {
159
+ pattern: new RegExp(`\\b(${JS_IDENT}ParamsSchema)\\b`, "g"),
160
+ key: "parameters"
161
+ },
162
+ {
163
+ pattern: new RegExp(`\\b(${JS_IDENT}SecurityScheme)\\b`, "g"),
164
+ key: "securitySchemes"
165
+ },
166
+ {
167
+ pattern: new RegExp(`\\b(${JS_IDENT}RequestBody)\\b`, "g"),
168
+ key: "requestBodies"
169
+ },
170
+ {
171
+ pattern: new RegExp(`\\b(${JS_IDENT}Response)\\b`, "g"),
172
+ key: "responses"
173
+ },
174
+ {
175
+ pattern: new RegExp(`\\b(${JS_IDENT}HeaderSchema)\\b`, "g"),
176
+ key: "headers"
177
+ },
178
+ {
179
+ pattern: new RegExp(`\\b(${JS_IDENT}Example)\\b`, "g"),
180
+ key: "examples"
181
+ },
182
+ {
183
+ pattern: new RegExp(`\\b(${JS_IDENT}Link)\\b`, "g"),
184
+ key: "links"
185
+ },
186
+ {
187
+ pattern: new RegExp(`\\b(${JS_IDENT}Callback)\\b`, "g"),
188
+ key: "callbacks"
189
+ },
190
+ {
191
+ pattern: new RegExp(`\\b(${JS_IDENT}MediaTypeSchema)\\b`, "g"),
192
+ key: "mediaTypes"
24
193
  }
25
- return baseSchema;
194
+ ];
195
+ /** Pattern to find locally exported constants */
196
+ const EXPORT_CONST_PATTERN = new RegExp(`export\\s+const\\s+(${JS_IDENT})\\s*=`, "g");
197
+ function makeImports(code, fromFile, components, split = false) {
198
+ const fallbackPrefix = split ? ".." : ".";
199
+ const resolvePath = (key) => {
200
+ const target = components?.[key];
201
+ return target?.import ?? (target ? makeModuleSpec(fromFile, target) : `${fallbackPrefix}/${key}`);
202
+ };
203
+ const defined = new Set(Array.from(code.matchAll(EXPORT_CONST_PATTERN), (m) => m[1]).filter(Boolean));
204
+ const needsCreateRoute = code.includes("createRoute(");
205
+ const needsZ = code.includes("z.");
206
+ const honoImports = [needsCreateRoute && "createRoute", needsZ && "z"].filter(Boolean);
207
+ return [
208
+ honoImports.length > 0 ? `import{${honoImports.join(",")}}from'@hono/zod-openapi'` : "",
209
+ ...IMPORT_PATTERNS.flatMap(({ pattern, key }) => {
210
+ const tokens = [...new Set(Array.from(code.matchAll(pattern), (m) => m[1]))].filter((t) => Boolean(t) && !defined.has(t)).sort();
211
+ return tokens.length > 0 ? [renderNamedImport(tokens, resolvePath(key))] : [];
212
+ }),
213
+ "\n",
214
+ code,
215
+ ""
216
+ ].filter(Boolean).join("\n");
26
217
  }
27
218
  //#endregion
28
219
  //#region src/helper/openapi.ts
@@ -192,10 +383,9 @@ function makeCallbacks(callbacks, readonly) {
192
383
  return Object.entries(callbacks).map(([callbackKey, pathItem]) => {
193
384
  if (isRefObject(pathItem)) return `${JSON.stringify(callbackKey)}:${makeRef(pathItem.$ref)}`;
194
385
  if (!isRecord(pathItem)) return void 0;
195
- const pathItemRecord = pathItem;
196
- const pathItemCode = makeMethodsCode(pathItemRecord);
386
+ const pathItemCode = makeMethodsCode(pathItem);
197
387
  if (pathItemCode) return `${JSON.stringify(callbackKey)}:{${pathItemCode}}`;
198
- const nestedCode = Object.entries(pathItemRecord).map(([pathExpr, inner]) => {
388
+ const nestedCode = Object.entries(pathItem).map(([pathExpr, inner]) => {
199
389
  if (!isRecord(inner)) return void 0;
200
390
  const code = makeMethodsCode(inner);
201
391
  return code ? `${JSON.stringify(pathExpr)}:{${code}}` : void 0;
@@ -344,7 +534,7 @@ function makeParameters(parameters, readonly) {
344
534
  return acc;
345
535
  }
346
536
  const baseSchema = zodToOpenAPI(schema, { parameters: param }, readonly);
347
- const z = param.in === "query" && (schema.type === "number" || schema.type === "integer") ? applyNumberCoerce(baseSchema, schema.type, schema.format) : param.in === "query" && schema.type === "boolean" ? baseSchema.replace("boolean", "stringbool").replace(/\.default\("true"\)/g, ".default(true)").replace(/\.default\("false"\)/g, ".default(false)") : param.in === "query" && schema.type === "date" ? `z.coerce.${baseSchema.replace("z.", "")}` : param.in === "query" && (schema.type === "object" || schema.type === "array") ? baseSchema.replace(/z\.(int\d*)\(\)((?:\.(?:min|max|gt|lt|positive|negative|nonnegative|nonpositive|multipleOf)\([^)]*\))*)/g, (_, type, constraints) => `z.coerce.number().pipe(z.${type}()${constraints})`).replace(/z\.bigint\(\)/g, "z.coerce.bigint()").replace(/z\.number\(\)/g, "z.coerce.number()").replace(/z\.boolean\(\)/g, "z.stringbool()").replace(/z\.date\(\)/g, "z.coerce.date()") : baseSchema;
537
+ const z = param.in === "query" && (schema.type === "number" || schema.type === "integer") ? coerce(baseSchema, schema.type, schema.format) : param.in === "query" && schema.type === "boolean" ? baseSchema.replace("boolean", "stringbool").replace(/\.default\("true"\)/g, ".default(true)").replace(/\.default\("false"\)/g, ".default(false)") : param.in === "query" && schema.type === "date" ? `z.coerce.${baseSchema.replace("z.", "")}` : param.in === "query" && (schema.type === "object" || schema.type === "array") ? baseSchema.replace(/z\.(int\d*)\(\)((?:\.(?:min|max|gt|lt|positive|negative|nonnegative|nonpositive|multipleOf)\([^)]*\))*)/g, (_, type, constraints) => `z.coerce.number().pipe(z.${type}()${constraints})`).replace(/z\.bigint\(\)/g, "z.coerce.bigint()").replace(/z\.number\(\)/g, "z.coerce.number()").replace(/z\.boolean\(\)/g, "z.stringbool()").replace(/z\.date\(\)/g, "z.coerce.date()") : baseSchema;
348
538
  acc[param.in][makeSafeKey(param.name)] = z;
349
539
  return acc;
350
540
  }, {});
@@ -412,7 +602,7 @@ function makeOperation(operation, readonly) {
412
602
  ].filter((v) => v !== void 0).join(",")}}`;
413
603
  }
414
604
  function makePathItem(pathItem) {
415
- const additionalOpsCode = pathItem.additionalOperations ? Object.entries(pathItem.additionalOperations).map(([opName, op]) => `${JSON.stringify(opName)}:${makeOperation(op)}`).join(",") : void 0;
605
+ const additionalOperationsCode = pathItem.additionalOperations ? Object.entries(pathItem.additionalOperations).map(([opName, op]) => `${JSON.stringify(opName)}:${makeOperation(op)}`).join(",") : void 0;
416
606
  return `{${[
417
607
  pathItem.$ref ? `$ref:${makeRef(pathItem.$ref)}` : void 0,
418
608
  pathItem.summary ? `summary:${JSON.stringify(pathItem.summary)}` : void 0,
@@ -426,7 +616,7 @@ function makePathItem(pathItem) {
426
616
  pathItem.patch ? `patch:${makeOperation(pathItem.patch)}` : void 0,
427
617
  pathItem.trace ? `trace:${makeOperation(pathItem.trace)}` : void 0,
428
618
  pathItem.query ? `query:${makeOperation(pathItem.query)}` : void 0,
429
- additionalOpsCode ? `additionalOperations:{${additionalOpsCode}}` : void 0,
619
+ additionalOperationsCode ? `additionalOperations:{${additionalOperationsCode}}` : void 0,
430
620
  pathItem.servers ? `servers:${JSON.stringify(pathItem.servers)}` : void 0,
431
621
  pathItem.parameters ? `parameters:${makePathParameters(pathItem.parameters)}` : void 0
432
622
  ].filter((v) => v !== void 0).join(",")}}`;
@@ -1343,218 +1533,26 @@ function ast(code) {
1343
1533
  return topoSort(decls).map((d) => d.fullText).join("\n\n");
1344
1534
  }
1345
1535
  //#endregion
1346
- //#region src/helper/code.ts
1347
- /**
1348
- * Builds a relative module specifier from `fromFile` to a configured output.
1349
- *
1350
- * Computes the relative path from the directory of `fromFile` to the `target.output`,
1351
- * stripping `.ts` extension and `/index` suffix for clean ES module import paths.
1352
- *
1353
- * @param fromFile - The absolute path of the source file that will import the target.
1354
- * @param target - Configuration for the target module.
1355
- * @param target.output - The absolute path to the output file or directory.
1356
- * @param target.split - When true, treats output as a directory path rather than a file.
1357
- * @returns A relative module specifier (e.g., `'../schemas'`, `'./user'`, `'.'`).
1358
- *
1359
- * @example
1360
- * ```ts
1361
- * // File to directory (strips /index)
1362
- * makeModuleSpec('/src/routes/index.ts', { output: '/src/schemas/index.ts' })
1363
- * // → '../schemas'
1364
- *
1365
- * // File to file (strips .ts extension)
1366
- * makeModuleSpec('/src/routes/user.ts', { output: '/src/schemas/user.ts' })
1367
- * // → '../schemas/user'
1368
- *
1369
- * // Split mode: same directory
1370
- * makeModuleSpec('/src/routes/index.ts', { output: '/src/routes', split: true })
1371
- * // → '.'
1372
- *
1373
- * // Ensures dot-relative prefix
1374
- * makeModuleSpec('/src/index.ts', { output: '/src/schemas.ts' })
1375
- * // → './schemas'
1376
- * ```
1377
- */
1378
- function makeModuleSpec(fromFile, target) {
1379
- const stripped = path.relative(path.dirname(fromFile), target.output).replace(/\\/g, "/").replace(/\.ts$/, "").replace(/\/index$/, "");
1380
- return stripped === "" ? "." : stripped.startsWith(".") ? stripped : `./${stripped}`;
1381
- }
1382
- /**
1383
- * Generates a const declaration prefix with optional export.
1384
- *
1385
- * Combines the text and suffix, converts to PascalCase, and prepends
1386
- * `export const ` or `const ` based on the `exportVariable` flag.
1387
- *
1388
- * @param exportVariable - Whether to add the `export` keyword.
1389
- * @param text - The base name for the constant (will be converted to PascalCase).
1390
- * @param suffix - The suffix to append to the name (added before PascalCase conversion).
1391
- * @returns A string like `'export const UserSchema='` or `'const UserSchema='`.
1392
- *
1393
- * @example
1394
- * ```ts
1395
- * makeConst(true, 'User', 'Schema')
1396
- * // → 'export const UserSchema='
1397
- *
1398
- * makeConst(false, 'post', 'Response')
1399
- * // → 'const PostResponse='
1400
- *
1401
- * // Handles kebab-case input
1402
- * makeConst(true, 'user-profile', 'Schema')
1403
- * // → 'export const UserProfileSchema='
1404
- * ```
1405
- */
1406
- function makeConst(exportVariable, text, suffix) {
1407
- return `${exportVariable ? "export const " : "const "}${toIdentifierPascalCase(ensureSuffix(text, suffix))}=`;
1408
- }
1409
- /**
1410
- * Generates a string of export const statements for the given value.
1411
- *
1412
- * Iterates over the keys of `value`, converts each key to PascalCase with the suffix,
1413
- * and serializes the value as JSON. Multiple entries are separated by double newlines.
1414
- *
1415
- * @param value - Object containing values to export (keys become constant names).
1416
- * @param suffix - Suffix to append to each export name (before PascalCase conversion).
1417
- * @param readonly - Whether to add `as const` assertion for TypeScript readonly inference.
1418
- * @returns A string of TypeScript export const statements separated by `\n\n`.
1419
- *
1420
- * @example
1421
- * ```ts
1422
- * // Single export
1423
- * makeExportConst({ user: { id: 1 } }, 'Example')
1424
- * // → 'export const UserExample={"id":1}'
1425
- *
1426
- * // With as const assertion
1427
- * makeExportConst({ user: { id: 1 } }, 'Example', true)
1428
- * // → 'export const UserExample={"id":1} as const'
1429
- *
1430
- * // Multiple exports
1431
- * makeExportConst({ user: { id: 1 }, post: { title: 'Hello' } }, 'Data')
1432
- * // → 'export const UserData={"id":1}\n\nexport const PostData={"title":"Hello"}'
1433
- * ```
1434
- */
1435
- function makeExportConst(value, suffix, readonly) {
1436
- const asConst = readonly ? " as const" : "";
1437
- return Object.keys(value).map((key) => `export const ${toIdentifierPascalCase(ensureSuffix(key, suffix))}=${JSON.stringify(value[key])}${asConst}`).join("\n\n");
1438
- }
1439
- /**
1440
- * Universal import generator for OpenAPI-based Hono route files.
1441
- *
1442
- * Analyzes the provided code to auto-detect required imports and generates
1443
- * import statements for `@hono/zod-openapi` and OpenAPI component references.
1444
- *
1445
- * **Import Detection Logic:**
1446
- * - Detects `z.` usage to import `z` from `@hono/zod-openapi`
1447
- * - Detects `createRoute(` usage to import `createRoute`
1448
- * - Scans for OpenAPI component references by suffix pattern (e.g., `*Schema`, `*Response`)
1449
- * - Excludes locally defined exports from import generation
1450
- *
1451
- * **OpenAPI Component Patterns** (ordered per OpenAPI 3.0 spec):
1452
- * | Suffix | Component Type | Example |
1453
- * |--------|---------------|---------|
1454
- * | `*Schema` | schemas | `UserSchema` |
1455
- * | `*ParamsSchema` | parameters | `IdParamsSchema` |
1456
- * | `*SecurityScheme` | securitySchemes | `BearerSecurityScheme` |
1457
- * | `*RequestBody` | requestBodies | `CreateUserRequestBody` |
1458
- * | `*Response` | responses | `UserResponse` |
1459
- * | `*HeaderSchema` | headers | `AuthHeaderSchema` |
1460
- * | `*Example` | examples | `UserExample` |
1461
- * | `*Link` | links | `GetUserLink` |
1462
- * | `*Callback` | callbacks | `WebhookCallback` |
1463
- *
1464
- * @param code - The TypeScript code to analyze and prepend imports to.
1465
- * @param fromFile - The absolute path of the file where code will be written.
1466
- * @param components - Configuration mapping OpenAPI component types to output locations.
1467
- * @param split - When true, uses `'..'` as fallback prefix; otherwise `'.'`.
1468
- * @returns The code with generated import statements prepended.
1469
- *
1470
- * @see {@link https://swagger.io/docs/specification/v3_0/components/|OpenAPI Components}
1471
- *
1472
- * @example
1473
- * ```ts
1474
- * // Basic usage with schema reference
1475
- * const code = 'const route = createRoute({ request: { body: UserSchema } })'
1476
- * makeImports(code, '/src/routes/user.ts', { schemas: { output: '/src/schemas.ts' } })
1477
- * // → "import{createRoute}from'@hono/zod-openapi'\nimport{UserSchema}from'../schemas'\n\n..."
1478
- *
1479
- * // With z usage
1480
- * const code2 = 'z.string()'
1481
- * makeImports(code2, '/src/routes/user.ts', undefined)
1482
- * // → "import{z}from'@hono/zod-openapi'\n\n..."
1483
- * ```
1484
- */
1485
- /** Valid JavaScript identifier pattern (e.g., `UserSchema`, `_private`, `$special`) */
1486
- const JS_IDENT = "[A-Za-z_$][A-Za-z0-9_$]*";
1487
- /**
1488
- * Regex patterns for OpenAPI component types.
1489
- * Ordered per OpenAPI 3.0 specification.
1490
- * Note: (?<!Params)(?<!Header) excludes ParamsSchema/HeaderSchema from generic Schema matches
1491
- *
1492
- * Hoisted to module scope to avoid re-creating RegExp objects on every call.
1493
- */
1494
- const IMPORT_PATTERNS = [
1495
- {
1496
- pattern: new RegExp(`\\b(${JS_IDENT}(?<!Params)(?<!Header)(?<!MediaType)Schema)\\b`, "g"),
1497
- key: "schemas"
1498
- },
1499
- {
1500
- pattern: new RegExp(`\\b(${JS_IDENT}ParamsSchema)\\b`, "g"),
1501
- key: "parameters"
1502
- },
1503
- {
1504
- pattern: new RegExp(`\\b(${JS_IDENT}SecurityScheme)\\b`, "g"),
1505
- key: "securitySchemes"
1506
- },
1507
- {
1508
- pattern: new RegExp(`\\b(${JS_IDENT}RequestBody)\\b`, "g"),
1509
- key: "requestBodies"
1510
- },
1511
- {
1512
- pattern: new RegExp(`\\b(${JS_IDENT}Response)\\b`, "g"),
1513
- key: "responses"
1514
- },
1515
- {
1516
- pattern: new RegExp(`\\b(${JS_IDENT}HeaderSchema)\\b`, "g"),
1517
- key: "headers"
1518
- },
1519
- {
1520
- pattern: new RegExp(`\\b(${JS_IDENT}Example)\\b`, "g"),
1521
- key: "examples"
1522
- },
1523
- {
1524
- pattern: new RegExp(`\\b(${JS_IDENT}Link)\\b`, "g"),
1525
- key: "links"
1526
- },
1527
- {
1528
- pattern: new RegExp(`\\b(${JS_IDENT}Callback)\\b`, "g"),
1529
- key: "callbacks"
1530
- },
1531
- {
1532
- pattern: new RegExp(`\\b(${JS_IDENT}MediaTypeSchema)\\b`, "g"),
1533
- key: "mediaTypes"
1534
- }
1535
- ];
1536
- /** Pattern to find locally exported constants */
1537
- const EXPORT_CONST_PATTERN = new RegExp(`export\\s+const\\s+(${JS_IDENT})\\s*=`, "g");
1538
- function makeImports(code, fromFile, components, split = false) {
1539
- const fallbackPrefix = split ? ".." : ".";
1540
- const resolvePath = (key) => {
1541
- const target = components?.[key];
1542
- return target?.import ?? (target ? makeModuleSpec(fromFile, target) : `${fallbackPrefix}/${key}`);
1536
+ //#region src/helper/coerce.ts
1537
+ function coerce(baseSchema, schemaType, format) {
1538
+ const pipeCoerce = (coerceBase, typeBase, baseSchema) => {
1539
+ const afterType = baseSchema.slice(typeBase.length);
1540
+ const match = afterType.match(/^(?:\.(?:min|max|gt|lt|positive|negative|nonnegative|nonpositive|multipleOf)\([^)]*\))*/);
1541
+ const constraints = match ? match[0] : "";
1542
+ return `${coerceBase}.pipe(${typeBase}${constraints})${afterType.slice(constraints.length)}`;
1543
1543
  };
1544
- const defined = new Set(Array.from(code.matchAll(EXPORT_CONST_PATTERN), (m) => m[1]).filter(Boolean));
1545
- const needsCreateRoute = code.includes("createRoute(");
1546
- const needsZ = code.includes("z.");
1547
- const honoImports = [needsCreateRoute && "createRoute", needsZ && "z"].filter(Boolean);
1548
- return [
1549
- honoImports.length > 0 ? `import{${honoImports.join(",")}}from'@hono/zod-openapi'` : "",
1550
- ...IMPORT_PATTERNS.flatMap(({ pattern, key }) => {
1551
- const tokens = [...new Set(Array.from(code.matchAll(pattern), (m) => m[1]))].filter((t) => Boolean(t) && !defined.has(t)).sort();
1552
- return tokens.length > 0 ? [renderNamedImport(tokens, resolvePath(key))] : [];
1553
- }),
1554
- "\n",
1555
- code,
1556
- ""
1557
- ].filter(Boolean).join("\n");
1544
+ if (schemaType === "number") {
1545
+ if (format === "float" || format === "float32") return pipeCoerce("z.coerce.number()", "z.float32()", baseSchema);
1546
+ if (format === "float64") return pipeCoerce("z.coerce.number()", "z.float64()", baseSchema);
1547
+ return baseSchema.replace("z.number()", "z.coerce.number()");
1548
+ }
1549
+ if (schemaType === "integer") {
1550
+ if (format === "int32") return pipeCoerce("z.coerce.number()", "z.int32()", baseSchema);
1551
+ if (format === "int64") return pipeCoerce("z.coerce.bigint()", "z.int64()", baseSchema);
1552
+ if (format === "bigint") return baseSchema.replace("z.bigint()", "z.coerce.bigint()");
1553
+ return pipeCoerce("z.coerce.number()", "z.int()", baseSchema);
1554
+ }
1555
+ return baseSchema;
1558
1556
  }
1559
1557
  //#endregion
1560
1558
  //#region src/generator/zod-to-openapi/type/index.ts
@@ -1852,15 +1850,8 @@ function makeSingleTypeString(schema, type, selfTypeName, cyclicGroup, readonly)
1852
1850
  if (type === "object") return makeObjectTypeString(schema, selfTypeName, cyclicGroup, readonly);
1853
1851
  return "unknown";
1854
1852
  }
1855
- /**
1856
- * Wraps a type in parentheses if it needs grouping for array syntax.
1857
- *
1858
- * When an inner type starts with 'readonly ' (for readonly arrays),
1859
- * we need to wrap it in parentheses to avoid invalid syntax like
1860
- * `readonly readonly T[][]`. Instead, we get `readonly (readonly T[])[]`.
1861
- */
1862
- const wrapForArrayElement = (type) => type.startsWith("readonly ") ? `(${type})` : type;
1863
1853
  function makeArrayTypeString(schema, selfTypeName, cyclicGroup, readonly) {
1854
+ const wrapForArrayElement = (type) => type.startsWith("readonly ") ? `(${type})` : type;
1864
1855
  const prefix = readonly ? "readonly " : "";
1865
1856
  if (!schema.items) return `${prefix}unknown[]`;
1866
1857
  const items = schema.items;
@@ -1964,7 +1955,7 @@ function parametersCode(components, exportParameters, exportParametersTypes, rea
1964
1955
  const meta = { parameters: { ...parameter } };
1965
1956
  const schema = parameter.schema ?? getSchemaFromContent(parameter.content);
1966
1957
  const baseSchema = schema ? zodToOpenAPI(schema, meta) : "z.any()";
1967
- const z = parameter.in === "query" && (schema?.type === "number" || schema?.type === "integer") ? applyNumberCoerce(baseSchema, schema.type, schema.format) : parameter.in === "query" && schema?.type === "boolean" ? baseSchema.replace("boolean", "stringbool") : parameter.in === "query" && schema?.type === "date" ? `z.coerce.${baseSchema.replace("z.", "")}` : parameter.in === "query" && (schema?.type === "object" || schema?.type === "array") ? baseSchema.replace(/z\.(int\d*)\(\)((?:\.(?:min|max|gt|lt|positive|negative|nonnegative|nonpositive|multipleOf)\([^)]*\))*)/g, (_, type, constraints) => `z.coerce.number().pipe(z.${type}()${constraints})`).replace(/z\.bigint\(\)/g, "z.coerce.bigint()").replace(/z\.number\(\)/g, "z.coerce.number()").replace(/z\.boolean\(\)/g, "z.stringbool()").replace(/z\.date\(\)/g, "z.coerce.date()") : baseSchema;
1958
+ const z = parameter.in === "query" && (schema?.type === "number" || schema?.type === "integer") ? coerce(baseSchema, schema.type, schema.format) : parameter.in === "query" && schema?.type === "boolean" ? baseSchema.replace("boolean", "stringbool") : parameter.in === "query" && schema?.type === "date" ? `z.coerce.${baseSchema.replace("z.", "")}` : parameter.in === "query" && (schema?.type === "object" || schema?.type === "array") ? baseSchema.replace(/z\.(int\d*)\(\)((?:\.(?:min|max|gt|lt|positive|negative|nonnegative|nonpositive|multipleOf)\([^)]*\))*)/g, (_, type, constraints) => `z.coerce.number().pipe(z.${type}()${constraints})`).replace(/z\.bigint\(\)/g, "z.coerce.bigint()").replace(/z\.number\(\)/g, "z.coerce.number()").replace(/z\.boolean\(\)/g, "z.stringbool()").replace(/z\.date\(\)/g, "z.coerce.date()") : baseSchema;
1968
1959
  return zodToOpenAPISchema(toIdentifierPascalCase(ensureSuffix(k, "ParamsSchema")), z, exportParameters, exportParametersTypes, true, readonly);
1969
1960
  }).join("\n\n");
1970
1961
  }
@@ -2522,4 +2513,4 @@ function zodOpenAPIHono(openapi, options) {
2522
2513
  ].filter((s) => s.length > 0).join("\n\n")}`;
2523
2514
  }
2524
2515
  //#endregion
2525
- export { zodToOpenAPI as _, schemasCode as a, pathItemsCode as c, makeSplitSchemaFile as d, makeConst as f, ast as g, analyzeCircularSchemas as h, componentsCode as i, parametersCode as l, makeImports as m, webhookCode as n, responsesCode as o, makeExportConst as p, routeCode as r, requestBodiesCode as s, zodOpenAPIHono as t, headersCode as u, makeCallback as v, makeRef as y };
2516
+ export { makeConst as _, schemasCode as a, pathItemsCode as c, makeSplitSchemaFile as d, analyzeCircularSchemas as f, makeRef as g, makeCallback as h, componentsCode as i, parametersCode as l, zodToOpenAPI as m, webhookCode as n, responsesCode as o, ast as p, routeCode as r, requestBodiesCode as s, zodOpenAPIHono as t, headersCode as u, makeExportConst as v, makeImports as y };
@@ -1,7 +1,7 @@
1
- import { i as isMediaWithSchema, n as isHttpMethod, p as isRefObject, s as isOperation, t as isContentBody, v as isSecurityArray, y as isSecurityScheme } from "./guard-BabA4f3q.js";
2
- import { d as toIdentifierPascalCase, f as uncapitalize, i as makeBarrel, l as renderNamedImport, n as ensureSuffix, p as zodToOpenAPISchema, s as methodPath } from "./utils-B9bUHU9P.js";
3
- import { _ as zodToOpenAPI, a as schemasCode, c as pathItemsCode, d as makeSplitSchemaFile, f as makeConst, g as ast, h as analyzeCircularSchemas, i as componentsCode, l as parametersCode, m as makeImports, n as webhookCode, o as responsesCode, p as makeExportConst, r as routeCode, s as requestBodiesCode, t as zodOpenAPIHono, u as headersCode, v as makeCallback, y as makeRef } from "./openapi-BZ9b1y3X.js";
4
- import { n as fmt, t as core } from "./core-BlUaJE2n.js";
1
+ import { d as toIdentifierPascalCase, f as uncapitalize, i as makeBarrel, l as renderNamedImport, n as ensureSuffix, p as zodToOpenAPISchema, s as methodPath } from "./utils-BqOCY-3W.js";
2
+ import { _ as makeConst, a as schemasCode, c as pathItemsCode, d as makeSplitSchemaFile, f as analyzeCircularSchemas, g as makeRef, h as makeCallback, i as componentsCode, l as parametersCode, m as zodToOpenAPI, n as webhookCode, o as responsesCode, p as ast, r as routeCode, s as requestBodiesCode, t as zodOpenAPIHono, u as headersCode, v as makeExportConst, y as makeImports } from "./openapi-Bq_Z1BrA.js";
3
+ import { i as isMediaWithSchema, n as isHttpMethod, p as isRefObject, s as isOperation, t as isContentBody, v as isSecurityArray, y as isSecurityScheme } from "./guard-BzaK5_qz.js";
4
+ import { n as fmt, t as core } from "./core-CaXcQRYE.js";
5
5
  import { a as writeFile, i as unlink, n as readFile, r as readdir, t as mkdir } from "./fsp-Bv1yR6UV.js";
6
6
  import path from "node:path";
7
7
  import { Project } from "ts-morph";
@@ -1600,7 +1600,7 @@ const TEST_IMPORT_SOURCE = {
1600
1600
  "vite-plus": "vite-plus/test",
1601
1601
  bun: "bun:test"
1602
1602
  };
1603
- function makeTestFile(spec, appImportPath = "./app", basePath = "/", framework = "vitest") {
1603
+ function makeTestFile(spec, appImportPath = "./app", basePath = "/", testFramework = "vitest") {
1604
1604
  const testCases = extractTestCases(spec);
1605
1605
  const apiTitle = spec.info?.title || "API";
1606
1606
  const usedSchemaNames = new Set(testCases.flatMap((tc) => tc.usedSchemaRefs));
@@ -1616,7 +1616,7 @@ function makeTestFile(spec, appImportPath = "./app", basePath = "/", framework =
1616
1616
  }).join("");
1617
1617
  const body = `${mockFunctions ? `${mockFunctions}\n\n` : ""}describe('${escapeString(apiTitle)}',()=>{${tagDescribes}})\n`;
1618
1618
  const fakerImport = body.includes("faker.") ? `\nimport{faker}from'@faker-js/faker'` : "";
1619
- return `${`import{describe,it,expect}from'${TEST_IMPORT_SOURCE[framework]}'${fakerImport}\nimport app from'${appImportPath}'\n`}\n${body}`;
1619
+ return `${`import{describe,it,expect}from'${TEST_IMPORT_SOURCE[testFramework]}'${fakerImport}\nimport app from'${appImportPath}'\n`}\n${body}`;
1620
1620
  }
1621
1621
  /**
1622
1622
  * Extract the first path segment from an API path.
@@ -1626,7 +1626,7 @@ function getPathFirstSegment(path) {
1626
1626
  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());
1627
1627
  return sanitized === "" ? "__root" : sanitized;
1628
1628
  }
1629
- function makeHandlerTestCode(spec, handlerPath, _routeNames, importFrom, basePath = "/", framework = "vitest") {
1629
+ function makeHandlerTestCode(spec, handlerPath, _routeNames, importFrom, basePath = "/", testFramework = "vitest") {
1630
1630
  const handlerFileName = handlerPath.split("/").pop()?.replace(/\.ts$/, "") ?? "";
1631
1631
  const relevantCases = extractTestCases(spec).filter((tc) => {
1632
1632
  return getPathFirstSegment(tc.path) === handlerFileName;
@@ -1636,7 +1636,7 @@ function makeHandlerTestCode(spec, handlerPath, _routeNames, importFrom, basePat
1636
1636
  const testCasesCode = relevantCases.map((tc) => makeTestCase(tc, basePath, spec.components?.schemas)).join("");
1637
1637
  const body = `${mockFunctions ? `${mockFunctions}\n\n` : ""}describe('${handlerFileName.charAt(0).toUpperCase() + handlerFileName.slice(1)}',()=>{${testCasesCode}})\n`;
1638
1638
  const fakerImport = body.includes("faker.") ? `\nimport{faker}from'@faker-js/faker'` : "";
1639
- return `${`import{describe,it,expect}from'${TEST_IMPORT_SOURCE[framework]}'${fakerImport}\nimport app from'${importFrom}'\n`}\n${body}`;
1639
+ return `${`import{describe,it,expect}from'${TEST_IMPORT_SOURCE[testFramework]}'${fakerImport}\nimport app from'${importFrom}'\n`}\n${body}`;
1640
1640
  }
1641
1641
  //#endregion
1642
1642
  //#region src/merge/index.ts
@@ -1989,6 +1989,16 @@ function mergeTestFile(existingCode, generatedCode) {
1989
1989
  const newBlocks = [...generatedBlocks.entries()].filter(([route]) => !existingRoutes.has(route)).map(([, block]) => block.text);
1990
1990
  const { existingFile, generatedFile } = createSourcePair(existingCode, generatedCode);
1991
1991
  const mergedImports = mergeImports(existingFile, generatedFile);
1992
+ const TEST_FRAMEWORK_MODULES = new Set([
1993
+ "vitest",
1994
+ "bun:test",
1995
+ "vite-plus/test"
1996
+ ]);
1997
+ const generatedTestModule = generatedFile.getImportDeclarations().map((d) => d.getModuleSpecifierValue()).find((spec) => TEST_FRAMEWORK_MODULES.has(spec));
1998
+ const filteredImports = generatedTestModule ? mergedImports.filter((line) => {
1999
+ const spec = line.match(/from\s+'([^']+)'/)?.[1] ?? "";
2000
+ return !TEST_FRAMEWORK_MODULES.has(spec) || spec === generatedTestModule;
2001
+ }) : mergedImports;
1992
2002
  const bodyStart = getBodyStart(existingFile);
1993
2003
  const bodyWithStaleRemoved = applyRangeOps(existingCode, bodyStart, staleRanges.filter(([start]) => start >= bodyStart).map(([start, end]) => [
1994
2004
  start,
@@ -1997,7 +2007,7 @@ function mergeTestFile(existingCode, generatedCode) {
1997
2007
  ])).replace(/\n{3,}/g, "\n\n");
1998
2008
  const existingMocks = extractMockFunctions(existingCode);
1999
2009
  const bodyWithRemovals = insertMissingMocks(bodyWithStaleRemoved, [...extractMockFunctions(generatedCode).entries()].filter(([name]) => !existingMocks.has(name)).map(([, block]) => block.text));
2000
- if (newBlocks.length === 0) return `${[mergedImports.length > 0 ? mergedImports.join("\n") : "", bodyWithRemovals.trim()].filter(Boolean).join("\n\n")}\n`;
2010
+ if (newBlocks.length === 0) return `${[filteredImports.length > 0 ? filteredImports.join("\n") : "", bodyWithRemovals.trim()].filter(Boolean).join("\n\n")}\n`;
2001
2011
  const lines = bodyWithRemovals.split("\n");
2002
2012
  const insertLineIndex = lines.findLastIndex((line) => /^\s*\}\s*\)\s*;?\s*$/.test(line));
2003
2013
  const body = (insertLineIndex !== -1 ? [
@@ -2010,7 +2020,7 @@ function mergeTestFile(existingCode, generatedCode) {
2010
2020
  "",
2011
2021
  ...newBlocks
2012
2022
  ]).join("\n").trim();
2013
- return `${[mergedImports.length > 0 ? mergedImports.join("\n") : "", body].filter(Boolean).join("\n\n")}\n`;
2023
+ return `${[filteredImports.length > 0 ? filteredImports.join("\n") : "", body].filter(Boolean).join("\n\n")}\n`;
2014
2024
  }
2015
2025
  /**
2016
2026
  * Syncs barrel file (index.ts) with the generated version.
@@ -2153,7 +2163,7 @@ async function removeStaleFiles(handlerPath, generatedFileNames) {
2153
2163
  * @param test - Whether to generate corresponding test files.
2154
2164
  * @returns A `Result` indicating success or error with message.
2155
2165
  */
2156
- async function zodOpenAPIHonoHandler(openapi, output, test = false, pathAlias = void 0, routeImport = void 0, routeHandler = false, basePath = "/", framework = "vitest") {
2166
+ async function zodOpenAPIHonoHandler(openapi, output, test = false, pathAlias = void 0, routeImport = void 0, routeHandler = false, basePath = "/", testFramework = "vitest") {
2157
2167
  const paths = openapi.paths;
2158
2168
  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))));
2159
2169
  const { handlerPath, importFrom, testImportFrom } = makePaths(output, pathAlias, routeImport);
@@ -2182,7 +2192,7 @@ async function zodOpenAPIHonoHandler(openapi, output, test = false, pathAlias =
2182
2192
  error: writeResult.error
2183
2193
  };
2184
2194
  if (test) {
2185
- const testContent = makeHandlerTestCode(openapi, `${handlerPath}/${handler.fileName}`, [...handler.routeNames], testImportFrom, basePath, framework);
2195
+ const testContent = makeHandlerTestCode(openapi, `${handlerPath}/${handler.fileName}`, [...handler.routeNames], testImportFrom, basePath, testFramework);
2186
2196
  if (testContent) {
2187
2197
  const testFmtResult = await fmt(testContent);
2188
2198
  const testCode = testFmtResult.ok ? testFmtResult.value : testContent;
@@ -2309,10 +2319,10 @@ function app(openapi, output, basePath, pathAlias, routeImport = void 0, routeHa
2309
2319
  *
2310
2320
  * Used by the CLI and vite-plugin when `template` is configured.
2311
2321
  */
2312
- async function template(openAPI, output, test, basePath, pathAlias, routeImport, routeHandler, framework = "vitest") {
2322
+ async function template(openAPI, output, test, basePath, pathAlias, routeImport, routeHandler, testFramework = "vitest") {
2313
2323
  const dir = output.endsWith("/index.ts") ? path.dirname(path.dirname(output)) : path.dirname(output);
2314
2324
  const target = path.join(dir, "index.ts");
2315
- const [appFmtResult, stubHandlersResult] = await Promise.all([fmt(app(openAPI, output, basePath, pathAlias, routeImport, routeHandler)), zodOpenAPIHonoHandler(openAPI, output, test, pathAlias, routeImport, routeHandler, basePath, framework)]);
2325
+ const [appFmtResult, stubHandlersResult] = await Promise.all([fmt(app(openAPI, output, basePath, pathAlias, routeImport, routeHandler)), zodOpenAPIHonoHandler(openAPI, output, test, pathAlias, routeImport, routeHandler, basePath, testFramework)]);
2316
2326
  if (!appFmtResult.ok) return {
2317
2327
  ok: false,
2318
2328
  error: appFmtResult.error
@@ -2340,8 +2350,8 @@ async function template(openAPI, output, test, basePath, pathAlias, routeImport,
2340
2350
  }
2341
2351
  //#endregion
2342
2352
  //#region src/core/test/index.ts
2343
- async function test(openAPI, output, importPath, basePath = "/", framework = "vitest") {
2344
- const testCode = makeTestFile(openAPI, importPath, basePath, framework);
2353
+ async function test(openAPI, output, importPath, basePath = "/", testFramework = "vitest") {
2354
+ const testCode = makeTestFile(openAPI, importPath, basePath, testFramework);
2345
2355
  const [fmtResult, mkdirResult, existingResult] = await Promise.all([
2346
2356
  fmt(testCode),
2347
2357
  mkdir(path.dirname(output)),
@@ -1,7 +1,7 @@
1
- import { c as isOperationLike, f as isRecord, o as isOpenAPIPaths } from "./guard-BabA4f3q.js";
2
- import { d as toIdentifierPascalCase, s as methodPath, t as capitalize } from "./utils-B9bUHU9P.js";
3
- import { t as core } from "./core-BlUaJE2n.js";
4
- import { a as parsePathItem, i as operationHasArgs, n as hasNoContentResponse, o as resolveSplitOutDir, r as makeOperationDeps, t as formatPath } from "./hono-rpc-BOLdCVD_.js";
1
+ import { d as toIdentifierPascalCase, s as methodPath, t as capitalize } from "./utils-BqOCY-3W.js";
2
+ import { c as isOperationLike, f as isRecord, o as isOpenAPIPaths } from "./guard-BzaK5_qz.js";
3
+ import { t as core } from "./core-CaXcQRYE.js";
4
+ import { a as parsePathItem, i as operationHasArgs, n as hasNoContentResponse, o as resolveSplitOutDir, r as makeOperationDeps, t as formatPath } from "./hono-rpc-CvPU8S0M.js";
5
5
  import path from "node:path";
6
6
  //#region src/helper/query.ts
7
7
  /**
@@ -1,8 +1,8 @@
1
1
  import { parseConfig } from "../config/index.js";
2
- import { f as isRecord } from "../guard-BabA4f3q.js";
3
- import { r as setFormatOptions } from "../core-BlUaJE2n.js";
4
- import { _ as examples, a as takibi, c as securitySchemes, d as requestBodies, f as pathItems, g as headers, h as links, i as template, l as schemas, m as mediaTypes, n as webhooks, o as route, p as parameters, r as test, s as mock, t as parseOpenAPI, u as responses, v as callbacks } from "../openapi-D2QO8Njt.js";
5
- import { t as docs } from "../docs-Da_MOdAe.js";
2
+ import { f as isRecord } from "../guard-BzaK5_qz.js";
3
+ import { r as setFormatOptions } from "../core-CaXcQRYE.js";
4
+ import { _ as examples, a as takibi, c as securitySchemes, d as requestBodies, f as pathItems, g as headers, h as links, i as template, l as schemas, m as mediaTypes, n as webhooks, o as route, p as parameters, r as test, s as mock, t as parseOpenAPI, u as responses, v as callbacks } from "../openapi-CSP6nmmf.js";
5
+ import { t as docs } from "../docs-BnLvHynR.js";
6
6
  import { rpc } from "../core/rpc/index.js";
7
7
  import { svelteQuery } from "../core/svelte-query/index.js";
8
8
  import { swr } from "../core/swr/index.js";
@@ -13,13 +13,6 @@ import path from "node:path";
13
13
  import fsp from "node:fs/promises";
14
14
  //#region src/vite-plugin/index.ts
15
15
  /**
16
- * Type guard for configuration objects.
17
- *
18
- * @param value - Value to check
19
- * @returns True if value is a valid configuration object
20
- */
21
- const isConfiguration = (value) => typeof value === "object" && value !== null;
22
- /**
23
16
  * Converts a relative path to an absolute path.
24
17
  *
25
18
  * @param relativePath - Relative path
@@ -52,7 +45,7 @@ const readConfigurationWithHotReload = async (server) => {
52
45
  } else server.moduleGraph.invalidateAll();
53
46
  const loadedModule = await server.ssrLoadModule(`${absoluteConfigPath}?t=${String(Date.now())}`);
54
47
  const defaultExport = isRecord(loadedModule) ? Reflect.get(loadedModule, "default") : void 0;
55
- if (!isConfiguration(defaultExport)) return {
48
+ if (!(typeof defaultExport === "object" && defaultExport !== null)) return {
56
49
  ok: false,
57
50
  error: "Config must export default object"
58
51
  };
@@ -72,20 +65,6 @@ const readConfigurationWithHotReload = async (server) => {
72
65
  }
73
66
  };
74
67
  /**
75
- * Lists TypeScript files in a directory (shallow, non-recursive).
76
- *
77
- * @param directoryPath - Directory path to scan
78
- * @returns Promise resolving to array of absolute file paths
79
- */
80
- const listTypeScriptFilesShallow = async (directoryPath) => fsp.stat(directoryPath).then((fileStats) => fileStats.isDirectory() ? fsp.readdir(directoryPath, { withFileTypes: true }).then((directoryEntries) => directoryEntries.filter((entry) => entry.isFile() && entry.name.endsWith(".ts")).map((entry) => path.join(directoryPath, entry.name))) : []).catch(() => []);
81
- /**
82
- * Deletes specified TypeScript files.
83
- *
84
- * @param filePaths - Array of file paths to delete
85
- * @returns Promise resolving to array of deleted file paths
86
- */
87
- const deleteTypeScriptFiles = async (filePaths) => Promise.all(filePaths.map((filePath) => fsp.unlink(filePath).then(() => filePath).catch(() => null))).then((results) => results.filter((result) => result !== null));
88
- /**
89
68
  * Creates a debounced version of a function.
90
69
  *
91
70
  * Delays invocation until after the specified milliseconds have elapsed
@@ -120,7 +99,11 @@ const runAllGenerationTasks = async (config) => {
120
99
  */
121
100
  const runSplitAwareJob = async (name, output, isSplit, generate) => {
122
101
  const absOutput = toAbsolutePath(output);
123
- if (isSplit) await deleteTypeScriptFiles(await listTypeScriptFilesShallow(absOutput));
102
+ if (isSplit) {
103
+ const listTypeScriptFilesShallow = async (directoryPath) => fsp.stat(directoryPath).then((fileStats) => fileStats.isDirectory() ? fsp.readdir(directoryPath, { withFileTypes: true }).then((directoryEntries) => directoryEntries.filter((entry) => entry.isFile() && entry.name.endsWith(".ts")).map((entry) => path.join(directoryPath, entry.name))) : []).catch(() => []);
104
+ const deleteTypeScriptFiles = async (filePaths) => Promise.all(filePaths.map((filePath) => fsp.unlink(filePath).then(() => filePath).catch(() => null))).then((results) => results.filter((result) => result !== null));
105
+ await deleteTypeScriptFiles(await listTypeScriptFilesShallow(absOutput));
106
+ }
124
107
  const result = await generate(absOutput);
125
108
  if (!result.ok) return `❌ ${name}: ${result.error}`;
126
109
  return `✅ ${name}${isSplit ? "(split)" : ""} -> ${absOutput}`;
@@ -233,7 +216,7 @@ const runAllGenerationTasks = async (config) => {
233
216
  if (!config.test) return void 0;
234
217
  return (async () => {
235
218
  const outputPath = toAbsolutePath(config.test?.output ?? "");
236
- const result = await test(openAPI, outputPath, config.test?.import ?? "", config.basePath ?? "/", config.test?.framework);
219
+ const result = await test(openAPI, outputPath, config.test?.import ?? "", config.basePath ?? "/", config.test?.testFramework);
237
220
  return result.ok ? `✅ test -> ${outputPath}` : `❌ test: ${result.error}`;
238
221
  })();
239
222
  };
@@ -261,7 +244,7 @@ const runAllGenerationTasks = async (config) => {
261
244
  return (async () => {
262
245
  const absPath = toAbsolutePath(routeOutputPath);
263
246
  if (!isTypeScriptFile(absPath)) return `❌ template: Invalid output format: ${absPath}`;
264
- const result = await template(openAPI, absPath, tmpl.test, config.basePath ?? "/", tmpl.pathAlias, config["zod-openapi"]?.routes?.import, tmpl.routeHandler, tmpl.framework);
247
+ const result = await template(openAPI, absPath, tmpl.test, config.basePath ?? "/", tmpl.pathAlias, config["zod-openapi"]?.routes?.import, tmpl.routeHandler, tmpl.testFramework);
265
248
  return result.ok ? `✅ template -> ${absPath}` : `❌ template: ${result.error}`;
266
249
  })();
267
250
  };
@@ -294,17 +277,6 @@ const runAllGenerationTasks = async (config) => {
294
277
  return Promise.all(generationJobs).then((logs) => ({ logs }));
295
278
  };
296
279
  /**
297
- * Checks if a file path matches input file patterns.
298
- *
299
- * @param filePath - Absolute path to check
300
- * @param inputDirectory - Directory containing input files
301
- * @returns True if the file is an input file (yaml/json/tsp)
302
- */
303
- const isInputFile = (filePath, inputDirectory) => {
304
- if (!filePath.startsWith(inputDirectory)) return false;
305
- return filePath.endsWith(".yaml") || filePath.endsWith(".json") || filePath.endsWith(".tsp");
306
- };
307
- /**
308
280
  * Adds glob patterns to the Vite file watcher.
309
281
  *
310
282
  * Watches the input file and related files (.yaml, .json, .tsp) in the
@@ -376,37 +348,6 @@ const extractOutputPaths = (config) => {
376
348
  config.docs?.output
377
349
  ].filter((outputPath) => outputPath !== void 0).map(toAbsolutePath);
378
350
  };
379
- /**
380
- * Cleans up output paths that exist in previous config but not in current config.
381
- *
382
- * When an output path is removed from the configuration, this function
383
- * removes the corresponding file or directory completely.
384
- *
385
- * @param previousConfiguration - Previous configuration
386
- * @param currentConfiguration - Current configuration
387
- * @returns Array of cleaned up paths
388
- */
389
- const cleanupStaleOutputs = async (previousConfiguration, currentConfiguration) => {
390
- const previousPaths = new Set(extractOutputPaths(previousConfiguration));
391
- const currentPaths = new Set(extractOutputPaths(currentConfiguration));
392
- const stalePaths = [...previousPaths].filter((stalePath) => !currentPaths.has(stalePath));
393
- return (await Promise.all(stalePaths.map(async (stalePath) => {
394
- const fileStats = await fsp.stat(stalePath).catch(() => null);
395
- if (!fileStats) return null;
396
- if (fileStats.isDirectory()) {
397
- await fsp.rm(stalePath, {
398
- recursive: true,
399
- force: true
400
- }).catch(() => {});
401
- return stalePath;
402
- }
403
- if (fileStats.isFile() && (stalePath.endsWith(".ts") || stalePath.endsWith(".md"))) {
404
- await fsp.unlink(stalePath).catch(() => {});
405
- return stalePath;
406
- }
407
- return null;
408
- }))).filter((result) => result !== null);
409
- };
410
351
  function honoTakibiVite() {
411
352
  const pluginState = {
412
353
  current: null,
@@ -431,6 +372,27 @@ function honoTakibiVite() {
431
372
  return;
432
373
  }
433
374
  if (pluginState.current) {
375
+ const cleanupStaleOutputs = async (previousConfiguration, currentConfiguration) => {
376
+ const previousPaths = new Set(extractOutputPaths(previousConfiguration));
377
+ const currentPaths = new Set(extractOutputPaths(currentConfiguration));
378
+ const stalePaths = [...previousPaths].filter((stalePath) => !currentPaths.has(stalePath));
379
+ return (await Promise.all(stalePaths.map(async (stalePath) => {
380
+ const fileStats = await fsp.stat(stalePath).catch(() => null);
381
+ if (!fileStats) return null;
382
+ if (fileStats.isDirectory()) {
383
+ await fsp.rm(stalePath, {
384
+ recursive: true,
385
+ force: true
386
+ }).catch(() => {});
387
+ return stalePath;
388
+ }
389
+ if (fileStats.isFile() && (stalePath.endsWith(".ts") || stalePath.endsWith(".md"))) {
390
+ await fsp.unlink(stalePath).catch(() => {});
391
+ return stalePath;
392
+ }
393
+ return null;
394
+ }))).filter((result) => result !== null);
395
+ };
434
396
  const cleanedPaths = await cleanupStaleOutputs(pluginState.current, nextConfiguration.value);
435
397
  for (const cleanedPath of cleanedPaths) console.log(`✅ cleanup: ${cleanedPath}`);
436
398
  }
@@ -467,7 +429,7 @@ function honoTakibiVite() {
467
429
  await handleConfigurationChange(server);
468
430
  return;
469
431
  }
470
- if (pluginState.inputDirectory && isInputFile(absoluteChangedPath, pluginState.inputDirectory)) debouncedRunGeneration();
432
+ if (pluginState.inputDirectory && absoluteChangedPath.startsWith(pluginState.inputDirectory) && (absoluteChangedPath.endsWith(".yaml") || absoluteChangedPath.endsWith(".json") || absoluteChangedPath.endsWith(".tsp"))) debouncedRunGeneration();
471
433
  });
472
434
  await runGenerationAndReload(server);
473
435
  })().catch((error) => console.error("❌ watch error:", error));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hono-takibi",
3
- "version": "0.9.995",
3
+ "version": "0.9.996",
4
4
  "description": "Hono Takibi is a code generator from OpenAPI to @hono/zod-openapi",
5
5
  "keywords": [
6
6
  "hono",
@@ -72,18 +72,18 @@
72
72
  "@apidevtools/swagger-parser": "^12.1.0",
73
73
  "@typespec/compiler": "^1.10.0",
74
74
  "@typespec/openapi3": "^1.10.0",
75
- "oxfmt": "^0.42.0",
75
+ "oxfmt": "^0.43.0",
76
76
  "ts-morph": "^27.0.2",
77
77
  "tsx": "^4.21.0",
78
78
  "typescript": "^6.0.2",
79
79
  "zod": "^4.3.6"
80
80
  },
81
81
  "devDependencies": {
82
- "@types/node": "^25.5.0",
82
+ "@types/node": "^25.5.2",
83
83
  "@typespec/http": "^1.10.0",
84
84
  "@typespec/rest": "^0.80.0",
85
85
  "@typespec/versioning": "^0.80.0",
86
- "tsdown": "0.21.5"
86
+ "tsdown": "0.21.7"
87
87
  },
88
88
  "scripts": {
89
89
  "dev": "vite --host",
File without changes
File without changes