@hono/cli 0.0.1 → 0.1.1

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.
Files changed (3) hide show
  1. package/README.md +275 -26
  2. package/dist/cli.js +552 -58
  3. package/package.json +5 -3
package/README.md CHANGED
@@ -1,6 +1,10 @@
1
1
  # Hono CLI
2
2
 
3
- CLI for Hono
3
+ Hono CLI is a CLI for Humans and AI who use Hono.
4
+
5
+ It's not a `create-*` command, not only for dev, build, and deploy, but also not a Vite wrapper. Built on an entirely new concept.
6
+
7
+ Hono CLI will give you the `hono` command. For Humans, you can use sub-commands specialized for Hono for simple usages. For AI, providing sub-commands to build your Hono application efficiently with AI coding.
4
8
 
5
9
  ## Installation
6
10
 
@@ -14,59 +18,304 @@ npm install -g @hono/cli
14
18
  # Show help
15
19
  hono --help
16
20
 
17
- # Create a new Hono project
18
- hono create
19
-
20
- # Open documentation
21
+ # Display documentation
21
22
  hono docs
23
+
24
+ # Search documentation
25
+ hono search middleware
26
+
27
+ # Send request to Hono app
28
+ hono request
29
+
30
+ # Start server
31
+ hono serve
32
+
33
+ # Generate an optimized Hono app
34
+ hono optimize
22
35
  ```
23
36
 
24
37
  ## Commands
25
38
 
26
- - `create [target]` - Create a new Hono project
27
- - `docs` - Open Hono documentation in browser
39
+ - `docs [path]` - Display Hono documentation
40
+ - `search <query>` - Search Hono documentation
41
+ - `request [file]` - Send request to Hono app using `app.request()`
42
+ - `serve [entry]` - Start server for Hono app
43
+ - `optimize [entry]` - Generate an optimized Hono app
28
44
 
29
- ### `create`
45
+ ### `docs`
30
46
 
31
- Create a new Hono project using [create-hono](https://github.com/honojs/create-hono).
47
+ Display Hono documentation content directly in your terminal.
32
48
 
33
49
  ```bash
34
- hono create [target] [options]
50
+ hono docs [path]
35
51
  ```
36
52
 
37
53
  **Arguments:**
38
54
 
39
- - `target` - Target directory (optional)
55
+ - `path` - Documentation path (optional)
56
+
57
+ **Examples:**
58
+
59
+ ```bash
60
+ # Display main documentation summary (llms.txt)
61
+ hono docs
62
+
63
+ # Display specific documentation pages
64
+ hono docs /docs/concepts/motivation
65
+ hono docs /docs/guides/best-practices
66
+ hono docs /docs/api/context
67
+
68
+ # Display examples and tutorials
69
+ hono docs /examples/stytch-auth
70
+ hono docs /examples/basic
71
+ ```
72
+
73
+ ### `search`
74
+
75
+ Search through Hono documentation using fuzzy search powered by Algolia.
76
+
77
+ ```bash
78
+ hono search <query> [options]
79
+ ```
80
+
81
+ **Arguments:**
82
+
83
+ - `query` - Search query (required)
40
84
 
41
85
  **Options:**
42
86
 
43
- - `-t, --template <template>` - Template to use (aws-lambda, bun, cloudflare-workers, cloudflare-workers+vite, deno, fastly, lambda-edge, netlify, nextjs, nodejs, vercel, cloudflare-pages, x-basic)
44
- - `-i, --install` - Install dependencies
45
- - `-p, --pm <pm>` - Package manager to use (npm, bun, deno, pnpm, yarn)
46
- - `-o, --offline` - Use offline mode
87
+ - `-l, --limit <number>` - Number of results to show (default: 5, max: 20)
88
+ - `-p, --pretty` - Display results in human-readable format (default: JSON output)
47
89
 
48
90
  **Examples:**
49
91
 
50
92
  ```bash
51
- # Interactive project creation
52
- hono create
93
+ # Search for middleware documentation (JSON output by default)
94
+ hono search middleware
53
95
 
54
- # Create project in specific directory
55
- hono create my-app
96
+ # Search with pretty formatting for human-readable output
97
+ hono search middleware --pretty
98
+
99
+ # Search with custom result limit
100
+ hono search "getting started" --limit 10
101
+ ```
56
102
 
57
- # Create with Cloudflare Workers template
58
- hono create my-app --template cloudflare-workers
103
+ **Output Format:**
59
104
 
60
- # Create and install dependencies with Bun
61
- hono create my-app --pm bun --install
105
+ By default, the command outputs JSON for easy integration with other tools:
106
+
107
+ ```json
108
+ {
109
+ "query": "middleware",
110
+ "total": 5,
111
+ "results": [
112
+ {
113
+ "title": "Middleware ​",
114
+ "category": "",
115
+ "url": "https://hono.dev/docs/guides/middleware#middleware",
116
+ "path": "/docs/guides/middleware"
117
+ },
118
+ {
119
+ "title": "Third-party Middleware - Hono",
120
+ "category": "Middleware",
121
+ "url": "https://hono.dev/docs/middleware/third-party#VPSidebarNav",
122
+ "path": "/docs/middleware/third-party"
123
+ }
124
+ ]
125
+ }
62
126
  ```
63
127
 
64
- ### `docs`
128
+ With the `--pretty` flag, results are displayed in a human-readable format:
65
129
 
66
- Open Hono documentation in your default browser.
130
+ ```
131
+ 1. Middleware ​
132
+ URL: https://hono.dev/docs/guides/middleware#middleware
133
+ Command: hono docs /docs/guides/middleware
134
+
135
+ 2. Third-party Middleware - Hono
136
+ Category: Middleware
137
+ URL: https://hono.dev/docs/middleware/third-party#VPSidebarNav
138
+ Command: hono docs /docs/middleware/third-party
139
+ ```
140
+
141
+ ### `request`
142
+
143
+ Send HTTP requests to your Hono application using the built-in `app.request()` method. This is particularly useful for testing and development.
67
144
 
68
145
  ```bash
69
- hono docs
146
+ hono request [file] [options]
147
+ ```
148
+
149
+ **Arguments:**
150
+
151
+ - `file` - Path to the Hono app file (TypeScript/JSX supported, optional)
152
+
153
+ **Options:**
154
+
155
+ - `-P, --path <path>` - Request path (default: "/")
156
+ - `-X, --method <method>` - HTTP method (default: GET)
157
+ - `-d, --data <data>` - Request body data
158
+ - `-H, --header <header>` - Custom headers (can be used multiple times)
159
+ - `-w, --watch` - Watch for changes and resend request
160
+
161
+ **Examples:**
162
+
163
+ ```bash
164
+ # GET request to default app root (uses src/index.ts or src/index.tsx)
165
+ hono request
166
+
167
+ # GET request to specific path
168
+ hono request -P /users/123
169
+
170
+ # POST request with data
171
+ hono request -P /api/users -X POST -d '{"name":"Alice"}'
172
+
173
+ # Request to specific file
174
+ hono request -P /api src/your-app.ts
175
+
176
+ # Request with custom headers
177
+ hono request -P /api/protected \
178
+ -H 'Authorization: Bearer token' \
179
+ -H 'User-Agent: MyApp' \
180
+ src/your-app.ts
181
+ ```
182
+
183
+ **Response Format:**
184
+
185
+ The command returns a JSON object with the following structure:
186
+
187
+ ```json
188
+ {
189
+ "status": 200,
190
+ "body": "{\"message\":\"Hello World\"}",
191
+ "headers": {
192
+ "content-type": "application/json",
193
+ "x-custom-header": "value"
194
+ }
195
+ }
196
+ ```
197
+
198
+ ### `serve`
199
+
200
+ Start a server for your Hono application. This is a simple server specialized for Hono applications with built-in TypeScript and JSX support.
201
+
202
+ ```bash
203
+ hono serve [entry] [options]
204
+ ```
205
+
206
+ **Arguments:**
207
+
208
+ - `entry` - Entry file for your Hono app (TypeScript/JSX supported, optional)
209
+
210
+ **Options:**
211
+
212
+ - `-p, --port <port>` - Port number (default: 7070)
213
+ - `--show-routes` - Show registered routes
214
+ - `--use <middleware>` - Use middleware (can be used multiple times)
215
+
216
+ **Examples:**
217
+
218
+ ```bash
219
+ # Start server with default empty app (no entry file needed)
220
+ hono serve
221
+
222
+ # Start server on specific port
223
+ hono serve -p 3000 src/app.ts
224
+
225
+ # Start server with specific entry file
226
+ hono serve src/app.ts
227
+
228
+ # Start server and show routes
229
+ hono serve --show-routes src/app.ts
230
+
231
+ # Start server with middleware
232
+ hono serve --use 'cors()' src/app.ts
233
+
234
+ # Start server with multiple middleware
235
+ hono serve --use 'cors()' --use 'logger()' src/app.ts
236
+
237
+ # Start server with authentication and static file serving
238
+ hono serve \
239
+ --use 'basicAuth({ username: "foo", password: "bar" })' \
240
+ --use "serveStatic({ root: './' })"
241
+ ```
242
+
243
+ ### `optimize`
244
+
245
+ Generate an optimized Hono class and export bundled file.
246
+
247
+ ```bash
248
+ hono optimize [entry] [options]
249
+ ```
250
+
251
+ **Arguments:**
252
+
253
+ - `entry` - Entry file for your Hono app (TypeScript/JSX supported, optional)
254
+
255
+ **Options:**
256
+
257
+ - `-o, --outfile <outfile>` - Output file
258
+
259
+ **Examples:**
260
+
261
+ ```bash
262
+ # Generate an optimized Hono class and export bundled file to dist/index.js
263
+ hono optimize
264
+
265
+ # Specify entry file and output file
266
+ hono optimize -o dist/app.js src/app.ts
267
+
268
+ # Export bundled file with minification
269
+ hono optimize -m
270
+ ```
271
+
272
+ ## Tips
273
+
274
+ ### Using Hono CLI with AI Code Agents
275
+
276
+ When working with AI code agents like Claude Code, you can configure them to use the `hono` CLI for efficient documentation access and testing. Add the following to your project's `CLAUDE.md` or similar configuration:
277
+
278
+ ````markdown
279
+ ## Hono Development
280
+
281
+ Use the `hono` CLI for efficient development. View all commands with `hono --help`.
282
+
283
+ ### Core Commands
284
+
285
+ - **`hono docs [path]`** - Browse Hono documentation
286
+ - **`hono search <query>`** - Search documentation
287
+ - **`hono request [file]`** - Test app requests without starting a server
288
+
289
+ ### Quick Examples
290
+
291
+ ```bash
292
+ # Search for topics
293
+ hono search middleware
294
+ hono search "getting started"
295
+
296
+ # View documentation
297
+ hono docs /docs/api/context
298
+ hono docs /docs/guides/middleware
299
+
300
+ # Test your app
301
+ hono request -P /api/users src/index.ts
302
+ hono request -P /api/users -X POST -d '{"name":"Alice"}' src/index.ts
303
+ ```
304
+
305
+ ### Workflow
306
+
307
+ 1. Search documentation: `hono search <query>`
308
+ 2. Read relevant docs: `hono docs [path]`
309
+ 3. Test implementation: `hono request [file]`
310
+ ````
311
+
312
+ ### Pipeline Integration with jq
313
+
314
+ The `search` command outputs JSON by default, making it easy to pipe results to other commands:
315
+
316
+ ```bash
317
+ # Search and view documentation in one command
318
+ hono search "middleware" | jq '.results[0].path' | hono docs
70
319
  ```
71
320
 
72
321
  ## Authors
package/dist/cli.js CHANGED
@@ -3,84 +3,578 @@
3
3
  // src/cli.ts
4
4
  import { Command } from "commander";
5
5
  import { readFileSync } from "fs";
6
- import { dirname, join } from "path";
6
+ import { dirname as dirname2, join as join2 } from "path";
7
7
  import { fileURLToPath } from "url";
8
8
 
9
- // src/commands/create/index.ts
10
- import { spawn } from "child_process";
11
- function createCommand(program2) {
12
- program2.command("create").description("Create a new Hono project").argument("[target]", "target directory").option(
13
- "-t, --template <template>",
14
- "Template to use (aws-lambda, bun, cloudflare-workers, cloudflare-workers+vite, deno, fastly, lambda-edge, netlify, nextjs, nodejs, vercel, cloudflare-pages, x-basic)"
15
- ).option("-i, --install", "Install dependencies").option("-p, --pm <pm>", "Package manager to use (npm, bun, deno, pnpm, yarn)").option("-o, --offline", "Use offline mode").action(
16
- (target, options) => {
17
- const args = ["create", "hono@latest"];
18
- if (target) {
19
- args.push(target);
9
+ // src/commands/docs/index.ts
10
+ async function fetchAndDisplayContent(url, fallbackUrl) {
11
+ try {
12
+ const response = await fetch(url);
13
+ if (!response.ok) {
14
+ throw new Error(`Failed to fetch documentation: ${response.status} ${response.statusText}`);
15
+ }
16
+ const content = await response.text();
17
+ console.log("\n" + content);
18
+ } catch (error) {
19
+ console.error(
20
+ "Error fetching documentation:",
21
+ error instanceof Error ? error.message : String(error)
22
+ );
23
+ console.log(`
24
+ Please visit: ${fallbackUrl || "https://hono.dev/docs"}`);
25
+ }
26
+ }
27
+ function docsCommand(program2) {
28
+ program2.command("docs").argument(
29
+ "[path]",
30
+ "Documentation path (e.g., /docs/concepts/motivation, /examples/stytch-auth)",
31
+ ""
32
+ ).description("Display Hono documentation").action(async (path) => {
33
+ let finalPath = path;
34
+ if (!path) {
35
+ if (!process.stdin.isTTY) {
36
+ try {
37
+ const chunks = [];
38
+ for await (const chunk of process.stdin) {
39
+ chunks.push(chunk);
40
+ }
41
+ const stdinInput = Buffer.concat(chunks).toString().trim();
42
+ if (stdinInput) {
43
+ finalPath = stdinInput.replace(/^["'](.*)["']$/, "$1");
44
+ }
45
+ } catch (error) {
46
+ console.error("Error reading from stdin:", error);
47
+ }
20
48
  }
21
- if (options.template) {
22
- args.push("--template", options.template);
49
+ if (!finalPath) {
50
+ console.log("Fetching Hono documentation...");
51
+ await fetchAndDisplayContent("https://hono.dev/llms.txt");
52
+ return;
23
53
  }
24
- if (options.install) {
25
- args.push("--install");
54
+ }
55
+ const normalizedPath = finalPath.startsWith("/") ? finalPath : `/${finalPath}`;
56
+ const basePath = normalizedPath.slice(1);
57
+ const markdownUrl = `https://raw.githubusercontent.com/honojs/website/refs/heads/main/${basePath}.md`;
58
+ const webUrl = `https://hono.dev${normalizedPath}`;
59
+ console.log(`Fetching Hono documentation for ${finalPath}...`);
60
+ await fetchAndDisplayContent(markdownUrl, webUrl);
61
+ });
62
+ }
63
+
64
+ // src/commands/optimize/index.ts
65
+ import * as esbuild2 from "esbuild";
66
+ import { buildInitParams, serializeInitParams } from "hono/router/reg-exp-router";
67
+ import { execFile } from "child_process";
68
+ import { existsSync, realpathSync, statSync } from "fs";
69
+ import { dirname, join, resolve } from "path";
70
+
71
+ // src/utils/build.ts
72
+ import * as esbuild from "esbuild";
73
+ async function* buildAndImportApp(filePath, options = {}) {
74
+ let resolveApp;
75
+ let appPromise;
76
+ const preparePromise = () => {
77
+ appPromise = new Promise((resolve4) => {
78
+ resolveApp = resolve4;
79
+ });
80
+ };
81
+ preparePromise();
82
+ const context2 = await esbuild.context({
83
+ entryPoints: [filePath],
84
+ bundle: true,
85
+ write: false,
86
+ format: "esm",
87
+ target: "node20",
88
+ jsx: "automatic",
89
+ jsxImportSource: "hono/jsx",
90
+ platform: "node",
91
+ external: options.external || [],
92
+ plugins: [
93
+ {
94
+ name: "watch",
95
+ setup(build2) {
96
+ build2.onEnd(async (result) => {
97
+ try {
98
+ const code = result.outputFiles?.[0]?.text || "";
99
+ const dataUrl = `data:text/javascript;base64,${Buffer.from(code).toString("base64")}`;
100
+ const module = await import(dataUrl);
101
+ const app = module.default;
102
+ if (!app) {
103
+ throw new Error("Failed to build app");
104
+ }
105
+ if (!app || typeof app.request !== "function") {
106
+ throw new Error("No valid Hono app exported from the file");
107
+ }
108
+ try {
109
+ resolveApp(app);
110
+ } catch {
111
+ }
112
+ } catch (error) {
113
+ console.error("Error building app", error);
114
+ }
115
+ });
116
+ }
26
117
  }
27
- if (options.pm) {
28
- args.push("--pm", options.pm);
118
+ ]
119
+ });
120
+ await context2.watch();
121
+ if (!options.watch) {
122
+ await context2.dispose();
123
+ }
124
+ do {
125
+ yield await appPromise;
126
+ preparePromise();
127
+ } while (options.watch);
128
+ }
129
+
130
+ // src/commands/optimize/index.ts
131
+ var DEFAULT_ENTRY_CANDIDATES = ["src/index.ts", "src/index.tsx", "src/index.js", "src/index.jsx"];
132
+ function optimizeCommand(program2) {
133
+ program2.command("optimize").description("Build optimized Hono class").argument("[entry]", "entry file").option("-o, --outfile [outfile]", "output file", "dist/index.js").option("-m, --minify", "minify output file").action(async (entry, options) => {
134
+ if (!entry) {
135
+ entry = DEFAULT_ENTRY_CANDIDATES.find((entry2) => existsSync(entry2)) ?? DEFAULT_ENTRY_CANDIDATES[0];
136
+ }
137
+ const appPath = resolve(process.cwd(), entry);
138
+ if (!existsSync(appPath)) {
139
+ throw new Error(`Entry file ${entry} does not exist`);
140
+ }
141
+ const appFilePath = realpathSync(appPath);
142
+ const buildIterator = buildAndImportApp(appFilePath, {
143
+ external: ["@hono/node-server"]
144
+ });
145
+ const app = (await buildIterator.next()).value;
146
+ let routerName;
147
+ let importStatement;
148
+ let assignRouterStatement;
149
+ try {
150
+ const serialized = serializeInitParams(
151
+ buildInitParams({
152
+ paths: app.routes.map(({ path }) => path)
153
+ })
154
+ );
155
+ const hasPreparedRegExpRouter = await new Promise((resolve4) => {
156
+ const child = execFile(process.execPath, [
157
+ "--input-type=module",
158
+ "-e",
159
+ "try { (await import('hono/router/reg-exp-router')).PreparedRegExpRouter && process.exit(0) } finally { process.exit(1) }"
160
+ ]);
161
+ child.on("exit", (code) => {
162
+ resolve4(code === 0);
163
+ });
164
+ });
165
+ if (hasPreparedRegExpRouter) {
166
+ routerName = "PreparedRegExpRouter";
167
+ importStatement = "import { PreparedRegExpRouter } from 'hono/router/reg-exp-router'";
168
+ assignRouterStatement = `const routerParams = ${serialized}
169
+ this.router = new PreparedRegExpRouter(...routerParams)`;
170
+ } else {
171
+ routerName = "RegExpRouter";
172
+ importStatement = "import { RegExpRouter } from 'hono/router/reg-exp-router'";
173
+ assignRouterStatement = "this.router = new RegExpRouter()";
29
174
  }
30
- if (options.offline) {
31
- args.push("--offline");
175
+ } catch {
176
+ routerName = "TrieRouter";
177
+ importStatement = "import { TrieRouter } from 'hono/router/trie-router'";
178
+ assignRouterStatement = "this.router = new TrieRouter()";
179
+ }
180
+ console.log("[Optimized]");
181
+ console.log(` Router: ${routerName}`);
182
+ const outfile = resolve(process.cwd(), options.outfile);
183
+ await esbuild2.build({
184
+ entryPoints: [appFilePath],
185
+ outfile,
186
+ bundle: true,
187
+ minify: options.minify,
188
+ format: "esm",
189
+ target: "node20",
190
+ platform: "node",
191
+ jsx: "automatic",
192
+ jsxImportSource: "hono/jsx",
193
+ plugins: [
194
+ {
195
+ name: "hono-optimize",
196
+ setup(build2) {
197
+ const honoPseudoImportPath = "hono-optimized-pseudo-import-path";
198
+ build2.onResolve({ filter: /^hono$/ }, async (args) => {
199
+ if (!args.importer) {
200
+ return void 0;
201
+ }
202
+ const resolved = await build2.resolve(args.path, {
203
+ kind: "import-statement",
204
+ resolveDir: args.resolveDir
205
+ });
206
+ return {
207
+ path: join(dirname(resolved.path), honoPseudoImportPath)
208
+ };
209
+ });
210
+ build2.onLoad({ filter: new RegExp(`/${honoPseudoImportPath}$`) }, async () => {
211
+ return {
212
+ contents: `
213
+ import { HonoBase } from 'hono/hono-base'
214
+ ${importStatement}
215
+ export class Hono extends HonoBase {
216
+ constructor(options = {}) {
217
+ super(options)
218
+ ${assignRouterStatement}
219
+ }
220
+ }
221
+ `
222
+ };
223
+ });
224
+ }
225
+ }
226
+ ]
227
+ });
228
+ const outfileStat = statSync(outfile);
229
+ console.log(` Output: ${options.outfile} (${(outfileStat.size / 1024).toFixed(2)} KB)`);
230
+ });
231
+ }
232
+
233
+ // src/commands/request/index.ts
234
+ import { existsSync as existsSync2, realpathSync as realpathSync2 } from "fs";
235
+ import { resolve as resolve2 } from "path";
236
+ var DEFAULT_ENTRY_CANDIDATES2 = ["src/index.ts", "src/index.tsx", "src/index.js", "src/index.jsx"];
237
+ function requestCommand(program2) {
238
+ program2.command("request").description("Send request to Hono app using app.request()").argument("[file]", "Path to the Hono app file").option("-P, --path <path>", "Request path", "/").option("-X, --method <method>", "HTTP method", "GET").option("-d, --data <data>", "Request body data").option("-w, --watch", "Watch for changes and re-run the request", false).option(
239
+ "-H, --header <header>",
240
+ "Custom headers",
241
+ (value, previous) => {
242
+ return previous ? [...previous, value] : [value];
243
+ },
244
+ []
245
+ ).action(async (file, options) => {
246
+ const path = options.path || "/";
247
+ const watch = options.watch;
248
+ const buildIterator = getBuildIterator(file, watch);
249
+ for await (const app of buildIterator) {
250
+ const result = await executeRequest(app, path, options);
251
+ console.log(JSON.stringify(result, null, 2));
252
+ }
253
+ });
254
+ }
255
+ function getBuildIterator(appPath, watch) {
256
+ let entry;
257
+ let resolvedAppPath;
258
+ if (appPath) {
259
+ entry = appPath;
260
+ resolvedAppPath = resolve2(process.cwd(), entry);
261
+ } else {
262
+ entry = DEFAULT_ENTRY_CANDIDATES2.find((candidate) => existsSync2(resolve2(process.cwd(), candidate))) ?? DEFAULT_ENTRY_CANDIDATES2[0];
263
+ resolvedAppPath = resolve2(process.cwd(), entry);
264
+ }
265
+ if (!existsSync2(resolvedAppPath)) {
266
+ throw new Error(`Entry file ${entry} does not exist`);
267
+ }
268
+ const appFilePath = realpathSync2(resolvedAppPath);
269
+ return buildAndImportApp(appFilePath, {
270
+ external: ["@hono/node-server"],
271
+ watch
272
+ });
273
+ }
274
+ async function executeRequest(app, requestPath, options) {
275
+ const url = new URL(requestPath, "http://localhost");
276
+ const requestInit = {
277
+ method: options.method || "GET"
278
+ };
279
+ if (options.data) {
280
+ requestInit.body = options.data;
281
+ }
282
+ if (options.header && options.header.length > 0) {
283
+ const headers = new Headers();
284
+ for (const header of options.header) {
285
+ const [key, value] = header.split(":", 2);
286
+ if (key && value) {
287
+ headers.set(key.trim(), value.trim());
32
288
  }
33
- const npm = spawn("npm", args, {
34
- stdio: "inherit"
35
- });
36
- npm.on("error", (error) => {
37
- console.error(`Failed to execute npm: ${error.message}`);
38
- throw new Error(`Failed to execute npm: ${error.message}`);
289
+ }
290
+ requestInit.headers = headers;
291
+ }
292
+ const request = new Request(url.href, requestInit);
293
+ const response = await app.request(request);
294
+ const responseHeaders = {};
295
+ response.headers.forEach((value, key) => {
296
+ responseHeaders[key] = value;
297
+ });
298
+ const body = await response.text();
299
+ return {
300
+ status: response.status,
301
+ body,
302
+ headers: responseHeaders
303
+ };
304
+ }
305
+
306
+ // src/commands/search/index.ts
307
+ function searchCommand(program2) {
308
+ program2.command("search").argument("<query>", "Search query for Hono documentation").option("-l, --limit <number>", "Number of results to show (default: 5)", (value) => {
309
+ const parsed = parseInt(value, 10);
310
+ if (isNaN(parsed) || parsed < 1 || parsed > 20) {
311
+ console.warn("Limit must be a number between 1 and 20\n");
312
+ return 5;
313
+ }
314
+ return parsed;
315
+ }).option("-p, --pretty", "Display results in human-readable format").description("Search Hono documentation").action(async (query, options) => {
316
+ const ALGOLIA_APP_ID = "1GIFSU1REV";
317
+ const ALGOLIA_API_KEY = "c6a0f86b9a9f8551654600f28317a9e9";
318
+ const ALGOLIA_INDEX = "hono";
319
+ const searchUrl = `https://${ALGOLIA_APP_ID}-dsn.algolia.net/1/indexes/${ALGOLIA_INDEX}/query`;
320
+ try {
321
+ if (options.pretty) {
322
+ console.log(`Searching for "${query}"...`);
323
+ }
324
+ const response = await fetch(searchUrl, {
325
+ method: "POST",
326
+ headers: {
327
+ "X-Algolia-API-Key": ALGOLIA_API_KEY,
328
+ "X-Algolia-Application-Id": ALGOLIA_APP_ID,
329
+ "Content-Type": "application/json"
330
+ },
331
+ body: JSON.stringify({
332
+ query,
333
+ hitsPerPage: options.limit || 5
334
+ })
39
335
  });
40
- npm.on("exit", (code) => {
41
- if (code !== 0) {
42
- throw new Error(`npm create hono@latest exited with code ${code}`);
336
+ if (!response.ok) {
337
+ throw new Error(`Search failed: ${response.status} ${response.statusText}`);
338
+ }
339
+ const data = await response.json();
340
+ if (data.hits.length === 0) {
341
+ if (options.pretty) {
342
+ console.log("\nNo results found.");
343
+ } else {
344
+ console.log(JSON.stringify({ query, total: 0, results: [] }, null, 2));
345
+ }
346
+ return;
347
+ }
348
+ const cleanHighlight = (text) => text.replace(/<[^>]*>/g, "");
349
+ const results = data.hits.map((hit) => {
350
+ let title = hit.title;
351
+ let highlightedTitle = title;
352
+ if (!title && hit._highlightResult?.hierarchy?.lvl1) {
353
+ title = cleanHighlight(hit._highlightResult.hierarchy.lvl1.value);
354
+ highlightedTitle = hit._highlightResult.hierarchy.lvl1.value;
355
+ }
356
+ if (!title) {
357
+ title = hit.hierarchy?.lvl1 || hit.hierarchy?.lvl0 || "Untitled";
358
+ highlightedTitle = title;
359
+ }
360
+ const hierarchyParts = [];
361
+ if (hit.hierarchy?.lvl0 && hit.hierarchy.lvl0 !== "Documentation") {
362
+ hierarchyParts.push(hit.hierarchy.lvl0);
363
+ }
364
+ if (hit.hierarchy?.lvl1 && hit.hierarchy.lvl1 !== title) {
365
+ hierarchyParts.push(cleanHighlight(hit.hierarchy.lvl1));
43
366
  }
367
+ if (hit.hierarchy?.lvl2) {
368
+ hierarchyParts.push(cleanHighlight(hit.hierarchy.lvl2));
369
+ }
370
+ const category = hierarchyParts.length > 0 ? hierarchyParts.join(" > ") : "";
371
+ const url = hit.url;
372
+ const urlPath = new URL(url).pathname;
373
+ return {
374
+ title,
375
+ highlightedTitle,
376
+ category,
377
+ url,
378
+ path: urlPath
379
+ };
44
380
  });
381
+ if (options.pretty) {
382
+ console.log(`
383
+ Found ${data.hits.length} results:
384
+ `);
385
+ const formatHighlight = (text) => {
386
+ return text.replace(/<span class="algolia-docsearch-suggestion--highlight">/g, "\x1B[33m").replace(/<\/span>/g, "\x1B[0m");
387
+ };
388
+ results.forEach((result, index) => {
389
+ console.log(`${index + 1}. ${formatHighlight(result.highlightedTitle || result.title)}`);
390
+ if (result.category) {
391
+ console.log(` Category: ${result.category}`);
392
+ }
393
+ console.log(` URL: ${result.url}`);
394
+ console.log(` Command: hono docs ${result.path}`);
395
+ console.log("");
396
+ });
397
+ } else {
398
+ const jsonResults = results.map(({ highlightedTitle, ...result }) => result);
399
+ console.log(
400
+ JSON.stringify(
401
+ {
402
+ query,
403
+ total: data.hits.length,
404
+ results: jsonResults
405
+ },
406
+ null,
407
+ 2
408
+ )
409
+ );
410
+ }
411
+ } catch (error) {
412
+ console.error(
413
+ "Error searching documentation:",
414
+ error instanceof Error ? error.message : String(error)
415
+ );
416
+ console.log("\nPlease visit: https://hono.dev/docs");
45
417
  }
46
- );
418
+ });
47
419
  }
48
420
 
49
- // src/commands/docs/index.ts
50
- import { exec } from "child_process";
51
- import { platform } from "os";
52
- function docsCommand(program2) {
53
- program2.command("docs").description("Open Hono documentation in browser").action(() => {
54
- const url = "https://hono.dev";
55
- let openCommand;
56
- switch (platform()) {
57
- case "darwin":
58
- openCommand = "open";
59
- break;
60
- case "win32":
61
- openCommand = "start";
62
- break;
63
- default:
64
- openCommand = "xdg-open";
65
- break;
66
- }
67
- exec(`${openCommand} ${url}`, (error) => {
68
- if (error) {
69
- console.error(`Failed to open browser: ${error.message}`);
70
- console.log(`Please visit: ${url}`);
421
+ // src/commands/serve/index.ts
422
+ import { serve } from "@hono/node-server";
423
+ import { serveStatic } from "@hono/node-server/serve-static";
424
+ import { Hono } from "hono";
425
+ import { showRoutes } from "hono/dev";
426
+ import { existsSync as existsSync3, realpathSync as realpathSync3 } from "fs";
427
+ import { resolve as resolve3 } from "path";
428
+
429
+ // src/commands/serve/builtin-map.ts
430
+ var builtinMap = {
431
+ // Authentication
432
+ basicAuth: "hono/basic-auth",
433
+ bearerAuth: "hono/bearer-auth",
434
+ // Security & Validation
435
+ csrf: "hono/csrf",
436
+ secureHeaders: "hono/secure-headers",
437
+ jwt: "hono/jwt",
438
+ jwk: "hono/jwk",
439
+ // Request/Response Processing
440
+ cors: "hono/cors",
441
+ etag: "hono/etag",
442
+ compress: "hono/compress",
443
+ prettyJSON: "hono/pretty-json",
444
+ bodyLimit: "hono/body-limit",
445
+ combine: "hono/combine",
446
+ contextStorage: "hono/context-storage",
447
+ methodOverride: "hono/method-override",
448
+ trailingSlash: "hono/trailing-slash",
449
+ // Utilities & Performance
450
+ cache: "hono/cache",
451
+ timeout: "hono/timeout",
452
+ poweredBy: "hono/powered-by",
453
+ // Logging & Monitoring
454
+ logger: "hono/logger",
455
+ timing: "hono/timing",
456
+ requestId: "hono/request-id",
457
+ // Internationalization
458
+ language: "hono/language",
459
+ // Access Control
460
+ ipRestriction: "hono/ip-restriction",
461
+ // Rendering
462
+ jsxRenderer: "hono/jsx-renderer",
463
+ // Static Files (Node.js specific)
464
+ serveStatic: "@hono/node-server/serve-static",
465
+ // Helpers - Accepts
466
+ accepts: "hono/accepts",
467
+ // Helpers - Adapter
468
+ env: "hono/adapter",
469
+ getRuntimeKey: "hono/adapter",
470
+ // Helpers - Connection Info (Node.js specific)
471
+ getConnInfo: "@hono/node-server/conninfo",
472
+ // Helpers - Cookie
473
+ getCookie: "hono/cookie",
474
+ getSignedCookie: "hono/cookie",
475
+ setCookie: "hono/cookie",
476
+ setSignedCookie: "hono/cookie",
477
+ deleteCookie: "hono/cookie",
478
+ // Helpers - CSS
479
+ css: "hono/css",
480
+ rawCssString: "hono/css",
481
+ viewTransition: "hono/css",
482
+ createCssContext: "hono/css",
483
+ createCssMiddleware: "hono/css",
484
+ // Helpers - HTML
485
+ html: "hono/html",
486
+ raw: "hono/html",
487
+ // Helpers - JWT (different from middleware)
488
+ sign: "hono/jwt",
489
+ verify: "hono/jwt",
490
+ decode: "hono/jwt",
491
+ // Helpers - Proxy
492
+ proxy: "hono/proxy",
493
+ // Helpers - Streaming
494
+ stream: "hono/streaming",
495
+ streamText: "hono/streaming",
496
+ streamSSE: "hono/streaming"
497
+ };
498
+
499
+ // src/commands/serve/index.ts
500
+ [serveStatic].forEach((f) => {
501
+ if (typeof f === "function") {
502
+ }
503
+ });
504
+ function serveCommand(program2) {
505
+ program2.command("serve").description("Start server").argument("[entry]", "entry file").option("-p, --port <port>", "port number").option("--show-routes", "show registered routes").option(
506
+ "--use <middleware>",
507
+ "use middleware",
508
+ (value, previous) => {
509
+ return previous ? [...previous, value] : [value];
510
+ },
511
+ []
512
+ ).action(
513
+ async (entry, options) => {
514
+ let app;
515
+ if (!entry) {
516
+ app = new Hono();
71
517
  } else {
72
- console.log(`Opening ${url} in your browser...`);
518
+ const appPath = resolve3(process.cwd(), entry);
519
+ if (!existsSync3(appPath)) {
520
+ app = new Hono();
521
+ } else {
522
+ const appFilePath = realpathSync3(appPath);
523
+ const buildIterator = buildAndImportApp(appFilePath, {
524
+ external: ["@hono/node-server"]
525
+ });
526
+ app = (await buildIterator.next()).value;
527
+ }
73
528
  }
74
- });
75
- });
529
+ const allFunctions = {};
530
+ const uniqueModules = [...new Set(Object.values(builtinMap))];
531
+ for (const modulePath of uniqueModules) {
532
+ try {
533
+ const module = await import(modulePath);
534
+ for (const [funcName, modulePathInMap] of Object.entries(builtinMap)) {
535
+ if (modulePathInMap === modulePath && module[funcName]) {
536
+ allFunctions[funcName] = module[funcName];
537
+ }
538
+ }
539
+ } catch (error) {
540
+ }
541
+ }
542
+ const baseApp = new Hono();
543
+ for (const use of options.use || []) {
544
+ const functionNames = Object.keys(allFunctions);
545
+ const functionValues = Object.values(allFunctions);
546
+ const func = new Function("c", "next", ...functionNames, `return (${use})`);
547
+ baseApp.use(async (c, next) => {
548
+ const middleware = func(c, next, ...functionValues);
549
+ return typeof middleware === "function" ? middleware(c, next) : middleware;
550
+ });
551
+ }
552
+ baseApp.route("/", app);
553
+ if (options.showRoutes) {
554
+ showRoutes(baseApp);
555
+ }
556
+ serve(
557
+ {
558
+ fetch: baseApp.fetch,
559
+ port: options.port ? Number.parseInt(options.port) : 7070
560
+ },
561
+ (info) => {
562
+ console.log(`Listening on http://localhost:${info.port}`);
563
+ }
564
+ );
565
+ }
566
+ );
76
567
  }
77
568
 
78
569
  // src/cli.ts
79
570
  var __filename = fileURLToPath(import.meta.url);
80
- var __dirname = dirname(__filename);
81
- var packageJson = JSON.parse(readFileSync(join(__dirname, "../package.json"), "utf-8"));
571
+ var __dirname = dirname2(__filename);
572
+ var packageJson = JSON.parse(readFileSync(join2(__dirname, "../package.json"), "utf-8"));
82
573
  var program = new Command();
83
574
  program.name("hono").description("CLI for Hono").version(packageJson.version, "-v, --version", "display version number");
84
- createCommand(program);
85
575
  docsCommand(program);
576
+ optimizeCommand(program);
577
+ searchCommand(program);
578
+ requestCommand(program);
579
+ serveCommand(program);
86
580
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hono/cli",
3
- "version": "0.0.1",
3
+ "version": "0.1.1",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "hono": "dist/cli.js"
@@ -33,13 +33,15 @@
33
33
  },
34
34
  "homepage": "https://hono.dev",
35
35
  "dependencies": {
36
- "commander": "^14.0.1"
36
+ "@hono/node-server": "^1.19.5",
37
+ "commander": "^14.0.1",
38
+ "esbuild": "^0.25.10",
39
+ "hono": "^4.9.12"
37
40
  },
38
41
  "devDependencies": {
39
42
  "@hono/eslint-config": "^1.1.1",
40
43
  "@types/node": "^24.7.0",
41
44
  "eslint": "^9.37.0",
42
- "hono": "4.4.13",
43
45
  "np": "^10.2.0",
44
46
  "prettier": "^3.6.2",
45
47
  "publint": "^0.3.14",