feat(artifacts): prefer server-assembled bundle delivery for downloads#93
feat(artifacts): prefer server-assembled bundle delivery for downloads#93riglar wants to merge 3 commits into
Conversation
Artifact and HTML-report downloads now try a bundle-delivery path first: the
API returns a signed manifest plus a URL to a delivery service that streams
the ZIP straight from storage, so large downloads don't flow through the API.
The client relays the signed { manifest, sig } to that URL (no auth header —
the manifest is the signed token) and streams the result to disk.
Falls back to the existing inline download automatically when bundle delivery
isn't offered (501) or anything about the path doesn't pan out, so behaviour
is unchanged on older deployments. Applies to artifacts and the HTML report;
junit and allure keep using their existing endpoints.
Adds a shared tryBundleDownload helper in the API gateway and unit tests
covering the bundle path, the no-auth relay, the 501 fallback, and the HTML
report.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Code reviewNo issues found. Checked for bugs and CLAUDE.md compliance. |
| await this.streamResponseToFile(zipRes, destinationPath, operation); | ||
| return true; |
There was a problem hiding this comment.
This final streamResponseToFile call is the one failure mode in tryBundleDownload that isn't converted to return false. Every other step (fetch manifest, json(), missing fields, bundle POST, !zipRes.ok) is guarded and falls back to inline. But streamResponseToFile throws on a mid-stream failure — a dropped/truncated connection during the ZIP download, or a null body on an otherwise-OK response (it explicitly throws when res.body === null, and pipeline propagates source-stream errors):
dcd-cli/src/gateways/api-gateway.ts
Lines 246 to 250 in 8e6e796
Because both call sites invoke this as an if (await this.tryBundleDownload(...)) return; condition that sits before the inline try block, a throw here escapes past the inline /download fallback entirely — contradicting the PR's stated intent ("falls back silently to inline /download on 501 or any bundle-path failure") and leaving a partial file on disk. Wrapping the stream so it returns false restores the fallback (the inline path re-opens the destination with flags: 'w', so the partial file is truncated on retry):
| await this.streamResponseToFile(zipRes, destinationPath, operation); | |
| return true; | |
| try { | |
| await this.streamResponseToFile(zipRes, destinationPath, operation); | |
| } catch { | |
| return false; | |
| } | |
| return true; |
streamResponseToFile threw past both tryBundleDownload call sites on a mid-stream failure or null body, escaping the inline fallback and leaving a partial file. Wrap it to return false like every other failure path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
What
Artifact and HTML-report downloads now try a bundle-delivery path before the inline download:
GET /results/{uploadId}/artifacts-bundle?results=…or…/report-bundle).{ manifest, sig }to that URL — no auth header, since the manifest is itself the signed access token — and streams the ZIP to disk.Large downloads stream directly from storage instead of flowing through the API.
Compatibility
Fully backwards-compatible, no version negotiation:
501→ the CLI falls back to the existing inline download.How
tryBundleDownloadhelper in the API gateway;downloadArtifactsZipand the HTML branch ofdownloadReportGenericcall it first and fall back onfalse.Testing
pnpm typecheck+pnpm lintclean.pnpm test— 171 passing, including new coverage: streams from the bundle URL and skips the inline endpoint, sends no auth header to the (pre-signed) bundle URL, falls back to inline on501, and uses bundle delivery for the HTML report. Existing inline-download tests unchanged.🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.