Skip to content

feat(nuxi): add q and quit shortcuts to dev server#1378

Open
userquin wants to merge 22 commits into
nuxt:mainfrom
userquin:add-q-dev-shortcut
Open

feat(nuxi): add q and quit shortcuts to dev server#1378
userquin wants to merge 22 commits into
nuxt:mainfrom
userquin:add-q-dev-shortcut

Conversation

@userquin

@userquin userquin commented Jul 23, 2026

Copy link
Copy Markdown
Member

🔗 Linked issue

📚 Description

This PR adds the same shortcut at Vite where pressing q + enter stops the server.

image

Copilot AI review requested due to automatic review settings July 23, 2026 12:52
@userquin
userquin requested a review from danielroe as a code owner July 23, 2026 12:52
@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@userquin, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 7 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 53e3db6a-5c0b-4cf1-a2a3-37821d132310

📥 Commits

Reviewing files that changed from the base of the PR and between 1967b94 and ecbf37b.

📒 Files selected for processing (1)
  • packages/nuxi/test/unit/initialize.spec.ts
📝 Walkthrough

Walkthrough

The development server now supports TTY-only interactive quitting, emits a typed closing event, and cleans up readline and server resources during shutdown. Initialization accepts an optional pre-quit callback, coordinates watcher, listener, server, and lock cleanup, and sets the process exit code on failure. Fork-mode restarts temporarily disable and then restore this callback. Unit tests cover initialization, readiness, shutdown, and closing behavior.

Estimated code review effort: 3 (Moderate) | ~25 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding quit shortcuts to the Nuxt dev server.
Description check ✅ Passed The description is directly related to the change and explains the new q+Enter stop shortcut.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@pkg-pr-new

pkg-pr-new Bot commented Jul 23, 2026

Copy link
Copy Markdown
  • nuxt-cli-playground

    npm i https://pkg.pr.new/create-nuxt@1378
    
    npm i https://pkg.pr.new/nuxi@1378
    
    npm i https://pkg.pr.new/@nuxt/cli@1378
    

commit: ecbf37b

@codecov-commenter

codecov-commenter commented Jul 23, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 57.89474% with 16 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (main@b545a2b). Learn more about missing BASE report.

Files with missing lines Patch % Lines
packages/nuxi/src/dev/utils.ts 33.33% 9 Missing and 1 partial ⚠️
packages/nuxi/src/dev/index.ts 78.94% 4 Missing ⚠️
packages/nuxi/src/commands/dev.ts 50.00% 2 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main    #1378   +/-   ##
=======================================
  Coverage        ?   46.68%           
=======================================
  Files           ?       52           
  Lines           ?     1752           
  Branches        ?      504           
=======================================
  Hits            ?      818           
  Misses          ?      751           
  Partials        ?      183           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@codspeed-hq

codspeed-hq Bot commented Jul 23, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 2 untouched benchmarks


Comparing userquin:add-q-dev-shortcut (ecbf37b) with main (b545a2b)

Open in CodSpeed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/nuxi/src/dev/index.ts`:
- Around line 150-157: Update the readline quit handler in initialize to invoke
the complete shutdown callback supplied by the command layer instead of the
initializer’s local close(). Ensure the callback remains the public wrapper
returned by the dev command, including cleanupCurrentFork?.(), before
process.exit(0), while preserving the existing quit aliases and exit behavior.
- Around line 136-145: Update the close function so devServer.releaseLock()
always runs in a finally block, even when listener or watcher shutdown rejects.
Ensure shutdown failures propagate to the surrounding finally/error handling and
result in a non-zero process exit status instead of being masked as successful
completion.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 4a085477-5ae0-4dab-9f96-a35c07b8c020

📥 Commits

Reviewing files that changed from the base of the PR and between b545a2b and f0ff0b2.

📒 Files selected for processing (1)
  • packages/nuxi/src/dev/index.ts

Comment thread packages/nuxi/src/dev/index.ts Outdated
Comment thread packages/nuxi/src/dev/index.ts Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds interactive quit shortcuts to the nuxi dev server so users can stop the server by typing q or quit (similar to Vite’s behavior), gated behind an interactive TTY check.

Changes:

  • Add a TTY-only readline listener that triggers shutdown when the user enters q or quit.
  • Refactor the dev server shutdown logic into a shared close() function and ensure the readline interface is closed during shutdown.
  • Introduce std-env checks (hasTTY, isCI) to avoid enabling the shortcut in CI/non-interactive environments.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/nuxi/src/dev/index.ts Outdated
Comment thread packages/nuxi/src/dev/index.ts Outdated
Copilot AI review requested due to automatic review settings July 23, 2026 12:58

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 2 comments.

Comment thread packages/nuxi/src/dev/index.ts Outdated
Comment thread packages/nuxi/src/dev/index.ts Outdated
Copilot AI review requested due to automatic review settings July 23, 2026 14:12

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

Comment thread packages/nuxi/src/dev/utils.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/nuxi/src/dev/utils.ts`:
- Around line 538-541: Update close() and the reload flow around `#load`() so
readline remains available across reloads and the quit handler is re-registered
on each ready event. Separate final-shutdown cleanup from reload cleanup, or
recreate `#rl` before handler registration, while preserving full cleanup when the
dev server actually shuts down.
- Around line 523-524: Guard the quit prompt console.log in the relevant dev
utility flow so it only executes when readline is active. Reuse the existing
readline-active state or check available in the surrounding code, while
preserving the current prompt formatting when a listener is installed.
- Around line 520-521: Bind the `#quitListener` method to the current
NuxtDevServer instance before registering it with `#rl` in the listener setup.
Preserve the existing removeAllListeners('line') behavior and register the bound
callback so its this context invokes NuxtDevServer.close() reliably.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 9b9044df-37b4-4e71-a000-de46833ef12f

📥 Commits

Reviewing files that changed from the base of the PR and between f0ff0b2 and cde0f75.

📒 Files selected for processing (2)
  • packages/nuxi/src/dev/index.ts
  • packages/nuxi/src/dev/utils.ts

Comment thread packages/nuxi/src/dev/utils.ts Outdated
Comment thread packages/nuxi/src/dev/utils.ts Outdated
Comment thread packages/nuxi/src/dev/utils.ts
Copilot AI review requested due to automatic review settings July 23, 2026 14:33

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/nuxi/src/dev/utils.ts (1)

534-538: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Route q through the complete shutdown path.

This handler calls NuxtDevServer.close() directly, bypassing the shutdown callback in packages/nuxi/src/dev/index.ts that closes watchers and the listener and releases the lock. Additionally, process.exit(0) in finally converts any close failure into a successful exit. Use the shared final-shutdown callback and exit only after cleanup succeeds.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/nuxi/src/dev/utils.ts` around lines 534 - 538, Update the `q`
handler in `NuxtDevServer` to invoke the shared final-shutdown callback from the
dev entrypoint instead of calling `this.close()` directly, ensuring watchers,
the listener, and lock are released. Remove the unconditional `process.exit(0)`
from `finally`; exit successfully only after the complete shutdown callback
resolves, while allowing close failures to propagate.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@packages/nuxi/src/dev/utils.ts`:
- Around line 534-538: Update the `q` handler in `NuxtDevServer` to invoke the
shared final-shutdown callback from the dev entrypoint instead of calling
`this.close()` directly, ensuring watchers, the listener, and lock are released.
Remove the unconditional `process.exit(0)` from `finally`; exit successfully
only after the complete shutdown callback resolves, while allowing close
failures to propagate.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b21f8b3c-0316-4a4a-ae4a-f8d9c3270978

📥 Commits

Reviewing files that changed from the base of the PR and between cde0f75 and a3dae4a.

📒 Files selected for processing (1)
  • packages/nuxi/src/dev/utils.ts

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

Comment thread packages/nuxi/src/dev/utils.ts Outdated
Comment thread packages/nuxi/src/dev/index.ts Outdated
Copilot AI review requested due to automatic review settings July 23, 2026 14:50

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/nuxi/src/dev/utils.ts (1)

517-527: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Close readline on every shutdown path.

#rl is only cleaned up inside #quitListener, but the programmatic shutdown path in packages/nuxi/src/dev/index.ts calls devServer.close() without closing #rl. Centralize readline cleanup and invoke it from close() as well so callers do not leave stdin active and block clean process termination.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/nuxi/src/dev/utils.ts` around lines 517 - 527, Centralize the
readline interface cleanup currently performed by `#quitListener` into a dedicated
cleanup method, then invoke it from close() before or during shutdown. Preserve
the existing quit behavior while ensuring programmatic devServer.close() also
removes listeners and closes `#rl` so stdin does not remain active.
🧹 Nitpick comments (1)
packages/nuxi/src/dev/utils.ts (1)

517-536: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Add coverage for the interactive lifecycle.

Please cover TTY/non-TTY and CI gating, prompt registration, q/quit/exit handling, readline cleanup, and the emitted closing event. Codecov reports only 33.33% patch coverage for this file.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/nuxi/src/dev/utils.ts` around lines 517 - 536, Add tests covering
the interactive lifecycle around the readline setup and `#quitListener` methods:
verify TTY/non-CI gating, prompt and line-listener registration, and no setup
for non-TTY or CI environments. Exercise q, quit, and exit inputs to confirm
readline listeners are removed, the interface is closed and cleared, and closing
is emitted.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@packages/nuxi/src/dev/utils.ts`:
- Around line 517-527: Centralize the readline interface cleanup currently
performed by `#quitListener` into a dedicated cleanup method, then invoke it from
close() before or during shutdown. Preserve the existing quit behavior while
ensuring programmatic devServer.close() also removes listeners and closes `#rl` so
stdin does not remain active.

---

Nitpick comments:
In `@packages/nuxi/src/dev/utils.ts`:
- Around line 517-536: Add tests covering the interactive lifecycle around the
readline setup and `#quitListener` methods: verify TTY/non-CI gating, prompt and
line-listener registration, and no setup for non-TTY or CI environments.
Exercise q, quit, and exit inputs to confirm readline listeners are removed, the
interface is closed and cleared, and closing is emitted.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 84280014-3925-4d3c-9bb7-cff0bd28953c

📥 Commits

Reviewing files that changed from the base of the PR and between a3dae4a and 1db0e92.

📒 Files selected for processing (2)
  • packages/nuxi/src/dev/index.ts
  • packages/nuxi/src/dev/utils.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/nuxi/src/dev/index.ts

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (2)

packages/nuxi/src/dev/utils.ts:543

  • NuxtDevServer.close() does not dispose the readline interface created on ready. If consumers call initialize(...).close() (or a shutdown path other than typing q), the open readline handle can keep stdin in flowing mode and prevent the process from exiting cleanly.
  async close(): Promise<void> {
    if (this.#currentNuxt) {
      await this.#currentNuxt.close()
    }
  }

packages/nuxi/src/dev/index.ts:125

  • closeWatchers() is called before the try/finally, so if it throws, the dev lock may not be released. Wrapping it in the same try/finally makes lock release robust even if watcher cleanup errors.
  async function close() {
    devServer.closeWatchers()
    try {
      await Promise.all([
        devServer.listener.close(),
        devServer.close(),
      ])
    }
    finally {
      devServer.releaseLock()
    }
  }

Comment thread packages/nuxi/src/dev/utils.ts
Copilot AI review requested due to automatic review settings July 23, 2026 16:33

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (2)

packages/nuxi/src/dev/index.ts:126

  • close() releases the dev lock in a finally block, so the lock can be removed even if listener.close() / devServer.close() fails. In non-exit paths (e.g. restart handling), this can leave a partially running process unlocked and allow concurrent starts, making the lock state unreliable. Releasing the lock only after a successful shutdown keeps the lock consistent with actual server state.
  async function close() {
    devServer.closeWatchers()
    try {
      await Promise.all([
        devServer.listener.close(),
        devServer.close(),
      ])
    }
    finally {
      devServer.releaseLock()
    }
  }

packages/nuxi/src/dev/utils.ts:536

  • The new interactive quit shortcut (q/quit/exit) and the new closing event are user-visible behavior, but there’s no unit/integration test asserting that entering q emits closing and disposes the readline handler. This module already has vitest unit coverage (e.g. packages/nuxi/test/unit/file-watcher.spec.ts imports from src/dev/utils), so adding a focused test would help prevent regressions (listener leaks, accidental double-close, etc.).
    if (!this.#rl && hasTTY && !isCI) {
      this.#rl = readline.createInterface({ input: process.stdin })
    }

    if (this.#rl) {
      this.#rl.removeAllListeners('line')
      this.#rl.addListener('line', this.#quitListener.bind(this))

      // eslint-disable-next-line no-console
      console.log(`\n${colors.dim('  press ')}${colors.bold(`q + enter`)}${colors.dim(` to quit`)}\n`)
    }
  }

  #quitListener(line: string) {
    if (line === 'q' || line === 'quit' || line === 'exit') {
      this.#rl?.removeAllListeners('line')
      this.#rl?.close()
      this.#rl = undefined
      this.emit('closing')
    }

Comment thread packages/nuxi/src/commands/dev.ts Outdated
Copilot AI review requested due to automatic review settings July 23, 2026 18:00

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

packages/nuxi/src/dev/index.ts:125

  • releaseLock() is executed in a finally block, so the lock is removed even if listener.close() or devServer.close() rejects. If initialize().close() is used programmatically (e.g. during restarts) and shutdown fails, releasing the lock can allow concurrent starts while the previous server is still partially running. Consider only releasing the lock after a successful shutdown (and let the process exit hook handle cleanup on hard termination).
    finally {
      devServer.releaseLock()
    }

Comment thread packages/nuxi/src/dev/index.ts Outdated
Comment thread packages/nuxi/test/unit/initialize.spec.ts
Copilot AI review requested due to automatic review settings July 23, 2026 18:13

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/nuxi/test/unit/initialize.spec.ts (1)

85-106: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for the failure/exit-code branch.

This test only asserts exitSpy was called, but doesn't verify process.exitCode. The source sets exitCode = 0 on success and exitCode = 1 if close() rejects — neither branch's exit code is asserted here, and the failure path (close() throwing) isn't exercised at all.

       expect(onBeforeQuit).toHaveBeenCalledWith(instance)
       expect(exitSpy).toHaveBeenCalled()
+      expect(process.exitCode).toBe(0)
     }
     finally {
       exitSpy.mockRestore()
     }
   })
+
+  it('sets exitCode to 1 and still exits when close() rejects during shutdown', async () => {
+    const exitSpy = vi.spyOn(process, 'exit').mockImplementation(() => undefined as never)
+    try {
+      const result = await initialize(baseDevContext())
+      const instance = vi.mocked(NuxtDevServer).mock.results[0]!.value
+      instance.close.mockRejectedValueOnce(new Error('boom'))
+
+      instance.emit('closing')
+      await vi.waitFor(() => expect(exitSpy).toHaveBeenCalled())
+
+      expect(process.exitCode).toBe(1)
+    }
+    finally {
+      exitSpy.mockRestore()
+    }
+  })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/nuxi/test/unit/initialize.spec.ts` around lines 85 - 106, The
shutdown test around initialize and the “closing” handler needs coverage for
both exit-code outcomes. Assert process.exitCode is 0 after a successful close,
then add a failure-path test where instance.close rejects and verify
process.exitCode is 1 while preserving the existing onBeforeQuit and
process.exit assertions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/nuxi/src/commands/dev.ts`:
- Around line 140-147: Update the onRestart callback in initialize so
initializeOptions.onBeforeQuit delegates to the current cleanupCurrentFork
binding rather than capturing its value once. Preserve the existing temporary
disable during close() and ensure later IPC-triggered fork restarts invoke
cleanupCurrentFork for the active fork when quitting.

---

Nitpick comments:
In `@packages/nuxi/test/unit/initialize.spec.ts`:
- Around line 85-106: The shutdown test around initialize and the “closing”
handler needs coverage for both exit-code outcomes. Assert process.exitCode is 0
after a successful close, then add a failure-path test where instance.close
rejects and verify process.exitCode is 1 while preserving the existing
onBeforeQuit and process.exit assertions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 9c24c454-29b1-4b6d-b0cf-14dd8801aaf4

📥 Commits

Reviewing files that changed from the base of the PR and between 1db0e92 and 2f9697a.

📒 Files selected for processing (4)
  • packages/nuxi/src/commands/dev.ts
  • packages/nuxi/src/dev/index.ts
  • packages/nuxi/src/dev/utils.ts
  • packages/nuxi/test/unit/initialize.spec.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/nuxi/src/dev/index.ts
  • packages/nuxi/src/dev/utils.ts

Comment thread packages/nuxi/src/commands/dev.ts Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (2)

packages/nuxi/src/dev/index.ts:119

  • releaseLock() is called in a finally block, which removes the lock file (and unregisters the process exit cleanup) even if listener.close() / devServer.close() fails. If shutdown is only partially successful and the process continues (e.g. during restarts), this can leave the dev server running while unlocked, allowing concurrent starts and making the lock state inconsistent. Release the lock only after a successful shutdown completes.
  async function close() {
    devServer.closeWatchers()
    try {
      await Promise.all([
        devServer.listener.close(),

packages/nuxi/src/commands/dev.ts:144

  • initializeOptions.onBeforeQuit is assigned to the current cleanupCurrentFork function by value. If restartWithFork() runs again later (it reassigns cleanupCurrentFork), the stored hook will still point at the old fork’s cleanup function, so quitting can leave the active fork running. Use a wrapper that calls the latest cleanupCurrentFork at invocation time.
    onRestart(async () => {
      // Temporarily disable the quit hook during restart to avoid double-cleanup
      Object.assign(initializeOptions, { onBeforeQuit: undefined })
      // Close the in-process dev server
      await close()

Comment thread packages/nuxi/test/unit/initialize.spec.ts
Copilot AI review requested due to automatic review settings July 23, 2026 18:25

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (2)

packages/nuxi/src/dev/index.ts:125

  • releaseLock() is executed in a finally, so the lock file can be released even when listener.close() or devServer.close() fails (e.g. during an in-process restart where the process continues running). That can leave a still-running/partially-running server without a lock, enabling concurrent starts and making the lock state inconsistent.
  async function close() {
    devServer.closeWatchers()
    try {
      await Promise.all([
        devServer.listener.close(),
        devServer.close(),
      ])
    }
    finally {
      devServer.releaseLock()
    }

packages/nuxi/src/dev/utils.ts:536

  • There’s currently no test that verifies the interactive quit shortcut behavior (typing q/quit causes the readline listener to be detached and emits closing). The new initialize unit test covers the downstream closing handler, but it doesn’t protect the stdin/readline path against regressions or listener leaks.
  #quitListener(line: string) {
    if (line === 'q' || line === 'quit' || line === 'exit') {
      this.#rl?.removeAllListeners('line')
      this.#rl?.close()
      this.#rl = undefined
      this.emit('closing')
    }

@userquin

Copy link
Copy Markdown
Member Author

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (2)
packages/nuxi/src/dev/index.ts:125

  • releaseLock() is executed in a finally, so the lock file can be released even when listener.close() or devServer.close() fails (e.g. during an in-process restart where the process continues running). That can leave a still-running/partially-running server without a lock, enabling concurrent starts and making the lock state inconsistent.
  async function close() {
    devServer.closeWatchers()
    try {
      await Promise.all([
        devServer.listener.close(),
        devServer.close(),
      ])
    }
    finally {
      devServer.releaseLock()
    }

packages/nuxi/src/dev/utils.ts:536

  • There’s currently no test that verifies the interactive quit shortcut behavior (typing q/quit causes the readline listener to be detached and emits closing). The new initialize unit test covers the downstream closing handler, but it doesn’t protect the stdin/readline path against regressions or listener leaks.
  #quitListener(line: string) {
    if (line === 'q' || line === 'quit' || line === 'exit') {
      this.#rl?.removeAllListeners('line')
      this.#rl?.close()
      this.#rl = undefined
      this.emit('closing')
    }

Thanks for flagging this. I looked into adding a dedicated test for the q/quit readline shortcut, but the wiring for #rl/#quitListener lives inside #initializeNuxt(), which is a large private method that also drives Nitro build, type generation, dist-directory watching, Vite HMR hooks and lockfile handling. Exercising that path for real would mean mocking most of loadKit, listen, acquireLock/updateLock, showBanner, writeNuxtManifest and fs.watch just to reach the handful of lines that set up the readline listener — a lot of surface for a test whose only goal is the quit shortcut, and one that would be fragile against unrelated refactors of #initializeNuxt.

Given the low-confidence flag, I'd rather not bolt a heavy integration test onto this PR for that. If we want solid coverage of the interactive quit path (detach on q/quit/exit, no listener leak, correct behavior under non-TTY/CI), the right move would be a small follow-up refactor extracting the readline wiring (hasTTY && !isCI check, createInterface, #quitListener) into its own private method or standalone function, so it can be unit-tested in isolation without dragging in the rest of #initializeNuxt. Happy to open that as a separate PR/issue if it sounds good — didn't want to expand scope here without a nod first.

Copilot AI review requested due to automatic review settings July 23, 2026 18:58

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (2)

packages/nuxi/src/dev/index.ts:126

  • releaseLock() is currently executed in a finally block, so it will run even if listener.close() or devServer.close() fails. That can remove the lock while the dev server is still partially running, allowing concurrent starts and making the lock state inconsistent. Release the lock only after a successful shutdown; if shutdown fails, keep the lock (the process.on('exit') handler installed by acquireLock() will still clean up on actual process exit).
  async function close() {
    devServer.closeWatchers()
    try {
      await Promise.all([
        devServer.listener.close(),
        devServer.close(),
      ])
    }
    finally {
      devServer.releaseLock()
    }
  }

packages/nuxi/src/dev/utils.ts:536

  • The new quit shortcut installs a stdin readline listener and emits a new closing event, but there is still no test that validates the user-facing behavior (i.e., that entering q/quit on stdin triggers closing and that the readline listener is detached). The new initialize.spec.ts tests the closing event handler, but it doesn't cover the readline-driven path introduced here.
    if (!this.#rl && hasTTY && !isCI) {
      this.#rl = readline.createInterface({ input: process.stdin })
    }

    if (this.#rl) {
      this.#rl.removeAllListeners('line')
      this.#rl.addListener('line', this.#quitListener.bind(this))

      // eslint-disable-next-line no-console
      console.log(`\n${colors.dim('  press ')}${colors.bold(`q + enter`)}${colors.dim(` to quit`)}\n`)
    }
  }

  #quitListener(line: string) {
    if (line === 'q' || line === 'quit' || line === 'exit') {
      this.#rl?.removeAllListeners('line')
      this.#rl?.close()
      this.#rl = undefined
      this.emit('closing')
    }

Comment on lines 140 to 149
onRestart(async () => {
// Temporarily disable the quit hook during restart to avoid double-cleanup
Object.assign(initializeOptions, { onBeforeQuit: undefined })
// Close the in-process dev server
await close()
await restartWithFork()
// Delegate to the live `cleanupCurrentFork` binding so later fork
// restarts (triggered via IPC) are always cleaned up correctly on quit
Object.assign(initializeOptions, { onBeforeQuit: () => cleanupCurrentFork?.() })
})

@userquin userquin Jul 23, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not just make onRestart callback void | Promise<void> ? if so we can also change onBeforeQuit, but not sure if we should await

/cc @danielroe

Copilot AI review requested due to automatic review settings July 23, 2026 19:06

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

packages/nuxi/src/dev/index.ts:125

  • close() always calls devServer.releaseLock() in a finally block, so the lock is released even when listener.close() / devServer.close() rejects. This can leave the process still running (e.g. during restart flows) without a lock, and it also disables the lock's process.on('exit') cleanup handler while the server may still be active. Release the lock only after a successful shutdown.
    finally {
      devServer.releaseLock()
    }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants