@hono/cli 0.0.1 → 0.1.0

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 +274 -26
  2. package/dist/cli.js +509 -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,303 @@ 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
+
160
+ **Examples:**
161
+
162
+ ```bash
163
+ # GET request to default app root (uses src/index.ts or src/index.tsx)
164
+ hono request
165
+
166
+ # GET request to specific path
167
+ hono request -P /users/123
168
+
169
+ # POST request with data
170
+ hono request -P /api/users -X POST -d '{"name":"Alice"}'
171
+
172
+ # Request to specific file
173
+ hono request -P /api src/your-app.ts
174
+
175
+ # Request with custom headers
176
+ hono request -P /api/protected \
177
+ -H 'Authorization: Bearer token' \
178
+ -H 'User-Agent: MyApp' \
179
+ src/your-app.ts
180
+ ```
181
+
182
+ **Response Format:**
183
+
184
+ The command returns a JSON object with the following structure:
185
+
186
+ ```json
187
+ {
188
+ "status": 200,
189
+ "body": "{\"message\":\"Hello World\"}",
190
+ "headers": {
191
+ "content-type": "application/json",
192
+ "x-custom-header": "value"
193
+ }
194
+ }
195
+ ```
196
+
197
+ ### `serve`
198
+
199
+ Start a server for your Hono application. This is a simple server specialized for Hono applications with built-in TypeScript and JSX support.
200
+
201
+ ```bash
202
+ hono serve [entry] [options]
203
+ ```
204
+
205
+ **Arguments:**
206
+
207
+ - `entry` - Entry file for your Hono app (TypeScript/JSX supported, optional)
208
+
209
+ **Options:**
210
+
211
+ - `-p, --port <port>` - Port number (default: 7070)
212
+ - `--show-routes` - Show registered routes
213
+ - `--use <middleware>` - Use middleware (can be used multiple times)
214
+
215
+ **Examples:**
216
+
217
+ ```bash
218
+ # Start server with default empty app (no entry file needed)
219
+ hono serve
220
+
221
+ # Start server on specific port
222
+ hono serve -p 3000 src/app.ts
223
+
224
+ # Start server with specific entry file
225
+ hono serve src/app.ts
226
+
227
+ # Start server and show routes
228
+ hono serve --show-routes src/app.ts
229
+
230
+ # Start server with middleware
231
+ hono serve --use 'cors()' src/app.ts
232
+
233
+ # Start server with multiple middleware
234
+ hono serve --use 'cors()' --use 'logger()' src/app.ts
235
+
236
+ # Start server with authentication and static file serving
237
+ hono serve \
238
+ --use 'basicAuth({ username: "foo", password: "bar" })' \
239
+ --use "serveStatic({ root: './' })"
240
+ ```
241
+
242
+ ### `optimize`
243
+
244
+ Generate an optimized Hono class and export bundled file.
245
+
246
+ ```bash
247
+ hono optimize [entry] [options]
248
+ ```
249
+
250
+ **Arguments:**
251
+
252
+ - `entry` - Entry file for your Hono app (TypeScript/JSX supported, optional)
253
+
254
+ **Options:**
255
+
256
+ - `-o, --outfile <outfile>` - Output file
257
+
258
+ **Examples:**
259
+
260
+ ```bash
261
+ # Generate an optimized Hono class and export bundled file to dist/index.js
262
+ hono optimize
263
+
264
+ # Specify entry file and output file
265
+ hono optimize -o dist/app.js src/app.ts
266
+
267
+ # Export bundled file with minification
268
+ hono optimize -m
269
+ ```
270
+
271
+ ## Tips
272
+
273
+ ### Using Hono CLI with AI Code Agents
274
+
275
+ 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:
276
+
277
+ ````markdown
278
+ ## Hono Development
279
+
280
+ Use the `hono` CLI for efficient development. View all commands with `hono --help`.
281
+
282
+ ### Core Commands
283
+
284
+ - **`hono docs [path]`** - Browse Hono documentation
285
+ - **`hono search <query>`** - Search documentation
286
+ - **`hono request [file]`** - Test app requests without starting a server
287
+
288
+ ### Quick Examples
289
+
290
+ ```bash
291
+ # Search for topics
292
+ hono search middleware
293
+ hono search "getting started"
294
+
295
+ # View documentation
296
+ hono docs /docs/api/context
297
+ hono docs /docs/guides/middleware
298
+
299
+ # Test your app
300
+ hono request -P /api/users src/index.ts
301
+ hono request -P /api/users -X POST -d '{"name":"Alice"}' src/index.ts
302
+ ```
303
+
304
+ ### Workflow
305
+
306
+ 1. Search documentation: `hono search <query>`
307
+ 2. Read relevant docs: `hono docs [path]`
308
+ 3. Test implementation: `hono request [file]`
309
+ ````
310
+
311
+ ### Pipeline Integration with jq
312
+
313
+ The `search` command outputs JSON by default, making it easy to pipe results to other commands:
314
+
315
+ ```bash
316
+ # Search and view documentation in one command
317
+ hono search "middleware" | jq '.results[0].path' | hono docs
70
318
  ```
71
319
 
72
320
  ## Authors
package/dist/cli.js CHANGED
@@ -3,84 +3,535 @@
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
+ import { extname } from "path";
74
+ import { pathToFileURL } from "url";
75
+ async function buildAndImportApp(filePath, options = {}) {
76
+ const ext = extname(filePath);
77
+ if ([".ts", ".tsx", ".jsx"].includes(ext)) {
78
+ const result = await esbuild.build({
79
+ entryPoints: [filePath],
80
+ bundle: true,
81
+ write: false,
82
+ format: "esm",
83
+ target: "node20",
84
+ jsx: "automatic",
85
+ jsxImportSource: "hono/jsx",
86
+ platform: "node",
87
+ external: options.external || []
88
+ });
89
+ const code = result.outputFiles[0].text;
90
+ const dataUrl = `data:text/javascript;base64,${Buffer.from(code).toString("base64")}`;
91
+ const module = await import(dataUrl);
92
+ return module.default;
93
+ } else {
94
+ const module = await import(pathToFileURL(filePath).href);
95
+ return module.default;
96
+ }
97
+ }
98
+
99
+ // src/commands/optimize/index.ts
100
+ var DEFAULT_ENTRY_CANDIDATES = ["src/index.ts", "src/index.tsx", "src/index.js", "src/index.jsx"];
101
+ function optimizeCommand(program2) {
102
+ 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) => {
103
+ if (!entry) {
104
+ entry = DEFAULT_ENTRY_CANDIDATES.find((entry2) => existsSync(entry2)) ?? DEFAULT_ENTRY_CANDIDATES[0];
105
+ }
106
+ const appPath = resolve(process.cwd(), entry);
107
+ if (!existsSync(appPath)) {
108
+ throw new Error(`Entry file ${entry} does not exist`);
109
+ }
110
+ const appFilePath = realpathSync(appPath);
111
+ const app = await buildAndImportApp(appFilePath, {
112
+ external: ["@hono/node-server"]
113
+ });
114
+ let routerName;
115
+ let importStatement;
116
+ let assignRouterStatement;
117
+ try {
118
+ const serialized = serializeInitParams(
119
+ buildInitParams({
120
+ paths: app.routes.map(({ path }) => path)
121
+ })
122
+ );
123
+ const hasPreparedRegExpRouter = await new Promise((resolve4) => {
124
+ const child = execFile(process.execPath, [
125
+ "--input-type=module",
126
+ "-e",
127
+ "try { (await import('hono/router/reg-exp-router')).PreparedRegExpRouter && process.exit(0) } finally { process.exit(1) }"
128
+ ]);
129
+ child.on("exit", (code) => {
130
+ resolve4(code === 0);
131
+ });
132
+ });
133
+ if (hasPreparedRegExpRouter) {
134
+ routerName = "PreparedRegExpRouter";
135
+ importStatement = "import { PreparedRegExpRouter } from 'hono/router/reg-exp-router'";
136
+ assignRouterStatement = `const routerParams = ${serialized}
137
+ this.router = new PreparedRegExpRouter(...routerParams)`;
138
+ } else {
139
+ routerName = "RegExpRouter";
140
+ importStatement = "import { RegExpRouter } from 'hono/router/reg-exp-router'";
141
+ assignRouterStatement = "this.router = new RegExpRouter()";
26
142
  }
27
- if (options.pm) {
28
- args.push("--pm", options.pm);
143
+ } catch {
144
+ routerName = "TrieRouter";
145
+ importStatement = "import { TrieRouter } from 'hono/router/trie-router'";
146
+ assignRouterStatement = "this.router = new TrieRouter()";
147
+ }
148
+ console.log("[Optimized]");
149
+ console.log(` Router: ${routerName}`);
150
+ const outfile = resolve(process.cwd(), options.outfile);
151
+ await esbuild2.build({
152
+ entryPoints: [appFilePath],
153
+ outfile,
154
+ bundle: true,
155
+ minify: options.minify,
156
+ format: "esm",
157
+ target: "node20",
158
+ platform: "node",
159
+ jsx: "automatic",
160
+ jsxImportSource: "hono/jsx",
161
+ plugins: [
162
+ {
163
+ name: "hono-optimize",
164
+ setup(build3) {
165
+ const honoPseudoImportPath = "hono-optimized-pseudo-import-path";
166
+ build3.onResolve({ filter: /^hono$/ }, async (args) => {
167
+ if (!args.importer) {
168
+ return void 0;
169
+ }
170
+ const resolved = await build3.resolve(args.path, {
171
+ kind: "import-statement",
172
+ resolveDir: args.resolveDir
173
+ });
174
+ return {
175
+ path: join(dirname(resolved.path), honoPseudoImportPath)
176
+ };
177
+ });
178
+ build3.onLoad({ filter: new RegExp(`/${honoPseudoImportPath}$`) }, async () => {
179
+ return {
180
+ contents: `
181
+ import { HonoBase } from 'hono/hono-base'
182
+ ${importStatement}
183
+ export class Hono extends HonoBase {
184
+ constructor(options = {}) {
185
+ super(options)
186
+ ${assignRouterStatement}
187
+ }
188
+ }
189
+ `
190
+ };
191
+ });
192
+ }
193
+ }
194
+ ]
195
+ });
196
+ const outfileStat = statSync(outfile);
197
+ console.log(` Output: ${options.outfile} (${(outfileStat.size / 1024).toFixed(2)} KB)`);
198
+ });
199
+ }
200
+
201
+ // src/commands/request/index.ts
202
+ import { existsSync as existsSync2, realpathSync as realpathSync2 } from "fs";
203
+ import { resolve as resolve2 } from "path";
204
+ var DEFAULT_ENTRY_CANDIDATES2 = ["src/index.ts", "src/index.tsx", "src/index.js", "src/index.jsx"];
205
+ function requestCommand(program2) {
206
+ 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(
207
+ "-H, --header <header>",
208
+ "Custom headers",
209
+ (value, previous) => {
210
+ return previous ? [...previous, value] : [value];
211
+ },
212
+ []
213
+ ).action(async (file, options) => {
214
+ const path = options.path || "/";
215
+ const result = await executeRequest(file, path, options);
216
+ console.log(JSON.stringify(result, null, 2));
217
+ });
218
+ }
219
+ async function executeRequest(appPath, requestPath, options) {
220
+ let entry;
221
+ let resolvedAppPath;
222
+ if (appPath) {
223
+ entry = appPath;
224
+ resolvedAppPath = resolve2(process.cwd(), entry);
225
+ } else {
226
+ entry = DEFAULT_ENTRY_CANDIDATES2.find((candidate) => existsSync2(resolve2(process.cwd(), candidate))) ?? DEFAULT_ENTRY_CANDIDATES2[0];
227
+ resolvedAppPath = resolve2(process.cwd(), entry);
228
+ }
229
+ if (!existsSync2(resolvedAppPath)) {
230
+ throw new Error(`Entry file ${entry} does not exist`);
231
+ }
232
+ const appFilePath = realpathSync2(resolvedAppPath);
233
+ const app = await buildAndImportApp(appFilePath, {
234
+ external: ["@hono/node-server"]
235
+ });
236
+ if (!app || typeof app.request !== "function") {
237
+ throw new Error("No valid Hono app exported from the file");
238
+ }
239
+ const url = new URL(requestPath, "http://localhost");
240
+ const requestInit = {
241
+ method: options.method || "GET"
242
+ };
243
+ if (options.data) {
244
+ requestInit.body = options.data;
245
+ }
246
+ if (options.header && options.header.length > 0) {
247
+ const headers = new Headers();
248
+ for (const header of options.header) {
249
+ const [key, value] = header.split(":", 2);
250
+ if (key && value) {
251
+ headers.set(key.trim(), value.trim());
29
252
  }
30
- if (options.offline) {
31
- args.push("--offline");
253
+ }
254
+ requestInit.headers = headers;
255
+ }
256
+ const request = new Request(url.href, requestInit);
257
+ const response = await app.request(request);
258
+ const responseHeaders = {};
259
+ response.headers.forEach((value, key) => {
260
+ responseHeaders[key] = value;
261
+ });
262
+ const body = await response.text();
263
+ return {
264
+ status: response.status,
265
+ body,
266
+ headers: responseHeaders
267
+ };
268
+ }
269
+
270
+ // src/commands/search/index.ts
271
+ function searchCommand(program2) {
272
+ program2.command("search").argument("<query>", "Search query for Hono documentation").option("-l, --limit <number>", "Number of results to show (default: 5)", "5").option("-p, --pretty", "Display results in human-readable format").description("Search Hono documentation").action(async (query, options) => {
273
+ const ALGOLIA_APP_ID = "1GIFSU1REV";
274
+ const ALGOLIA_API_KEY = "c6a0f86b9a9f8551654600f28317a9e9";
275
+ const ALGOLIA_INDEX = "hono";
276
+ const limit = Math.max(1, Math.min(20, parseInt(options.limit, 10) || 5));
277
+ const searchUrl = `https://${ALGOLIA_APP_ID}-dsn.algolia.net/1/indexes/${ALGOLIA_INDEX}/query`;
278
+ try {
279
+ if (options.pretty) {
280
+ console.log(`Searching for "${query}"...`);
32
281
  }
33
- const npm = spawn("npm", args, {
34
- stdio: "inherit"
282
+ const response = await fetch(searchUrl, {
283
+ method: "POST",
284
+ headers: {
285
+ "X-Algolia-API-Key": ALGOLIA_API_KEY,
286
+ "X-Algolia-Application-Id": ALGOLIA_APP_ID,
287
+ "Content-Type": "application/json"
288
+ },
289
+ body: JSON.stringify({
290
+ query,
291
+ hitsPerPage: limit
292
+ })
35
293
  });
36
- npm.on("error", (error) => {
37
- console.error(`Failed to execute npm: ${error.message}`);
38
- throw new Error(`Failed to execute npm: ${error.message}`);
39
- });
40
- npm.on("exit", (code) => {
41
- if (code !== 0) {
42
- throw new Error(`npm create hono@latest exited with code ${code}`);
294
+ if (!response.ok) {
295
+ throw new Error(`Search failed: ${response.status} ${response.statusText}`);
296
+ }
297
+ const data = await response.json();
298
+ if (data.hits.length === 0) {
299
+ if (options.pretty) {
300
+ console.log("\nNo results found.");
301
+ } else {
302
+ console.log(JSON.stringify({ query, total: 0, results: [] }, null, 2));
303
+ }
304
+ return;
305
+ }
306
+ const cleanHighlight = (text) => text.replace(/<[^>]*>/g, "");
307
+ const results = data.hits.map((hit) => {
308
+ let title = hit.title;
309
+ let highlightedTitle = title;
310
+ if (!title && hit._highlightResult?.hierarchy?.lvl1) {
311
+ title = cleanHighlight(hit._highlightResult.hierarchy.lvl1.value);
312
+ highlightedTitle = hit._highlightResult.hierarchy.lvl1.value;
313
+ }
314
+ if (!title) {
315
+ title = hit.hierarchy?.lvl1 || hit.hierarchy?.lvl0 || "Untitled";
316
+ highlightedTitle = title;
317
+ }
318
+ const hierarchyParts = [];
319
+ if (hit.hierarchy?.lvl0 && hit.hierarchy.lvl0 !== "Documentation") {
320
+ hierarchyParts.push(hit.hierarchy.lvl0);
43
321
  }
322
+ if (hit.hierarchy?.lvl1 && hit.hierarchy.lvl1 !== title) {
323
+ hierarchyParts.push(cleanHighlight(hit.hierarchy.lvl1));
324
+ }
325
+ if (hit.hierarchy?.lvl2) {
326
+ hierarchyParts.push(cleanHighlight(hit.hierarchy.lvl2));
327
+ }
328
+ const category = hierarchyParts.length > 0 ? hierarchyParts.join(" > ") : "";
329
+ const url = hit.url;
330
+ const urlPath = new URL(url).pathname;
331
+ return {
332
+ title,
333
+ highlightedTitle,
334
+ category,
335
+ url,
336
+ path: urlPath
337
+ };
44
338
  });
339
+ if (options.pretty) {
340
+ console.log(`
341
+ Found ${data.hits.length} results:
342
+ `);
343
+ const formatHighlight = (text) => {
344
+ return text.replace(/<span class="algolia-docsearch-suggestion--highlight">/g, "\x1B[33m").replace(/<\/span>/g, "\x1B[0m");
345
+ };
346
+ results.forEach((result, index) => {
347
+ console.log(`${index + 1}. ${formatHighlight(result.highlightedTitle || result.title)}`);
348
+ if (result.category) {
349
+ console.log(` Category: ${result.category}`);
350
+ }
351
+ console.log(` URL: ${result.url}`);
352
+ console.log(` Command: hono docs ${result.path}`);
353
+ console.log("");
354
+ });
355
+ } else {
356
+ const jsonResults = results.map(({ highlightedTitle, ...result }) => result);
357
+ console.log(
358
+ JSON.stringify(
359
+ {
360
+ query,
361
+ total: data.hits.length,
362
+ results: jsonResults
363
+ },
364
+ null,
365
+ 2
366
+ )
367
+ );
368
+ }
369
+ } catch (error) {
370
+ console.error(
371
+ "Error searching documentation:",
372
+ error instanceof Error ? error.message : String(error)
373
+ );
374
+ console.log("\nPlease visit: https://hono.dev/docs");
45
375
  }
46
- );
376
+ });
47
377
  }
48
378
 
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}`);
379
+ // src/commands/serve/index.ts
380
+ import { serve } from "@hono/node-server";
381
+ import { serveStatic } from "@hono/node-server/serve-static";
382
+ import { Hono } from "hono";
383
+ import { showRoutes } from "hono/dev";
384
+ import { existsSync as existsSync3, realpathSync as realpathSync3 } from "fs";
385
+ import { resolve as resolve3 } from "path";
386
+
387
+ // src/commands/serve/builtin-map.ts
388
+ var builtinMap = {
389
+ // Authentication
390
+ basicAuth: "hono/basic-auth",
391
+ bearerAuth: "hono/bearer-auth",
392
+ // Security & Validation
393
+ csrf: "hono/csrf",
394
+ secureHeaders: "hono/secure-headers",
395
+ jwt: "hono/jwt",
396
+ jwk: "hono/jwk",
397
+ // Request/Response Processing
398
+ cors: "hono/cors",
399
+ etag: "hono/etag",
400
+ compress: "hono/compress",
401
+ prettyJSON: "hono/pretty-json",
402
+ bodyLimit: "hono/body-limit",
403
+ combine: "hono/combine",
404
+ contextStorage: "hono/context-storage",
405
+ methodOverride: "hono/method-override",
406
+ trailingSlash: "hono/trailing-slash",
407
+ // Utilities & Performance
408
+ cache: "hono/cache",
409
+ timeout: "hono/timeout",
410
+ poweredBy: "hono/powered-by",
411
+ // Logging & Monitoring
412
+ logger: "hono/logger",
413
+ timing: "hono/timing",
414
+ requestId: "hono/request-id",
415
+ // Internationalization
416
+ language: "hono/language",
417
+ // Access Control
418
+ ipRestriction: "hono/ip-restriction",
419
+ // Rendering
420
+ jsxRenderer: "hono/jsx-renderer",
421
+ // Static Files (Node.js specific)
422
+ serveStatic: "@hono/node-server/serve-static",
423
+ // Helpers - Accepts
424
+ accepts: "hono/accepts",
425
+ // Helpers - Adapter
426
+ env: "hono/adapter",
427
+ getRuntimeKey: "hono/adapter",
428
+ // Helpers - Connection Info (Node.js specific)
429
+ getConnInfo: "@hono/node-server/conninfo",
430
+ // Helpers - Cookie
431
+ getCookie: "hono/cookie",
432
+ getSignedCookie: "hono/cookie",
433
+ setCookie: "hono/cookie",
434
+ setSignedCookie: "hono/cookie",
435
+ deleteCookie: "hono/cookie",
436
+ // Helpers - CSS
437
+ css: "hono/css",
438
+ rawCssString: "hono/css",
439
+ viewTransition: "hono/css",
440
+ createCssContext: "hono/css",
441
+ createCssMiddleware: "hono/css",
442
+ // Helpers - HTML
443
+ html: "hono/html",
444
+ raw: "hono/html",
445
+ // Helpers - JWT (different from middleware)
446
+ sign: "hono/jwt",
447
+ verify: "hono/jwt",
448
+ decode: "hono/jwt",
449
+ // Helpers - Proxy
450
+ proxy: "hono/proxy",
451
+ // Helpers - Streaming
452
+ stream: "hono/streaming",
453
+ streamText: "hono/streaming",
454
+ streamSSE: "hono/streaming"
455
+ };
456
+
457
+ // src/commands/serve/index.ts
458
+ [serveStatic].forEach((f) => {
459
+ if (typeof f === "function") {
460
+ }
461
+ });
462
+ function serveCommand(program2) {
463
+ program2.command("serve").description("Start server").argument("[entry]", "entry file").option("-p, --port <port>", "port number").option("--show-routes", "show registered routes").option(
464
+ "--use <middleware>",
465
+ "use middleware",
466
+ (value, previous) => {
467
+ return previous ? [...previous, value] : [value];
468
+ },
469
+ []
470
+ ).action(
471
+ async (entry, options) => {
472
+ let app;
473
+ if (!entry) {
474
+ app = new Hono();
71
475
  } else {
72
- console.log(`Opening ${url} in your browser...`);
476
+ const appPath = resolve3(process.cwd(), entry);
477
+ if (!existsSync3(appPath)) {
478
+ app = new Hono();
479
+ } else {
480
+ const appFilePath = realpathSync3(appPath);
481
+ app = await buildAndImportApp(appFilePath, {
482
+ external: ["@hono/node-server"]
483
+ });
484
+ }
73
485
  }
74
- });
75
- });
486
+ const allFunctions = {};
487
+ const uniqueModules = [...new Set(Object.values(builtinMap))];
488
+ for (const modulePath of uniqueModules) {
489
+ try {
490
+ const module = await import(modulePath);
491
+ for (const [funcName, modulePathInMap] of Object.entries(builtinMap)) {
492
+ if (modulePathInMap === modulePath && module[funcName]) {
493
+ allFunctions[funcName] = module[funcName];
494
+ }
495
+ }
496
+ } catch (error) {
497
+ }
498
+ }
499
+ const baseApp = new Hono();
500
+ for (const use of options.use || []) {
501
+ const functionNames = Object.keys(allFunctions);
502
+ const functionValues = Object.values(allFunctions);
503
+ const func = new Function("c", "next", ...functionNames, `return (${use})`);
504
+ baseApp.use(async (c, next) => {
505
+ const middleware = func(c, next, ...functionValues);
506
+ return typeof middleware === "function" ? middleware(c, next) : middleware;
507
+ });
508
+ }
509
+ baseApp.route("/", app);
510
+ if (options.showRoutes) {
511
+ showRoutes(baseApp);
512
+ }
513
+ serve(
514
+ {
515
+ fetch: baseApp.fetch,
516
+ port: options.port ? Number.parseInt(options.port) : 7070
517
+ },
518
+ (info) => {
519
+ console.log(`Listening on http://localhost:${info.port}`);
520
+ }
521
+ );
522
+ }
523
+ );
76
524
  }
77
525
 
78
526
  // src/cli.ts
79
527
  var __filename = fileURLToPath(import.meta.url);
80
- var __dirname = dirname(__filename);
81
- var packageJson = JSON.parse(readFileSync(join(__dirname, "../package.json"), "utf-8"));
528
+ var __dirname = dirname2(__filename);
529
+ var packageJson = JSON.parse(readFileSync(join2(__dirname, "../package.json"), "utf-8"));
82
530
  var program = new Command();
83
531
  program.name("hono").description("CLI for Hono").version(packageJson.version, "-v, --version", "display version number");
84
- createCommand(program);
85
532
  docsCommand(program);
533
+ optimizeCommand(program);
534
+ searchCommand(program);
535
+ requestCommand(program);
536
+ serveCommand(program);
86
537
  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.0",
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",