Skip to content

Commit 5802901

Browse files
Brooooooklynclaude
andauthored
fix(platform): stop before the traffic pause when an upgrade cannot finish (#437)
## Problem An operator upgraded a self-hosted platform from 0.27 to 1.1. Every upgrade attempt failed **after** Void paused application traffic, and every retry failed the same way. ``` void platform upgrade close traffic gate ──▶ application traffic paused upload platform Worker (cf CLI, no terminal) └─ Worker was changed in the dashboard / with the API (for example a hand-set EMAIL_FROM) cf strict mode: "Aborting the upload operation because of conflicts. ... remove the `--strict` flag" ✗ run fails, gate stays closed ──▶ traffic stays paused retry ──▶ same abort (--yes does not answer cf's prompt) ``` Causes, checked against cf 1.0.0-beta.17 and live throwaway Workers: | Cause | Detail | | --- | --- | | cf strict mode is hard-coded for `deploy` and `versions upload` | With `last_deployed_from` = `api`, or `dash` with a config diff that is not add-only, cf asks. Without a TTY it aborts. No flag or env var skips it. | | Void runs cf with `stdio: ['ignore', 'pipe', 'pipe']` | So cf can never ask. | | The check happens after the traffic gate | The installer closes the gate before the first Worker upload. | | Operators had to edit Workers by hand | The installer never set `EMAIL_FROM`, and the API README told operators to set it in the dashboard. | | Void's own trigger-record check | An API-edited Worker's active version has no Void record, so the deploy refused it ("predates safe trigger snapshots"), also after the gate. | | Email permissions | The runtime token needs Email Routing permissions. Nothing checked them before the pause, and the late error did not name a permission. | ## What this PR changes ``` upgrade | repair | enable | rollback | install --resume │ ├─ find platform Workers the run uploads that need approval: (no Worker change yet) │ last_deployed_from dash | api │ or active version without a Void deployment record │ or (interrupted cutover) the version the run restores has no record │ unreadable (after 2 retries) or no single version at 100% ──▶ STOP │ ├─ email on? check runtime token: Zone Read, Zone Settings, (no Worker change yet) │ Email Routing Rules, Email Routing Addresses (read-only GETs) │ ├─ none ────────────────▶ normal run (unchanged) ├─ no TTY or --yes ─────▶ STOP "No platform Worker changed. This command did not pause │ application traffic. ... Run this command once in a terminal" └─ TTY ─▶ note + "Replace the configuration of these Workers?" (default No) ├─ No ──────▶ STOP (same wording) └─ Yes ▼ installPlatform ├─ provision resources, prepare database ├─ for dash/api Workers: pause spinner ─▶ cf versions upload, stdio inherited │ (cf shows its diff and asks; version is NOT deployed) ─▶ re-read marker │ └─ refused ─▶ STOP "No deployment changed. This command did not pause application traffic." ├─ traffic gate └─ deploy (agreed Workers may have an active version without a Void record) ``` Other changes: - **Email preflight.** When platform email is on, the runtime token's Email Routing permissions are checked before the pause. A denial names each missing permission and keeps the saved token (edit its permissions; the value stays the same). A `401` on the token saved on this machine warns and continues, because the platform may use a newer token. A late shared-email setup failure now names the likely permission. - **Default sender.** Platform mail (invitations, welcome, deploy failures, error alerts) sends from `<display name> <noreply@<EMAIL_SENDER_DOMAIN>>`. An explicit `EMAIL_FROM` still wins. Void Cloud's hosted sender does not change. - **cf strict-abort text.** It no longer says "Wrangler" or "remove the `--strict` flag". It names an outside change only for the marker cases. A remote secret with the same name as a Void value says which secret to delete. A workflow owned by another Worker says to delete or rename it. - **Docs.** Maintenance (upgrade, changes made outside Void, a paused-traffic note), credentials (email permissions before an upgrade), email (default sender), CI, CLI reference, and the API README no longer tells operators to set `EMAIL_FROM` / `AUTH_REDIRECT_URI` by hand. ## Decisions | Topic | Decision | | --- | --- | | Override without asking | No new flag. `--yes` is not consent to replace outside changes. cf has no non-interactive override, so CI stops and names the command to run once in a terminal. | | Where cf asks | Inside `installPlatform`, after resources and the database are ready, before the gate. An upgrade that adds a resource (for example the deployment queue) needs it to exist before cf can upload a valid version. | | What cf uploads | An undeployed version built from the run's own config. The real deploy follows in the same run. | | `dash` markers | Always flagged, also when cf would accept an add-only diff. Void owns these Workers, so it shows the change and asks, and never replaces it without approval. | | Failed marker or version read | Retry twice on 429, 5xx or a network error, then stop before any Worker change. Nothing has changed at that point, so a stop costs only a retry; a guess could pause traffic and then fail. A 404 means the Worker does not exist, unless its marker shows an outside change. | | Email check scope | Only when platform email is on and the installation is not staying disabled. A platform without email makes no new request. | | Saved token on this machine | `403` stops with text that names the saved token and the env var. `401` warns and continues. | | Sender override | No new installer option. `EMAIL_FROM` set in the dashboard is a change outside Void, and the next upgrade asks before it replaces it. | ## Behavior changes to note in the release - `void platform upgrade|repair|enable|rollback` and `install --resume` stop before they change a platform Worker or pause traffic when a platform Worker was changed in the Cloudflare dashboard or API. In a terminal they ask first. In CI, run the command once in a terminal. - These commands, and their `--plan`, also stop when Cloudflare cannot answer for a platform Worker after 2 retries, or when a platform Worker has no single version at 100% of traffic. - With platform email on, these commands and a fresh install check the runtime token's Email Routing permissions before they pause traffic. This includes Account → Email Routing Addresses: main's docs already listed it, but a repeat upgrade did not use it, so a token without it now stops the upgrade. - Self-hosted platform mail sends from `noreply@<mail domain>` by default. `EMAIL_FROM` is optional. A platform that had email on but no `EMAIL_FROM` starts to send mail after the upgrade: invitations report "sent", and the first error alert per project can cover all errors since its alerts started to fail. ## Known limits - cf's strict mode also asks about a remote secret with the same name as a Void value, and about a workflow that another Worker owns. These do not change `last_deployed_from`, so Void cannot see them before the gate. The error after the gate now says what to delete. - A `dash` Worker whose diff is add-only now stops CI, where main uploaded it without asking. - The email check sends read requests only. A token with Read instead of Edit access to Email Routing, or without Email Sending, passes it and fails after the pause; the error then names the permission. A safe write probe does not exist (an address POST sends mail). - The database lifecycle lock can add a column to its own table before the check runs. So the stop text says "No platform Worker changed", not "Nothing changed". - A run that reuses the active version (no upload) stops CI when the marker is `dash` or `api`, for example a repair after a dashboard rollback to Void's own version. Main did not run cf on that path, so it passed. - A platform release that adds a Durable Object class with a binding cannot upload to an existing platform Worker (same on main): an undeployed version cannot bind a class that Cloudflare has not created yet. No release does this yet. When a Worker was changed outside Void, the review upload then fails before the pause, so traffic is not paused. The deploy path needs its own change. - `install --resume` and `--plan` cannot read the database before the run. During the one-time change to configurable sign-in, they can ask once more than needed. They never stop after the pause. - Not checked live: a real dashboard edit (`dash`), and whether Void's custom-domain attach changes the marker (the test account has no zone). cf aborts on `api` with the same rule when Void uploads, so an `api` marker adds a new stop only on the reuse path above. ## Testing | Check | Result | | --- | --- | | `vp run test` (changed-file selection) | 550 files passed, 1 skipped; 11371 tests passed (before the last fix; focused platform files after it: 493 passed) | | Focused files (outside changes, installer, cutover, management, lifecycle, setup flow, errors, provision, operation process, api email) | all pass; each new test fails without its change | | `vp run check`, `vp lint`, `vp fmt --check`, `vp run build:core` | pass | | Live, throwaway Workers on the e2e account (all deleted) | cf under a PTY: answer No keeps `api`; Yes uploads an undeployed version and resets the marker to `cf_cli`; consent then deploy succeeds; API settings PATCH removes the Void record; Void's own operations keep `cf_cli` | | Live, read-only email probes | all four GETs answer 200 on a mail zone; unknown zone and invalid token are reported as expected | | Real cf 1.0.0-beta.17 against a mock API | marker, secret and workflow aborts give the new messages | | PTY with expect | Ctrl-C at the cf prompt ends the run with no gate write and no leftover child | 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_0123r5HnanmW2VUrZ7VWDufH --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent 134a2ee commit 5802901

32 files changed

Lines changed: 3748 additions & 74 deletions

‎docs/guide/email/sending.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ Check both failure shapes: `result.error` reports a request failure, while `resu
3434

3535
## Setup {#setup}
3636

37-
On a platform with email enabled, deploy without additional email configuration. Ask your administrator for its shared mail domain. Administrators [enable email during installation or upgrade](/guide/platform/installation/credentials#runtime-token-permissions).
37+
On a platform with email enabled, deploy without additional email configuration. Ask your administrator for its shared mail domain. Administrators [enable email during installation or upgrade](/guide/platform/installation/credentials#enable-email).
3838

3939
Each project on an email-enabled platform has:
4040

‎docs/guide/platform/administration/email.md‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ reconciliation verifies delivery afterward; a deleting project stays closed
2727
and its deletion can be retried. See [operator email commands](../../../reference/cli/platform-email.md#operator-email)
2828
for the recovery requirements.
2929

30+
### Mail that the platform sends {#platform-sender}
31+
32+
The platform sends its own mail, for example invitations, welcome mail, failed-deploy notices, and error alerts. This mail comes from `noreply@<mail domain>`, and the sender name is the platform's display name. Void sets this sender, so you do not need to configure one. Do not set a different sender on the platform Workers in the Cloudflare dashboard. That change stops the next `upgrade`, `repair`, `enable`, or `rollback` until you let Void replace it. See [Platform Workers changed outside Void](/guide/platform/installation/maintenance#workers-changed-outside-void).
33+
34+
Mail to an address that is not verified needs Cloudflare Email Sending onboarding and [Workers Paid](https://dash.cloudflare.com/?to=/:account/workers/plans).
35+
3036
### Deciding who mail may reach
3137

3238
By default, mail from the shared sender reaches only the recipients each project has verified. For a team whose applications mainly mail colleagues, widen that once for the whole installation:
@@ -114,7 +120,7 @@ Use the apex of the selected Cloudflare zone. Platform-managed custom inbound em
114120
void platform email domain-add other.example --project hr-portal --token-stdin --yes < token.txt
115121
```
116122

117-
Project owners see administrator-registered domains in `void email domain list` and `status`; `status` names the `void platform email` command for any step they cannot take themselves, and `sync` and `remove` point them at `domain-sync` and `domain-remove`. A domain an owner registered before you switched to `admin` stays theirs to renew, sync and remove. The runtime token needs the email permissions listed in the [self-hosting guide](/guide/platform/installation/credentials#runtime-token-permissions) for this to work.
123+
Project owners see administrator-registered domains in `void email domain list` and `status`; `status` names the `void platform email` command for any step they cannot take themselves, and `sync` and `remove` point them at `domain-sync` and `domain-remove`. A domain an owner registered before you switched to `admin` stays theirs to renew, sync and remove. The runtime token needs the [email permissions](/guide/platform/installation/credentials#enable-email) for this to work.
118124

119125
A permission refusal leaves setup blocked. After correcting it, run `domain-sync`
120126
to resume that same setup operation; repeat `domain-rotate-secret` to resume a

‎docs/guide/platform/installation/ci.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ void platform install \
5151

5252
Mutations require `--yes` in CI; a read-only `--plan` does not. Custom runtimes also require `--runtime <directory>`. For a new installation, run `--plan` with the same account, name, and domain options as the unattended install. If you use `--auth-config`, pass the same file to both commands. Register `<platform-api-url>/auth/callback` when creating a login provider's OAuth or OIDC client manually; automatic Access setup manages its own application. A custom API hostname is optional.
5353

54+
In CI, `upgrade`, `repair`, `enable`, `rollback`, and `install --resume` stop before they change a platform Worker or pause traffic when a platform Worker was changed in the Cloudflare dashboard or with the Cloudflare API, or has an active version without a Void deployment record. See [Platform Workers changed outside Void](/guide/platform/installation/maintenance#workers-changed-outside-void).
55+
5456
:::
5557

5658
## Customize the Platform

‎docs/guide/platform/installation/credentials.md‎

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,21 +68,39 @@ Void checks access before provisioning. If it reports a missing permission, upda
6868

6969
## Enable Email {#enable-email}
7070

71-
Choose a shared sender domain and its Cloudflare zone ID. The mail zone may differ from the application zone, but it must belong to the platform's Cloudflare account. To enable email on an existing installation, run:
71+
Choose a shared sender domain and its Cloudflare zone ID. The mail zone may differ from the application zone, but it must belong to the platform's Cloudflare account.
72+
73+
Before you enable email, enable [Cloudflare Email Routing](https://developers.cloudflare.com/email-service/get-started/route-emails/) for the exact shared sender domain and confirm that its MX records point to Cloudflare. For a subdomain, add that name under the zone's **Email Routing → Settings → Subdomains**. Cloudflare adds the required MX and SPF records. The installer's shared-mail bootstrap verifies those records; it does not add them. Keep existing mail-provider records on other domain names in place.
74+
75+
Enabling email lets administrators [register email domains for projects](/guide/platform/administration/email#registering-email-domains-for-projects) and lets projects register destination addresses through the runtime token. The runtime token needs these permissions:
76+
77+
| Scope | Permission | Access |
78+
| ------- | ----------------------- | ------ |
79+
| Account | Email Routing Addresses | Edit |
80+
| Account | Email Sending | Edit |
81+
| Zone | Zone | Read |
82+
| Zone | Zone Settings | Edit |
83+
| Zone | Email Routing Rules | Edit |
84+
85+
The zone permissions must include each zone that will carry mail, including the mail zone. Email Sending onboarding for arbitrary recipients needs Workers Paid.
86+
87+
For a new installation, set `VOID_EMAIL_SENDER_DOMAIN` and `VOID_EMAIL_SHARED_ZONE_ID` before `void platform install`. The runtime-token link then preselects these permissions.
88+
89+
For an existing installation, add the permissions to its runtime token first. Edit the token in the Cloudflare dashboard. The token value stays the same. Or set `VOID_PLATFORM_RUNTIME_CLOUDFLARE_API_TOKEN` to a new token that has all runtime permissions. Then run:
7290

7391
```sh
7492
VOID_EMAIL_SENDER_DOMAIN=mail.example.com \
7593
VOID_EMAIL_SHARED_ZONE_ID=your-zone-id \
7694
void platform upgrade your-installation-id
7795
```
7896

79-
Set the same two variables before `void platform install` to enable email during a new installation. Void records the pair for later upgrades; an ordinary upgrade cannot replace it.
97+
Void records the domain and zone for later upgrades; an ordinary upgrade cannot replace them.
8098

81-
Before installing, enable [Cloudflare Email Routing](https://developers.cloudflare.com/email-service/get-started/route-emails/) for the exact shared sender domain and confirm that its MX records point to Cloudflare. For a subdomain, add that name under the zone's **Email Routing → Settings → Subdomains**. Cloudflare adds the required MX and SPF records. The installer's shared-mail bootstrap verifies those records; it does not add them. Keep existing mail-provider records on other domain names in place.
99+
Before the CLI changes anything or pauses traffic, it checks that the runtime token can read the mail zone and its Email Routing settings, rules, and addresses. If Cloudflare denies a request, the command stops and names each missing permission. Void keeps a saved runtime token, so you can add the permission to the same token and run the command again. The check cannot confirm Edit access or Email Sending, so select those permissions as the table shows.
82100

83-
Enabling email lets administrators [register email domains for projects](/guide/platform/administration/email#registering-email-domains-for-projects) and lets projects register destination addresses through the runtime token. That needs **Email Routing Addresses: Edit** and **Email Sending: Edit** on the account, plus **Zone: Read**, **Zone Settings: Edit** and **Email Routing Rules: Edit** on the zones that will carry mail. The runtime-token link preselects them when email is enabled. Email Sending onboarding for arbitrary recipients needs Workers Paid.
101+
Without `VOID_PLATFORM_RUNTIME_CLOUDFLARE_API_TOKEN`, `upgrade`, `repair`, `enable`, and `rollback` check the runtime token saved on this machine. The platform can use a newer token, for example one set from another machine. If Cloudflare does not accept the saved token, the CLI shows a warning and continues without the check. To check the token that the platform uses, set `VOID_PLATFORM_RUNTIME_CLOUDFLARE_API_TOKEN` to it.
84102

85-
The installer deploys the email gateway, prepares the shared mail route, and verifies inbound readiness before opening platform traffic. If setup fails, correct the reported permission, exact-domain MX records, or routing conflict, then rerun the same install or upgrade command. A fresh install resumes with `void platform install --resume --name <installation-id>`; it continues the recorded email operation after the configuration is corrected.
103+
The installer deploys the email gateway, prepares the shared mail route, and verifies inbound readiness before opening platform traffic. If setup fails, fix the problem that the error names, for example a missing permission, the exact-domain MX records, or a routing conflict. Then run the same install or upgrade command again. A fresh install resumes with `void platform install --resume --name <installation-id>`; it continues the recorded email operation after the configuration is corrected.
86104

87105
## R2 Upload Credentials
88106

‎docs/guide/platform/installation/maintenance.md‎

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,13 @@ An upgrade validates the runtime, applies supported pending migrations, deploys
113113

114114
Before starting, Void checks access to the installation's Cloudflare account, Workers, and existing R2 buckets. If credentials are rejected, set `CLOUDFLARE_API_TOKEN` to a management token scoped to that account and retry. This overrides the installation's saved management token.
115115

116-
::: details Migration safety, credential rotation, and interrupted upgrades
116+
Some upgrades pause application traffic during the cutover to the new version. Traffic resumes after the new version passes its health checks. If an upgrade or another lifecycle command fails after it paused traffic, traffic stays paused until a later run succeeds. To resume:
117117

118-
Some upgrades briefly put the platform into maintenance mode. Traffic resumes after the new version passes health checks. If an upgrade is interrupted during maintenance, rerun the same command to complete it.
118+
1. Run `void platform status <id>` to see the saved progress.
119+
2. Fix the problem that the error names.
120+
3. Run the resume command that the CLI printed. It is the same command, with the same runtime.
121+
122+
::: details Migration safety, credential rotation, and rollback
119123

120124
To update the runtime Cloudflare token, GitHub OAuth credentials when GitHub login is enabled, or R2 credentials,
121125
provide their `VOID_PLATFORM_*` environment variables when running the upgrade.
@@ -141,6 +145,32 @@ Let running rotations finish before you roll back. If an older proxy runs next t
141145

142146
:::
143147

148+
### Platform Workers changed outside Void {#workers-changed-outside-void}
149+
150+
Void manages the platform's own Workers. Do not change them in the Cloudflare dashboard or with the Cloudflare API. To change the platform, use the Void commands on this page.
151+
152+
`upgrade`, `repair`, `enable`, `rollback`, and `install --resume` deploy the platform Workers again. Before they change a platform Worker or pause traffic, Void finds each platform Worker that:
153+
154+
- was changed in the Cloudflare dashboard or with the Cloudflare API.
155+
- has an active version without a Void deployment record. This can occur when an earlier run replaced a change made outside Void, and then stopped or failed. If that run stopped during the cutover to the new version, the command first makes the earlier versions active again. Void checks those earlier versions too. A version that an older Void release deployed can also have no record.
156+
157+
The `--plan` preview of `upgrade`, `repair`, `enable`, and `rollback` shows the same list as a warning.
158+
159+
In a terminal, Void lists these Workers and asks **Replace the configuration of these Workers?** The default answer is No.
160+
161+
- If you answer No, the command stops. No platform Worker changed, and the command did not pause traffic.
162+
- If you answer Yes, the command continues. First, Void prepares the platform resources and the database. Then, before it pauses traffic or deploys a Worker, the Cloudflare CLI asks you again for each Worker changed in the dashboard or with the API. For a dashboard change, it shows the differences. Void uploads a new version of each of these Workers and does not deploy it yet. For a Worker that has an active version without a Void deployment record, the Cloudflare CLI does not ask again.
163+
164+
If you refuse in the Cloudflare CLI, the command stops. Void does not undo the resource and database changes. No deployment changed, and the command did not pause traffic.
165+
166+
The replacement removes values that Void does not set, for example an `EMAIL_FROM` variable that you added. It also resets values that Void sets, for example `AUTH_REDIRECT_URI`.
167+
168+
Without a terminal, for example in CI, or with `--yes`, the command stops before it changes a platform Worker, and it does not pause traffic. Run the command that Void prints once in a terminal, and answer the questions there. After that run completes, the command works in CI again.
169+
170+
If Void cannot read a platform Worker, the command also stops before it changes a platform Worker or pauses traffic. Run the command again. If a platform Worker has no single version that gets 100% of traffic, for example after a gradual deployment, deploy one version of it to 100% of traffic in the Cloudflare dashboard. Then run the command once in a terminal, so that you can approve the dashboard change.
171+
172+
If an earlier run failed after it paused traffic, traffic stays paused after these stops. To resume traffic, see [Upgrade the platform](#upgrade-the-platform).
173+
144174
### Follow upgrade and rollback progress
145175

146176
Watching progress is optional: a running upgrade or rollback proceeds through

‎docs/guide/platform/installation/setup.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,6 @@ You can also rerun `void platform install` and choose **Continue setup** for an
260260

261261
Resume restores your selected Hyperdrive. If that selection is missing from the saved checkpoint, add `--hyperdrive <existing-id>` when resuming, using the same configuration you originally selected.
262262

263-
If Cloudflare rejects a saved runtime token during continued credential setup, Void opens the token page and asks for a replacement in the same run. Network or service failures do not discard saved tokens. Tokens supplied through the environment must be corrected there instead.
263+
If Cloudflare rejects a saved runtime token during continued credential setup, Void opens the token page and asks for a replacement in the same run. A missing [email permission](/guide/platform/installation/credentials#enable-email) does not discard the saved token: add the permission to the same token, then resume. Network or service failures do not discard saved tokens. Tokens supplied through the environment must be corrected there instead.
264264

265265
:::

‎docs/reference/cli/platform-installation.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,8 @@ After discovery on another machine, set `VOID_PLATFORM_DATABASE_URL`. See [Manag
137137

138138
Repair and upgrade preserve disabled state. New, resumed, and previously disabled installations block user traffic until all target Workers pass verification; the installer's health probes can still run. Routes and custom domains remain attached.
139139

140+
If a platform Worker was changed outside Void, or has an active version without a Void deployment record, a command that deploys the platform Workers stops before it changes a platform Worker or pauses traffic, unless you agree in a terminal to replace the changes. See [Platform Workers changed outside Void](../../guide/platform/installation/maintenance.md#workers-changed-outside-void).
141+
140142
`--runtime` selects a custom platform build. Relative paths resolve from your current directory. Void verifies the build before making changes; see [Platform Development](../../guide/platform/development/runtime.md#deploying-your-runtime) for creating one.
141143

142144
Without `--runtime`, the CLI uses its packaged platform version.

‎packages/deploy-cloudflare/src/operations.ts‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ export type CloudflareOperation =
4040
assetsDirectory?: string;
4141
previewAlias?: string;
4242
noBundle?: boolean;
43+
/**
44+
* Attach the Cloudflare CLI to this terminal so that it can ask the
45+
* operator. The result then has only the exit status.
46+
*/
47+
interactive?: boolean;
4348
} & WorkerTarget)
4449
| ({ type: 'triggers.deploy'; assets?: boolean } & WorkerTarget)
4550
| ({ type: 'triggers.clear-crons' | 'triggers.clear-domains' } & WorkerTarget)

‎packages/void/src/cli/cloudflare-operation-process.ts‎

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ function runCf(
105105
command: Array<string>,
106106
cwd = process.cwd(),
107107
bounds?: { timeoutMs: number; maxOutputBytes: number },
108+
interactive = false,
108109
): Result {
109110
const env = cfEnvironment();
110111
env.CF_QUIET = '1';
@@ -116,7 +117,8 @@ function runCf(
116117
windowsHide: true,
117118
maxBuffer: bounds?.maxOutputBytes ?? 32 * 1024 * 1024,
118119
...(bounds ? { timeout: bounds.timeoutMs } : {}),
119-
stdio: ['ignore', 'pipe', 'pipe'],
120+
// An interactive command shares this process's terminal, so cf can ask there.
121+
stdio: interactive ? 'inherit' : ['ignore', 'pipe', 'pipe'],
120122
});
121123
const stderr = [
122124
child.stderr || '',
@@ -1060,9 +1062,12 @@ async function workerChanges(): Promise<Result> {
10601062
...(request.secretsFilePath ? ['--secrets-file', request.secretsFilePath] : []),
10611063
],
10621064
directory,
1065+
undefined,
1066+
request.interactive === true,
10631067
),
10641068
);
1065-
if (called.status !== 0) {
1069+
// cf wrote an interactive upload to the terminal, so only its exit status is known.
1070+
if (called.status !== 0 || request.interactive === true) {
10661071
return called;
10671072
}
10681073
// cf renders uploads for the terminal; decode once at the process boundary.
@@ -1272,9 +1277,15 @@ async function main(): Promise<Result> {
12721277
return resources();
12731278
}
12741279

1280+
// Login, tail and an interactive upload write to the terminal, not a result envelope.
1281+
const terminalOperation =
1282+
operation?.type === 'auth.login' ||
1283+
operation?.type === 'tail' ||
1284+
((operation?.type === 'versions.upload' || operation?.type === 'worker.deploy') &&
1285+
operation.interactive === true);
12751286
try {
12761287
const output = await main();
1277-
if (operation.type !== 'auth.login' && operation.type !== 'tail') {
1288+
if (!terminalOperation) {
12781289
let data = output.data;
12791290
let humanOutput = '';
12801291
if (data === undefined && output.stdout.trim()) {
@@ -1303,7 +1314,7 @@ try {
13031314
process.exitCode = output.status;
13041315
} catch (error) {
13051316
const message = error instanceof Error ? error.message : 'Cloudflare operation failed.';
1306-
if (operation?.type !== 'auth.login' && operation?.type !== 'tail') {
1317+
if (!terminalOperation) {
13071318
process.stdout.write(JSON.stringify({ operationResult: 1, errors: [{ message }] }) + '\n');
13081319
}
13091320
process.stderr.write(message);

0 commit comments

Comments
 (0)