Skip to content

Commit e0dfcb2

Browse files
committed
fix(platform): exit when a production zone is misconfigured
Throwing from register() never stopped the process: Next logs 'Failed to prepare server' and keeps listening, answering 500 to everything, so the zone's guard looked alive to anything supervising it. It now logs and exits 1. Reproduced on a tiny production build of the same Next version, which stayed up before this and exits 1 after it. The live spec already asserted the exit, which is how this surfaced.
1 parent 930478a commit e0dfcb2

3 files changed

Lines changed: 22 additions & 8 deletions

File tree

‎apps/sim/platform/checks/live/zone-boot/refuses-to-start.spec.ts‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ import { expect, test } from '@playwright/test'
33

44
/**
55
* Starts the zone's real production build as its own process, the way a container does, under the
6-
* base path it was built with. `instrumentation.ts` runs `assertDeployableMode`, and Next exits the
7-
* server when that throws. A second zone, because the first must keep serving the suite before this.
6+
* base path it was built with. `instrumentation.ts` runs `assertDeployableMode` and exits when that
7+
* throws. A second zone, because the first must keep serving the suite before this. The message
8+
* arrives through the logger, whose JSON output escapes its quotes.
89
*/
910
const PORT = 3035
1011
const ORIGIN = `http://127.0.0.1:${PORT}`
@@ -56,7 +57,7 @@ test.afterEach(async () => {
5657
test('a production zone with no API origin refuses to start', async () => {
5758
const { exitCode, output } = startZone({})
5859
expect(await exitCode).toBe(1)
59-
expect(output.join('')).toContain('its data mode is "none"')
60+
expect(output.join('')).toMatch(/its data mode is \\?"none\\?"/)
6061
})
6162

6263
test('a production zone on fixtures refuses to start', async () => {
@@ -65,7 +66,7 @@ test('a production zone on fixtures refuses to start', async () => {
6566
SIM_PLATFORM_FIXTURES: '1',
6667
})
6768
expect(await exitCode).toBe(1)
68-
expect(output.join('')).toContain('its data mode is "fixture"')
69+
expect(output.join('')).toMatch(/its data mode is \\?"fixture\\?"/)
6970
})
7071

7172
test('a production zone with an API origin starts under the same command', async () => {

‎apps/sim/platform/docs/org-parity.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -376,7 +376,7 @@ The shell lane's rows, one per row of its audit. `shell.spec` is `checks/live/sh
376376
| Zone not built, flag on: `/o` renders legacy with no redirect, `/org` is absent | `next.config.ts` | Done | "/org is absent", in the `live (flag-off)` job |
377377
| Zone down or answering 5xx with the flag on: a legacy URL renders on the main app, a Platform page returns there, an open tab's client navigation is sent back, sign-in lands there | `proxy.ts`, `lib/platform/zone-availability.ts` | Done | "with the zone unavailable", run with the process gone and with every page answering 503; `proxy.test.ts` for each way a zone can fail. A deep link comes back to the organization's home, not to its own page |
378378
| A zone that recovers takes its organizations back | `lib/platform/zone-availability.ts` | Done | "a zone that comes back takes its organizations back" |
379-
| Per-organization rollout: `platform-org-ui` reaches a targeted organization by id and no other, on the same deployment, through the real AppConfig client against a fixture of its service | `lib/platform/rollout.ts`, `lib/core/config/feature-flags.ts` | Done | "a targeted organization is handed to the Platform", "an organization outside the rollout stays on /o". No real AppConfig deployment has been read |
379+
| Per-organization rollout: `platform-org-ui` reaches a targeted organization by id and no other, on the same deployment, through the real AppConfig client against a fixture of its service | `lib/platform/rollout.ts`, `lib/core/config/feature-flags.ts` | Done | "a targeted organization is handed to the Platform", "an organization outside the rollout stays on /o". No real AppConfig deployment has been read. Each route bundle polls on its own clock, so a change can reach the surface API and `/o` up to a poll apart, and until then `/o` can hand off to a zone that answers 404 |
380380
| A rollback under an open tab: a client navigation keeps working, because the rollout gate lives in a layout that does not re-render, and a reload's 404 offers a way off the zone | `live-shell.tsx`, `lib/data/landing.ts` | Done | "a rollback under an open tab"; `landing.test.ts` for the root's decision. The 404's home button used to loop back to it |
381381
| Self-hosted, no zone, no flag: boots, `/o` is served by the main app, `/org` is absent | `next.config.ts`, `lib/platform/rollout.ts` | Done | "a self-hosted deployment with no zone boots", "a self-hosted organization is served by the main app", "a self-hosted deployment has no /org", in the `live (flag-off)` job |
382-
| A production zone behind a base path with no API origin, or on fixtures, refuses to start | `instrumentation.ts`, `lib/data/mode.ts` | Done | "a production zone with no API origin refuses to start", "a production zone on fixtures refuses to start", "a production zone with an API origin starts", each a real `next start` of the build |
382+
| A production zone behind a base path with no API origin, or on fixtures, refuses to start | `instrumentation.ts`, `lib/data/mode.ts` | Done | "a production zone with no API origin refuses to start", "a production zone on fixtures refuses to start", "a production zone with an API origin starts", each a real `next start` of the build. `instrumentation.ts` exits, because Next only logs a failed `register()` and answers 500 |
Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,19 @@
1-
/** Runs when the server starts, so a misconfigured zone never takes a request. */
1+
/**
2+
* Runs when the server starts, so a misconfigured zone never takes a request. Throwing is not enough:
3+
* Next logs a failed `register()` and keeps the process up answering 500, which a supervisor reads as
4+
* running. Only exiting fails the deployment.
5+
*/
26
export async function register() {
37
if (process.env.NEXT_RUNTIME !== 'nodejs') return
48
const { assertDeployableMode } = await import('@/platform/lib/data/mode')
5-
assertDeployableMode()
9+
try {
10+
assertDeployableMode()
11+
} catch (error) {
12+
const [{ createLogger }, { getErrorMessage }] = await Promise.all([
13+
import('@sim/logger'),
14+
import('@sim/utils/errors'),
15+
])
16+
createLogger('PlatformZone').error('Refusing to start', { error: getErrorMessage(error) })
17+
process.exit(1)
18+
}
619
}

0 commit comments

Comments
 (0)