hono-takibi 0.9.26 → 0.9.30

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
@@ -33,31 +33,6 @@ If you have OpenAPI specifications, Hono Takibi automates the conversion process
33
33
  npx hono-takibi path/to/input.{yaml,json,tsp} -o path/to/output.ts
34
34
  ```
35
35
 
36
- ## CLI
37
-
38
- ### Options
39
-
40
- basic
41
-
42
- ```bash
43
- Options:
44
- --export-type export TypeScript type aliases
45
- --export-schema export Zod schema objects
46
- --template generate app file and handler stubs
47
- --test generate empty *.test.ts files
48
- --base-path <path> api prefix (default: /)
49
- ```
50
-
51
- template
52
-
53
- > **⚠️** When using the `--template` option, you must specify a valid directory path. Ensure the directory exists before executing the
54
-
55
- ### Example
56
-
57
- ```bash
58
- npx hono-takibi path/to/input.{yaml,json,tsp} -o path/to/output.ts --export-type --export-schema --template --base-path '/api/v1'
59
- ```
60
-
61
36
  input:
62
37
 
63
38
  ```yaml
@@ -112,6 +87,31 @@ export const getRoute = createRoute({
112
87
 
113
88
  ![](https://raw.githubusercontent.com/nakita628/hono-takibi/refs/heads/main/assets/demo/hono-takibi.gif)
114
89
 
90
+ ## CLI
91
+
92
+ ### Options
93
+
94
+ basic
95
+
96
+ ```bash
97
+ Options:
98
+ --export-type export TypeScript type aliases
99
+ --export-schema export Zod schema objects
100
+ --template generate app file and handler stubs
101
+ --test generate empty *.test.ts files
102
+ --base-path <path> api prefix (default: /)
103
+ ```
104
+
105
+ template
106
+
107
+ > **⚠️** When using the `--template` option, you must specify a valid directory path. Ensure the directory exists before executing the
108
+
109
+ ### Example
110
+
111
+ ```bash
112
+ npx hono-takibi path/to/input.{yaml,json,tsp} -o path/to/output.ts --export-type --export-schema --template --base-path '/api/v1'
113
+ ```
114
+
115
115
  ## HonoTakibiVite
116
116
 
117
117
  ### Automatic Code Regeneration & HMR
@@ -5,17 +5,16 @@
5
5
  *
6
6
  * ```mermaid
7
7
  * flowchart TD
8
- * A["Start honoTakibi()"] --> B["args = process.argv.slice(2)"]
9
- * B --> C{"isHelpRequested(args) ?"}
10
- * C -->|Yes| D["return { ok:true, value: HELP_TEXT }"]
11
- * C -->|No| E["cliResult = parseCli(args)"]
12
- * E --> F{"cliResult.ok ?"}
13
- * F -->|No| G["return { ok:false, error: cliResult.error }"]
14
- * F -->|Yes| H["cli = cliResult.value"]
15
- * H --> I["takibiResult = await takibi(cli.input, cli.output, ...options)"]
16
- * I --> J{"takibiResult.ok ?"}
17
- * J -->|No| K["return { ok:false, error: takibiResult.error }"]
18
- * J -->|Yes| L["return { ok:true, value: takibiResult.value }"]
8
+ * A["Start honoTakibi"] --> B["Parse args"]
9
+ * B --> C{"Help requested?"}
10
+ * C -- Yes --> D["Return help text"]
11
+ * C -- No --> E["parseCli(args)"]
12
+ * E --> F{"cliResult.ok?"}
13
+ * F -- No --> G["Return parse error"]
14
+ * F -- Yes --> H["Run takibi"]
15
+ * H --> I{"takibi.ok?"}
16
+ * I -- No --> J["Return takibi error"]
17
+ * I -- Yes --> K["Return success message"]
19
18
  * ```
20
19
  *
21
20
  * **Options**
package/dist/cli/index.js CHANGED
@@ -1,13 +1,16 @@
1
1
  import { existsSync } from 'node:fs';
2
2
  import { resolve } from 'node:path';
3
3
  import { config } from '../config/index.js';
4
- import { core } from '../core/core.js';
5
4
  import { route } from '../core/route.js';
5
+ import { rpc } from '../core/rpc.js';
6
6
  import { schema } from '../core/schema.js';
7
7
  import { takibi } from '../core/takibi.js';
8
- import { rpc } from '../generator/rpc/index.js';
9
8
  // import { honoRpcWithSWR } from '../generator/swr/index.js'
10
9
  import { parseCli } from '../utils/index.js';
10
+ /**
11
+ * CLI usage help text shown when `-h`/`--help` is provided.
12
+ * Kept as a single template for easy updates and snapshot stability.
13
+ */
11
14
  const HELP_TEXT = `Usage: hono-takibi <input.{yaml,json,tsp}> -o <routes.ts> [options]
12
15
 
13
16
  Options:
@@ -24,17 +27,16 @@ Options:
24
27
  *
25
28
  * ```mermaid
26
29
  * flowchart TD
27
- * A["Start honoTakibi()"] --> B["args = process.argv.slice(2)"]
28
- * B --> C{"isHelpRequested(args) ?"}
29
- * C -->|Yes| D["return { ok:true, value: HELP_TEXT }"]
30
- * C -->|No| E["cliResult = parseCli(args)"]
31
- * E --> F{"cliResult.ok ?"}
32
- * F -->|No| G["return { ok:false, error: cliResult.error }"]
33
- * F -->|Yes| H["cli = cliResult.value"]
34
- * H --> I["takibiResult = await takibi(cli.input, cli.output, ...options)"]
35
- * I --> J{"takibiResult.ok ?"}
36
- * J -->|No| K["return { ok:false, error: takibiResult.error }"]
37
- * J -->|Yes| L["return { ok:true, value: takibiResult.value }"]
30
+ * A["Start honoTakibi"] --> B["Parse args"]
31
+ * B --> C{"Help requested?"}
32
+ * C -- Yes --> D["Return help text"]
33
+ * C -- No --> E["parseCli(args)"]
34
+ * E --> F{"cliResult.ok?"}
35
+ * F -- No --> G["Return parse error"]
36
+ * F -- Yes --> H["Run takibi"]
37
+ * H --> I{"takibi.ok?"}
38
+ * I -- No --> J["Return takibi error"]
39
+ * I -- Yes --> K["Return success message"]
38
40
  * ```
39
41
  *
40
42
  * **Options**
@@ -50,7 +52,7 @@ Options:
50
52
  * - `{ ok: false, error: string }` on validation or generation errors
51
53
  */
52
54
  export async function honoTakibi() {
53
- // Slice the arguments to remove the first two (node and script path)
55
+ /** Slice the arguments to remove the first two (node and script path) */
54
56
  const args = process.argv.slice(2);
55
57
  const isHelpRequested = (args) => {
56
58
  return args.length === 1 && (args[0] === '--help' || args[0] === '-h');
@@ -63,11 +65,11 @@ export async function honoTakibi() {
63
65
  };
64
66
  }
65
67
  const abs = resolve(process.cwd(), 'hono-takibi.config.ts');
68
+ /** If config file does not exist, parse CLI arguments */
66
69
  if (!existsSync(abs)) {
67
70
  const cliResult = parseCli(args);
68
- if (!cliResult.ok) {
71
+ if (!cliResult.ok)
69
72
  return { ok: false, error: cliResult.error };
70
- }
71
73
  const cli = cliResult.value;
72
74
  const takibiResult = await takibi(cli.input, cli.output, cli.exportSchema ?? false, cli.exportType ?? false, cli.template ?? false, cli.test ?? false, cli.basePath);
73
75
  if (!takibiResult.ok) {
@@ -78,11 +80,13 @@ export async function honoTakibi() {
78
80
  value: takibiResult.value,
79
81
  };
80
82
  }
83
+ /** If config file exists, parse config file */
81
84
  const configResult = await config();
82
85
  if (!configResult.ok) {
83
86
  return { ok: false, error: configResult.error };
84
87
  }
85
88
  const c = configResult.value;
89
+ /** takibi */
86
90
  const takibiResult = c['zod-openapi']?.output
87
91
  ? await takibi(c.input, c['zod-openapi']?.output, c['zod-openapi']?.exportSchema ?? false, c['zod-openapi']?.exportType ?? false, false, // template
88
92
  false)
@@ -90,38 +94,27 @@ export async function honoTakibi() {
90
94
  if (takibiResult && !takibiResult.ok) {
91
95
  return { ok: false, error: takibiResult.error };
92
96
  }
93
- // schema
97
+ /** schema */
94
98
  const schemaResult = c['zod-openapi']?.schema
95
99
  ? await schema(c.input, c['zod-openapi'].schema.output, c['zod-openapi'].schema.exportType ?? false, c['zod-openapi']?.schema.split ?? false)
96
100
  : undefined;
97
101
  if (schemaResult && !schemaResult.ok) {
98
102
  return { ok: false, error: schemaResult.error };
99
103
  }
100
- // route
104
+ /** route */
101
105
  const routeResult = c['zod-openapi']?.route
102
106
  ? await route(c.input, c['zod-openapi'].route.output, c['zod-openapi'].route.import, c['zod-openapi'].route.split ?? false)
103
107
  : undefined;
104
108
  if (routeResult && !routeResult.ok) {
105
109
  return { ok: false, error: routeResult.error };
106
110
  }
111
+ /** rpc */
107
112
  const rpcResult = c.rpc
108
- ? await core(c.input, c.rpc.output, c.rpc.import, 'Generated RPC code written to', rpc)
113
+ ? await rpc(c.input, c.rpc.output, c.rpc.import, c.rpc.split ?? false)
109
114
  : undefined;
110
115
  if (rpcResult && !rpcResult.ok) {
111
116
  return { ok: false, error: rpcResult.error };
112
117
  }
113
- // const swrResult = c.swr
114
- // ? await core(
115
- // c.swr.input,
116
- // c.swr.output,
117
- // c.swr.import,
118
- // 'Generated SWR code written to',
119
- // honoRpcWithSWR,
120
- // )
121
- // : undefined
122
- // if (swrResult && !swrResult.ok) {
123
- // return { ok: false, error: swrResult.error }
124
- // }
125
118
  const results = [takibiResult?.value, rpcResult?.value].filter((v) => Boolean(v));
126
119
  return {
127
120
  ok: true,
@@ -16,8 +16,9 @@ type Config = {
16
16
  };
17
17
  };
18
18
  readonly rpc?: {
19
- readonly output: `${string}.ts`;
19
+ readonly output: string | `${string}.ts`;
20
20
  readonly import: string;
21
+ readonly split?: boolean;
21
22
  };
22
23
  };
23
24
  export declare function config(): Promise<{
@@ -27,5 +28,10 @@ export declare function config(): Promise<{
27
28
  readonly ok: false;
28
29
  readonly error: string;
29
30
  }>;
31
+ /**
32
+ * Helper to define a config with full type completion.
33
+ *
34
+ * @see config
35
+ */
30
36
  export declare function defineConfig(c: Config): Config;
31
37
  export {};
@@ -2,149 +2,32 @@ import { existsSync } from 'node:fs';
2
2
  import { resolve } from 'node:path';
3
3
  import { pathToFileURL } from 'node:url';
4
4
  import { register } from 'tsx/esm/api';
5
- const isYamlOrJsonOrTsp = (i) => typeof i === 'string' && (i.endsWith('.yaml') || i.endsWith('.json') || i.endsWith('.tsp'));
6
- const isTs = (o) => typeof o === 'string' && o.endsWith('.ts');
5
+ import { parseConfig } from '../utils/index.js';
7
6
  export async function config() {
8
7
  const abs = resolve(process.cwd(), 'hono-takibi.config.ts');
9
8
  if (!existsSync(abs))
10
9
  return { ok: false, error: `Config not found: ${abs}` };
11
10
  try {
12
11
  register();
13
- const mod = await import(pathToFileURL(abs).href);
14
- if (!('default' in mod)) {
12
+ const url = pathToFileURL(abs).href;
13
+ const mod = await import(/* @vite-ignore */ url);
14
+ if (!('default' in mod) || mod.default === undefined) {
15
15
  return { ok: false, error: 'Config must export default object' };
16
16
  }
17
- if (mod.default) {
18
- // input
19
- if (!isYamlOrJsonOrTsp(mod.default.input)) {
20
- return {
21
- ok: false,
22
- error: `Invalid input format for zod-openapi: ${String(mod.default.input)}`,
23
- };
24
- }
25
- // zod-openapi
26
- const zo = mod.default['zod-openapi'];
27
- if (zo) {
28
- // boolean flags
29
- if (zo.exportSchema !== undefined && typeof zo.exportSchema !== 'boolean') {
30
- return {
31
- ok: false,
32
- error: `Invalid exportSchema format for zod-openapi: ${String(zo.exportSchema)}`,
33
- };
34
- }
35
- if (zo.exportType !== undefined && typeof zo.exportType !== 'boolean') {
36
- return {
37
- ok: false,
38
- error: `Invalid exportType format for zod-openapi: ${String(zo.exportType)}`,
39
- };
40
- }
41
- const hasSchema = zo.schema !== undefined;
42
- const hasRoute = zo.route !== undefined;
43
- if (hasSchema || hasRoute) {
44
- if (Object.hasOwn(zo, 'output')) {
45
- return {
46
- ok: false,
47
- error: "Invalid config: When using 'zod-openapi.schema' or 'zod-openapi.route', do NOT set 'zod-openapi.output'.",
48
- };
49
- }
50
- }
51
- else {
52
- if (!isTs(zo.output)) {
53
- return {
54
- ok: false,
55
- error: `Invalid output format for zod-openapi: ${String(zo.output)}`,
56
- };
57
- }
58
- }
59
- if (hasSchema) {
60
- const s = zo.schema;
61
- if (!s)
62
- return { ok: false, error: 'Invalid config: zod-openapi.schema is undefined' };
63
- if (s.split !== undefined && typeof s.split !== 'boolean') {
64
- return {
65
- ok: false,
66
- error: `Invalid schema split format for zod-openapi: ${String(s.split)}`,
67
- };
68
- }
69
- if (typeof s.output !== 'string') {
70
- return { ok: false, error: `Invalid schema output path: ${String(s.output)}` };
71
- }
72
- if (s.split === true) {
73
- if (isTs(s.output)) {
74
- return {
75
- ok: false,
76
- error: `Invalid schema output path for split mode (must be a directory, not .ts): ${s.output}`,
77
- };
78
- }
79
- }
80
- else {
81
- if (!isTs(s.output)) {
82
- return {
83
- ok: false,
84
- error: `Invalid schema output path for non-split mode (must be .ts file): ${s.output}`,
85
- };
86
- }
87
- }
88
- if (s.exportType !== undefined && typeof s.exportType !== 'boolean') {
89
- return {
90
- ok: false,
91
- error: `Invalid schema exportType format for zod-openapi: ${String(s.exportType)}`,
92
- };
93
- }
94
- }
95
- if (hasRoute) {
96
- const r = zo.route;
97
- if (!r)
98
- return { ok: false, error: 'Invalid config: zod-openapi.route is undefined' };
99
- if (typeof r.import !== 'string') {
100
- return {
101
- ok: false,
102
- error: `Invalid route import format for zod-openapi: ${String(r.import)}`,
103
- };
104
- }
105
- if (r.split !== undefined && typeof r.split !== 'boolean') {
106
- return {
107
- ok: false,
108
- error: `Invalid route split format for zod-openapi: ${String(r.split)}`,
109
- };
110
- }
111
- if (typeof r.output !== 'string') {
112
- return { ok: false, error: `Invalid route output path: ${String(r.output)}` };
113
- }
114
- if (r.split === true) {
115
- if (isTs(r.output)) {
116
- return {
117
- ok: false,
118
- error: `Invalid route output path for split mode (must be a directory, not .ts): ${r.output}`,
119
- };
120
- }
121
- }
122
- else {
123
- if (!isTs(r.output)) {
124
- return {
125
- ok: false,
126
- error: `Invalid route output path for non-split mode (must be .ts file): ${r.output}`,
127
- };
128
- }
129
- }
130
- }
131
- }
132
- const rpc = mod.default.rpc;
133
- if (rpc) {
134
- if (!isTs(rpc.output)) {
135
- return { ok: false, error: `Invalid output format for rpc: ${String(rpc.output)}` };
136
- }
137
- if (typeof rpc.import !== 'string') {
138
- return { ok: false, error: `Invalid import format for rpc: ${String(rpc.import)}` };
139
- }
140
- }
141
- }
142
- return { ok: true, value: mod.default };
17
+ const result = parseConfig(mod.default);
18
+ if (!result.ok)
19
+ return { ok: false, error: result.error };
20
+ return { ok: true, value: result.value };
143
21
  }
144
22
  catch (e) {
145
23
  return { ok: false, error: e instanceof Error ? e.message : String(e) };
146
24
  }
147
25
  }
26
+ /**
27
+ * Helper to define a config with full type completion.
28
+ *
29
+ * @see config
30
+ */
148
31
  export function defineConfig(c) {
149
32
  return c;
150
33
  }
@@ -35,16 +35,16 @@ export async function route(input, output, importPath, split) {
35
35
  const importHono = `import { createRoute${includeZ ? ', z' : ''} } from '@hono/zod-openapi'`;
36
36
  const importSchemas = schemaTokens.length > 0 ? `import { ${schemaTokens.join(',')} } from '${importPath}'` : '';
37
37
  const finalSrc = [importHono, importSchemas, '\n', routesSrc].filter(Boolean).join('\n');
38
- const fmtCode = await fmt(finalSrc);
39
- if (!fmtCode.ok)
40
- return { ok: false, error: fmtCode.error };
41
- const mk = await mkdir(path.dirname(output));
42
- if (!mk.ok)
43
- return { ok: false, error: mk.error };
44
- const wr = await writeFile(output, fmtCode.value);
45
- return wr.ok
38
+ const fmtResult = await fmt(finalSrc);
39
+ if (!fmtResult.ok)
40
+ return { ok: false, error: fmtResult.error };
41
+ const mkdirResult = await mkdir(path.dirname(output));
42
+ if (!mkdirResult.ok)
43
+ return { ok: false, error: mkdirResult.error };
44
+ const writeResult = await writeFile(output, fmtResult.value);
45
+ return writeResult.ok
46
46
  ? { ok: true, value: `Generated route code written to ${output}` }
47
- : { ok: false, error: wr.error };
47
+ : { ok: false, error: writeResult.error };
48
48
  }
49
49
  const outDir = output.replace(/\.ts$/, '');
50
50
  const blocks = extractRouteBlocks(routesSrc);
@@ -54,16 +54,16 @@ export async function route(input, output, importPath, split) {
54
54
  const importHono = `import { createRoute${includeZ ? ', z' : ''} } from '@hono/zod-openapi'`;
55
55
  const importSchemas = schemaTokens.length > 0 ? `import { ${schemaTokens.join(',')} } from '${importPath}'` : '';
56
56
  const finalSrc = [importHono, importSchemas, '\n', routesSrc].filter(Boolean).join('\n');
57
- const fmtCode = await fmt(finalSrc);
58
- if (!fmtCode.ok)
59
- return { ok: false, error: fmtCode.error };
60
- const mk = await mkdir(path.dirname(output));
61
- if (!mk.ok)
62
- return { ok: false, error: mk.error };
63
- const wr = await writeFile(output, fmtCode.value);
64
- return wr.ok
57
+ const fmtResult = await fmt(finalSrc);
58
+ if (!fmtResult.ok)
59
+ return { ok: false, error: fmtResult.error };
60
+ const mkdirResult = await mkdir(path.dirname(output));
61
+ if (!mkdirResult.ok)
62
+ return { ok: false, error: mkdirResult.error };
63
+ const writeResult = await writeFile(output, fmtResult.value);
64
+ return writeResult.ok
65
65
  ? { ok: true, value: `Generated route code written to ${output}` }
66
- : { ok: false, error: wr.error };
66
+ : { ok: false, error: writeResult.error };
67
67
  }
68
68
  for (const { name, block } of blocks) {
69
69
  const includeZ = block.includes('z.');
@@ -71,26 +71,29 @@ export async function route(input, output, importPath, split) {
71
71
  const importHono = `import { createRoute${includeZ ? ', z' : ''} } from '@hono/zod-openapi'`;
72
72
  const importSchemas = schemaTokens.length > 0 ? `import { ${schemaTokens.join(',')} } from '${importPath}'` : '';
73
73
  const fileSrc = [importHono, importSchemas, '\n', block, ''].filter(Boolean).join('\n');
74
- const fmtCode = await fmt(fileSrc);
75
- if (!fmtCode.ok)
76
- return { ok: false, error: fmtCode.error };
74
+ const fmtResult = await fmt(fileSrc);
75
+ if (!fmtResult.ok)
76
+ return { ok: false, error: fmtResult.error };
77
77
  const filePath = `${outDir}/${lowerFirst(name)}.ts`;
78
- const mk = await mkdir(path.dirname(filePath));
79
- if (!mk.ok)
80
- return { ok: false, error: mk.error };
81
- const wr = await writeFile(filePath, fmtCode.value);
82
- if (!wr.ok)
83
- return { ok: false, error: wr.error };
78
+ const mkdirResult = await mkdir(path.dirname(filePath));
79
+ if (!mkdirResult.ok)
80
+ return { ok: false, error: mkdirResult.error };
81
+ const writeResult = await writeFile(filePath, fmtResult.value);
82
+ if (!writeResult.ok)
83
+ return { ok: false, error: writeResult.error };
84
84
  }
85
- const indexBody = `${blocks.map(({ name }) => `export * from './${lowerFirst(name)}'`).join('\n')}\n`;
86
- const indexFmt = await fmt(indexBody);
87
- if (!indexFmt.ok)
88
- return { ok: false, error: indexFmt.error };
89
- const mkIndex = await mkdir(path.dirname(`${outDir}/index.ts`));
90
- if (!mkIndex.ok)
91
- return { ok: false, error: mkIndex.error };
92
- const wrIndex = await writeFile(`${outDir}/index.ts`, indexFmt.value);
93
- if (!wrIndex.ok)
94
- return { ok: false, error: wrIndex.error };
85
+ const indexBody = `${blocks
86
+ .sort()
87
+ .map(({ name }) => `export * from './${lowerFirst(name)}'`)
88
+ .join('\n')}\n`;
89
+ const fmtResult = await fmt(indexBody);
90
+ if (!fmtResult.ok)
91
+ return { ok: false, error: fmtResult.error };
92
+ const mkdirResult = await mkdir(path.dirname(`${outDir}/index.ts`));
93
+ if (!mkdirResult.ok)
94
+ return { ok: false, error: mkdirResult.error };
95
+ const writeResult = await writeFile(`${outDir}/index.ts`, fmtResult.value);
96
+ if (!writeResult.ok)
97
+ return { ok: false, error: writeResult.error };
95
98
  return { ok: true, value: `Generated route code written to ${outDir}/*.ts (index.ts included)` };
96
99
  }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Generate RPC client wrappers from an OpenAPI/TypeSpec source.
3
+ *
4
+ * - When `split=true`, writes one file per RPC function under `output` (directory) and an `index.ts` barrel.
5
+ * - Otherwise, emits a single `.ts` file at `output`.
6
+ */
7
+ export declare function rpc(input: `${string}.yaml` | `${string}.json` | `${string}.tsp`, output: string | `${string}.ts`, importPath: string, split?: boolean): Promise<{
8
+ readonly ok: true;
9
+ readonly value: string;
10
+ } | {
11
+ readonly ok: false;
12
+ readonly error: string;
13
+ }>;