diff --git a/src/compiler/config/test/validate-dev-server.spec.ts b/src/compiler/config/test/validate-dev-server.spec.ts index ca4cb248d84..85def6eba9e 100644 --- a/src/compiler/config/test/validate-dev-server.spec.ts +++ b/src/compiler/config/test/validate-dev-server.spec.ts @@ -276,4 +276,32 @@ describe('validateDevServer', () => { const { config } = validateConfig(inputConfig, mockLoadConfigInit()); expect(config.devServer.prerenderConfig).toBe(wwwOutputTarget.prerenderConfig); }); + + describe('pingRoute', () => { + it('should default to /ping', () => { + // Ensure the pingRoute is not set in the inputConfig so we know we're testing the + // default value added during validation + delete inputConfig.devServer.pingRoute; + const { config } = validateConfig(inputConfig, mockLoadConfigInit()); + expect(config.devServer.pingRoute).toBe('/ping'); + }); + + it('should set user defined pingRoute', () => { + inputConfig.devServer = { ...inputDevServerConfig, pingRoute: '/my-ping' }; + const { config } = validateConfig(inputConfig, mockLoadConfigInit()); + expect(config.devServer.pingRoute).toBe('/my-ping'); + }); + + it('should prefix pingRoute with a "/"', () => { + inputConfig.devServer = { ...inputDevServerConfig, pingRoute: 'my-ping' }; + const { config } = validateConfig(inputConfig, mockLoadConfigInit()); + expect(config.devServer.pingRoute).toBe('/my-ping'); + }); + + it('should clear ping route if set to null', () => { + inputConfig.devServer = { ...inputDevServerConfig, pingRoute: null }; + const { config } = validateConfig(inputConfig, mockLoadConfigInit()); + expect(config.devServer.pingRoute).toBe(null); + }); + }); }); diff --git a/src/compiler/config/validate-dev-server.ts b/src/compiler/config/validate-dev-server.ts index 14ebdea7168..d92c67967ee 100644 --- a/src/compiler/config/validate-dev-server.ts +++ b/src/compiler/config/validate-dev-server.ts @@ -29,6 +29,16 @@ export const validateDevServer = (config: d.ValidatedConfig, diagnostics: d.Diag devServer.address = devServer.address.split('/')[0]; + // Validate "ping" route option + if (devServer.pingRoute !== null) { + let pingRoute = isString(devServer.pingRoute) ? devServer.pingRoute : '/ping'; + if (!pingRoute.startsWith('/')) { + pingRoute = `/${pingRoute}`; + } + + devServer.pingRoute = pingRoute; + } + // split on `:` to get the domain and the (possibly present) port // separately. we've already sliced off the protocol (if present) above // so we can safely split on `:` here. diff --git a/src/declarations/stencil-public-compiler.ts b/src/declarations/stencil-public-compiler.ts index 3e4c2c7bbaf..d638add9dde 100644 --- a/src/declarations/stencil-public-compiler.ts +++ b/src/declarations/stencil-public-compiler.ts @@ -651,6 +651,15 @@ export interface DevServerConfig extends StencilDevServerConfig { prerenderConfig?: string; protocol?: 'http' | 'https'; srcIndexHtml?: string; + + /** + * Route to be used for the "ping" sub-route of the Stencil dev server. + * This route will return a 200 status code once the Stencil build has finished. + * Setting this to `null` will disable the ping route. + * + * Defaults to `/ping` + */ + pingRoute?: string | null; } export interface HistoryApiFallback { diff --git a/src/dev-server/request-handler.ts b/src/dev-server/request-handler.ts index 27f18c0d2a9..dc8704ecb10 100644 --- a/src/dev-server/request-handler.ts +++ b/src/dev-server/request-handler.ts @@ -26,6 +26,21 @@ export function createRequestHandler(devServerConfig: d.DevServerConfig, serverC return serverCtx.serve302(req, res); } + if (devServerConfig.pingRoute !== null && req.pathname === devServerConfig.pingRoute) { + return serverCtx + .getBuildResults() + .then((result) => { + if (!result.hasSuccessfulBuild) { + return serverCtx.serve500(incomingReq, res, 'Build not successful', 'build error'); + } + + res.writeHead(200, 'OK'); + res.write('OK'); + res.end(); + }) + .catch(() => serverCtx.serve500(incomingReq, res, 'Error getting build results', 'ping error')); + } + if (isDevClient(req.pathname) && devServerConfig.websocket) { return serveDevClient(devServerConfig, serverCtx, req, res); } diff --git a/src/dev-server/test/req-handler.spec.ts b/src/dev-server/test/req-handler.spec.ts index e679588f203..152dfe55e5c 100644 --- a/src/dev-server/test/req-handler.spec.ts +++ b/src/dev-server/test/req-handler.spec.ts @@ -439,6 +439,32 @@ describe('request-handler', () => { expect(h).toBe(`88mph`); }); }); + + describe('pingRoute', () => { + it('should return a 200 for successful build', async () => { + serverCtx.getBuildResults = () => + Promise.resolve({ hasSuccessfulBuild: true }) as Promise; + + const handler = createRequestHandler(devServerConfig, serverCtx); + + req.url = '/ping'; + + await handler(req, res); + expect(res.$statusCode).toBe(200); + }); + + it('should return a 500 for unsuccessful build', async () => { + serverCtx.getBuildResults = () => + Promise.resolve({ hasSuccessfulBuild: false }) as Promise; + + const handler = createRequestHandler(devServerConfig, serverCtx); + + req.url = '/ping'; + + await handler(req, res); + expect(res.$statusCode).toBe(500); + }); + }); }); interface TestServerResponse extends ServerResponse {