Skip to content

fix: auto-detect Content-Type on upload to match aws s3 cp#97

Merged
designcode merged 1 commit intomainfrom
fix/content-disposition
May 8, 2026
Merged

fix: auto-detect Content-Type on upload to match aws s3 cp#97
designcode merged 1 commit intomainfrom
fix/content-disposition

Conversation

@designcode
Copy link
Copy Markdown
Collaborator

@designcode designcode commented May 8, 2026

Summary

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 would then offer it as a download instead of rendering. AWS CLI uses Python's mimetypes.guess_type() to derive Content-Type from the file extension; we now do the equivalent.

Approach

  • New 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 when the extension is unknown).
  • Inline table chosen over a mime-types dep 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) — always head() the source and propagate headData.contentType. 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. Stdin uploads leave it unset.
  • Folder markers (zero-byte puts in cp/mv/mk/touch) are unchanged.

Test plan

  • npm run lint and npm run format:check pass
  • npm test passes (655 unit tests, +8 from new mime suite)
  • npm run build succeeds
  • t3 cp ./index.html t3://bucket/test.html then curl --head confirms Content-Type: text/html
  • t3 cp t3://bucket/test.html t3://bucket/test-copy.html propagates text/html
  • t3 mv t3://bucket/test-copy.html t3://other/test.html propagates text/html
  • t3 objects put bucket/foo.css ./styles.css lands as text/css
  • t3 objects put bucket/foo.css ./styles.css --content-type application/x-custom overrides to the explicit value
  • cat ./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.xyz lands without a Content-Type header (server stores binary/octet-stream)

🤖 Generated with Claude Code


Note

Medium Risk
Changes object upload/copy behavior by setting/propagating Content-Type and making head() unconditional for remote→remote operations, which could affect performance and object metadata outcomes.

Overview
Fixes t3 cp, t3 mv, and t3 objects put to set or preserve Content-Type so uploaded/copied objects don’t default to application/octet-stream.

Adds getContentType() (extension-based MIME lookup) and uses it for local file uploads; for remote→remote cp/mv, it now always head()s the source and forwards headData.contentType to 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.

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>
@designcode designcode merged commit f5a9e2c into main May 8, 2026
3 checks passed
@designcode designcode deleted the fix/content-disposition branch May 8, 2026 13:47
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 8, 2026

🎉 This PR is included in version 3.0.0-beta.2 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants