add Variants []string to Asset and Dist, keep Extra for version info

Extra is for version-related sort metadata (build numbers, etc.).
Variants captures build qualifiers like "rocm", "jetpack5",
"fxdependent", "installer" — things the resolver should skip by
default unless explicitly requested.

Also update format classification docs: most formats (.pkg, .deb,
.dmg, .msi) are extractable — only .exe is ambiguous and needs
the "installer" variant tag when it's not the actual binary.
This commit is contained in:
AJ ONeal
2026-03-10 12:51:11 -06:00
parent 27950420dc
commit d1016eb589
3 changed files with 39 additions and 32 deletions

View File

@@ -364,8 +364,9 @@ type Asset struct {
Libc string // "musl"
Format string // ".tar.gz"
Channel string // "stable"
Extra string // "" (base) or "rocm", "jetpack5", "fxdependent"
Download string // full URL
Extra string // extra version info for sorting
Variants []string // ["rocm"], ["installer"], ["fxdependent"], etc.
Download string // full URL
...
}
```
@@ -378,10 +379,9 @@ hardware or runtime configuration beyond OS/arch/libc:
- `fxdependent`, `fxdependentWinDesktop` — .NET framework-dependent (pwsh)
- `profile` — debug profiling build (bun)
- `source` — source archive, not a binary
- `installer`non-extractable installer format (.pkg, .msi, .deb, .rpm,
.dmg, .msixbundle, .AppImage, .exe-installer)
- `installer``.exe` that is a GUI installer, not the actual tool binary
The resolver **deprioritizes** assets with non-empty `Extra` — they're only
The resolver **deprioritizes** assets with non-empty `Variants` — they're only
selected when the user explicitly requests that variant (e.g., `?variant=rocm`).
The full API still serves them for broader use cases.
@@ -392,20 +392,23 @@ selection naturally.
### Format Classification
All assets are stored — nothing is dropped at classification time. Installer
formats are tagged with `Extra = "installer"` so the resolver skips them by
default while the full API can still serve them:
All assets are stored — nothing is dropped at classification time.
- `.pkg` (macOS installer)
- `.msi` (Windows installer)
- `.deb`, `.rpm` (Linux package managers)
- `.dmg` (macOS disk image)
- `.sh` (self-extracting installer scripts)
- `.msixbundle`, `.AppImage`
- `.exe` when it's an installer, not the actual binary
Most formats are extractable and need no special tagging — the file extension
is enough signal for the install scripts:
Webi's default install path uses **extractable archives** (tar.gz, tar.xz, zip,
7z) and **bare binaries** only.
- `.tar.gz`, `.tar.xz`, `.tar.zst`, `.zip`, `.7z` — standard archives
- `.pkg` — macOS: extractable via `pkgutil --expand-full` (flat files, no install)
See: https://coolaj86.com/articles/how-to-extract-pkg-files-and-payload/
- `.deb` — extractable via `ar x` + `tar xf data.tar.*`
- `.dmg` — mountable via `hdiutil attach`
- `.msi` — extractable via `msiexec /a` or `lessmsi`
- `.AppImage` — self-contained, chmod +x and run
The one ambiguous format is `.exe` — it could be a bare binary (the actual tool)
or a GUI installer (which can't be automated). Assets where `.exe` is an installer
(not the tool itself) get `Variants: ["installer"]` so the resolver skips them
by default.
### Legacy Export Filtering
@@ -464,8 +467,9 @@ behavior must be preserved for backward compatibility.
- **Shell out to `node releases.js`?** No — all source types are implemented in
Go. The Go pipeline fetches and classifies everything directly.
- **Asset.Extra vs new Variant type?** `Extra` (string) serves the purpose.
The resolver already deprioritizes non-empty Extra. Keep it simple.
- **Asset.Extra vs Variants?** `Extra` stays as version-related sort info.
New `Variants []string` field captures build qualifiers (rocm, installer,
fxdependent). Resolver deprioritizes assets with any variants.
## Current Node.js Architecture (Reference)

View File

@@ -27,17 +27,19 @@ type Dist struct {
Size int64
LTS bool
Date string
Extra string
Extra string // extra version info for sorting
Variants []string // build qualifiers: "installer", "rocm", "fxdependent", etc.
}
// Query describes what the caller wants.
type Query struct {
OS buildmeta.OS
Arch buildmeta.Arch
Libc buildmeta.Libc
Formats []string // acceptable formats (e.g. ".tar.gz", ".zip"), in preference order
Channel string // "stable" (default), "beta", etc.
Version string // version prefix constraint ("24", "24.14", ""), empty = latest
OS buildmeta.OS
Arch buildmeta.Arch
Libc buildmeta.Libc
Formats []string // acceptable formats (e.g. ".tar.gz", ".zip"), in preference order
Channel string // "stable" (default), "beta", etc.
Version string // version prefix constraint ("24", "24.14", ""), empty = latest
Variants []string // if non-empty, only match assets with these variants
}
// Match is the resolved release.
@@ -134,7 +136,7 @@ func Best(dists []Dist, q Query) *Match {
ver: ver,
archRank: aRank,
formatRank: fRank,
hasExtra: d.Extra != "",
hasVariants: len(d.Variants) > 0,
}
if best == nil || c.betterThan(best) {
@@ -219,7 +221,7 @@ type candidate struct {
ver lexver.Version
archRank int
formatRank int
hasExtra bool // true if dist.Extra is non-empty (GPU variant, etc.)
hasVariants bool // true if dist has variant qualifiers (GPU, installer, etc.)
}
// betterThan returns true if c is a better match than other.
@@ -229,9 +231,9 @@ func (c *candidate) betterThan(other *candidate) bool {
if cmp != 0 {
return cmp > 0
}
// Prefer base variant over GPU/special variants (rocm, jetpack, etc.)
if c.hasExtra != other.hasExtra {
return !c.hasExtra
// Prefer base build over variant builds (rocm, installer, etc.)
if c.hasVariants != other.hasVariants {
return !c.hasVariants
}
if c.archRank != other.archRank {
return c.archRank < other.archRank

View File

@@ -29,7 +29,8 @@ type Asset struct {
Libc string
Format string
Download string
Extra string
Extra string // extra version info for sorting (e.g. build metadata)
Variants []string // build qualifiers: "installer", "rocm", "jetpack5", "fxdependent", etc.
}
// PackageData is the full set of assets for a package, plus metadata.