Deprecated: new packages should use
xpm.source(a string or aGLOBAL/CNmap). Existingurl_templatepackages remain supported for compatibility and will be migrated opportunistically.
A small contract between an xpkg description and the in-repo version
checker (.github/scripts/version-check.py). The package must explicitly
opt in with ci = { update = true }; url_template only describes the
asset URL. Without the CI opt-in, the package is maintained by hand.
The scan interval is centralized in .github/xpkg-ci.yml and is not
repeated in individual package files.
The contract
A package opts in with ci = { update = true } and places a single string
field xpm.<platform>.url_template next to that platform's version table:
xpm = {
linux = {
url_template = "https://github.com/astral-sh/uv/releases/download/{version}/uv-x86_64-unknown-linux-gnu.tar.gz",
["latest"] = { ref = "0.11.7" },
["0.11.7"] = { url = "...", sha256 = "..." },
},
macosx = {
url_template = "https://github.com/astral-sh/uv/releases/download/{version}/uv-aarch64-apple-darwin.tar.gz",
...
},
windows = {
url_template = "https://github.com/astral-sh/uv/releases/download/{version}/uv-x86_64-pc-windows-msvc.zip",
...
},
}
The placeholder {version} is the only token recognised today.
Resolution rules (v1)
- Updater scans every
pkgs/**/*.lua. - Only packages with
ci = { update = true }and at least one platform with aurl_templatefield are considered opt-in. - The updater treats
package.repoas a GitHub URL and extracts<owner>/<name>from it. - It calls
GET https://api.github.com/repos/<owner>/<name>/releases/latestand reads thetag_name. A leadingv(if present) is stripped to produce<version>. - The current "latest" is read from
xpm.linux["latest"].ref(whichever platform'slatestis checked first; they are required to agree). - If the upstream
<version>is not the same as the current one, the updater computes per-platform URLs by substituting{version}in eachurl_template, downloads each artifact, and records the sha256 sum.
In v1 only source = github-release is supported (implicitly — there
is no source field; the contract assumes GitHub). Packages whose
upstream is not a GitHub Release simply leave url_template
unset and continue to be maintained by hand.
XLINGS_RES mode (res_versioned)
Some packages — notably xlings itself — record their version entries
as ["<ver>"] = "XLINGS_RES" (resolved through the multi-mirror resource
service) instead of a hardcoded url + sha256. They cannot use
url_template (there is no single URL, and the resource service handles
integrity), so they previously had no auto-update path and went stale
silently (xlings latest was stuck 5 releases behind).
Such a platform opts in with res_versioned = true next to its version
table:
xpm = {
linux = {
res_versioned = true,
["latest"] = { ref = "0.4.60" },
["0.4.60"] = "XLINGS_RES",
},
-- macosx / windows likewise
}
Resolution is identical to url_template (rules 1–5 above: scan, read
package.repo, query releases/latest, compare against
["latest"].ref). The only difference is the bump shape: on an
update the updater appends a bare ["<upstream>"] = "XLINGS_RES" entry
and bumps ["latest"].ref — no artifact download, no sha256 (the
resource service resolves and verifies the binary, and provides mirror
fallback). A platform may set url_template or res_versioned, not
both; url_template wins if both are present.
What is NOT in scope (v1)
- The xlings package manager does not consume
url_template. It is a private convention between the package description and the in-repo updater. xpm version entries continue to record an expliciturl + sha256so xlings install behaves exactly as before. - Any tag/version syntax beyond "leading-v stripped". Date-based tags, monorepo tags, prefixed tags, and similar variants are out; packages that need them stay on manual maintenance for now.
- Pre-releases. The updater always pulls
releases/latest(which excludes drafts and pre-releases by GitHub's own definition). No knob to opt in. - Multi-arch. The current xpm shape is one platform → one URL, so this spec inherits the same shape. If a package needs distinct URLs per arch within the same platform, it stays on manual maintenance.
Behaviour when fields are missing or inconsistent
- No
url_templateand nores_versionedon any platform → package is skipped (manual mode). res_versioned = trueon a platform → that platform auto-bumps as a bare["<ver>"] = "XLINGS_RES"entry (no url/sha256). Requirespackage.repolike theurl_templatepath.url_templateon some platforms only → only those platforms are refreshed; other platforms are left alone.package.repomissing or not a GitHub URL → the package is skipped and a warning is emitted.- The string
{version}not present in the template → the template is rejected (lint failure) and the package is skipped.
Phase 1 vs Phase 2
Both phases now ship in the same script (.github/scripts/version-check.py)
behind the --apply flag and as separate scheduled workflows.
Phase 1 — version-check.yml (default, dry-run): wakes daily according
to the central policy. Reads opted-in url_templates, queries upstream, prints a JSON
report of "
Phase 2 — version-bump.yml (--apply): runs from the central
scheduler or on manual dispatch. For every opted-in package the dry-run flags
as update-available, the script:
- Downloads the new artifact for each opted-in platform and computes its sha256.
- Bumps
["latest"].refto the upstream version on every opted-in platform. - Appends a new
["<upstream>"] = { url, sha256 }block right after["latest"]on the same platform. Existing version blocks are left untouched, so consumers pinned to an older version keep working unchanged.
The workflow then commits the modified lua to a per-package branch
named auto/bump-<pkg>-<upstream> and opens a PR via gh. If that
branch already exists on origin (i.e. an open PR is already pending
for the same target version) the package is skipped — the run is
idempotent.
Manual one-shot:
# preview only — no file changes
python3 .github/scripts/version-check.py --workspace .
# apply (modifies pkgs/**/*.lua in place)
python3 .github/scripts/version-check.py --workspace . --apply [--only <pkg>]
Example: pkgs/u/uv.lua
uv is the reference implementation of this spec. See its xpm
block for the canonical shape; copy from there when adding
url_template to other packages.