Skip to content

feat: add dev server shortcuts #83

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: 8.x
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
"fast-glob": "^3.3.3",
"get-port": "^7.1.0",
"junk": "^4.0.1",
"open": "^10.1.2",
"picomatch": "^4.0.2",
"pretty-hrtime": "^1.0.3",
"tmp-cache": "^1.1.0",
Expand Down
39 changes: 38 additions & 1 deletion src/dev_server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { type UnWrapLazyImport } from '@poppinss/utils/types'

import debug from './debug.ts'
import { FileSystem } from './file_system.ts'
import { ShortcutsManager } from './shortcuts_manager.ts'
import type { DevServerOptions } from './types/common.ts'
import { type DevServerHooks, type WatcherHooks } from './types/hooks.ts'
import { getPort, loadHooks, parseConfig, runNode, throttle, watch } from './utils.ts'
Expand Down Expand Up @@ -68,6 +69,11 @@ export class DevServer {
*/
#httpServer?: ResultPromise

/**
* Keyboard shortcuts manager
*/
#shortcutsManager?: ShortcutsManager

/**
* Filesystem is used to decide which files to watch or entertain when
* using hot-hook
Expand Down Expand Up @@ -103,6 +109,29 @@ export class DevServer {
await this.#startHTTPServer(this.#stickyPort)
}, 'restartHTTPServer')

/**
* Sets up keyboard shortcuts
*/
#setupKeyboardShortcuts() {
this.#shortcutsManager = new ShortcutsManager({
logger: this.ui.logger,
callbacks: {
onRestart: () => this.#restartHTTPServer(),
onClear: () => this.#clearScreen(),
onQuit: () => this.close(),
},
})

this.#shortcutsManager.setup()
}

/**
* Cleanup keyboard shortcuts
*/
#cleanupKeyboardShortcuts() {
this.#shortcutsManager?.cleanup()
}

/**
* CLI UI to log colorful messages
*/
Expand Down Expand Up @@ -152,15 +181,20 @@ export class DevServer {
*/
async #postServerReady(message: { port: number; host: string; duration?: [number, number] }) {
const host = message.host === '0.0.0.0' ? '127.0.0.1' : message.host
const serverUrl = `http://${host}:${message.port}`
this.#shortcutsManager?.setServerUrl(serverUrl)

const displayMessage = this.ui
.sticker()
.add(`Server address: ${this.ui.colors.cyan(`http://${host}:${message.port}`)}`)
.add(`Server address: ${this.ui.colors.cyan(serverUrl)}`)
.add(`Mode: ${this.ui.colors.cyan(this.mode)}`)

if (message.duration) {
displayMessage.add(`Ready in: ${this.ui.colors.cyan(prettyHrtime(message.duration))}`)
}

displayMessage.add(`Press ${this.ui.colors.dim('h')} to show help`)

/**
* Run hooks before displaying the "displayMessage". It will allow hooks to add
* custom lines to the display message.
Expand Down Expand Up @@ -392,6 +426,7 @@ export class DevServer {
* Close watchers and the running child process
*/
async close() {
this.#cleanupKeyboardShortcuts()
await this.#watcher?.close()
if (this.#httpServer) {
this.#httpServer.removeAllListeners()
Expand Down Expand Up @@ -434,6 +469,7 @@ export class DevServer {
}

this.#clearScreen()
this.#setupKeyboardShortcuts()
this.ui.logger.info('starting HTTP server...')
await this.#startHTTPServer(this.#stickyPort)
}
Expand All @@ -460,6 +496,7 @@ export class DevServer {
this.#registerServerRestartHooks()

this.#clearScreen()
this.#setupKeyboardShortcuts()
this.ui.logger.info('starting HTTP server...')
await this.#startHTTPServer(this.#stickyPort)

Expand Down
143 changes: 143 additions & 0 deletions src/shortcuts_manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
/*
* @adonisjs/assembler
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

import type { Logger } from '@poppinss/cliui'

/**
* Keyboard shortcut definition
*/
export interface KeyboardShortcut {
key: string
description: string
handler: () => void
}

/**
* Callbacks for keyboard shortcuts actions
*/
export interface KeyboardShortcutsCallbacks {
onRestart: () => void
onClear: () => void
onQuit: () => void
}

/**
* Shortcuts manager options
*/
export interface ShortcutsManagerOptions {
logger: Logger
callbacks: KeyboardShortcutsCallbacks
}

/**
* Manages keyboard shortcuts for development server
*/
export class ShortcutsManager {
#logger: Logger
#callbacks: KeyboardShortcutsCallbacks
#serverUrl?: string
#keyPressHandler?: (data: Buffer) => void

#shortcuts: KeyboardShortcut[] = [
{
key: 'r',
description: 'restart server',
handler: () => {
this.#logger.log('')
this.#logger.info('Manual restart triggered...')
this.#callbacks.onRestart()
},
},
{
key: 'c',
description: 'clear console',
handler: () => {
this.#callbacks.onClear()
this.#logger.info('Console cleared')
},
},
{
key: 'o',
description: 'open in browser',
handler: () => this.#handleOpenBrowser(),
},
{
key: 'h',
description: 'show this help',
handler: () => this.showHelp(),
},
]

constructor(options: ShortcutsManagerOptions) {
this.#logger = options.logger
this.#callbacks = options.callbacks
}

/**
* Set server url for opening in browser
*/
setServerUrl(url: string) {
this.#serverUrl = url
}

/**
* Initialize keyboard shortcuts
*/
setup() {
if (!process.stdin.isTTY) return

process.stdin.setRawMode(true)

this.#keyPressHandler = (data: Buffer) => this.#handleKeyPress(data.toString())
process.stdin.on('data', this.#keyPressHandler)
}

/**
* Handle key press events
*/
#handleKeyPress(key: string) {
// Handle Ctrl+C (0x03) and Ctrl+D (0x04)
if (key === '\u0003' || key === '\u0004') return this.#callbacks.onQuit()

const shortcut = this.#shortcuts.find((s) => s.key === key)
if (shortcut) shortcut.handler()
}

/**
* Handle opening browser
*/
async #handleOpenBrowser() {
this.#logger.log('')
this.#logger.info(`Opening ${this.#serverUrl}...`)

const { default: open } = await import('open')
open(this.#serverUrl!)
}

/**
* Show available keyboard shortcuts
*/
showHelp() {
this.#logger.log('')
this.#logger.log('Available shortcuts:')

this.#shortcuts.forEach(({ key, description }) => this.#logger.log(`· ${key}: ${description}`))
}

/**
* Cleanup keyboard shortcuts
*/
cleanup() {
if (!process.stdin.isTTY) return

process.stdin.setRawMode(false)
process.stdin.removeListener('data', this.#keyPressHandler!)
this.#keyPressHandler = undefined
}
}
6 changes: 4 additions & 2 deletions tests/dev_server.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ test.group('DevServer', () => {
stream: 'stdout',
},
{
message: 'Server address: cyan(http://localhost:3334)\nMode: cyan(static)',
message:
'Server address: cyan(http://localhost:3334)\nMode: cyan(static)\nPress dim(h) to show help',
stream: 'stdout',
},
])
Expand Down Expand Up @@ -139,7 +140,8 @@ test.group('DevServer', () => {
stream: 'stdout',
},
{
message: 'Server address: cyan(http://localhost:3335)\nMode: cyan(watch)',
message:
'Server address: cyan(http://localhost:3335)\nMode: cyan(watch)\nPress dim(h) to show help',
stream: 'stdout',
},
])
Expand Down
Loading