fix: auto-detect Content-Type on upload to match aws s3 cp#97
Merged
designcode merged 1 commit intomainfrom May 8, 2026
Merged
fix: auto-detect Content-Type on upload to match aws s3 cp#97designcode merged 1 commit intomainfrom
designcode merged 1 commit intomainfrom
Conversation
Uploads via `t3 cp`, `t3 mv`, and `t3 objects put` were always landing with `Content-Type: application/octet-stream` because we never set the header. Browsers serving an HTML object then offered it as a download instead of rendering. The AWS CLI uses Python's `mimetypes.guess_type` to derive the Content-Type from the file extension; we lacked the equivalent. - Add `src/utils/mime.ts` with an inline table covering ~50 common web/dev extensions (markup, scripts, JSON, fonts, images, audio, video, archives). `getContentType()` returns `undefined` for unknown extensions so the SDK omits the header and the server default applies — same posture as `aws s3 cp` (the AWS CLI never emits a fallback `application/octet-stream`). - `cp.ts uploadFile` (local→remote): infer Content-Type from the local path. - `cp.ts copyObject` (remote→remote): always `head()` the source and propagate `headData.contentType` to the destination put. The head call was previously gated on `showProgress`. - `mv.ts moveObject` (remote→remote): propagate `headData.contentType` the same way. - `objects/put.ts`: `--content-type` still wins; otherwise infer from the file path when one is provided. Stdin uploads leave it unset. Folder markers (zero-byte puts in cp/mv/mk/touch) are unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
rbarabas
approved these changes
May 8, 2026
|
🎉 This PR is included in version 3.0.0-beta.2 🎉 The release is available on: Your semantic-release bot 📦🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Uploads via
t3 cp,t3 mv, andt3 objects putwere always landing withContent-Type: application/octet-streambecause we never set the header. Browsers serving an HTML object would then offer it as a download instead of rendering. AWS CLI uses Python'smimetypes.guess_type()to derive Content-Type from the file extension; we now do the equivalent.Approach
src/utils/mime.tswith an inline table covering ~50 common web/dev extensions (markup, scripts, JSON, fonts, images, audio, video, archives).getContentType()returnsundefinedfor unknown extensions so the SDK omits the header and the server default applies — same posture asaws s3 cp(the AWS CLI never emits a fallbackapplication/octet-streamwhen the extension is unknown).mime-typesdep to keep the bun-compiled binary lean. Coverage matches the file types people actually serve from buckets; unknowns fall through cleanly.Sites updated
cp.ts uploadFile(local→remote) — infer from local path.cp.ts copyObject(remote→remote) — alwayshead()the source and propagateheadData.contentType. The head call was previously gated onshowProgress.mv.ts moveObject(remote→remote) — propagateheadData.contentTypethe same way.objects/put.ts—--content-typestill wins; otherwise infer from the file path. Stdin uploads leave it unset.Test plan
npm run lintandnpm run format:checkpassnpm testpasses (655 unit tests, +8 from new mime suite)npm run buildsucceedst3 cp ./index.html t3://bucket/test.htmlthencurl --headconfirmsContent-Type: text/htmlt3 cp t3://bucket/test.html t3://bucket/test-copy.htmlpropagatestext/htmlt3 mv t3://bucket/test-copy.html t3://other/test.htmlpropagatestext/htmlt3 objects put bucket/foo.css ./styles.csslands astext/csst3 objects put bucket/foo.css ./styles.css --content-type application/x-customoverrides to the explicit valuecat ./styles.css | t3 objects put bucket/foo.css(stdin) lands as the server default (no Content-Type sent)t3 cp ./mystery.xyz t3://bucket/mystery.xyzlands without a Content-Type header (server storesbinary/octet-stream)🤖 Generated with Claude Code
Note
Medium Risk
Changes object upload/copy behavior by setting/propagating
Content-Typeand makinghead()unconditional for remote→remote operations, which could affect performance and object metadata outcomes.Overview
Fixes
t3 cp,t3 mv, andt3 objects putto set or preserveContent-Typeso uploaded/copied objects don’t default toapplication/octet-stream.Adds
getContentType()(extension-based MIME lookup) and uses it for local file uploads; for remote→remotecp/mv, it now alwayshead()s the source and forwardsheadData.contentTypeto the destination. Includes new unit tests for MIME inference behavior.Reviewed by Cursor Bugbot for commit fc15229. Bugbot is set up for automated code reviews on this repo. Configure here.