Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ hono request [file] [options]
- `-d, --data <data>` - Request body data
- `-H, --header <header>` - Custom headers (can be used multiple times)
- `-w, --watch` - Watch for changes and resend request
- `-e, --external <package>` - Mark package as external (can be used multiple times)

**Examples:**

Expand All @@ -178,6 +179,9 @@ hono request -P /api/protected \
-H 'Authorization: Bearer token' \
-H 'User-Agent: MyApp' \
src/your-app.ts

# Request with external packages (useful for Node.js native modules)
hono request -e pg -e dotenv src/your-app.ts
```

**Response Format:**
Expand Down Expand Up @@ -212,6 +216,7 @@ hono serve [entry] [options]
- `-p, --port <port>` - Port number (default: 7070)
- `--show-routes` - Show registered routes
- `--use <middleware>` - Use middleware (can be used multiple times)
- `-e, --external <package>` - Mark package as external (can be used multiple times)

**Examples:**

Expand All @@ -238,6 +243,9 @@ hono serve --use 'cors()' --use 'logger()' src/app.ts
hono serve \
--use 'basicAuth({ username: "foo", password: "bar" })' \
--use "serveStatic({ root: './' })"

# Start server with external packages (useful for Node.js native modules)
hono serve -e pg -e prisma src/app.ts
```

### `optimize`
Expand Down
68 changes: 68 additions & 0 deletions src/commands/request/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -338,4 +338,72 @@ describe('requestCommand', () => {
)
)
})

it('should handle single external option', async () => {
const mockApp = new Hono()
mockApp.get('/', (c) => c.json({ message: 'Hello' }))

const expectedPath = 'test-app.js'
setupBasicMocks(expectedPath, mockApp)

await program.parseAsync(['node', 'test', 'request', '-e', 'pg', 'test-app.js'])

expect(mockBuildAndImportApp).toHaveBeenCalledWith(expectedPath, {
external: ['@hono/node-server', 'pg'],
watch: false,
sourcemap: true,
})
})

it('should handle multiple external options', async () => {
const mockApp = new Hono()
mockApp.get('/', (c) => c.json({ message: 'Hello' }))

const expectedPath = 'test-app.js'
setupBasicMocks(expectedPath, mockApp)

await program.parseAsync([
'node',
'test',
'request',
'-e',
'pg',
'-e',
'dotenv',
'-e',
'prisma',
'test-app.js',
])

expect(mockBuildAndImportApp).toHaveBeenCalledWith(expectedPath, {
external: ['@hono/node-server', 'pg', 'dotenv', 'prisma'],
watch: false,
sourcemap: true,
})
})

it('should handle long flag name --external', async () => {
const mockApp = new Hono()
mockApp.get('/', (c) => c.json({ message: 'Hello' }))

const expectedPath = 'test-app.js'
setupBasicMocks(expectedPath, mockApp)

await program.parseAsync([
'node',
'test',
'request',
'--external',
'pg',
'--external',
'dotenv',
'test-app.js',
])

expect(mockBuildAndImportApp).toHaveBeenCalledWith(expectedPath, {
external: ['@hono/node-server', 'pg', 'dotenv'],
watch: false,
sourcemap: true,
})
})
})
17 changes: 14 additions & 3 deletions src/commands/request/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ interface RequestOptions {
header?: string[]
path?: string
watch: boolean
external?: string[]
}

export function requestCommand(program: Command) {
Expand All @@ -31,10 +32,19 @@ export function requestCommand(program: Command) {
},
[] as string[]
)
.option(
'-e, --external <package>',
'Mark package as external (can be used multiple times)',
(value: string, previous: string[]) => {
return previous ? [...previous, value] : [value]
},
[] as string[]
)
.action(async (file: string | undefined, options: RequestOptions) => {
const path = options.path || '/'
const watch = options.watch
const buildIterator = getBuildIterator(file, watch)
const external = options.external || []
const buildIterator = getBuildIterator(file, watch, external)
for await (const app of buildIterator) {
const result = await executeRequest(app, path, options)
console.log(JSON.stringify(result, null, 2))
Expand All @@ -44,7 +54,8 @@ export function requestCommand(program: Command) {

export function getBuildIterator(
appPath: string | undefined,
watch: boolean
watch: boolean,
external: string[] = []
): AsyncGenerator<Hono> {
// Determine entry file path
let entry: string
Expand All @@ -68,7 +79,7 @@ export function getBuildIterator(

const appFilePath = realpathSync(resolvedAppPath)
return buildAndImportApp(appFilePath, {
external: ['@hono/node-server'],
external: ['@hono/node-server', ...external],
watch,
sourcemap: true,
})
Expand Down
142 changes: 142 additions & 0 deletions src/commands/serve/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,3 +266,145 @@ export default app
)
})
})

describe('serveCommand external option', () => {
it('should pass external packages to buildAndImportApp', async () => {
const { Hono } = await import('hono')
const buildModule = await import('../../utils/build.js')

const mockApp = new Hono()
mockApp.get('/', (c) => c.json({ message: 'Hello' }))

const mockIterator = {
next: vi
.fn()
.mockResolvedValueOnce({ value: mockApp, done: false })
.mockResolvedValueOnce({ value: undefined, done: true }),
return: vi.fn().mockResolvedValue({ value: undefined, done: true }),
[Symbol.asyncIterator]() {
return this
},
}

const buildSpy = vi.spyOn(buildModule, 'buildAndImportApp').mockReturnValue(mockIterator)

const appDir = mkdtempSync(join(tmpdir(), 'hono-cli-external-test'))
const appFile = join(appDir, 'app.ts')
writeFileSync(appFile, 'export default {}')

const program = new Command()
const { serveCommand } = await import('./index.js')
serveCommand(program)

await program.parseAsync(['node', 'test', 'serve', '-e', 'pg', appFile])

expect(buildSpy).toHaveBeenCalledWith(
expect.any(String),
expect.objectContaining({
external: ['@hono/node-server', 'pg'],
})
)

buildSpy.mockRestore()
})

it('should pass multiple external packages to buildAndImportApp', async () => {
const { Hono } = await import('hono')
const buildModule = await import('../../utils/build.js')

const mockApp = new Hono()
mockApp.get('/', (c) => c.json({ message: 'Hello' }))

const mockIterator = {
next: vi
.fn()
.mockResolvedValueOnce({ value: mockApp, done: false })
.mockResolvedValueOnce({ value: undefined, done: true }),
return: vi.fn().mockResolvedValue({ value: undefined, done: true }),
[Symbol.asyncIterator]() {
return this
},
}

const buildSpy = vi.spyOn(buildModule, 'buildAndImportApp').mockReturnValue(mockIterator)

const appDir = mkdtempSync(join(tmpdir(), 'hono-cli-external-test'))
const appFile = join(appDir, 'app.ts')
writeFileSync(appFile, 'export default {}')

const program = new Command()
const { serveCommand } = await import('./index.js')
serveCommand(program)

await program.parseAsync([
'node',
'test',
'serve',
'-e',
'pg',
'-e',
'dotenv',
'-e',
'prisma',
appFile,
])

expect(buildSpy).toHaveBeenCalledWith(
expect.any(String),
expect.objectContaining({
external: ['@hono/node-server', 'pg', 'dotenv', 'prisma'],
})
)

buildSpy.mockRestore()
})

it('should handle --external long flag name', async () => {
const { Hono } = await import('hono')
const buildModule = await import('../../utils/build.js')

const mockApp = new Hono()
mockApp.get('/', (c) => c.json({ message: 'Hello' }))

const mockIterator = {
next: vi
.fn()
.mockResolvedValueOnce({ value: mockApp, done: false })
.mockResolvedValueOnce({ value: undefined, done: true }),
return: vi.fn().mockResolvedValue({ value: undefined, done: true }),
[Symbol.asyncIterator]() {
return this
},
}

const buildSpy = vi.spyOn(buildModule, 'buildAndImportApp').mockReturnValue(mockIterator)

const appDir = mkdtempSync(join(tmpdir(), 'hono-cli-external-test'))
const appFile = join(appDir, 'app.ts')
writeFileSync(appFile, 'export default {}')

const program = new Command()
const { serveCommand } = await import('./index.js')
serveCommand(program)

await program.parseAsync([
'node',
'test',
'serve',
'--external',
'pg',
'--external',
'dotenv',
appFile,
])

expect(buildSpy).toHaveBeenCalledWith(
expect.any(String),
expect.objectContaining({
external: ['@hono/node-server', 'pg', 'dotenv'],
})
)

buildSpy.mockRestore()
})
})
Loading