fj release download/upload/view lack scripting affordances (--pattern, --clobber, implicit latest) #122

Open
opened 2026-06-11 00:47:52 +00:00 by stephen · 1 comment
Owner

Observation

fj release covers the release lifecycle but its asset and lookup verbs lack the affordances that make releases scriptable: download has no glob/--clobber, upload has no --clobber, and nothing resolves "the latest release" without a literal tag.

Reproduced:

$ fj release download --help
Usage: fj release download [OPTIONS] <TAG>
  -n, --name <NAME>              Asset name. If omitted, all assets are downloaded
  -o, --output-dir <OUTPUT_DIR>  Output directory (default: cwd)

$ fj release upload --help
Usage: fj release upload [OPTIONS] <TAG> [FILES]...
  (no --clobber)

$ fj release view --help
Usage: fj release view [OPTIONS] <TAG>     # TAG is required

Three concrete gaps, all visible in src/cli/release.rs:

  1. Download is exact-match only, no glob. DownloadArgs (src/cli/release.rs:139-151) has -n/--name, and the handler filters with &a.name != filter (src/cli/release.rs:395-399), a string equality. gh release download takes --pattern '*.tar.gz'. To grab "all the linux assets" from fj you must download everything or loop one exact name at a time.

  2. No --clobber on download or upload; re-running is unsafe/ambiguous. Download unconditionally std::fs::writes over whatever is at the destination (src/cli/release.rs:410), so there is no "skip if present" and no explicit "overwrite" gate. Upload (upload_one, src/cli/release.rs:365-385) just POSTs the asset; uploading a name that already exists on the release has no clobber path, so a re-run of a publish script (e.g. after a partial failure) either errors or duplicates the asset name. gh release upload --clobber exists precisely to make republish idempotent.

  3. "Latest" is not addressable. Both view (ViewArgs, src/cli/release.rs:55-67) and download (src/cli/release.rs:139) require a positional <TAG> and call get_by_tag (src/cli/release.rs:226, :390). gh release view / gh release download with no tag operate on the latest release. A CI step that wants "download the assets from the most recent release" must first fj release list --json | jq -r '.[0].tag_name' and thread that back in.

Why it matters

These verbs are the install/publish surface, the part of fj that lands inside CI and bootstrap scripts:

  • A self-update or bootstrap script wants fj release download --pattern 'fj-*-linux-x86_64.tar.gz' from the latest release. Today it cannot express either half: no pattern, and no implicit latest.
  • A release pipeline that uploads built artifacts wants to be safely re-runnable. Without --clobber, a retried job fights the assets the previous attempt already uploaded.
  • Exact-name download forces callers to hardcode full asset filenames (which usually embed a version), so the script breaks on the next release unless it first scrapes the asset list back out.

fj release already gets the hard parts right (draft/prerelease, --asset on create, path-traversal-safe asset names via safe_asset_dest). The missing pieces are small, well-scoped flags that turn a manual tool into a scriptable one.

Possible directions (sketches)

  • (sketch) Add --pattern <glob> to fj release download (repeatable, OR-combined), matched against a.name with a glob crate, alongside the existing exact -n/--name. Mirror gh release download --pattern.
  • (sketch) Add --clobber to download (overwrite existing files; without it, skip-with-note instead of silently overwriting) and to upload (delete-then-reupload, or PATCH, an existing same-named asset). Makes both re-runnable.
  • (sketch) Make <TAG> optional on view and download: when omitted, resolve the latest published release (api::release::list(..., 1) first item, skipping drafts/prereleases unless asked). Optionally a --latest flag for explicitness.
  • (sketch, smaller) fj release list could also grow --exclude-drafts / --exclude-pre-releases to match gh and to make "latest stable" trivially selectable.
## Observation `fj release` covers the release lifecycle but its asset and lookup verbs lack the affordances that make releases scriptable: download has no glob/`--clobber`, upload has no `--clobber`, and nothing resolves "the latest release" without a literal tag. Reproduced: ``` $ fj release download --help Usage: fj release download [OPTIONS] <TAG> -n, --name <NAME> Asset name. If omitted, all assets are downloaded -o, --output-dir <OUTPUT_DIR> Output directory (default: cwd) $ fj release upload --help Usage: fj release upload [OPTIONS] <TAG> [FILES]... (no --clobber) $ fj release view --help Usage: fj release view [OPTIONS] <TAG> # TAG is required ``` Three concrete gaps, all visible in `src/cli/release.rs`: 1. **Download is exact-match only, no glob.** `DownloadArgs` (`src/cli/release.rs:139-151`) has `-n/--name`, and the handler filters with `&a.name != filter` (`src/cli/release.rs:395-399`), a string equality. `gh release download` takes `--pattern '*.tar.gz'`. To grab "all the linux assets" from `fj` you must download everything or loop one exact name at a time. 2. **No `--clobber` on download or upload; re-running is unsafe/ambiguous.** Download unconditionally `std::fs::write`s over whatever is at the destination (`src/cli/release.rs:410`), so there is no "skip if present" and no explicit "overwrite" gate. Upload (`upload_one`, `src/cli/release.rs:365-385`) just POSTs the asset; uploading a name that already exists on the release has no clobber path, so a re-run of a publish script (e.g. after a partial failure) either errors or duplicates the asset name. `gh release upload --clobber` exists precisely to make republish idempotent. 3. **"Latest" is not addressable.** Both `view` (`ViewArgs`, `src/cli/release.rs:55-67`) and `download` (`src/cli/release.rs:139`) require a positional `<TAG>` and call `get_by_tag` (`src/cli/release.rs:226`, `:390`). `gh release view` / `gh release download` with no tag operate on the latest release. A CI step that wants "download the assets from the most recent release" must first `fj release list --json | jq -r '.[0].tag_name'` and thread that back in. ## Why it matters These verbs are the install/publish surface, the part of `fj` that lands inside CI and bootstrap scripts: - A self-update or bootstrap script wants `fj release download --pattern 'fj-*-linux-x86_64.tar.gz'` from the latest release. Today it cannot express either half: no pattern, and no implicit latest. - A release pipeline that uploads built artifacts wants to be safely re-runnable. Without `--clobber`, a retried job fights the assets the previous attempt already uploaded. - Exact-name download forces callers to hardcode full asset filenames (which usually embed a version), so the script breaks on the next release unless it first scrapes the asset list back out. `fj release` already gets the hard parts right (draft/prerelease, `--asset` on create, path-traversal-safe asset names via `safe_asset_dest`). The missing pieces are small, well-scoped flags that turn a manual tool into a scriptable one. ## Possible directions (sketches) - *(sketch)* Add `--pattern <glob>` to `fj release download` (repeatable, OR-combined), matched against `a.name` with a glob crate, alongside the existing exact `-n/--name`. Mirror `gh release download --pattern`. - *(sketch)* Add `--clobber` to `download` (overwrite existing files; without it, skip-with-note instead of silently overwriting) and to `upload` (delete-then-reupload, or PATCH, an existing same-named asset). Makes both re-runnable. - *(sketch)* Make `<TAG>` optional on `view` and `download`: when omitted, resolve the latest published release (`api::release::list(..., 1)` first item, skipping drafts/prereleases unless asked). Optionally a `--latest` flag for explicitness. - *(sketch, smaller)* `fj release list` could also grow `--exclude-drafts` / `--exclude-pre-releases` to match `gh` and to make "latest stable" trivially selectable.
Author
Owner

Converted to backlog item rasterstate/fj#133 (p3, size M).

Converted to backlog item rasterstate/fj#133 (p3, size M).
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
rasterstate/fj#122
No description provided.