Skip to content

feat: support multi-path tar/tgz archive creation in obj upload#220

Merged
jason-rl merged 13 commits into
mainfrom
feature/tar-tgz-multi-path-upload
Apr 24, 2026
Merged

feat: support multi-path tar/tgz archive creation in obj upload#220
jason-rl merged 13 commits into
mainfrom
feature/tar-tgz-multi-path-upload

Conversation

@jason-rl
Copy link
Copy Markdown
Contributor

@jason-rl jason-rl commented Apr 23, 2026

Summary

If someone has a script to automatically create a tarball of files then upload the tar as an object, we can make it more convenient by doing the tar operations ourselves.

  • Multi-path upload: Change obj upload <path> to obj upload <paths...> to accept multiple files/directories in a single command
  • Automatic tar/tgz archive creation: When --content-type tar|tgz is specified with multiple paths or a single directory, the CLI creates the archive automatically before uploading. A single file with tar/tgz type uploads as-is.
  • Validation with helpful errors: Multi-path without tar/tgz type errors with guidance. Directory without tar/tgz type errors with "use --content-type tar or tgz".
  • Permission normalization: All tar entries are normalized to uid/gid 1000, mode 644 (non-executable files) or 755 (executable files and directories), regardless of source file permissions
  • mtime preservation: File modification times are preserved from the filesystem
  • nanotar over tar: Uses nanotar (~1KB, from unjs) instead of the tar package

Decision matrix

Content type Input Behavior
tar/tgz multiple paths create archive, upload
tar/tgz single directory create archive, upload
tar/tgz single file upload as-is
other multiple paths error
other single directory error
other single file existing behavior

Test plan

  • rli obj upload ./file.txt --name test --content-type text — single file, existing behavior
  • rli obj upload ./dir --name test --content-type tar — single dir, creates tar
  • rli obj upload ./dir --name test --content-type tgz — single dir, creates tgz
  • rli obj upload ./a.txt ./b.txt ./dir --name test --content-type tar — multi-path tar
  • rli obj upload ./a.txt ./b.txt --name test — ERROR
  • rli obj upload ./dir --name test — ERROR
  • rli obj upload ./file.tar --name test --content-type tar — single file, uploads as-is
  • Verify executable files get mode 755, non-executable get 644, dirs get 755
  • Verify uid/gid are 1000 for all entries
  • Verify mtime is preserved
  • pnpm test — 14 tests pass

🤖 Generated with Claude Code

jason-rl and others added 13 commits April 23, 2026 13:44
The tar package was previously only referenced in pnpm overrides.
Adding it as a direct dependency to support tar/tgz archive creation
during object uploads.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Changes the upload command from single <path> to variadic <paths...>.
When --content-type tar or tgz is specified with multiple paths or a
single directory, creates a tar/tgz archive automatically before upload.
Single file uploads preserve existing behavior.

Decision matrix:
- tar/tgz + multiple paths: create archive
- tar/tgz + single directory: create archive
- tar/tgz + single file: upload as-is
- non-tar + multiple paths: error with guidance
- non-tar + single directory: error with guidance
- non-tar + single file: existing behavior

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tests cover createTarBuffer helper and uploadObject decision matrix:
- Single file upload (existing behavior)
- Multi-file tar creation
- Directory tar/tgz creation
- Single tar file passthrough (no re-archiving)
- Error cases: multi-path without tar type, directory without tar type,
  nonexistent paths

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove unused `basename` import from upload.ts.
Update README.md with new command signature.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The tar package's tar.create() only supports writing to a file path or
stream, not returning a buffer directly. The docstring explains why we
write to a temp file and read it back.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Switch from the tar package (which requires a temp file roundtrip) to
nanotar (returns Uint8Array directly). Walk directories recursively and
normalize all entries: uid/gid 1000, mode 644 for non-executable files,
755 for executable files and directories. Preserve mtime from the
filesystem.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Skip symlinks with a warning to stderr (prevents infinite recursion from
symlink cycles) and wrap lstat/readFile in try-catch with descriptive
error messages instead of raw Node errors.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Guard against path traversal: compute common ancestor as archive root,
  strip leading ../ from entry names
- Use lstat consistently for validation (detect symlinks before archiving)
- Reject top-level symlink paths with clear error message
- Chain error causes in catch blocks for better diagnostics
- Rename singlePath boolean to isSinglePath to avoid variable shadowing
- Add mtime documentation comment (nanotar expects ms, converts internally)
- Use console.warn for symlink skip warnings during recursive collection

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Use path.sep in commonAncestor for cross-platform support
- Error consistently on symlinks (collectEntries now throws instead of warn+skip)
- Reuse pre-validated stats in createTarBuffer to avoid double lstat
- Use caret version for nanotar dependency (^0.3.0)
- Add test for symlink error during archive creation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use fileBuffer.length instead of lstat size for consistency with the
archive branch and to avoid potential bugs if fileSize is later used
for Content-Length. Sort readdir results for deterministic tar archive
creation across different OSes and filesystems.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The previous behavior silently fell back to basename(absPath) when a
relative path started with "..", which could cause name collisions if
two files from different directories shared the same basename. Now
throws an explicit error so the user knows all paths must share a
common ancestor directory.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jason-rl jason-rl force-pushed the feature/tar-tgz-multi-path-upload branch from 0d9bb4b to e40583b Compare April 24, 2026 16:19
@dines-rl dines-rl self-requested a review April 24, 2026 17:34
@jason-rl jason-rl merged commit 3528701 into main Apr 24, 2026
16 checks passed
@jason-rl jason-rl deleted the feature/tar-tgz-multi-path-upload branch April 24, 2026 17:38
tode-rl pushed a commit that referenced this pull request May 12, 2026
🤖 I have created a release *beep* *boop*
---


##
[1.17.0](v1.16.0...v1.17.0)
(2026-05-12)


### Features

* add --public flag to agent create, fix object upload --public
([#219](#219))
([6e7a8b3](6e7a8b3))
* add clipboard keybinds to detail screens
([#231](#231))
([83874ca](83874ca))
* add TUI features and fix benchmark pagination total count
([#230](#230))
([7565d45](7565d45))
* agent object picker, multi-mount support, and TUI improvements
([#217](#217))
([dbe2a5c](dbe2a5c))
* pty support ([#234](#234))
([3cfd720](3cfd720))
* smart default download path + stdin/stdout support
([#222](#222))
([419a961](419a961))
* support multi-path tar/tgz archive creation in obj upload
([#220](#220))
([3528701](3528701))


### Bug Fixes

* menu header clipping and breadcrumb hyperlink
([#221](#221))
([3ef6271](3ef6271))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants