Add rwx package build to build and upload packages - #596
Draft
dan-manges wants to merge 3 commits into
Draft
Conversation
Zips the contents of a directory and uploads it to the RWX package
registry, printing the content digest returned by the server.
rwx package build [directory]
Ports the logic from rwx-cloud/packages#338, with one deliberate
difference: timestamp normalization is optional and applied inline to
the zip entry headers rather than by running `touch` over the source
tree. Passing `--timestamp YYYYMMDDHHmm` sets every entry's modification
time in the archive, so builds are reproducible for content-based
caching without mutating files on disk. Omitting it preserves each
file's real mtime.
Archive entries are written in sorted order so the same tree and
timestamp always produce identical bytes.
Co-authored-by: Dan Manges <dan@rwx.com>
Adds .rwx/integration/package-build-test.yml, which builds the real CLI and exercises `rwx package build` against the package registry, and registers it in .rwx/integration.yml so it runs on every pull request. Covers the happy path, digest reproducibility with --timestamp across mtime rewrites, mtime sensitivity without --timestamp, and that each server-side validation error is displayed to the user: missing rwx-package.yml (including the multi-line file listing), missing README.md, invalid YAML, a manifest with no name, a non-semver version, a name owned by another organization, and an unauthorized token. Also routes UploadPackage through decodeResponseJSON so 401 and 5xx responses are wrapped with the ErrUnauthenticated / ErrInternalServerError sentinels instead of classifying as `unknown` in telemetry. The unit tests now assert against error bodies captured verbatim from cloud.rwx.com. Co-authored-by: Dan Manges <dan@rwx.com>
rwx-cloud/cloud#8414 turns several unhandled package upload inputs into
400 validation responses and adds archive limits. All of them keep the
{"error": "<single string>"} contract, so the CLI displays them as-is.
Adds unit coverage for the new messages: missing description, manifest
that is not a mapping, invalid parameters, missing/unreadable file, not
a readable zip, the compressed, uncompressed and file count limits, and
the already-published error.
Manifest-scoped messages now name whichever file was uploaded, so the
integration assertions stay filename-agnostic and pass against both the
current and the updated server.
Co-authored-by: Dan Manges <dan@rwx.com>
dan-manges
force-pushed
the
dan/package-build
branch
from
August 10, 2026 19:51
7395acd to
4fe7317
Compare
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.
Implements
rwx package build, which zips a directory and uploads it to the RWX package registry, printing the content digest returned by the server.Ports the logic from rwx-cloud/packages#338.
Timestamps are optional and applied inline
The reference implementation shells out to
find "$DIRECTORY" -exec touch -t "$TIMESTAMP" {} \;before zipping. This version instead sets each entry's modification time directly in the zip header viaarchive/zip, so the source tree is never mutated.--timestampis optional. Omitting it preserves each file's real mtime.Changes
internal/api/config.goUploadPackageConfig/UploadPackageResult{Digest}internal/api/client.goClient.UploadPackage— multipartPOST /mint/api/leaves, form fieldfile; non-2xx surfaces the response bodyinternal/cli/interfaces.go,internal/mocks/api.gointernal/cli/service_package_build.goService.BuildPackage+ deterministiczipDirectorycmd/rwx/packages.gobuildsubcommand,--timestampflag,packagealias on the parentContents land at the archive root (not nested under the directory name), matching
cd dir && zip -r … .. Exec bits are preserved and symlinks are followed, matchingzipwithout-y.rwx package buildworks via apackagealias on the existingpackagesgroup, sorwx packages buildworks too. The group's existingHiddensetting was left as-is.Verification
go build ./...,go vet, andgolangci-lint run ./...are clean, and 17 new tests cover the three layers.Verified live against the API:
An earlier attempt returned
mint-leaf.yml has a leaf name owned by 'rwx-cli-smoke', but request was made under organization 'rwx', confirming the server parses the uploaded archive layout correctly.For review
rwx/packageproduces. Stable and correct, but a one-time cache miss for anything moving between the two.touch -ttreats the timestamp as local wall-clock; this parses it as UTC so digests don't vary by machine timezone. Worth confirming that's desired./mint/api/leaves/publishis deliberately out of scope.