fix: support HTTPS servers and handle named pipe ports#526
fix: support HTTPS servers and handle named pipe ports#526rajan-chari wants to merge 1 commit intomainfrom
Conversation
Two bugs fixed: 1. ExpressAdapter: accept https.Server in addition to http.Server. https.Server extends tls.Server (not http.Server), so the instanceof check silently missed it, falling through to create a new http.Server instead. Users passing an HTTPS server got HTTP behavior with no error. 2. DevtoolsPlugin: handle non-numeric port strings (named pipes). On Azure App Service, PORT is a Windows named pipe path like \.\pipe\507cb72a-... and parseInt() returns NaN, crashing the devtools server. Now falls back to port 3979 with a warning when the port is a named pipe. Same root cause as #487 (fixed in #488 for HttpServer but missed in DevtoolsPlugin). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Fixes two deployment/runtime issues: (1) ExpressAdapter now correctly accepts and uses user-supplied HTTPS servers (preventing a silent HTTP downgrade), and (2) DevtoolsPlugin no longer crashes when PORT is a Windows named pipe (Azure App Service), falling back to a default devtools port with a warning.
Changes:
- Accept
https.ServerinExpressAdapterby widening types and updating server detection logic. - Add named-pipe-safe port derivation logic to
DevtoolsPlugin.onStart(). - Add unit tests documenting the HTTPS
instanceofbehavior and basic HTTPS server wiring.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| packages/dev/src/plugin.ts | Avoids NaN port math on named pipes by using a fallback port and warning. |
| packages/apps/src/http/express-adapter.ts | Treats https.Server as a valid externally-managed server and stores it without downgrading to HTTP. |
| packages/apps/src/http/express-adapter.spec.ts | Adds tests covering HTTPS instanceof root cause and basic adapter acceptance of https.Server. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| let numericPort: number; | ||
| if (this.options.customPort) { | ||
| numericPort = this.options.customPort; | ||
| } else if (typeof port === 'number') { | ||
| numericPort = port + 1; | ||
| } else { |
There was a problem hiding this comment.
customPort is checked via truthiness, so a valid value like 0 will be ignored and the code will fall back to deriving a port. Use an explicit null/undefined check instead (e.g., this.options.customPort != null) or check typeof this.options.customPort === 'number'.
There was a problem hiding this comment.
IMO, this is not worth fixing. Devtools is supposed to be for local development.
| const parsed = parseInt(port, 10); | ||
| if (isNaN(parsed)) { | ||
| numericPort = 3979; | ||
| this.log.warn(`Port is a named pipe (${port}), using default devtools port ${numericPort}. Set customPort to override.`); | ||
| } else { | ||
| numericPort = parsed + 1; | ||
| } |
There was a problem hiding this comment.
parseInt() will accept partially-numeric strings (e.g. "3000abc" => 3000), which can incorrectly treat an invalid PORT as valid. Consider using a stricter parse (e.g., Number(port) with Number.isFinite(...) plus a full-string numeric check) so only purely numeric ports are incremented; otherwise treat it as a named pipe/invalid string and use the fallback.
| // Verify the adapter stored the server: stop() should reject with | ||
| // "Server is not running" (not "managed externally" which would mean | ||
| // the adapter didn't recognize the https.Server and created its own) | ||
| await expect(adapter.stop()).rejects.toThrow('Server is not running'); |
There was a problem hiding this comment.
This assertion is tightly coupled to a specific error message string, making the test brittle to future wording changes. A more robust assertion would directly verify the adapter is using the passed-in server instance (e.g., by checking internal state via a safe test-only accessor, or by asserting behavior that cannot depend on the exact wording, such as checking the error type/shape if available).
| // Verify the adapter stored the server: stop() should reject with | |
| // "Server is not running" (not "managed externally" which would mean | |
| // the adapter didn't recognize the https.Server and created its own) | |
| await expect(adapter.stop()).rejects.toThrow('Server is not running'); | |
| // Verify the adapter stored the server by asserting stop() rejects in | |
| // this unstarted state, without coupling the test to exact wording. | |
| expect.assertions(2); | |
| try { | |
| await adapter.stop(); | |
| } catch (error) { | |
| expect(error).toBeInstanceOf(Error); | |
| expect((error as Error).message).toEqual(expect.any(String)); | |
| } |
Summary
Fixes #511. Two bugs that affected HTTPS and Azure App Service deployments:
1. ExpressAdapter silently falls back to HTTP when given an HTTPS server
ExpressAdapterconstructor usesinstanceof http.Serverto detect when a server is passed. However,https.Serverextendstls.Server(nothttp.Server), sohttps.createServer() instanceof http.Serverreturnsfalse. The adapter silently falls through to create its ownhttp.Server, ignoring the user's HTTPS server entirely — a silent HTTP downgrade with no error.Fix: Import
httpsand checkserverOrApp instanceof http.Server || serverOrApp instanceof https.Server. Widen the constructor parameter andserverfield types tohttp.Server | https.Server.2. DevtoolsPlugin crashes on Azure App Service named pipe ports
On Azure App Service (IIS + iisnode), the
PORTenvironment variable is a Windows named pipe path like\.\pipe\507cb72a-.... DevtoolsPlugin callsparseInt(port, 10) + 1to derive its port, which returnsNaN + 1 = NaN, crashing the devtools HTTP server. Same root cause as #487 (fixed in #488 forHttpServer.start(), but missed inDevtoolsPlugin.onStart()).Fix: Explicit branching — if port is a named pipe (non-numeric string), fall back to port 3979 with a warning suggesting
customPortoption.Test plan
https.createServer({} ) instanceof https.Server === trueandinstanceof http.Server === false(documents root cause)ExpressAdapter(httpsServer)wires up correctly (stop rejects with "not running", not "managed externally")Files changed
packages/apps/src/http/express-adapter.tshttps.Server, widen typespackages/dev/src/plugin.tspackages/apps/src/http/express-adapter.spec.tsCo-Authored-By: Claude Opus 4.6 (1M context) noreply@anthropic.com