All guides
Delivery
How a release is packaged, published to the private bucket and streamed to an account whose plan includes the source.
Updated 2026-10-23
The release registry
A download is never addressed by a path from the request. The registry in src/config/kit-releases.ts maps a kit and a version to exactly one object key, one file name and one content type.
Releases are listed newest first. The downloads page serves the first release of the kit, and the kit page names the same version. Adding a version means adding a registry entry, packaging the archive and uploading the object.
- kitId: the identifier in the kit catalogue.
- version: the semantic version of the archive.
- artifactKey: the key inside the private R2 bucket.
- fileName: the name the browser saves the archive as.
- sizeBytes and sha256: recorded from the built archive, never guessed.
The entitlement
The capability is kit.source.download. It is derived from the plan at request time, together with the subscription state, and it is never read from the browser.
- A plan declares includesKitSource in src/config/pricing.ts.
- An active or trialing subscription on a plan that includes the source can download.
- A past due or incomplete subscription keeps access while the payment is retried.
- A canceled subscription falls back to the free plan, which does not include the source.
- A version that was already downloaded stays licensed under the licence terms, even after the subscription ends.
The bucket
The archive lives in a private R2 bucket. The Worker reads it through the KIT_BUCKET binding declared in wrangler.jsonc; no public bucket URL and no signed URL is handed to the browser.
bunx wrangler r2 bucket create aback-stack-kits
bun run kit:package
bun run kit:publish --bucket aback-stack-kitsPackaging a release
The packaging script uses git archive on HEAD, so only tracked files can enter the archive and an untracked environment file can never reach a customer. It refuses a dirty working tree unless --allow-dirty is passed, and it writes a manifest next to the archive with the commit, the size and the SHA-256.
Run it from a committed tree, record the size and the digest in the release registry, then upload the archive. Do not edit a published release in place: publish a new version so an existing customer can always identify the archive they received.
The download route
GET /api/kits/[kitId]/download resolves the session, calls the download service and maps the result. The service checks the kit, the entitlement, the release and the artifact in that order.
- 401 when there is no valid session.
- 403 when the plan does not include kit.source.download.
- 404 when the kit or the release does not exist.
- 503 when the bucket is not configured or the object is missing.
- 200 with Content-Disposition attachment, private, no-store caching and a streamed body.
Verifying a release
A download is verified on three levels before it is published.
- The clean checkout passes lint, typecheck, the test suite and the production build.
- The archive extracts into an empty directory and its bun install, checks and production build work as documented.
- The recorded SHA-256 matches the file that was uploaded, and GET /api/health reports the delivery as configured.