feat: support multi-path tar/tgz archive creation in obj upload#220
Merged
Conversation
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>
0d9bb4b to
e40583b
Compare
dines-rl
approved these changes
Apr 24, 2026
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>
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
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.
obj upload <path>toobj upload <paths...>to accept multiple files/directories in a single command--content-type tar|tgzis 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.nanotar(~1KB, from unjs) instead of thetarpackageDecision matrix
tar/tgztar/tgztar/tgzTest plan
rli obj upload ./file.txt --name test --content-type text— single file, existing behaviorrli obj upload ./dir --name test --content-type tar— single dir, creates tarrli obj upload ./dir --name test --content-type tgz— single dir, creates tgzrli obj upload ./a.txt ./b.txt ./dir --name test --content-type tar— multi-path tarrli obj upload ./a.txt ./b.txt --name test— ERRORrli obj upload ./dir --name test— ERRORrli obj upload ./file.tar --name test --content-type tar— single file, uploads as-ispnpm test— 14 tests pass🤖 Generated with Claude Code